From 5c95e7b4690ef7ca86cfefd56faa4074d975bd1b Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 16:58:59 +1100 Subject: [PATCH 01/21] Fix names --- src/meters/sma/index.ts | 2 +- src/meters/sunspec/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/meters/sma/index.ts b/src/meters/sma/index.ts index f2060513..b42fb365 100644 --- a/src/meters/sma/index.ts +++ b/src/meters/sma/index.ts @@ -13,7 +13,7 @@ export class SmaMeterSiteSamplePoller extends SiteSamplePollerBase { private model: SmaMeterConfig['model']; constructor({ smaMeterConfig }: { smaMeterConfig: SmaMeterConfig }) { - super({ name: 'SunSpecMeterPoller', pollingIntervalMs: 200 }); + super({ name: 'sma', pollingIntervalMs: 200 }); this.smaConnection = getSmaConnection(smaMeterConfig); this.model = smaMeterConfig.model; diff --git a/src/meters/sunspec/index.ts b/src/meters/sunspec/index.ts index 69627e41..a73355d1 100644 --- a/src/meters/sunspec/index.ts +++ b/src/meters/sunspec/index.ts @@ -26,7 +26,7 @@ export class SunSpecMeterSiteSamplePoller extends SiteSamplePollerBase { sunspecMeterConfig: SunSpecMeterConfig; invertersPoller: InvertersPoller; }) { - super({ name: 'SunSpecMeterPoller', pollingIntervalMs: 200 }); + super({ name: 'sunspec', pollingIntervalMs: 200 }); this.meterConnection = getSunSpecMeterConnection(sunspecMeterConfig); this.location = sunspecMeterConfig.location; From 8f7faf47c0543d7e5241fa6d2b61a5018280b3ec Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 16:59:05 +1100 Subject: [PATCH 02/21] Add Growatt meter --- src/helpers/config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/helpers/config.ts b/src/helpers/config.ts index 086b3e1c..062e4090 100644 --- a/src/helpers/config.ts +++ b/src/helpers/config.ts @@ -186,6 +186,12 @@ A longer time will smooth out load changes but may result in overshoot.`, }) .merge(modbusSchema) .describe('SMA meter configuration'), + z + .object({ + type: z.literal('growatt'), + }) + .merge(modbusSchema) + .describe('Growatt meter configuration'), z .object({ type: z.literal('powerwall2'), From 26d59e4f6eddb6c7fa7b7c3c133b25b1550eca58 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 17:05:04 +1100 Subject: [PATCH 03/21] Fix typo --- src/meters/sma/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/meters/sma/index.ts b/src/meters/sma/index.ts index b42fb365..fc9a4a49 100644 --- a/src/meters/sma/index.ts +++ b/src/meters/sma/index.ts @@ -33,7 +33,7 @@ export class SmaMeterSiteSamplePoller extends SiteSamplePollerBase { this.logger.trace( { duration, meterModel: meteringModel }, - 'polled SunSpec meter data', + 'polled meter data', ); const siteSample = generateSiteSample({ @@ -45,7 +45,7 @@ export class SmaMeterSiteSamplePoller extends SiteSamplePollerBase { return { success: false, error: new Error( - `Error loading SunSpec meter data: ${ + `Error loading meter data: ${ error instanceof Error ? error.message : 'Unknown error' }`, ), From bf05b7b2301a139750afd36d03fcd61615796a82 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 17:08:02 +1100 Subject: [PATCH 04/21] Add Growatt meter --- config.schema.json | 21 +++++ src/coordinator/helpers/siteSample.ts | 6 ++ src/meters/growatt/index.ts | 85 ++++++++++++++++++++ src/modbus/connection/growatt.ts | 30 +++++++ src/modbus/connections.ts | 24 +++++- src/modbus/models/growatt/meter.ts | 109 ++++++++++++++++++++++++++ 6 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 src/meters/growatt/index.ts create mode 100644 src/modbus/connection/growatt.ts create mode 100644 src/modbus/models/growatt/meter.ts diff --git a/config.schema.json b/config.schema.json index 46a00514..922f67e0 100644 --- a/config.schema.json +++ b/config.schema.json @@ -339,6 +339,27 @@ "additionalProperties": false, "description": "SMA meter configuration" }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "growatt" + }, + "connection": { + "$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/connection" + }, + "unitId": { + "$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId" + } + }, + "required": [ + "type", + "connection" + ], + "additionalProperties": false, + "description": "Growatt meter configuration" + }, { "type": "object", "properties": { diff --git a/src/coordinator/helpers/siteSample.ts b/src/coordinator/helpers/siteSample.ts index 0c977505..f4973fc6 100644 --- a/src/coordinator/helpers/siteSample.ts +++ b/src/coordinator/helpers/siteSample.ts @@ -5,6 +5,7 @@ import { SunSpecMeterSiteSamplePoller } from '../../meters/sunspec/index.js'; import type { SiteSamplePollerBase } from '../../meters/siteSamplePollerBase.js'; import type { InvertersPoller } from './inverterSample.js'; import { SmaMeterSiteSamplePoller } from '../../meters/sma/index.js'; +import { GrowattMeterSiteSamplePoller } from '../../meters/growatt/index.js'; export function getSiteSamplePollerInstance({ config, @@ -35,5 +36,10 @@ export function getSiteSamplePollerInstance({ smaMeterConfig: config.meter, }); } + case 'growatt': { + return new GrowattMeterSiteSamplePoller({ + growattMeterConfig: config.meter, + }); + } } } diff --git a/src/meters/growatt/index.ts b/src/meters/growatt/index.ts new file mode 100644 index 00000000..f9e82370 --- /dev/null +++ b/src/meters/growatt/index.ts @@ -0,0 +1,85 @@ +import type { SiteSample } from '../siteSample.js'; +import { SiteSamplePollerBase } from '../siteSamplePollerBase.js'; +import type { Result } from '../../helpers/result.js'; +import type { Config } from '../../helpers/config.js'; +import { getGrowattConnection } from '../../modbus/connections.js'; +import type { GrowattConnection } from '../../modbus/connection/growatt.js'; +import type { GrowattMeterModels } from '../../modbus/models/growatt/meter.js'; + +type GrowattMeterConfig = Extract; + +export class GrowattMeterSiteSamplePoller extends SiteSamplePollerBase { + private growattConnection: GrowattConnection; + + constructor({ + growattMeterConfig, + }: { + growattMeterConfig: GrowattMeterConfig; + }) { + super({ name: 'growattMeter', pollingIntervalMs: 200 }); + + this.growattConnection = getGrowattConnection(growattMeterConfig); + + void this.startPolling(); + } + + override async getSiteSample(): Promise> { + try { + const start = performance.now(); + + const meterModel = await this.growattConnection.getMeterModel(); + + const end = performance.now(); + const duration = end - start; + + this.logger.trace({ duration, meterModel }, 'polled meter data'); + + const siteSample = generateSiteSample({ + meter: meterModel, + }); + + return { success: true, value: siteSample }; + } catch (error) { + return { + success: false, + error: new Error( + `Error loading meter data: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, + ), + }; + } + } + + override onDestroy() { + this.growattConnection.client.close(() => {}); + } +} + +function generateSiteSample({ + meter, +}: { + meter: GrowattMeterModels; +}): SiteSample { + return { + date: new Date(), + realPower: { + type: 'noPhase', + net: + meter.PactogridTotal > 0 + ? -meter.PactogridTotal + : meter.PactouserTotal, + }, + reactivePower: { + type: 'noPhase', + net: 0, + }, + voltage: { + type: 'perPhase', + phaseA: meter.Vac1, + phaseB: meter.Vac2, + phaseC: meter.Vac3, + }, + frequency: meter.Fac, + }; +} diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts new file mode 100644 index 00000000..34c8a65b --- /dev/null +++ b/src/modbus/connection/growatt.ts @@ -0,0 +1,30 @@ +import { ModbusConnection } from './base.js'; +import { + GrowattMeter1Model, + GrowattMeter2Model, + type GrowattMeterModels, +} from '../models/growatt/meter.js'; + +export class GrowattConnection extends ModbusConnection { + async getMeterModel(): Promise { + const model1 = await GrowattMeter1Model.read({ + modbusConnection: this, + address: { + start: 37, + length: 10, + }, + }); + + const model2 = await GrowattMeter2Model.read({ + modbusConnection: this, + address: { + start: 1015, + length: 24, + }, + }); + + const data = { ...model1, ...model2 }; + + return data; + } +} diff --git a/src/modbus/connections.ts b/src/modbus/connections.ts index ea1c8aca..467d2a3d 100644 --- a/src/modbus/connections.ts +++ b/src/modbus/connections.ts @@ -1,4 +1,5 @@ -import type { Config, ModbusSchema } from '../helpers/config.js'; +import type { ModbusSchema } from '../helpers/config.js'; +import { GrowattConnection } from './connection/growatt.js'; import { SmaConnection } from './connection/sma.js'; export function getModbusConnectionKey({ @@ -18,7 +19,7 @@ const smaConnectionsMap = new Map(); export function getSmaConnection({ connection, unitId, -}: Extract): SmaConnection { +}: ModbusSchema): SmaConnection { const key = getModbusConnectionKey({ connection, unitId }); if (smaConnectionsMap.has(key)) { @@ -31,3 +32,22 @@ export function getSmaConnection({ return smaConnection; } + +const growattConnectionsMap = new Map(); + +export function getGrowattConnection({ + connection, + unitId, +}: ModbusSchema): GrowattConnection { + const key = getModbusConnectionKey({ connection, unitId }); + + if (growattConnectionsMap.has(key)) { + return growattConnectionsMap.get(key)!; + } + + const growattConnection = new GrowattConnection({ connection, unitId }); + + growattConnectionsMap.set(key, growattConnection); + + return growattConnection; +} diff --git a/src/modbus/models/growatt/meter.ts b/src/modbus/models/growatt/meter.ts new file mode 100644 index 00000000..c5587681 --- /dev/null +++ b/src/modbus/models/growatt/meter.ts @@ -0,0 +1,109 @@ +import { + registersToUint16, + registersToUint32, +} from '../../../sunspec/helpers/converters.js'; +import { modbusModelFactory } from '../../modbusModelFactory.js'; + +export type GrowattMeterModels = GrowattMeter1 & GrowattMeter2; + +type GrowattMeter1 = { + // Grid frequency + Fac: number; + // Three/single phase grid voltage + Vac1: number; + // Three/single phase grid voltage + Vac2: number; + // Three/single phase grid voltage + Vac3: number; +}; + +type GrowattMeter2 = { + // AC power to user + PactouserR: number; + // AC power to user + PactouserS: number; + // AC power to user + PactouserT: number; + // AC power to user + PactouserTotal: number; + // AC power to grid + PactogridR: number; + // AC power to grid + PactogridS: number; + // AC power to grid + PactogridT: number; + // AC power to grid + PactogridTotal: number; +}; + +export const GrowattMeter1Model = modbusModelFactory({ + name: 'GrowattMeter1Model', + mapping: { + Fac: { + start: 0, + end: 1, + readConverter: (value) => registersToUint16(value, -2), + }, + Vac1: { + start: 1, + end: 2, + readConverter: (value) => registersToUint16(value, -1), + }, + Vac2: { + start: 2, + end: 3, + readConverter: (value) => registersToUint16(value, -1), + }, + Vac3: { + start: 3, + end: 4, + readConverter: (value) => registersToUint16(value, -1), + }, + }, +}); + +export const GrowattMeter2Model = modbusModelFactory({ + name: 'GrowattMeter2Model', + mapping: { + PactouserR: { + start: 0, + end: 2, + readConverter: (value) => registersToUint32(value, -1), + }, + PactouserS: { + start: 2, + end: 4, + readConverter: (value) => registersToUint32(value, -1), + }, + PactouserT: { + start: 4, + end: 6, + readConverter: (value) => registersToUint32(value, -1), + }, + PactouserTotal: { + start: 6, + end: 8, + readConverter: (value) => registersToUint32(value, -1), + }, + PactogridR: { + start: 8, + end: 10, + readConverter: (value) => registersToUint32(value, -1), + }, + PactogridS: { + start: 10, + end: 12, + readConverter: (value) => registersToUint32(value, -1), + }, + PactogridT: { + start: 12, + end: 14, + readConverter: (value) => registersToUint32(value, -1), + }, + PactogridTotal: { + start: 14, + end: 16, + readConverter: (value) => registersToUint32(value, -1), + }, + }, +}); From 85ced2c64ffcc87750c572c60d10b21b8e8f4882 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 17:20:35 +1100 Subject: [PATCH 05/21] Fix input registers --- src/modbus/modbusModelFactory.ts | 57 +++++++++++++++++++++--------- src/modbus/models/growatt/meter.ts | 2 ++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/modbus/modbusModelFactory.ts b/src/modbus/modbusModelFactory.ts index 0a3a76e3..54ad4c95 100644 --- a/src/modbus/modbusModelFactory.ts +++ b/src/modbus/modbusModelFactory.ts @@ -22,8 +22,13 @@ export function modbusModelFactory< // eslint-disable-next-line @typescript-eslint/no-explicit-any Model extends Record, WriteableKeys extends keyof Model = never, ->(config: { +>({ + name, + registerType = 'holding', + mapping, +}: { name: string; + registerType?: 'holding' | 'input'; mapping: Mapping; }): { read(params: { @@ -40,7 +45,7 @@ export function modbusModelFactory< read: async ({ modbusConnection, address }) => { const logger = modbusConnection.logger.child({ module: 'modbusModelFactory', - model: config.name, + model: name, type: 'read', }); @@ -48,11 +53,22 @@ export function modbusModelFactory< await modbusConnection.connect(); - const registers = - await modbusConnection.client.readHoldingRegisters( - address.start, - address.length, - ); + const registers = await (async () => { + switch (registerType) { + case 'holding': { + return await modbusConnection.client.readHoldingRegisters( + address.start, + address.length, + ); + } + case 'input': { + return await modbusConnection.client.readInputRegisters( + address.start, + address.length, + ); + } + } + })(); const end = performance.now(); const duration = end - start; @@ -62,7 +78,7 @@ export function modbusModelFactory< duration, tags: { operation: 'read', - model: config.name, + model: name, addressStart: address.start.toString(), addressLength: address.length.toString(), }, @@ -75,13 +91,13 @@ export function modbusModelFactory< return convertReadRegisters({ registers: registers.data, - mapping: config.mapping, + mapping, }); }, write: async ({ modbusConnection, address, values }) => { const logger = modbusConnection.logger.child({ module: 'modbusModelFactory', - model: config.name, + model: name, type: 'write', }); @@ -91,14 +107,23 @@ export function modbusModelFactory< const registerValues = convertWriteRegisters({ values, - mapping: config.mapping, + mapping, length: address.length, }); - await modbusConnection.client.writeRegisters( - address.start, - registerValues, - ); + await (async () => { + switch (registerType) { + case 'holding': { + return await modbusConnection.client.writeRegisters( + address.start, + registerValues, + ); + } + case 'input': { + throw new Error('Cannot write to input registers'); + } + } + })(); const end = performance.now(); const duration = end - start; @@ -108,7 +133,7 @@ export function modbusModelFactory< duration, tags: { operation: 'write', - model: config.name, + model: name, addressStart: address.start.toString(), addressLength: address.length.toString(), }, diff --git a/src/modbus/models/growatt/meter.ts b/src/modbus/models/growatt/meter.ts index c5587681..811df84a 100644 --- a/src/modbus/models/growatt/meter.ts +++ b/src/modbus/models/growatt/meter.ts @@ -38,6 +38,7 @@ type GrowattMeter2 = { export const GrowattMeter1Model = modbusModelFactory({ name: 'GrowattMeter1Model', + registerType: 'input', mapping: { Fac: { start: 0, @@ -64,6 +65,7 @@ export const GrowattMeter1Model = modbusModelFactory({ export const GrowattMeter2Model = modbusModelFactory({ name: 'GrowattMeter2Model', + registerType: 'input', mapping: { PactouserR: { start: 0, From a4fadc9618d0f810e4c7c04c766255fb4755794f Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 17:25:54 +1100 Subject: [PATCH 06/21] Fix helpers --- src/modbus/models/growatt/meter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modbus/models/growatt/meter.ts b/src/modbus/models/growatt/meter.ts index 811df84a..4069017d 100644 --- a/src/modbus/models/growatt/meter.ts +++ b/src/modbus/models/growatt/meter.ts @@ -1,7 +1,7 @@ import { registersToUint16, registersToUint32, -} from '../../../sunspec/helpers/converters.js'; +} from '../../helpers/converters.js'; import { modbusModelFactory } from '../../modbusModelFactory.js'; export type GrowattMeterModels = GrowattMeter1 & GrowattMeter2; From cc9c64d2e5700c19b0e600eeff4c46394b76d297 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 18:36:31 +1100 Subject: [PATCH 07/21] Initial Growatt inverter support --- src/coordinator/helpers/inverterSample.ts | 8 ++ src/helpers/config.ts | 6 + src/inverter/growatt/index.ts | 150 ++++++++++++++++++++++ src/modbus/connection/growatt.ts | 16 +++ src/modbus/models/growatt/inveter.ts | 32 +++++ 5 files changed, 212 insertions(+) create mode 100644 src/inverter/growatt/index.ts create mode 100644 src/modbus/models/growatt/inveter.ts diff --git a/src/coordinator/helpers/inverterSample.ts b/src/coordinator/helpers/inverterSample.ts index 4e657024..db2385c1 100644 --- a/src/coordinator/helpers/inverterSample.ts +++ b/src/coordinator/helpers/inverterSample.ts @@ -10,6 +10,7 @@ import { SunSpecInverterDataPoller } from '../../inverter/sunspec/index.js'; import type { InverterConfiguration } from './inverterController.js'; import type { Logger } from 'pino'; import { SmaInverterDataPoller } from '../../inverter/sma/index.js'; +import { GrowattInverterDataPoller } from '../../inverter/growatt/index.js'; export class InvertersPoller extends EventEmitter<{ data: [DerSample]; }> { @@ -45,6 +46,13 @@ export class InvertersPoller extends EventEmitter<{ inverterIndex: index, }).on('data', inverterOnData); } + case 'growatt': { + return new GrowattInverterDataPoller({ + growattInverterConfig: inverterConfig, + applyControl: config.inverterControl.enabled, + inverterIndex: index, + }).on('data', inverterOnData); + } } }, ); diff --git a/src/helpers/config.ts b/src/helpers/config.ts index 062e4090..3319d6ba 100644 --- a/src/helpers/config.ts +++ b/src/helpers/config.ts @@ -146,6 +146,12 @@ export const configSchema = z.object({ }) .merge(modbusSchema) .describe('SMA inverter configuration'), + z + .object({ + type: z.literal('growatt'), + }) + .merge(modbusSchema) + .describe('Growatt inverter configuration'), ]), ) .describe('Inverter configuration'), diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts new file mode 100644 index 00000000..bf18a876 --- /dev/null +++ b/src/inverter/growatt/index.ts @@ -0,0 +1,150 @@ +import { type InverterData } from '../inverterData.js'; +import type { Result } from '../../helpers/result.js'; +import { ConnectStatus } from '../../sep2/models/connectStatus.js'; +import { OperationalModeStatus } from '../../sep2/models/operationModeStatus.js'; +import { DERTyp } from '../../sunspec/models/nameplate.js'; +import { InverterDataPollerBase } from '../inverterDataPollerBase.js'; +import { type InverterConfiguration } from '../../coordinator/helpers/inverterController.js'; +import type { Config } from '../../helpers/config.js'; +import { withRetry } from '../../helpers/withRetry.js'; +import { writeLatency } from '../../helpers/influxdb.js'; +import { getGrowattConnection } from '../../modbus/connections.js'; +import type { GrowattConnection } from '../../modbus/connection/growatt.js'; +import type { GrowattInverterModels } from '../../modbus/models/growatt/inveter.js'; + +export class GrowattInverterDataPoller extends InverterDataPollerBase { + private growattConnection: GrowattConnection; + + constructor({ + growattInverterConfig, + inverterIndex, + applyControl, + }: { + growattInverterConfig: Extract< + Config['inverters'][number], + { type: 'growatt' } + >; + inverterIndex: number; + applyControl: boolean; + }) { + super({ + name: 'GrowattInverterDataPoller', + pollingIntervalMs: 200, + applyControl, + inverterIndex, + }); + + this.growattConnection = getGrowattConnection(growattInverterConfig); + + void this.startPolling(); + } + + override async getInverterData(): Promise> { + try { + return await withRetry( + async () => { + const start = performance.now(); + + const inverterModel = + await this.growattConnection.getInverterModel(); + + writeLatency({ + field: 'GrowattInverterDataPoller', + duration: performance.now() - start, + tags: { + inverterIndex: this.inverterIndex.toString(), + model: 'inverter', + }, + }); + + const models: InverterModels = { + inverter: inverterModel, + }; + + const end = performance.now(); + const duration = end - start; + + this.logger.trace( + { duration, models }, + 'Got inverter data', + ); + + const inverterData = generateInverterData(models); + + return { + success: true, + value: inverterData, + }; + }, + { + attempts: 3, + delayMilliseconds: 100, + functionName: 'get inverter data', + }, + ); + } catch (error) { + this.logger.error(error, 'Failed to get inverter data'); + + return { + success: false, + error: new Error( + `Error loading inverter data: ${error instanceof Error ? error.message : 'Unknown error'}`, + ), + }; + } + } + + override onDestroy(): void { + this.growattConnection.client.close(() => {}); + } + + // eslint-disable-next-line @typescript-eslint/require-await + override async onControl( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _inverterConfiguration: InverterConfiguration, + ): Promise { + throw new Error('Method not implemented.'); + } +} + +type InverterModels = { + inverter: GrowattInverterModels; +}; + +export function generateInverterData({ + inverter, +}: InverterModels): InverterData { + return { + date: new Date(), + inverter: { + realPower: inverter.Ppv, + reactivePower: 0, + voltagePhaseA: 0, + voltagePhaseB: null, + voltagePhaseC: null, + frequency: 0, + }, + nameplate: { + type: DERTyp.PV, + maxW: 0, + maxVA: 0, + maxVar: 0, + }, + settings: { + maxW: 0, + maxVA: 0, + maxVar: 0, + }, + status: generateInverterDataStatus(), + }; +} + +export function generateInverterDataStatus(): InverterData['status'] { + return { + operationalModeStatus: OperationalModeStatus.OperationalMode, + genConnectStatus: + ConnectStatus.Available | + ConnectStatus.Connected | + ConnectStatus.Operating, + }; +} diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts index 34c8a65b..50b2099d 100644 --- a/src/modbus/connection/growatt.ts +++ b/src/modbus/connection/growatt.ts @@ -4,8 +4,24 @@ import { GrowattMeter2Model, type GrowattMeterModels, } from '../models/growatt/meter.js'; +import { + GrowattInveter1Model, + type GrowattInverterModels, +} from '../models/growatt/inveter.js'; export class GrowattConnection extends ModbusConnection { + async getInverterModel(): Promise { + const model1 = await GrowattInveter1Model.read({ + modbusConnection: this, + address: { + start: 0, + length: 3, + }, + }); + + return model1; + } + async getMeterModel(): Promise { const model1 = await GrowattMeter1Model.read({ modbusConnection: this, diff --git a/src/modbus/models/growatt/inveter.ts b/src/modbus/models/growatt/inveter.ts new file mode 100644 index 00000000..f609d25a --- /dev/null +++ b/src/modbus/models/growatt/inveter.ts @@ -0,0 +1,32 @@ +import { + registersToUint16, + registersToUint32, +} from '../../helpers/converters.js'; +import { modbusModelFactory } from '../../modbusModelFactory.js'; + +export type GrowattInverterModels = GrowattInveter1; + +type GrowattInveter1 = { + // Inverter run state + // 0:waiting, 1:normal, 3:fault + InverterStatus: number; + // Input power + Ppv: number; +}; + +export const GrowattInveter1Model = modbusModelFactory({ + name: 'GrowattInveter1Model', + registerType: 'input', + mapping: { + InverterStatus: { + start: 0, + end: 1, + readConverter: registersToUint16, + }, + Ppv: { + start: 1, + end: 3, + readConverter: (value) => registersToUint32(value, -1), + }, + }, +}); From 0b66a3e9cf304787ff2ba282b23e1f18285787b3 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 18:36:54 +1100 Subject: [PATCH 08/21] Update config.schema.json --- config.schema.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/config.schema.json b/config.schema.json index 922f67e0..70c133fc 100644 --- a/config.schema.json +++ b/config.schema.json @@ -252,6 +252,27 @@ ], "additionalProperties": false, "description": "SMA inverter configuration" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "growatt" + }, + "connection": { + "$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/connection" + }, + "unitId": { + "$ref": "#/definitions/config/properties/inverters/items/anyOf/0/properties/unitId" + } + }, + "required": [ + "type", + "connection" + ], + "additionalProperties": false, + "description": "Growatt inverter configuration" } ] }, From b1927cf650573c20ad067e8427c9a967e64c3909 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 21:05:14 +1100 Subject: [PATCH 09/21] Refactor GrowattConnection to use new ModbusConnection --- src/meters/growatt/index.ts | 7 +++---- src/modbus/connection/growatt.ts | 33 +++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/meters/growatt/index.ts b/src/meters/growatt/index.ts index f9e82370..feb71eef 100644 --- a/src/meters/growatt/index.ts +++ b/src/meters/growatt/index.ts @@ -2,8 +2,7 @@ import type { SiteSample } from '../siteSample.js'; import { SiteSamplePollerBase } from '../siteSamplePollerBase.js'; import type { Result } from '../../helpers/result.js'; import type { Config } from '../../helpers/config.js'; -import { getGrowattConnection } from '../../modbus/connections.js'; -import type { GrowattConnection } from '../../modbus/connection/growatt.js'; +import { GrowattConnection } from '../../modbus/connection/growatt.js'; import type { GrowattMeterModels } from '../../modbus/models/growatt/meter.js'; type GrowattMeterConfig = Extract; @@ -18,7 +17,7 @@ export class GrowattMeterSiteSamplePoller extends SiteSamplePollerBase { }) { super({ name: 'growattMeter', pollingIntervalMs: 200 }); - this.growattConnection = getGrowattConnection(growattMeterConfig); + this.growattConnection = new GrowattConnection(growattMeterConfig); void this.startPolling(); } @@ -52,7 +51,7 @@ export class GrowattMeterSiteSamplePoller extends SiteSamplePollerBase { } override onDestroy() { - this.growattConnection.client.close(() => {}); + this.growattConnection.onDestroy(); } } diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts index 50b2099d..debcffad 100644 --- a/src/modbus/connection/growatt.ts +++ b/src/modbus/connection/growatt.ts @@ -1,4 +1,4 @@ -import { ModbusConnection } from './base.js'; +import type { ModbusConnection } from './base.js'; import { GrowattMeter1Model, GrowattMeter2Model, @@ -8,15 +8,32 @@ import { GrowattInveter1Model, type GrowattInverterModels, } from '../models/growatt/inveter.js'; +import { getModbusConnection } from '../connections.js'; +import type { ModbusSchema } from '../../helpers/config.js'; +import type { Logger } from 'pino'; + +export class GrowattConnection { + protected readonly modbusConnection: ModbusConnection; + protected readonly unitId: number; + private logger: Logger; + + constructor({ connection, unitId }: ModbusSchema) { + this.modbusConnection = getModbusConnection(connection); + this.unitId = unitId; + this.logger = this.modbusConnection.logger.child({ + module: 'GrowattConnection', + unitId, + }); + } -export class GrowattConnection extends ModbusConnection { async getInverterModel(): Promise { const model1 = await GrowattInveter1Model.read({ - modbusConnection: this, + modbusConnection: this.modbusConnection, address: { start: 0, length: 3, }, + unitId: this.unitId, }); return model1; @@ -24,23 +41,29 @@ export class GrowattConnection extends ModbusConnection { async getMeterModel(): Promise { const model1 = await GrowattMeter1Model.read({ - modbusConnection: this, + modbusConnection: this.modbusConnection, address: { start: 37, length: 10, }, + unitId: this.unitId, }); const model2 = await GrowattMeter2Model.read({ - modbusConnection: this, + modbusConnection: this.modbusConnection, address: { start: 1015, length: 24, }, + unitId: this.unitId, }); const data = { ...model1, ...model2 }; return data; } + + public onDestroy(): void { + this.modbusConnection.client.close(() => {}); + } } From b12c4e0650f87fe77d5abca0a9f0bd07db09e473 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 26 Oct 2024 21:07:39 +1100 Subject: [PATCH 10/21] Fix growatt inverter --- src/inverter/growatt/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index bf18a876..958b3b7a 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -8,8 +8,7 @@ import { type InverterConfiguration } from '../../coordinator/helpers/inverterCo import type { Config } from '../../helpers/config.js'; import { withRetry } from '../../helpers/withRetry.js'; import { writeLatency } from '../../helpers/influxdb.js'; -import { getGrowattConnection } from '../../modbus/connections.js'; -import type { GrowattConnection } from '../../modbus/connection/growatt.js'; +import { GrowattConnection } from '../../modbus/connection/growatt.js'; import type { GrowattInverterModels } from '../../modbus/models/growatt/inveter.js'; export class GrowattInverterDataPoller extends InverterDataPollerBase { @@ -34,7 +33,7 @@ export class GrowattInverterDataPoller extends InverterDataPollerBase { inverterIndex, }); - this.growattConnection = getGrowattConnection(growattInverterConfig); + this.growattConnection = new GrowattConnection(growattInverterConfig); void this.startPolling(); } @@ -95,7 +94,7 @@ export class GrowattInverterDataPoller extends InverterDataPollerBase { } override onDestroy(): void { - this.growattConnection.client.close(() => {}); + this.growattConnection.onDestroy(); } // eslint-disable-next-line @typescript-eslint/require-await From 3fd84c75e7e5e0b4bdca83fe4cd2e22b887d5d35 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sun, 27 Oct 2024 10:17:13 +1100 Subject: [PATCH 11/21] Merge fixes --- src/modbus/connection/base.ts | 6 +++--- src/modbus/connection/growatt.ts | 2 +- src/modbus/models/growatt/inveter.ts | 2 +- src/modbus/models/growatt/meter.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modbus/connection/base.ts b/src/modbus/connection/base.ts index 535b6d85..887ee882 100644 --- a/src/modbus/connection/base.ts +++ b/src/modbus/connection/base.ts @@ -6,7 +6,7 @@ import { Mutex } from 'async-mutex'; const connectionTimeoutMs = 10_000; -type RegisterType = 'holding' | 'input'; +export type ModbusRegisterType = 'holding' | 'input'; export class ModbusConnection { private readonly client: ModbusRTU.default; @@ -105,7 +105,7 @@ export class ModbusConnection { start, length, }: { - type: RegisterType; + type: ModbusRegisterType; unitId: number; start: number; length: number; @@ -130,7 +130,7 @@ export class ModbusConnection { start, data, }: { - type: RegisterType; + type: ModbusRegisterType; unitId: number; start: number; data: number[]; diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts index debcffad..6cc76ed7 100644 --- a/src/modbus/connection/growatt.ts +++ b/src/modbus/connection/growatt.ts @@ -64,6 +64,6 @@ export class GrowattConnection { } public onDestroy(): void { - this.modbusConnection.client.close(() => {}); + this.modbusConnection.close(); } } diff --git a/src/modbus/models/growatt/inveter.ts b/src/modbus/models/growatt/inveter.ts index f609d25a..fab8bc45 100644 --- a/src/modbus/models/growatt/inveter.ts +++ b/src/modbus/models/growatt/inveter.ts @@ -16,7 +16,7 @@ type GrowattInveter1 = { export const GrowattInveter1Model = modbusModelFactory({ name: 'GrowattInveter1Model', - registerType: 'input', + type: 'input', mapping: { InverterStatus: { start: 0, diff --git a/src/modbus/models/growatt/meter.ts b/src/modbus/models/growatt/meter.ts index 4069017d..74dc42ca 100644 --- a/src/modbus/models/growatt/meter.ts +++ b/src/modbus/models/growatt/meter.ts @@ -38,7 +38,7 @@ type GrowattMeter2 = { export const GrowattMeter1Model = modbusModelFactory({ name: 'GrowattMeter1Model', - registerType: 'input', + type: 'input', mapping: { Fac: { start: 0, @@ -65,7 +65,7 @@ export const GrowattMeter1Model = modbusModelFactory({ export const GrowattMeter2Model = modbusModelFactory({ name: 'GrowattMeter2Model', - registerType: 'input', + type: 'input', mapping: { PactouserR: { start: 0, From 2d9590d537c7f89a0d3b7e82845ebcd52107cf4b Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sun, 27 Oct 2024 19:58:08 +1100 Subject: [PATCH 12/21] Add async to function --- src/modbus/connection/base.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modbus/connection/base.ts b/src/modbus/connection/base.ts index 887ee882..52e4aa56 100644 --- a/src/modbus/connection/base.ts +++ b/src/modbus/connection/base.ts @@ -99,7 +99,7 @@ export class ModbusConnection { } } - readRegisters({ + async readRegisters({ type, unitId, start, @@ -124,7 +124,7 @@ export class ModbusConnection { }); } - writeRegisters({ + async writeRegisters({ type, unitId, start, From 5b95b39f80d2c582b0e7bb93ee74c28c631c2f28 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Mon, 28 Oct 2024 20:04:39 +1100 Subject: [PATCH 13/21] Return target solar watts for inverter configuration --- src/coordinator/helpers/inverterController.ts | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/coordinator/helpers/inverterController.ts b/src/coordinator/helpers/inverterController.ts index ffeef551..117f1000 100644 --- a/src/coordinator/helpers/inverterController.ts +++ b/src/coordinator/helpers/inverterController.ts @@ -47,6 +47,7 @@ export type InverterConfiguration = | { type: 'disconnect' } | { type: 'limit'; + targetSolarWatts: number; targetSolarPowerRatio: number; }; @@ -277,7 +278,7 @@ export class InverterController { })), ); - const inverterConfiguration = ((): InverterConfiguration => { + const rampedInverterConfiguration = ((): InverterConfiguration => { const configuration = calculateInverterConfiguration({ activeInverterControlLimit: this.controlLimitsCache.activeInverterControlLimit, @@ -293,7 +294,10 @@ export class InverterController { // ramp the target solar power ratio to prevent sudden changes (e.g. 0% > 100% > 0%) // prevents inverter from potential hardware damage // also prevents feedback cycle with batteries constantly switching between charge/discharge - const previousTargetSolarPowerRatio = (() => { + const previousTarget = ((): { + targetSolarWatts: number; + targetSolarPowerRatio: number; + } | null => { if (!this.lastAppliedInverterConfiguration) { return null; } @@ -301,33 +305,44 @@ export class InverterController { switch (this.lastAppliedInverterConfiguration.type) { case 'disconnect': // slowly ramp from 0 if previously disconnected - return 0; + return { + targetSolarWatts: 0, + targetSolarPowerRatio: 0, + }; case 'limit': - return this.lastAppliedInverterConfiguration - .targetSolarPowerRatio; + return this.lastAppliedInverterConfiguration; } })(); - if (previousTargetSolarPowerRatio === null) { + if (previousTarget === null) { return configuration; } + // max 10% change + const maxChange = 0.1; + + const rampedTargetSolarWatts = cappedChange({ + previousValue: previousTarget.targetSolarWatts, + targetValue: configuration.targetSolarWatts, + maxChange, + }); + const rampedTargetSolarPowerRatio = cappedChange({ - previousValue: previousTargetSolarPowerRatio, + previousValue: previousTarget.targetSolarPowerRatio, targetValue: configuration.targetSolarPowerRatio, - // max 10% change - maxChange: 0.1, + maxChange, }); return { type: 'limit', + targetSolarWatts: rampedTargetSolarWatts, targetSolarPowerRatio: rampedTargetSolarPowerRatio, }; } } })(); - return inverterConfiguration; + return rampedInverterConfiguration; } } @@ -420,6 +435,7 @@ export function calculateInverterConfiguration({ return { type: 'limit', + targetSolarWatts, targetSolarPowerRatio: roundToDecimals(targetSolarPowerRatio, 4), }; } From a95077ab714322ea722ea542a08880d6c0427bdd Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Mon, 28 Oct 2024 20:07:54 +1100 Subject: [PATCH 14/21] Typo --- src/modbus/models/growatt/inveter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modbus/models/growatt/inveter.ts b/src/modbus/models/growatt/inveter.ts index fab8bc45..1d793065 100644 --- a/src/modbus/models/growatt/inveter.ts +++ b/src/modbus/models/growatt/inveter.ts @@ -4,9 +4,9 @@ import { } from '../../helpers/converters.js'; import { modbusModelFactory } from '../../modbusModelFactory.js'; -export type GrowattInverterModels = GrowattInveter1; +export type GrowattInverterModels = GrowattInverter1; -type GrowattInveter1 = { +type GrowattInverter1 = { // Inverter run state // 0:waiting, 1:normal, 3:fault InverterStatus: number; @@ -14,7 +14,7 @@ type GrowattInveter1 = { Ppv: number; }; -export const GrowattInveter1Model = modbusModelFactory({ +export const GrowattInveter1Model = modbusModelFactory({ name: 'GrowattInveter1Model', type: 'input', mapping: { From 5cd25ae6996f838868396ba301cbc19495b0aa3d Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Mon, 28 Oct 2024 20:08:20 +1100 Subject: [PATCH 15/21] Rename file --- src/inverter/growatt/index.ts | 2 +- src/modbus/connection/growatt.ts | 2 +- src/modbus/models/growatt/{inveter.ts => inverter.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/modbus/models/growatt/{inveter.ts => inverter.ts} (100%) diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index 958b3b7a..4b8505b9 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -9,7 +9,7 @@ import type { Config } from '../../helpers/config.js'; import { withRetry } from '../../helpers/withRetry.js'; import { writeLatency } from '../../helpers/influxdb.js'; import { GrowattConnection } from '../../modbus/connection/growatt.js'; -import type { GrowattInverterModels } from '../../modbus/models/growatt/inveter.js'; +import type { GrowattInverterModels } from '../../modbus/models/growatt/inverter.js'; export class GrowattInverterDataPoller extends InverterDataPollerBase { private growattConnection: GrowattConnection; diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts index 6cc76ed7..aa8a2384 100644 --- a/src/modbus/connection/growatt.ts +++ b/src/modbus/connection/growatt.ts @@ -7,7 +7,7 @@ import { import { GrowattInveter1Model, type GrowattInverterModels, -} from '../models/growatt/inveter.js'; +} from '../models/growatt/inverter.js'; import { getModbusConnection } from '../connections.js'; import type { ModbusSchema } from '../../helpers/config.js'; import type { Logger } from 'pino'; diff --git a/src/modbus/models/growatt/inveter.ts b/src/modbus/models/growatt/inverter.ts similarity index 100% rename from src/modbus/models/growatt/inveter.ts rename to src/modbus/models/growatt/inverter.ts From 378907e2430bb5be07170c3ceee788b33c2f71d4 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sun, 3 Nov 2024 15:28:11 +1100 Subject: [PATCH 16/21] Growatt inverter control experiment --- src/inverter/growatt/index.ts | 24 +++++++++++++--- src/modbus/connection/growatt.ts | 16 +++++++++++ src/modbus/models/growatt/inverterControl.ts | 29 ++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 src/modbus/models/growatt/inverterControl.ts diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index 4b8505b9..7c6566d0 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -97,12 +97,28 @@ export class GrowattInverterDataPoller extends InverterDataPollerBase { this.growattConnection.onDestroy(); } - // eslint-disable-next-line @typescript-eslint/require-await override async onControl( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _inverterConfiguration: InverterConfiguration, + inverterConfiguration: InverterConfiguration, ): Promise { - throw new Error('Method not implemented.'); + const targetPowerRatio = (() => { + switch (inverterConfiguration.type) { + case 'disconnect': + return 0; + case 'limit': + return ( + (inverterConfiguration.targetSolarWatts / + inverterConfiguration.invertersCount - + 250) / + 6000 + ); + } + })(); + + const ActivePRate = Math.floor(targetPowerRatio * 100); + + await this.growattConnection.writeInverterControlModel({ + ActivePRate, + }); } } diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts index aa8a2384..9e10b023 100644 --- a/src/modbus/connection/growatt.ts +++ b/src/modbus/connection/growatt.ts @@ -11,6 +11,10 @@ import { import { getModbusConnection } from '../connections.js'; import type { ModbusSchema } from '../../helpers/config.js'; import type { Logger } from 'pino'; +import { + GrowattInverterControl1Model, + type GrowattInverterControl, +} from '../models/growatt/inverterControl.js'; export class GrowattConnection { protected readonly modbusConnection: ModbusConnection; @@ -63,6 +67,18 @@ export class GrowattConnection { return data; } + async writeInverterControlModel(values: GrowattInverterControl) { + return await GrowattInverterControl1Model.write({ + modbusConnection: this.modbusConnection, + address: { + start: 3, + length: 1, + }, + unitId: this.unitId, + values, + }); + } + public onDestroy(): void { this.modbusConnection.close(); } diff --git a/src/modbus/models/growatt/inverterControl.ts b/src/modbus/models/growatt/inverterControl.ts new file mode 100644 index 00000000..6c4113b9 --- /dev/null +++ b/src/modbus/models/growatt/inverterControl.ts @@ -0,0 +1,29 @@ +import { + registersToUint16, + uint16ToRegisters, +} from '../../helpers/converters.js'; +import { modbusModelFactory } from '../../modbusModelFactory.js'; + +export type GrowattInverterControl = GrowattInverterControl1; + +export type GrowattInverterControl1 = { + // Inverter Max output active power percent + // 0-100 or 255 + // 255: power is not belimited + ActivePRate: number; +}; + +export const GrowattInverterControl1Model = modbusModelFactory< + GrowattInverterControl1, + keyof GrowattInverterControl1 +>({ + name: 'GrowattInverterControl1Model', + mapping: { + ActivePRate: { + start: 0, + end: 1, + readConverter: registersToUint16, + writeConverter: uint16ToRegisters, + }, + }, +}); From 374a8e6423018749ffc7e1d1b67b69b9837e069d Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sun, 3 Nov 2024 15:36:34 +1100 Subject: [PATCH 17/21] Clamp value --- src/inverter/growatt/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index 7c6566d0..c5feef66 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -114,7 +114,12 @@ export class GrowattInverterDataPoller extends InverterDataPollerBase { } })(); - const ActivePRate = Math.floor(targetPowerRatio * 100); + // clamp between 0 and 100 + // no decimal points, round down + const ActivePRate = Math.max( + Math.min(Math.floor(targetPowerRatio * 100), 100), + 0, + ); await this.growattConnection.writeInverterControlModel({ ActivePRate, From f1f0c5d54846cc5a5e9c6b2f239e898350e94fb2 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Sat, 9 Nov 2024 20:00:26 +1100 Subject: [PATCH 18/21] Lint --- src/inverter/growatt/index.ts | 6 +++--- src/meters/growatt/index.ts | 8 ++++---- src/modbus/connection/growatt.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index c5feef66..12621544 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -1,15 +1,15 @@ import { type InverterData } from '../inverterData.js'; -import type { Result } from '../../helpers/result.js'; +import { type Result } from '../../helpers/result.js'; import { ConnectStatus } from '../../sep2/models/connectStatus.js'; import { OperationalModeStatus } from '../../sep2/models/operationModeStatus.js'; import { DERTyp } from '../../sunspec/models/nameplate.js'; import { InverterDataPollerBase } from '../inverterDataPollerBase.js'; import { type InverterConfiguration } from '../../coordinator/helpers/inverterController.js'; -import type { Config } from '../../helpers/config.js'; +import { type Config } from '../../helpers/config.js'; import { withRetry } from '../../helpers/withRetry.js'; import { writeLatency } from '../../helpers/influxdb.js'; import { GrowattConnection } from '../../modbus/connection/growatt.js'; -import type { GrowattInverterModels } from '../../modbus/models/growatt/inverter.js'; +import { type GrowattInverterModels } from '../../modbus/models/growatt/inverter.js'; export class GrowattInverterDataPoller extends InverterDataPollerBase { private growattConnection: GrowattConnection; diff --git a/src/meters/growatt/index.ts b/src/meters/growatt/index.ts index feb71eef..b4c8031e 100644 --- a/src/meters/growatt/index.ts +++ b/src/meters/growatt/index.ts @@ -1,9 +1,9 @@ -import type { SiteSample } from '../siteSample.js'; +import { type SiteSample } from '../siteSample.js'; import { SiteSamplePollerBase } from '../siteSamplePollerBase.js'; -import type { Result } from '../../helpers/result.js'; -import type { Config } from '../../helpers/config.js'; +import { type Result } from '../../helpers/result.js'; +import { type Config } from '../../helpers/config.js'; import { GrowattConnection } from '../../modbus/connection/growatt.js'; -import type { GrowattMeterModels } from '../../modbus/models/growatt/meter.js'; +import { type GrowattMeterModels } from '../../modbus/models/growatt/meter.js'; type GrowattMeterConfig = Extract; diff --git a/src/modbus/connection/growatt.ts b/src/modbus/connection/growatt.ts index 9e10b023..f717f745 100644 --- a/src/modbus/connection/growatt.ts +++ b/src/modbus/connection/growatt.ts @@ -1,4 +1,4 @@ -import type { ModbusConnection } from './base.js'; +import { type ModbusConnection } from './base.js'; import { GrowattMeter1Model, GrowattMeter2Model, @@ -9,8 +9,8 @@ import { type GrowattInverterModels, } from '../models/growatt/inverter.js'; import { getModbusConnection } from '../connections.js'; -import type { ModbusSchema } from '../../helpers/config.js'; -import type { Logger } from 'pino'; +import { type ModbusSchema } from '../../helpers/config.js'; +import { type Logger } from 'pino'; import { GrowattInverterControl1Model, type GrowattInverterControl, From 3ab7bddfb9630ede7195a995b1543d5f05fa8537 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Mon, 11 Nov 2024 17:17:10 +1100 Subject: [PATCH 19/21] Merge fixes --- src/{ => connections}/modbus/models/growatt/inverter.ts | 0 .../modbus/models/growatt/inverterControl.ts | 0 src/{ => connections}/modbus/models/growatt/meter.ts | 0 src/inverter/growatt/index.ts | 6 +++--- src/meters/growatt/index.ts | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/{ => connections}/modbus/models/growatt/inverter.ts (100%) rename src/{ => connections}/modbus/models/growatt/inverterControl.ts (100%) rename src/{ => connections}/modbus/models/growatt/meter.ts (100%) diff --git a/src/modbus/models/growatt/inverter.ts b/src/connections/modbus/models/growatt/inverter.ts similarity index 100% rename from src/modbus/models/growatt/inverter.ts rename to src/connections/modbus/models/growatt/inverter.ts diff --git a/src/modbus/models/growatt/inverterControl.ts b/src/connections/modbus/models/growatt/inverterControl.ts similarity index 100% rename from src/modbus/models/growatt/inverterControl.ts rename to src/connections/modbus/models/growatt/inverterControl.ts diff --git a/src/modbus/models/growatt/meter.ts b/src/connections/modbus/models/growatt/meter.ts similarity index 100% rename from src/modbus/models/growatt/meter.ts rename to src/connections/modbus/models/growatt/meter.ts diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index 12621544..2d048800 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -2,14 +2,14 @@ import { type InverterData } from '../inverterData.js'; import { type Result } from '../../helpers/result.js'; import { ConnectStatus } from '../../sep2/models/connectStatus.js'; import { OperationalModeStatus } from '../../sep2/models/operationModeStatus.js'; -import { DERTyp } from '../../sunspec/models/nameplate.js'; import { InverterDataPollerBase } from '../inverterDataPollerBase.js'; import { type InverterConfiguration } from '../../coordinator/helpers/inverterController.js'; import { type Config } from '../../helpers/config.js'; import { withRetry } from '../../helpers/withRetry.js'; import { writeLatency } from '../../helpers/influxdb.js'; -import { GrowattConnection } from '../../modbus/connection/growatt.js'; -import { type GrowattInverterModels } from '../../modbus/models/growatt/inverter.js'; +import { type GrowattInverterModels } from '../../connections/modbus/models/growatt/inverter.js'; +import { GrowattConnection } from '../../connections/modbus/connection/growatt.js'; +import { DERTyp } from '../../connections/sunspec/models/nameplate.js'; export class GrowattInverterDataPoller extends InverterDataPollerBase { private growattConnection: GrowattConnection; diff --git a/src/meters/growatt/index.ts b/src/meters/growatt/index.ts index b4c8031e..f5f5610e 100644 --- a/src/meters/growatt/index.ts +++ b/src/meters/growatt/index.ts @@ -2,8 +2,8 @@ import { type SiteSample } from '../siteSample.js'; import { SiteSamplePollerBase } from '../siteSamplePollerBase.js'; import { type Result } from '../../helpers/result.js'; import { type Config } from '../../helpers/config.js'; -import { GrowattConnection } from '../../modbus/connection/growatt.js'; -import { type GrowattMeterModels } from '../../modbus/models/growatt/meter.js'; +import { type GrowattMeterModels } from '../../connections/modbus/models/growatt/meter.js'; +import { GrowattConnection } from '../../connections/modbus/connection/growatt.js'; type GrowattMeterConfig = Extract; From ff14dd51f9b06cefd8949f12ff45c4136a026eb5 Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Tue, 12 Nov 2024 21:19:12 +1100 Subject: [PATCH 20/21] Fix types --- src/inverter/growatt/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index 2d048800..7562535b 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -1,7 +1,7 @@ import { type InverterData } from '../inverterData.js'; import { type Result } from '../../helpers/result.js'; -import { ConnectStatus } from '../../sep2/models/connectStatus.js'; -import { OperationalModeStatus } from '../../sep2/models/operationModeStatus.js'; +import { ConnectStatusValue } from '../../sep2/models/connectStatus.js'; +import { OperationalModeStatusValue } from '../../sep2/models/operationModeStatus.js'; import { InverterDataPollerBase } from '../inverterDataPollerBase.js'; import { type InverterConfiguration } from '../../coordinator/helpers/inverterController.js'; import { type Config } from '../../helpers/config.js'; @@ -161,10 +161,10 @@ export function generateInverterData({ export function generateInverterDataStatus(): InverterData['status'] { return { - operationalModeStatus: OperationalModeStatus.OperationalMode, + operationalModeStatus: OperationalModeStatusValue.OperationalMode, genConnectStatus: - ConnectStatus.Available | - ConnectStatus.Connected | - ConnectStatus.Operating, + ConnectStatusValue.Available | + ConnectStatusValue.Connected | + ConnectStatusValue.Operating, }; } From d564c2abaa1383263e608a14957b0dc1596f2b2d Mon Sep 17 00:00:00 2001 From: Long Zheng Date: Mon, 18 Nov 2024 13:10:42 +1100 Subject: [PATCH 21/21] Fix return types --- src/inverter/growatt/index.ts | 79 +++++++++++------------------------ src/meters/growatt/index.ts | 32 +++++--------- 2 files changed, 35 insertions(+), 76 deletions(-) diff --git a/src/inverter/growatt/index.ts b/src/inverter/growatt/index.ts index 7562535b..b3c6efcf 100644 --- a/src/inverter/growatt/index.ts +++ b/src/inverter/growatt/index.ts @@ -1,11 +1,9 @@ import { type InverterData } from '../inverterData.js'; -import { type Result } from '../../helpers/result.js'; import { ConnectStatusValue } from '../../sep2/models/connectStatus.js'; import { OperationalModeStatusValue } from '../../sep2/models/operationModeStatus.js'; import { InverterDataPollerBase } from '../inverterDataPollerBase.js'; import { type InverterConfiguration } from '../../coordinator/helpers/inverterController.js'; import { type Config } from '../../helpers/config.js'; -import { withRetry } from '../../helpers/withRetry.js'; import { writeLatency } from '../../helpers/influxdb.js'; import { type GrowattInverterModels } from '../../connections/modbus/models/growatt/inverter.js'; import { GrowattConnection } from '../../connections/modbus/connection/growatt.js'; @@ -38,59 +36,32 @@ export class GrowattInverterDataPoller extends InverterDataPollerBase { void this.startPolling(); } - override async getInverterData(): Promise> { - try { - return await withRetry( - async () => { - const start = performance.now(); - - const inverterModel = - await this.growattConnection.getInverterModel(); - - writeLatency({ - field: 'GrowattInverterDataPoller', - duration: performance.now() - start, - tags: { - inverterIndex: this.inverterIndex.toString(), - model: 'inverter', - }, - }); - - const models: InverterModels = { - inverter: inverterModel, - }; - - const end = performance.now(); - const duration = end - start; - - this.logger.trace( - { duration, models }, - 'Got inverter data', - ); + override async getInverterData(): Promise { + const start = performance.now(); + + const inverterModel = await this.growattConnection.getInverterModel(); + + writeLatency({ + field: 'GrowattInverterDataPoller', + duration: performance.now() - start, + tags: { + inverterIndex: this.inverterIndex.toString(), + model: 'inverter', + }, + }); + + const models: InverterModels = { + inverter: inverterModel, + }; + + const end = performance.now(); + const duration = end - start; + + this.logger.trace({ duration, models }, 'Got inverter data'); + + const inverterData = generateInverterData(models); - const inverterData = generateInverterData(models); - - return { - success: true, - value: inverterData, - }; - }, - { - attempts: 3, - delayMilliseconds: 100, - functionName: 'get inverter data', - }, - ); - } catch (error) { - this.logger.error(error, 'Failed to get inverter data'); - - return { - success: false, - error: new Error( - `Error loading inverter data: ${error instanceof Error ? error.message : 'Unknown error'}`, - ), - }; - } + return inverterData; } override onDestroy(): void { diff --git a/src/meters/growatt/index.ts b/src/meters/growatt/index.ts index f5f5610e..3aaebf72 100644 --- a/src/meters/growatt/index.ts +++ b/src/meters/growatt/index.ts @@ -1,6 +1,5 @@ import { type SiteSample } from '../siteSample.js'; import { SiteSamplePollerBase } from '../siteSamplePollerBase.js'; -import { type Result } from '../../helpers/result.js'; import { type Config } from '../../helpers/config.js'; import { type GrowattMeterModels } from '../../connections/modbus/models/growatt/meter.js'; import { GrowattConnection } from '../../connections/modbus/connection/growatt.js'; @@ -22,32 +21,21 @@ export class GrowattMeterSiteSamplePoller extends SiteSamplePollerBase { void this.startPolling(); } - override async getSiteSample(): Promise> { - try { - const start = performance.now(); + override async getSiteSample(): Promise { + const start = performance.now(); - const meterModel = await this.growattConnection.getMeterModel(); + const meterModel = await this.growattConnection.getMeterModel(); - const end = performance.now(); - const duration = end - start; + const end = performance.now(); + const duration = end - start; - this.logger.trace({ duration, meterModel }, 'polled meter data'); + this.logger.trace({ duration, meterModel }, 'polled meter data'); - const siteSample = generateSiteSample({ - meter: meterModel, - }); + const siteSample = generateSiteSample({ + meter: meterModel, + }); - return { success: true, value: siteSample }; - } catch (error) { - return { - success: false, - error: new Error( - `Error loading meter data: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - ), - }; - } + return siteSample; } override onDestroy() {