UNPKG

iobroker.sun2000

Version:
1,562 lines (1,547 loc) 64.8 kB
'use strict'; const { deviceType, driverClasses, storeType, getDeviceStatusInfo, batteryStatus, dataRefreshRate, dataType } = require(`${__dirname}/../types.js`); const { RiemannSum, isSunshine } = require(`${__dirname}/../tools.js`); const DriverBase = require(`${__dirname}/driver_base.js`); const ServiceQueueMap = require(`${__dirname}/../controls/inverter_service_queue.js`); class InverterInfo extends DriverBase { constructor(stateInstance, device) { super(stateInstance, device, { name: 'Huawei DriverInfo', }); this._newInstance = null; //https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md const newFields = [ { address: 30000, length: 71, info: 'inverter model info (indicator)', type: deviceType.inverter, states: [ { state: { id: 'info.model', name: 'model', type: 'string', role: 'info.name', desc: 'reg:30000, len:15' }, register: { reg: 30000, type: dataType.string, length: 8 }, }, { state: { id: 'info.serialNumber', name: 'serial number', type: 'string', role: 'info.serial', desc: 'reg:30015, len:10' }, register: { reg: 30015, type: dataType.string, length: 6 }, }, { state: { id: 'info.modelID', name: 'Model ID', type: 'number', role: 'info.hardware', desc: 'reg:30070, len:1' }, register: { reg: 30070, type: dataType.uint16 }, }, ], readErrorHook: () => { //err,reg this.log.error(`Can not connect to Huawei inverter for modbus ID ${this._modbusId}!`); //reg.lastread = this._newNowTime(); //try it once //return true; }, postHook: path => { const detectedModelId = this.stateCache.get(`${path}info.modelID`)?.value; if (detectedModelId) { const model = this.stateCache.get(`${path}info.model`)?.value; this.log.info(`Identified a Huawei ${model} model ${detectedModelId} for modbus ID ${this._modbusId}`); const model_sun2000M1 = [424, 425, 426, 427, 428, 429, 463, 142]; if (model_sun2000M1.includes(detectedModelId) || detectedModelId >= 430) { this._newInstance = new InverterSun2000_M1(this.state, device, { modelId: detectedModelId }); } else { this._newInstance = new InverterSun2000(this.state, device, { modelId: detectedModelId }); } } else { this.log.error(`Huawei inverter could not be identified for modbus ID ${this._modbusId}!`); } }, }, ]; this.registerFields.push.apply(this.registerFields, newFields); } get newInstance() { return this._newInstance; } //overload get modbusAllowed() { if (isSunshine(this.adapter)) { if (!this._modbusAllowed) { this._modbusAllowed = true; this._errorCount = 0; } } else { if (this._errorCount > 3 && this._modbusAllowed) { this.log.warn(`It will try again when the sun rises for modbus id ${this._modbusId} :-)`); this._modbusAllowed = false; } } return this._modbusAllowed; } } class InverterSun2000 extends DriverBase { constructor(stateInstance, inverter, options) { super(stateInstance, inverter, { name: 'sun2000', driverClass: driverClasses.inverter, ...options, }); //TestMode //this._testMode = this.adapter.settings.address === '192.168.2.54'; this._testMode = false; this.log.debug(`### TestMode (#60) ${this._testMode} ${this.adapter.settings.address}`); this.solarSum = new RiemannSum(); this.adapter.getState(`${this.deviceInfo.path}.derived.dailySolarYield`, (err, state) => { if (!err && state) { this.solarSum.setStart(state?.val, state?.ts); } }); this.activePowerSum = new RiemannSum(); this.adapter.getState(`${this.deviceInfo.path}.derived.dailyActiveEnergy`, (err, state) => { if (!err && state) { this.activePowerSum.setStart(state?.val, state?.ts); } }); this.control = new ServiceQueueMap(this.adapter, this.deviceInfo); //https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md const newFields = [ { address: 30000, length: 83, //NRGKick und enpal-box info: 'inverter model info', type: deviceType.inverter, states: [ { state: { id: 'info.model', name: 'Model', type: 'string', role: 'info.name' }, register: { reg: 30000, type: dataType.string, length: 8 }, store: storeType.never, }, { state: { id: 'info.modelID', name: 'Model ID', type: 'number', role: 'info.hardware' }, register: { reg: 30070, type: dataType.uint16 }, store: storeType.never, }, { state: { id: 'info.serialNumber', name: 'Serial number', type: 'string', role: 'info.serial' }, register: { reg: 30015, type: dataType.string, length: 6 }, store: storeType.never, }, { state: { id: 'info.numberPVStrings', name: 'Number of PV strings', type: 'number', unit: '', role: 'value', desc: 'reg:30071, len:1' }, register: { reg: 30071, type: dataType.uint16 }, }, { state: { id: 'info.numberMPPTrackers', name: 'Number of MPP trackers', type: 'number', unit: '', role: 'value', desc: 'reg:30072, len:1', }, register: { reg: 30072, type: dataType.uint16 }, }, { state: { id: 'info.ratedPower', name: 'Rated power', type: 'number', unit: 'kW', role: 'value.power', desc: 'reg:30073, len:2' }, register: { reg: 30073, type: dataType.int32, gain: 1000 }, }, ], postHook: () => { if (!this._testMode) return; this.log.debug('### TEST MODE - PostHook for InverterSun2000 ####'); this.identifySubdevices('sun2000', this.modbusId) .then(ret => { this.log.debug(`### PostHook for InverterSun2000 - ret: ${JSON.stringify(ret)}`); for (const [i, inverter] of ret.entries()) { this.log.info(`${this._name} identifies an inverter sun2000: OID=${inverter.obj_id}, modbus id: ${inverter.slave_id}`); const device = { index: i, duration: 0, modbusId: inverter.slave_id, driverClass: driverClasses.emmaCharger, }; this.adapter.devices.push(device); this.adapter.initDevicePath(device); } }) .catch(err => { this.log.warn(`### PostHook for InverterSun2000 - err: ${err}`); }); }, }, { address: 32080, length: 2, info: 'Inverter Activ Power', refresh: dataRefreshRate.high, type: deviceType.inverter, states: [ { state: { id: 'activePower', name: 'Active power', type: 'number', unit: 'kW', role: 'value.power.active', desc: 'reg:32080, len:2, Power currently used', }, register: { reg: 32080, type: dataType.int32, gain: 1000 }, store: storeType.always, }, ], postHook: path => { //calculate daily active energy const activePower = this.stateCache.get(`${path}activePower`)?.value ?? 0; this.activePowerSum.add(activePower); //riemann Sum }, }, { address: 37765, length: 2, info: 'Battery Charge And Discharge Power', refresh: dataRefreshRate.high, type: deviceType.battery, states: [ { state: { id: 'battery.chargeDischargePower', name: 'Charge/Discharge power', desc: 'reg:37765, len:2 (>0 charging, <0 discharging)', type: 'number', unit: 'kW', role: 'value.power', }, register: { reg: 37765, type: dataType.int32, gain: 1000 }, }, ], //Check if the address field is active checkIfActive: () => this._batteryExists(), }, { address: 32064, length: 2, info: 'Input Power', refresh: dataRefreshRate.high, type: deviceType.inverter, states: [ { state: { id: 'inputPower', name: 'Input power', type: 'number', unit: 'kW', role: 'value.power.produced', desc: 'reg:32064, len:2, Power from solar', }, register: { reg: 32064, type: dataType.int32, gain: 1000 }, store: storeType.always, }, { state: { id: 'derived.inputPowerWithEfficiencyLoss', name: 'Input power with efficiency loss', type: 'number', unit: 'kW', role: 'value.power', desc: 'Power from solar with efficiency loss', }, }, ], postHook: path => { //https://community.home-assistant.io/t/integration-solar-inverter-huawei-2000l/132350/1483?u=wlcrs const inPower = this.stateCache.get(`${path}inputPower`)?.value; //https://wiki.selfhtml.org/wiki/JavaScript/Operatoren/Optional_Chaining_Operator //const ratedPower = state ? state.val : undefined; const ratedPower = this.stateCache.get(`${path}info.ratedPower`)?.value; let inPowerEff = inPower; if (inPower < ratedPower * 0.2) { if (inPower < ratedPower * 0.1) { inPowerEff *= 0.9; } else { inPowerEff *= 0.95; } } else { inPowerEff *= 0.98; } this.stateCache.set(`${path}derived.inputPowerWithEfficiencyLoss`, inPowerEff, { type: 'number' }); this.solarSum.add(inPowerEff); //riemann Sum }, }, { address: 37052, length: 10, info: 'battery unit1 (indicator)', states: [ { state: { id: 'battery.unit.1.SN', name: 'Serial number', type: 'string', unit: '', role: 'value' }, register: { reg: 37052, type: dataType.string, length: 6 }, store: storeType.never, }, ], readErrorHook: (err, reg) => { //modbus Error 2 - illegal address if (err.modbusCode === 2) { reg.lastread = this._newNowTime(); //try it once this.stateCache.set(`${this._getStatePath(reg.type)}battery.unit.1.SN`, '', { stored: true }); return true; //error self handle } }, }, //-- { address: 38200, length: 10, info: 'battery unit1 Pack1 (indicator)', states: [ { state: { id: 'battery.unit.1.batteryPack.1.SN', name: 'Serial number', type: 'string', unit: '', role: 'value' }, register: { reg: 38200, type: dataType.string, length: 6 }, store: storeType.never, }, ], readErrorHook: (err, reg) => { //modbus Error 2 - illegal address if (err.modbusCode === 2) { reg.lastread = this._newNowTime(); //try it only once this.stateCache.set(`${this._getStatePath(reg.type)}battery.unit.1.batteryPack.1.SN`, '', { stored: true }); return true; //error self handle } }, }, { address: 38242, length: 10, info: 'battery unit1 Pack2 (indicator)', states: [ { state: { id: 'battery.unit.1.batteryPack.2.SN', name: 'Serial number', type: 'string', unit: '', role: 'value' }, register: { reg: 38242, type: dataType.string, length: 6 }, store: storeType.never, }, ], readErrorHook: (err, reg) => { //modbus Error 2 - illegal address if (err.modbusCode === 2) { reg.lastread = this._newNowTime(); //try it only once this.stateCache.set(`${this._getStatePath(reg.type)}battery.unit.1.batteryPack.2.SN`, '', { stored: true }); return true; //error self handle } }, }, { address: 38284, length: 10, info: 'battery unit1 Pack3 (indicator)', states: [ { state: { id: 'battery.unit.1.batteryPack.3.SN', name: 'Serial number', type: 'string', unit: '', role: 'value' }, register: { reg: 38284, type: dataType.string, length: 6 }, store: storeType.never, }, ], readErrorHook: (err, reg) => { //modbus Error 2 - illegal address if (err.modbusCode === 2) { reg.lastread = this._newNowTime(); //try it only once this.stateCache.set(`${this._getStatePath(reg.type)}battery.unit.1.batteryPack.3.SN`, '', { stored: true }); return true; //error self handle } }, }, { address: 38229, length: 13, info: 'battery Pack1 information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.batteryPack.1.SOC', name: 'State of capacity', type: 'number', unit: '%', role: 'value.battery', desc: 'reg:38229 len:1', }, register: { reg: 38229, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.unit.1.batteryPack.1.totalCharge', name: 'Total charge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:38238, len:2', }, register: { reg: 38238, type: dataType.uint32, gain: 100 }, }, { state: { id: 'battery.unit.1.batteryPack.1.totalDischarge', name: 'Total discharge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:38240, len:2', }, register: { reg: 38240, type: dataType.uint32, gain: 100 }, }, ], checkIfActive: () => this._batteryExists(1, 1), }, { address: 38271, length: 13, info: 'battery Pack2 information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.batteryPack.2.SOC', name: 'State of capacity', type: 'number', unit: '%', role: 'value.battery', desc: 'reg:38271, len:1', }, register: { reg: 38271, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.unit.1.batteryPack.2.totalCharge', name: 'Total charge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:38280, len:2', }, register: { reg: 38280, type: dataType.uint32, gain: 100 }, }, { state: { id: 'battery.unit.1.batteryPack.2.totalDischarge', name: 'Total discharge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:38282, len:2', }, register: { reg: 38282, type: dataType.uint32, gain: 100 }, }, ], checkIfActive: () => this._batteryExists(1, 2), }, { address: 38313, length: 13, info: 'battery Pack3 information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.batteryPack.3.SOC', name: 'State of capacity', type: 'number', unit: '%', role: 'value.battery', desc: 'reg:38313, len:1', }, register: { reg: 38313, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.unit.1.batteryPack.3.totalCharge', name: 'Total charge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:38322, len:2', }, register: { reg: 38322, type: dataType.uint32, gain: 100 }, }, { state: { id: 'battery.unit.1.batteryPack.3.totalDischarge', name: 'Total discharge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:38324, len:2', }, register: { reg: 38324, type: dataType.uint32, gain: 100 }, }, ], checkIfActive: () => this._batteryExists(1, 3), }, { address: 38233, length: 3, info: 'Battery Pack1 Charge And Discharge Power', refresh: dataRefreshRate.high, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.batteryPack.1.chargeDischargePower', name: 'Charge/Discharge power', desc: 'reg:38233, len:2 (>0 charging, <0 discharging)', type: 'number', unit: 'kW', role: 'value.power', }, register: { reg: 38233, type: dataType.int32, gain: 1000 }, }, { state: { id: 'battery.unit.1.batteryPack.1.voltage', name: 'Voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:38235, len:1', }, register: { reg: 38235, type: dataType.uint16, gain: 10 }, }, ], checkIfActive: () => this._batteryExists(1, 1), }, { address: 38275, length: 3, info: 'Battery Pack2 Charge And Discharge Power', refresh: dataRefreshRate.high, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.batteryPack.2.chargeDischargePower', name: 'Charge/Discharge power', desc: 'reg:38275, len:2 (>0 charging, <0 discharging)', type: 'number', unit: 'kW', role: 'value.power', }, register: { reg: 38275, type: dataType.int32, gain: 1000 }, }, { state: { id: 'battery.unit.1.batteryPack.2.voltage', name: 'Voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:38277, len:1', }, register: { reg: 38277, type: dataType.uint16, gain: 10 }, }, ], checkIfActive: () => this._batteryExists(1, 2), }, { address: 38317, length: 3, info: 'Battery Pack3 Charge And Discharge Power', refresh: dataRefreshRate.high, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.batteryPack.3.chargeDischargePower', name: 'Charge/Discharge power', desc: 'reg:38317, len:2 (>0 charging, <0 discharging)', type: 'number', unit: 'kW', role: 'value.power', }, register: { reg: 38317, type: dataType.int32, gain: 1000 }, }, { state: { id: 'battery.unit.1.batteryPack.3.voltage', name: 'Voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:38319, len:1', }, register: { reg: 38319, type: dataType.uint16, gain: 10 }, }, ], checkIfActive: () => this._batteryExists(1, 3), }, //++ { address: 37000, length: 23, info: 'battery Unit1 information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.runningStatus', name: 'Running status', type: 'number', unit: '', role: 'value', desc: 'reg:37000, len:1', }, register: { reg: 37000, type: dataType.uint16 }, //mapper: value => Promise.resolve(batteryStatus[value]), }, { state: { id: 'battery.unit.1.batterySOC', name: 'Battery SOC', type: 'number', unit: '%', role: 'value.battery', desc: 'reg:37004, len:1', }, register: { reg: 37004, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.unit.1.RatedChargePower', name: 'Rated charge power', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:37007, len:2', }, register: { reg: 37007, type: dataType.uint32 }, }, { state: { id: 'battery.unit.1.RatedDischargePower', name: 'Rated discharge power', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:37009, len:2', }, register: { reg: 37009, type: dataType.uint32 }, }, { state: { id: 'battery.unit.1.batteryTemperature', name: 'Battery temperature', type: 'number', unit: '°C', role: 'value.temperature', desc: 'reg:37022, len:1', }, register: { reg: 37022, type: dataType.uint16, gain: 10 }, mapper: value => Promise.resolve(this._checkValidNumber(value, -100, 100)), }, ], checkIfActive: () => this._batteryExists(1), }, { address: 37046, length: 4, info: 'battery information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.maximumChargePower', name: 'Maximum charge power', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:37046, len:2', }, register: { reg: 37046, type: dataType.uint32 }, }, { state: { id: 'battery.maximumDischargePower', name: 'Maximum discharge power', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:37048, len:2', }, register: { reg: 37048, type: dataType.uint32 }, }, ], checkIfActive: () => this._batteryExists(), }, { address: 37758, length: 30, info: 'battery information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.ratedCapacity', name: 'Rated capacity', type: 'number', unit: 'Wh', role: 'value.capacity', desc: 'reg:37758, len:2', }, register: { reg: 37758, type: dataType.uint32 }, }, { state: { id: 'battery.SOC', name: 'State of capacity', type: 'number', unit: '%', role: 'value.battery', desc: 'reg:37760, len:1' }, register: { reg: 37760, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.runningStatus', name: 'Running status', type: 'number', role: 'value', desc: 'reg:37762, len:1' }, register: { reg: 37762, type: dataType.uint16, length: 1 }, //mapper: value => Promise.resolve(batteryStatus[value]), }, { state: { id: 'battery.derived.runningStatus', name: 'Running status', type: 'string', unit: '', role: 'value' }, }, { state: { id: 'battery.busVoltage', name: 'Bus voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37763, len:1' }, register: { reg: 37763, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.busCurrent', name: 'Bus current', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:37764, len:1' }, register: { reg: 37764, type: dataType.int16, gain: 10 }, }, { state: { id: 'battery.totalCharge', name: 'Total charge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:37780, len:2', }, register: { reg: 37780, type: dataType.uint32, gain: 100 }, }, { state: { id: 'battery.totalDischarge', name: 'Total discharge', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:37782, len:2', }, register: { reg: 37782, type: dataType.uint32, gain: 100 }, }, { state: { id: 'battery.currentDayChargeCapacity', name: 'Current day charge capacity', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:37784, len:2', }, register: { reg: 37784, type: dataType.uint32, gain: 100 }, }, { state: { id: 'battery.currentDayDischargeCapacity', name: 'Current day discharge capacity', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:37786, len:2', }, register: { reg: 37786, type: dataType.uint32, gain: 100 }, }, ], checkIfActive: () => this._batteryExists(), postHook: path => { const runningStatus = this.stateCache.get(`${path}battery.runningStatus`)?.value; this.stateCache.set(`${path}battery.derived.runningStatus`, batteryStatus[runningStatus]); }, }, { //for NRGKick address: 47000, length: 1, info: 'battery unit1 (static)', type: deviceType.battery, states: [ { state: { id: 'battery.unit.1.productMode', name: 'Product mode', type: 'number', unit: '', role: 'value', desc: 'reg:47000, len:1' }, register: { reg: 47000, type: dataType.uint16 }, }, ], checkIfActive: () => this._batteryExists(1), }, { address: 47075, length: 14, info: 'additional battery information', refresh: dataRefreshRate.low, type: deviceType.battery, states: [ { state: { id: 'battery.maximumChargingPower', name: 'Maximum charging power', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:47075, len:2', }, register: { reg: 47075, type: dataType.uint32 }, }, { state: { id: 'battery.maximumDischargingPower', name: 'Maximum discharging power', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:47077, len:2', }, register: { reg: 47077, type: dataType.uint32 }, }, { state: { id: 'battery.chargingCutoffCapacity', name: 'Charging cutoff capacity', type: 'number', unit: '%', role: 'value', desc: 'reg:47081, len:1', }, register: { reg: 47081, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.dischargeCutoffCapacity', name: 'Discharge cutoff capacity', type: 'number', unit: '%', role: 'value', desc: 'reg:47082, len:1', }, register: { reg: 47082, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.forcedChargeDischargePeriod', name: 'Forced charge discharge period', type: 'number', unit: 'mins', role: 'value', desc: 'reg:47083, len:1', }, register: { reg: 47083, type: dataType.uint16 }, }, { state: { id: 'battery.workingModeSettings', name: 'Working mode settings', type: 'number', unit: '', role: 'value', desc: 'reg:47086, len:1', }, register: { reg: 47086, type: dataType.uint16 }, }, { state: { id: 'battery.chargeFromGridFunction', name: 'Charge from grid function', type: 'number', unit: '', role: 'value', desc: 'reg:47087, len:1', }, register: { reg: 47087, type: dataType.uint16 }, }, { state: { id: 'battery.gridChargeCutoffSOC', name: 'Grid charge cutoff SOC', type: 'number', unit: '%', role: 'value', desc: 'reg:47088, len:1', }, register: { reg: 47088, type: dataType.uint16, gain: 10 }, }, ], checkIfActive: () => this._batteryExists(), }, { address: 47101, length: 6, info: 'additional battery information', type: deviceType.battery, states: [ { state: { id: 'battery.targetSOC', name: 'Target SOC', type: 'number', unit: '%', role: 'value', desc: 'reg: 47101 , len: 1' }, register: { reg: 47101, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.backupPowerSOC', name: 'Backup power SOC', type: 'number', unit: '%', role: 'value', desc: 'reg: 47102, len: 1' }, register: { reg: 47102, type: dataType.uint16, gain: 10 }, }, { state: { id: 'battery.productModel', name: 'Product model', type: 'number', unit: '', role: 'value', desc: 'reg: 47106 , len: 1' }, register: { reg: 47106, type: dataType.uint16 }, }, ], checkIfActive: () => this._batteryExists(), }, { address: 32000, length: 11, info: 'inverter status', refresh: dataRefreshRate.low, type: deviceType.inverter, states: [ { state: { id: 'state1', name: 'State 1', type: 'number', unit: '', role: 'value', desc: 'reg:32000, len:1' }, register: { reg: 32000, type: dataType.uint16 }, }, { state: { id: 'state2', name: 'State 2', type: 'number', unit: '', role: 'value', desc: 'reg:32001, len:1' }, register: { reg: 32001, type: dataType.uint16 }, }, { state: { id: 'state3', name: 'State 3', type: 'number', unit: '', role: 'value', desc: 'reg:32002, len:1' }, register: { reg: 32002, type: dataType.uint16 }, }, { state: { id: 'alarm1', name: 'Alarm 1', type: 'number', unit: '', role: 'value', desc: 'reg:32008, len:1' }, register: { reg: 32008, type: dataType.uint16 }, }, { state: { id: 'alarm2', name: 'Alarm 2', type: 'number', unit: '', role: 'value', desc: 'reg:32009, len:1' }, register: { reg: 32009, type: dataType.uint16 }, }, { state: { id: 'alarm3', name: 'Alarm 3', type: 'number', unit: '', role: 'value', desc: 'reg:32010, len:1' }, register: { reg: 32010, type: dataType.uint16 }, }, ], }, { address: 32016, length: 48, info: 'inverter PV strings', refresh: dataRefreshRate.medium, type: deviceType.inverter, states: [], //Before 32000 read preHook: (path, reg) => { //create states for strings const noPVString = this.stateCache.get(`${path}info.numberPVStrings`)?.value; if (noPVString > 0) { if (!stringFieldsTemplate.generated) { stringFieldsTemplate.generated = 0; } if (stringFieldsTemplate.generated < noPVString) { for (let i = stringFieldsTemplate.generated; i < noPVString; i++) { //clonen //const statePV = Object.assign({},stringFieldsTemplate.states[0]); const statePV = JSON.parse(JSON.stringify(stringFieldsTemplate.states[0])); const stateCu = JSON.parse(JSON.stringify(stringFieldsTemplate.states[1])); const statePo = JSON.parse(JSON.stringify(stringFieldsTemplate.states[2])); statePV.state.id = `string.PV${i + 1}Voltage`; statePV.register.reg = (stringFieldsTemplate.states[0].register?.reg ?? 0) + i * 2; statePV.register.type = stringFieldsTemplate.states[0].register?.type; //types are not copied?! stateCu.state.id = `string.PV${i + 1}Current`; stateCu.register.reg = (stringFieldsTemplate.states[1].register?.reg ?? 0) + i * 2; stateCu.register.type = stringFieldsTemplate.states[1].register?.type; statePo.state.id = `string.PV${i + 1}Power`; //this.adapter.log.debug('### PUSH STRINGS'); reg.states.push(statePV); reg.states.push(stateCu); reg.states.push(statePo); } reg.length = noPVString * 2; } stringFieldsTemplate.generated = noPVString; //this.adapter.log.debug(JSON.stringify(reg)); } }, //After 32000 read postHook: path => { //set strings const noPVString = this.stateCache.get(`${path}info.numberPVStrings`)?.value; if (noPVString > 0) { for (let i = 1; i <= noPVString; i++) { const voltage = this.stateCache.get(`${path}string.PV${i}Voltage`)?.value; const current = this.stateCache.get(`${path}string.PV${i}Current`)?.value; this.stateCache.set(`${path}string.PV${i}Power`, Math.round(voltage * current), { type: 'number' }); } } }, }, { //read also in standby mode address: 32089, length: 1, info: 'inverter deviceStatus', refresh: dataRefreshRate.low, standby: true, type: deviceType.inverter, states: [ { state: { id: 'deviceStatus', name: 'Device status', type: 'number', unit: '', role: 'value', desc: 'reg:32089, len:1' }, register: { reg: 32089, type: dataType.uint16 }, /* mapper: async value => { if (this._testMode) { this.log.info(`testMode: Die Sonne scheint? ${isSunshine(this.adapter)}`); if (!isSunshine(this.adapter)) { return 2; } //return 2; } return value; }, */ }, { state: { id: 'derived.deviceStatus', name: 'Device status information', type: 'string', unit: '', role: 'value' }, }, ], readErrorHook: (err, reg) => { //(err,reg) if (err.modbusCode === undefined) { reg.lastread = this._newNowTime(); //return true; //Error has been self handled } }, postHook: path => { //DeviceStatus const deviceStatus = this.stateCache.get(`${path}deviceStatus`)?.value; this.stateCache.set(`${path}derived.deviceStatus`, getDeviceStatusInfo(deviceStatus)); }, }, { address: 32066, length: 50, info: 'inverter status', refresh: dataRefreshRate.low, type: deviceType.inverter, states: [ { state: { id: 'grid.voltageL1-L2', name: 'Voltage L1-L2', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32066, len:1' }, register: { reg: 32066, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.voltageL2-L3', name: 'Voltage L2-L3', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32067, len:1' }, register: { reg: 32067, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.voltageL3-L1', name: 'Voltage L3-L1', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32068, len:1' }, register: { reg: 32068, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.voltageL1', name: 'Voltage L1', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32068, len:1' }, register: { reg: 32069, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.voltageL2', name: 'Voltage L2', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32070, len:1' }, register: { reg: 32070, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.voltageL3', name: 'Voltage L3', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32071, len:1' }, register: { reg: 32071, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.currentL1', name: 'Current L1', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:32072, len:2' }, register: { reg: 32072, type: dataType.int32, gain: 1000 }, }, { state: { id: 'grid.currentL2', name: 'Current L2', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:32074, len:2' }, register: { reg: 32074, type: dataType.int32, gain: 1000 }, }, { state: { id: 'grid.currentL3', name: 'Current L3', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:32076, len:2' }, register: { reg: 32076, type: dataType.int32, gain: 1000 }, }, { state: { id: 'peakActivePowerCurrentDay', name: 'Peak active power of current day', type: 'number', unit: 'kW', role: 'value.power.max', desc: 'reg:32078, len:2', }, register: { reg: 32078, type: dataType.int32, gain: 1000 }, }, { state: { id: 'reactivePower', name: 'Reactive power', type: 'number', unit: 'kVar', role: 'value.power.reactive', desc: 'reg:32082, len:2', }, register: { reg: 32082, type: dataType.int32, gain: 1000 }, }, { state: { id: 'powerFactor', name: 'Power factor', type: 'number', unit: '', role: 'value', desc: 'reg:32084, len:1' }, register: { reg: 32084, type: dataType.int16, gain: 1000 }, }, { state: { id: 'grid.frequency', name: 'Grid frequency', type: 'number', unit: 'Hz', role: 'value.frequency', desc: 'reg:32085, len:1' }, register: { reg: 32085, type: dataType.uint16, gain: 100 }, mapper: value => Promise.resolve(this._checkValidNumber(value, 0, 100)), }, { state: { id: 'efficiency', name: 'Efficiency', type: 'number', unit: '%', role: 'value', desc: 'reg:32086, len:1' }, register: { reg: 32086, type: dataType.uint16, gain: 100 }, mapper: value => Promise.resolve(this._checkValidNumber(value, 0, 100)), }, { state: { id: 'internalTemperature', name: 'Internal temperature', type: 'number', unit: '°C', role: 'value.temperature', desc: 'reg:32087, len:1', }, register: { reg: 32087, type: dataType.int16, gain: 10 }, mapper: value => Promise.resolve(this._checkValidNumber(value, -100, 100)), }, { state: { id: 'isulationResistance', name: 'Isulation resistance', type: 'number', unit: 'MOhm', role: 'value', desc: 'reg:32088, len:1', }, register: { reg: 32088, type: dataType.uint16, gain: 1000 }, }, { state: { id: 'faultCode', name: 'Fault code', type: 'number', unit: '', role: 'value', desc: 'reg:32090, len:1' }, register: { reg: 32090, type: dataType.uint16 }, }, { state: { id: 'startupTime', name: 'Startup time', type: 'number', unit: '', role: 'value', desc: 'reg:32091, len:2' }, register: { reg: 32091, type: dataType.uint32 }, }, { state: { id: 'shutdownTime', name: 'Shutdown time', type: 'number', unit: '', role: 'value', desc: 'reg:32093, len:2' }, register: { reg: 32093, type: dataType.uint32 }, }, { state: { id: 'accumulatedEnergyYield', name: 'Accumulated energy yield', type: 'number', unit: 'kWh', role: 'value.power.produced', desc: 'reg:32106, len:2', }, register: { reg: 32106, type: dataType.uint32, gain: 100 }, }, { state: { id: 'dailyEnergyYield', name: 'Daily energy yield', type: 'number', unit: 'kWh', role: 'value.power.produced', desc: 'reg:32114, len:2', }, register: { reg: 32114, type: dataType.uint32, gain: 100 }, }, { state: { id: 'derived.shutdownTime', name: 'shutdown time', type: 'number', unit: '', role: 'value.time', desc: 'fixed time' }, }, { state: { id: 'derived.startupTime', name: 'startup time', type: 'number', unit: '', role: 'value.time', desc: 'fixed time' }, }, ], postHook: path => { /** * Adjusts a given time value by converting it from seconds to milliseconds * and applying the local timezone offset. * * @param {number} value - The time value in seconds to be adjusted. If the * value is greater than zero, it is converted to milliseconds and adjusted * for the local timezone. * @returns {number} - The adjusted time value in milliseconds. */ function fixTime(value) { if (value > 0) { value = value * 1000; const offset = new Date(value).getTimezoneOffset(); value += offset * 60000; } return value; } const shutdown = this.stateCache.get(`${path}shutdownTime`)?.value; this.stateCache.set(`${path}derived.shutdownTime`, fixTime(shutdown), { type: 'number' }); const startup = this.stateCache.get(`${path}startupTime`)?.value; this.stateCache.set(`${path}derived.startupTime`, fixTime(startup), { type: 'number' }); }, }, { address: 40125, length: 3, info: 'grid power scheduling', refresh: dataRefreshRate.low, type: deviceType.inverter, states: [ { state: { id: 'grid.scheduling.activePowerPercentageDerating', name: '[power grid scheduling] Fixed active power derated', type: 'number', unit: '%', role: 'value', desc: 'reg:40125, len:1', }, register: { reg: 40125, type: dataType.uint16, gain: 10 }, }, { state: { id: 'grid.scheduling.FixedActivePowerDerated', name: '[power grid scheduling] Fixed active power derated', type: 'number', unit: 'W', role: 'value.power', desc: 'reg:40126, len:2', }, register: { reg: 40126, type: dataType.uint32 }, }, ], }, { address: 37100, length: 38, info: 'meter info', refresh: dataRefreshRate.high, type: deviceType.meter, states: [ { state: { id: 'meter.status', name: 'Meter status', type: 'number', unit: '', role: 'value', desc: 'reg:37100, len:2 (0: offline 1: normal)', }, register: { reg: 37100, type: dataType.uint16 }, }, { state: { id: 'meter.voltageL1', name: 'Phase 1 voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37101, len:2' }, register: { reg: 37101, type: dataType.int32, gain: 10 }, }, { state: { id: 'meter.voltageL2', name: 'Phase 2 voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37103, len:2' }, register: { reg: 37103, type: dataType.int32, gain: 10 }, }, { state: { id: 'meter.voltageL3', name: 'Phase 3 voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37105, len:2' }, register: { reg: 37105, type: dataType.int32, gain: 10 }, }, { state: { id: 'meter.currentL1', name: 'Phase 1 current', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:37107, len:2' }, register: { reg: 37107, type: dataType.int32, gain: 100 }, }, { state: { id: 'meter.currentL2', name: 'Phase 2 current', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:37109, len:2' }, register: { reg: 37109, type: dataType.int32, gain: 100 }, }, { state: { id: 'meter.currentL3', name: 'Phase 3 current', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:37111, len:2' }, register: { reg: 37111, type: dataType.int32, gain: 100 }, }, { state: { id: 'meter.activePower', name: 'Active power', type: 'number', unit: 'kW', role: 'value.power.active', desc: 'reg:37113, len:2 (>0: feed-in to grid. <0: supply from grid.)', }, register: { reg: 37113, type: dataType.int32, gain: 1000 }, }, { state: { id: 'meter.derived.signConventionForPowerFeed-in', name: 'Sign convention for power feed-in', type: 'number', unit: '', role: 'value', desc: '1 : positive value indicates that energy is being supplied to the grid, -1 : positive value indicates that energy is being consumed from the grid', }, }, { state: { id: 'meter.derived.feed-inPower', name: 'feed-in power', type: 'number', unit: 'kW', role: 'value.power.active', desc: 'Power to grid', }, }, { state: { id: 'meter.reactivePower', name: 'Reactive power', type: 'number', unit: 'VAr', role: 'value.power.reactive', desc: 'reg:37115, len:2', }, register: { reg: 37115, type: dataType.int32 }, }, { state: { id: 'meter.powerFactor', name: 'Power factor', type: 'number', unit: '', role: 'value', desc: 'reg:37117, len:1' }, register: { reg: 37117, type: dataType.int16, gain: 1000 }, }, { state: { id: 'meter.gridFrequency', name: 'Grid frequency', type: 'number', unit: 'Hz', role: 'value.frequency', desc: 'reg:37118, len:1', }, register: { reg: 37118, type: dataType.int16, gain: 100 }, mapper: value => Promise.resolve(this._checkValidNumber(value, 0, 100)), }, { state: { id: 'meter.positiveActiveEnergy', name: 'Positive active energy', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:37119, len:2', }, register: { reg: 37119, type: dataType.int32, gain: 100 }, }, { state: { id: 'meter.reverseActiveEnergy', name: 'Reverse active energy', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'reg:37121, len:2', }, register: { reg: 37121, type: dataType.int32, gain: 100 }, }, { state: { id: 'meter.accumulatedReactivePower', name: 'Accumulated reactive power', type: 'number', unit: 'kVarh', role: 'value.power.reactive.consumption', desc: 'reg:37123, len:2', }, register: { reg: 37123, type: dataType.int32, gain: 100 }, }, { state: { id: 'meter.voltageL1-L2', name: 'Voltage L1-L2', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37126, len:2' }, register: { reg: 37126, type: dataType.int32, gain: 10 }, }, { state: { id: 'meter.voltageL2-L3', name: 'Voltage L2-L3', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37128, len:2' }, register: { reg: 37128, type: dataType.int32, gain: 10 }, }, { state: { id: 'meter.voltageL3-L1', name: 'Voltage L3-L1', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:37130, len:2' }, register: { reg: 37130, type: dataType.int32, gain: 10 }, }, { state: { id: 'meter.activePowerL1', name: 'Active power L1', type: 'number', unit: 'W', role: 'value.current', desc: 'reg:37132, len:2', }, register: { reg: 37132, type: dataType.int32 }, }, { state: { id: 'meter.activePowerL2', name: 'Active power L2', type: 'number', unit: 'W', role: 'value.current', desc: 'reg:37134, len:2', }, register: { reg: 37134, type: dataType.int32 }, }, { state: { id: 'meter.activePowerL3', name: 'Active power L3', type: 'number', unit: 'W', role: 'value.current', desc: 'reg:37136, len:2', }, register: { reg: 37136, type: dataType.int32 }, }, ], postHook: () => { this.stateCache.set(`meter.derived.signConventionForPowerFeed-in`, 1, { type: 'number' }); const activePower = this.stateCache.get('meter.activePower')?.value ?? 0; this.stateCache.set('meter.derived.feed-inPower', activePower, { type: 'number' }); }, }, { //https://photomate.zendesk.com/hc/en-gb/articles/5701625507485-Export-limitation-for-SUN2000-inverters-via-FusionSolar-App address: 47415, length: 4, info: 'grid feed export', refresh: dataRefreshRate.low, type: deviceType.gridPowerControl, states: [ { state: { id: 'grid.activePowerControlMode', name: 'Active power control mode', type: 'number', unit: '', role: 'value', desc: 'reg:47415, len:1', }, register: { reg: 47415, type: dataType.uint16 }, }, { state: { id: 'grid.maximumFeedGridPower', name: 'Maximum feed grid power', type: 'number', unit: 'kW', role: 'value.power', desc: 'reg: 47416, len: 2', }, register: { reg: 47416, type: dataType.uint32, gain: 1000 }, }, { state: { id: 'grid.maximumFeedGridPower_percent', name: 'Maximum feed grid power %', type: 'number', unit: '%', role: 'value', desc: 'reg: 47418, len: 1', }, register: { reg: 47418, type: dataType.int16, gain: 10 }, }, ], }, ]; this.registerFields.push.apply(this.registerFields, newFields); //Template for StringsRegister const stringFieldsTemplate = { states: [ { state: { id: 'string.PV1Voltage', name: 'String voltage', type: 'number', unit: 'V', role: 'value.voltage', desc: 'reg:32016+2(n-1), len:1', }, register: { reg: 32016, type: dataType.int16, length: 1, gain: 10 }, }, { state: { id: 'string.PV1Current', name: 'String current', type: 'number', unit: 'A', role: 'value.current', desc: 'reg:32017+2(n-1), len:1', }, register: { reg: 32017, type: dataType.int16, length: 1, gain: 100 }, }, { state: { id: 'string.PV1Power', name: 'String power', type: 'number', unit: 'W', role: 'value.power' }, }, ], }; const newHooks = [ { //activePower adjust refresh: dataRefreshRate.high, fn: path => { const chargePower = this.stateCache.get(`${path}battery.chargeDischargePower`)?.value ?? 0; const inputPower = this.stateCache.get(`${path}inputPower`)?.value ?? 0; const activePower = this.stateCache.get(`${path}activePower`)?.value ?? 0; if (Math.abs(activePower - inputPower + chargePower) > 0) { this.log.debug(`activePower ${activePower} !== inputPower ${inputPower}-chargePower ${chargePower}`); //this.stateCache.set(`${path}activePower`, inputPower - chargePower, { type: 'number' }); } }, }, { refresh: dataRefreshRate.low, state: { id: 'derived.dailyInputYield', name: 'Portal yield today', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'yield from the portal', }, fn: path => { const disCharge = this.stateCache.get(`${path}battery.currentDayDischargeCapacity`)?.value ?? 0; const charge = this.stateCache.get(`${path}battery.currentDayChargeCapacity`)?.value ?? 0; //let inputYield = this.stateCache.get(`${path}dailyEnergyYield`)?.value ?? 0 * 0.97 + charge - disCharge; const activeEnergy = this.stateCache.get(`${path}derived.dailyActiveEnergy`)?.value ?? 0; //let inYield = activeEnergy + (charge - disCharge) * 0.97; //efficiency loss of battery 3% let inYield = activeEnergy + charge * 0.97 - disCharge * 1.03; //efficiency loss of battery 3% if (inYield < 0) { inYield = 0; } this.stateCache.set(`${path}derived.dailyInputYield`, inYield, { type: 'number' }); }, }, { refresh: dataRefreshRate.low, state: { id: 'derived.dailySolarYield', name: 'Solar yield today', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'Riemann sum of input power with efficiency loss', }, fn: path => { this.stateCache.set(`${path}derived.dailySolarYield`, this.solarSum.sum, { type: 'number' }); }, }, { refresh: dataRefreshRate.low, state: { id: 'derived.dailyActiveEnergy', name: 'Active Energy today', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'Amount of Riemann sum of sum of active power', }, fn: path => { this.stateCache.set(`${path}derived.dailyActiveEnergy`, this.activePowerSum.sum, { type: 'number' }); }, }, ]; this.postUpdateHooks.push.apply(this.postUpdateHooks, newHooks); } //Incorrect values come back in standby mode of any state