iobroker.sun2000 0.1.2-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,761 @@
1
+
2
+ const {deviceType,batteryStatus,dataRefreshRate,dataType} = require(__dirname + '/types.js');
3
+
4
+ class StateMap {
5
+ constructor () {
6
+ this.stateMap = new Map();
7
+ }
8
+
9
+ get(id) {
10
+ return this.stateMap.get(id);
11
+ }
12
+
13
+ set(id, value, options) {
14
+ if (options?.type == 'number' && isNaN(value)) return;
15
+ if (value !== null) {
16
+ if (options?.renew || this.get(id)?.value !== value) {
17
+ if (options?.stored ) {
18
+ this.stateMap.set(id, {id: id, value: value, stored: options.stored});
19
+ } else {
20
+ this.stateMap.set(id, {id: id, value: value});
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ values () {
27
+ return this.stateMap.values();
28
+ }
29
+
30
+ }
31
+
32
+ class Registers {
33
+ constructor(adapterInstance) {
34
+ this.adapter = adapterInstance;
35
+ this.stateCache = new StateMap();
36
+
37
+ this._loadStates();
38
+
39
+ //https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md
40
+ this.registerFields = [
41
+ {
42
+ address : 37765,
43
+ length : 2,
44
+ info : 'Battery Charge And Discharge Power',
45
+ refresh : dataRefreshRate.high,
46
+ type : deviceType.battery,
47
+ states : [{
48
+ state: {id: 'battery.chargeDischargePower', name: 'Charge/Discharge power', desc: '(>0 charging, <0 discharging)', type: 'number', unit: 'kW', role: 'value.power'},
49
+ register: {reg: 37765, type: dataType.int32, gain:1000},
50
+ storeAlways: true
51
+ }]
52
+ },
53
+ {
54
+ address : 32080,
55
+ length : 2,
56
+ info : 'Inverter Activ Power',
57
+ refresh : dataRefreshRate.high,
58
+ type : deviceType.inverter,
59
+ states : [{
60
+ state: {id: 'activePower', name: 'Active power', type: 'number', unit: 'kW', role: 'value.power.active', desc: 'Power currently used'},
61
+ register: {reg: 32080, type: dataType.int32, gain:1000},
62
+ storeAlways: true
63
+ }]
64
+ },
65
+ {
66
+ address : 32064,
67
+ length : 2,
68
+ info : 'Input Power',
69
+ refresh : dataRefreshRate.high,
70
+ type : deviceType.inverter,
71
+ states : [{
72
+ state: {id: 'inputPower', name: 'Input power' , type: 'number', unit: 'kW', role: 'value.power.produced', desc: 'Power from solar'},
73
+ register: {reg: 32064, type: dataType.int32, gain:1000},
74
+ storeAlways: true
75
+ },
76
+ {
77
+ state: {id: 'derived.inputPowerWithEfficiencyLoss', name: 'input power with efficiency loss', type: 'number', unit: 'kW', role: 'value.power', desc: ''}
78
+ }
79
+ ],
80
+ postHook: (path) => {
81
+ //https://community.home-assistant.io/t/integration-solar-inverter-huawei-2000l/132350/1483?u=wlcrs
82
+ const inPower = this.stateCache.get(path+'inputPower')?.value;
83
+ //https://wiki.selfhtml.org/wiki/JavaScript/Operatoren/Optional_Chaining_Operator
84
+ //const ratedPower = state ? state.val : undefined;
85
+ const ratedPower = this.stateCache.get(path+'info.ratedPower')?.value;
86
+ let inPowerEff = inPower;
87
+ if (inPower < ratedPower*0.2) {
88
+ if (inPower < ratedPower*0.1) {
89
+ inPowerEff *= 0.9;
90
+ } else {
91
+ inPowerEff *= 0.95;
92
+ }
93
+ } else {
94
+ inPowerEff *= 0.98;
95
+ }
96
+ //inputPower_with_efficiency_loss
97
+ this.stateCache.set(path+'derived.inputPowerWithEfficiencyLoss', inPowerEff, {type: 'number'});
98
+ }
99
+ },
100
+ {
101
+ address : 37113,
102
+ length : 2,
103
+ info : 'meter active power',
104
+ refresh : dataRefreshRate.high,
105
+ type : deviceType.meter,
106
+ states : [{
107
+ state: { id: 'meter.activePower', name: 'ActivePower', type: 'number', unit: 'kW', role: 'value.power.active', desc: '(>0: feed-in to grid. <0: supply from grid.)' },
108
+ register: { reg: 37113, type: dataType.int32, gain:1000 }
109
+ }]
110
+ },
111
+ {
112
+ address : 37000,
113
+ length : 68,
114
+ info : 'battery information',
115
+ refresh : dataRefreshRate.low,
116
+ type : deviceType.battery,
117
+ states: [
118
+ {
119
+ state: { id: 'battery.maximumChargePower', name: 'MaximumChargePower', type: 'number', unit: 'W', role: 'value.power', desc: '' },
120
+ register: { reg: 37046, type: dataType.uint32 }
121
+ },
122
+ { state: { id: 'battery.maximumDischargePower', name: 'MaximumDischargePower', type: 'number', unit: 'W', role: 'value.power', desc: '' },
123
+ register: { reg: 37048, type: dataType.uint32}
124
+ }]
125
+ },
126
+ {
127
+ address : 38200,
128
+ length : 100,
129
+ info : 'additional battery information',
130
+ refresh : dataRefreshRate.low,
131
+ type : deviceType.battery
132
+ },
133
+ {
134
+ address : 30000,
135
+ length : 81,
136
+ info : 'model info, SN, max Power (static info)',
137
+ type : deviceType.inverter,
138
+ states: [{
139
+ state: {id: 'info.model', name: 'Model', type: 'string', role: 'info.name'},
140
+ register: {reg: 30000, type: dataType.string, length: 15}
141
+ },
142
+ {
143
+ state: {id: 'info.modelID', name: 'Model ID', type: 'number', role: 'info.hardware'},
144
+ register: {reg: 30070, type: dataType.uint16}
145
+ },
146
+ {
147
+ state: {id: 'info.serialNumber', name: 'Serial number', type: 'string', role: 'info.serial'},
148
+ register: {reg: 30015, type: dataType.string, length: 10}
149
+ },
150
+ {
151
+ state: {id: 'info.ratedPower', name: 'Rated power', type: 'number', unit: 'kW', role: 'value.power'},
152
+ register: {reg: 30073, type: dataType.int32, gain:1000}
153
+ },
154
+ {
155
+ state: {id: 'info.numberMPPTrackers', name: 'Number of MPP trackers', type: 'number', unit: '', role: 'value'},
156
+ register: {reg: 30072, type: dataType.uint16}
157
+ }]
158
+ },
159
+ {
160
+ address : 37800,
161
+ length : 100,
162
+ info : 'additional battery information',
163
+ refresh : dataRefreshRate.low,
164
+ type : deviceType.battery
165
+ },
166
+ {
167
+ address : 38300,
168
+ length : 100,
169
+ info : 'additional battery information',
170
+ refresh : dataRefreshRate.low,
171
+ type : deviceType.battery
172
+ },
173
+ {
174
+ address : 38400,
175
+ length : 100,
176
+ info : 'additional battery information',
177
+ refresh : dataRefreshRate.low,
178
+ type : deviceType.battery
179
+ },
180
+ {
181
+ address : 47081,
182
+ length : 8,
183
+ info : 'additional battery information',
184
+ refresh : dataRefreshRate.low,
185
+ type : deviceType.battery,
186
+ states: [{
187
+ state: {id: 'battery.chargingCutoffCapacity', name: 'Charging Cutoff Capacity', type: 'number', unit: '%', role: 'value'},
188
+ register: {reg: 47081, type: dataType.uint16, gain: 10}
189
+ },
190
+ {
191
+ state: {id: 'battery.dischargeCutoffCapacity', name: 'Discharge Cutoff Capacity', type: 'number', unit: '%', role: 'value'},
192
+ register: {reg: 47082, type: dataType.uint16, gain: 10}
193
+ },
194
+ {
195
+ state: {id: 'battery.forcedChargeDischargePeriod', name: 'Forced Charge Discharge Period', type: 'number', unit: 'mins', role: 'value'},
196
+ register: {reg: 47083, type: dataType.uint16}
197
+ },
198
+ {
199
+ state: {id: 'battery.workingModeSettings', name: 'Working Mode Settings', type: 'number', unit: '', role: 'value'},
200
+ register: {reg: 47086, type: dataType.uint16}
201
+ },
202
+ {
203
+ state: {id: 'battery.chargeFromGridFunction', name: 'Charge From Grid Function', type: 'number', unit: '', role: 'value'},
204
+ register: {reg: 47087, type: dataType.uint16}
205
+ },
206
+ {
207
+ state: {id: 'battery.gridChargeCutoffSOC', name: 'Grid Charge Cutoff SOC', type: 'number', unit: '%', role: 'value'},
208
+ register: {reg: 47088, type: dataType.uint16, gain: 10}
209
+ }]
210
+ },
211
+ {
212
+ address : 32000,
213
+ length : 116,
214
+ info : 'inverter status',
215
+ refresh : dataRefreshRate.low,
216
+ type : deviceType.inverter,
217
+ states: [
218
+ {
219
+ state: {id: 'state1', name: 'State 1', type: 'number', unit: '', role: 'value'},
220
+ register: {reg: 32000, type: dataType.uint16}
221
+ },
222
+ {
223
+ state: {id: 'state2', name: 'State 2', type: 'number', unit: '', role: 'value'},
224
+ register: {reg: 32001, type: dataType.uint16}
225
+ },
226
+ {
227
+ state: {id: 'state3', name: 'State 3', type: 'number', unit: '', role: 'value'},
228
+ register: {reg: 32002, type: dataType.uint16}
229
+ },
230
+ {
231
+ state: {id: 'alarm1', name: 'Alarm 1', type: 'number', unit: '', role: 'value'},
232
+ register: {reg: 32008, type: dataType.uint16}
233
+ },
234
+ {
235
+ state: {id: 'alarm2', name: 'Alarm 2', type: 'number', unit: '', role: 'value'},
236
+ register: {reg: 32009, type: dataType.uint16}
237
+ },
238
+ {
239
+ state: {id: 'alarm3', name: 'Alarm 3', type: 'number', unit: '', role: 'value'},
240
+ register: {reg: 32010, type: dataType.uint16}
241
+ },
242
+ {
243
+ state: {id: 'grid.voltageL1-L2', name: 'Voltage L1-L2', type: 'number', unit: 'V', role: 'value.voltage'},
244
+ register: {reg: 32066, type: dataType.uint16, gain: 10}
245
+ },
246
+ {
247
+ state: {id: 'grid.voltageL2-L3', name: 'Voltage L2-L3', type: 'number', unit: 'V', role: 'value.voltage'},
248
+ register: {reg: 32067, type: dataType.uint16, gain: 10}
249
+ },
250
+ {
251
+ state: {id: 'grid.voltageL3-L1', name: 'Voltage L3-L1', type: 'number', unit: 'V', role: 'value.voltage'},
252
+ register: {reg: 32068, type: dataType.uint16, gain: 10}
253
+ },
254
+ {
255
+ state: {id: 'grid.voltageL1', name: 'Voltage L1', type: 'number', unit: 'V', role: 'value.voltage'},
256
+ register: {reg: 32069, type: dataType.uint16, gain: 10}
257
+ },
258
+ {
259
+ state: {id: 'grid.voltageL2', name: 'Voltage L2', type: 'number', unit: 'V', role: 'value.voltage'},
260
+ register: {reg: 32070, type: dataType.uint16, gain: 10}
261
+ },
262
+ {
263
+ state: {id: 'grid.voltageL3', name: 'Voltage L3', type: 'number', unit: 'V', role: 'value.voltage'},
264
+ register: {reg: 32071, type: dataType.uint16, gain: 10}
265
+ },
266
+ {
267
+ state: {id: 'grid.currentL1', name: 'Current L1', type: 'number', unit: 'A', role: 'value.current'},
268
+ register: {reg: 32072, type: dataType.int32, gain: 1000}
269
+ },
270
+ {
271
+ state: {id: 'grid.currentL2', name: 'Current L2', type: 'number', unit: 'A', role: 'value.current'},
272
+ register: {reg: 32074, type: dataType.int32, gain: 1000}
273
+ },
274
+ {
275
+ state: {id: 'grid.currentL3', name: 'Current L3', type: 'number', unit: 'A', role: 'value.current'},
276
+ register: {reg: 32076, type: dataType.int32, gain: 1000}
277
+ },
278
+ {
279
+ state: {id: 'peakActivePowerCurrentDay', name: 'Peak active power of current day', type: 'number', unit: 'kW', role: 'value.power.max'},
280
+ register: {reg: 32078, type: dataType.int32, gain: 1000}
281
+ },
282
+ {
283
+ state: {id: 'reactivePower', name: 'Reactive Power', type: 'number', unit: 'kVar', role: 'value.power.reactive'},
284
+ register: {reg: 32082, type: dataType.int32, gain: 1000}
285
+ },
286
+ {
287
+ state: {id: 'powerFactor', name: 'Power Factor', type: 'number', unit: '', role: 'value'},
288
+ register: {reg: 32084, type: dataType.int16, gain: 1000}
289
+ },
290
+ {
291
+ state: {id: 'grid.frequency', name: 'Grid Frequency', type: 'number', unit: 'Hz', role: 'value.frequency'},
292
+ register: {reg: 32085, type: dataType.uint16, gain: 100}
293
+ },
294
+ {
295
+ state: {id: 'efficiency', name: 'Efficiency', type: 'number', unit: '%', role: 'value'},
296
+ register: {reg: 32086, type: dataType.uint16, gain: 100}
297
+ },
298
+ {
299
+ state: {id: 'internalTemperature', name: 'Internal temperature', type: 'number', unit: '°C', role: 'value.temperature'},
300
+ register: {reg: 32087, type: dataType.int16, gain: 10}
301
+ },
302
+ {
303
+ state: {id: 'isulationResistance', name: 'Isulation Resistance', type: 'number', unit: 'MOhm', role: 'value'},
304
+ register: {reg: 32088, type: dataType.uint16, gain: 1000}
305
+ },
306
+ {
307
+ state: {id: 'deviceStatus', name: 'Device Status', type: 'number', unit: '', role: 'value'},
308
+ register: {reg: 32089, type: dataType.uint16}
309
+ },
310
+ {
311
+ state: {id: 'faultCode', name: 'Fault Code', type: 'number', unit: '', role: 'value'},
312
+ register: {reg: 32090, type: dataType.uint16}
313
+ },
314
+ {
315
+ state: {id: 'startupTime', name: 'Startup Time', type: 'number', unit: '', role: 'value.time'},
316
+ register: {reg: 32091, type: dataType.uint32}
317
+ },
318
+ {
319
+ state: {id: 'shutdownTime', name: 'Shutdown Time', type: 'number', unit: '', role: 'value.time'},
320
+ register: {reg: 32093, type: dataType.uint32}
321
+ },
322
+ {
323
+ state: {id: 'accumulatedEnergyYield', name: 'Accumulated Energy Yield', type: 'number', unit: 'kWh', role: 'value.power.produced'},
324
+ register: {reg: 32106, type: dataType.uint32, gain: 100}
325
+ },
326
+ {
327
+ state: {id: 'dailyEnergyYield', name: 'Daily Energy Yield', type: 'number', unit: 'kWh', role: 'value.power.produced'},
328
+ register: {reg: 32114, type: dataType.uint32, gain: 100}
329
+ }]
330
+ },
331
+ {
332
+ address : 37100,
333
+ length : 114,
334
+ info : 'meter info',
335
+ refresh : dataRefreshRate.low,
336
+ type : deviceType.meter,
337
+ states: [{
338
+ state: {id: 'meter.status', name: 'Meter Status', type: 'number', unit: '', role: 'value',desc: '(0: offline 1: normal)'},
339
+ register: {reg: 37100, type: dataType.uint16}
340
+ },
341
+ {
342
+ state: {id: 'meter.voltageL1', name: 'Phase 1 voltage', type: 'number', unit: 'V', role: 'value.voltage'},
343
+ register: {reg: 37101, type: dataType.int32, gain: 10}
344
+ },
345
+ {
346
+ state: {id: 'meter.voltageL2', name: 'Phase 2 voltage', type: 'number', unit: 'V', role: 'value.voltage'},
347
+ register: {reg: 37103, type: dataType.int32, gain:10}
348
+ },
349
+ {
350
+ state: {id: 'meter.voltageL3', name: 'Phase 3 voltage', type: 'number', unit: 'V', role: 'value.voltage'},
351
+ register: {reg: 37105, type: dataType.int32, gain:10}
352
+ },
353
+ {
354
+ state: {id: 'meter.currentL1', name: 'Phase 1 Current', type: 'number', unit: 'A', role: 'value.current'},
355
+ register: {reg: 37107, type: dataType.int32, gain:100}
356
+ },
357
+ {
358
+ state: {id: 'meter.currentL2', name: 'Phase 2 Current', type: 'number', unit: 'A', role: 'value.current'},
359
+ register: {reg: 37109, type: dataType.int32, gain:100}
360
+ },
361
+ {
362
+ state: {id: 'meter.currentL3', name: 'Phase 3 Current', type: 'number', unit: 'A', role: 'value.current'},
363
+ register: {reg: 37111, type: dataType.int32, gain:100}
364
+ },
365
+ {
366
+ state: {id: 'meter.reactivePower', name: 'Reactive Power', type: 'number', unit: 'VAr', role: 'value.power.reactive'},
367
+ register: {reg: 37115, type: dataType.int32}
368
+ },
369
+ {
370
+ state: {id: 'meter.powerFactor', name: 'Power Factor', type: 'number', unit: '', role: 'value'},
371
+ register: {reg: 37117, type: dataType.int16, gain: 1000}
372
+ },
373
+ {
374
+ state: {id: 'meter.gridFrequency', name: 'Grid Frequency', type: 'number', unit: 'Hz', role: 'value.frequency'},
375
+ register: {reg: 37118, type: dataType.int16, gain: 100}
376
+ },
377
+ {
378
+ state: {id: 'meter.positiveActiveEnergy', name: 'Positive Active Energy', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
379
+ register: {reg: 37119, type: dataType.int32, gain: 100}
380
+ },
381
+ {
382
+ state: {id: 'meter.reverseActiveEnergy', name: 'Reverse Active Energy', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
383
+ register: {reg: 37121, type: dataType.int32, gain: 100}
384
+ },
385
+ {
386
+ state: {id: 'meter.accumulatedReactivePower', name: 'Accumulated Reactive Power', type: 'number', unit: 'kVarh', role: 'value.power.reactive.consumption'},
387
+ register: {reg: 37123, type: dataType.int32, gain: 100}
388
+ },
389
+ {
390
+ state: {id: 'meter.voltageL1-L2', name: 'Voltage L1-L2', type: 'number', unit: 'V', role: 'value.voltage'},
391
+ register: {reg: 37126, type: dataType.int32, gain: 10}
392
+ },
393
+ {
394
+ state: {id: 'meter.voltageL2-L3', name: 'Voltage L2-L3', type: 'number', unit: 'V', role: 'value.voltage'},
395
+ register: {reg: 37128, type: dataType.int32, gain: 10}
396
+ },
397
+ {
398
+ state: {id: 'meter.voltageL3-L1', name: 'Voltage L3-L1', type: 'number', unit: 'V', role: 'value.voltage'},
399
+ register: {reg: 37130, type: dataType.int32, gain: 10}
400
+ },
401
+ {
402
+ state: {id: 'meter.activePowerL1', name: 'Active Power L1', type: 'number', unit: 'A', role: 'value.current'},
403
+ register: {reg: 37132, type: dataType.int32,}
404
+ },
405
+ {
406
+ state: {id: 'meter.activePowerL2', name: 'Active Power L2', type: 'number', unit: 'A', role: 'value.current'},
407
+ register: {reg: 37134, type: dataType.int32}
408
+ },
409
+ {
410
+ state: {id: 'meter.activePowerL3', name: 'Active Power L3', type: 'number', unit: 'A', role: 'value.current'},
411
+ register: {reg: 37136, type: dataType.int32}
412
+ }]
413
+ },
414
+ {
415
+ address : 37700,
416
+ length : 100,
417
+ info : 'battery information',
418
+ refresh : dataRefreshRate.low,
419
+ type : deviceType.battery,
420
+ states: [{
421
+ state: {id: 'battery.ratedCapacity', name: 'Rated Capacity', type: 'number', unit: 'Wh', role: 'value.capacity'},
422
+ register: {reg: 37758, type: dataType.uint32}
423
+ },
424
+ {
425
+ state: {id: 'battery.SOC', name: 'State of capacity', type: 'number', unit: '%', role: 'value.battery', desc: 'SOC'},
426
+ register: {reg: 37760, type: dataType.uint16, gain: 10}
427
+ },
428
+ {
429
+ state: {id: 'battery.runningState', name: 'Running state', type: 'string', role: 'value'},
430
+ register: {reg: 37762, type: dataType.uint16, length: 1},
431
+ mapper: value => Promise.resolve(batteryStatus[value])
432
+ },
433
+ {
434
+ state: {id: 'battery.busVoltage', name: 'Bus Voltage', type: 'number', unit: 'V', role: 'value.voltage'},
435
+ register: {reg: 37763, type: dataType.uint16, gain: 10}
436
+ },
437
+ {
438
+ state: {id: 'battery.busCurrent', name: 'Bus Current', type: 'number', unit: 'A', role: 'value.current'},
439
+ register: {reg: 37764, type: dataType.uint16, gain: 10}
440
+ },
441
+ {
442
+ state: {id: 'battery.totalCharge', name: 'Total Charge', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
443
+ register: {reg: 37780, type: dataType.uint32, gain: 100}
444
+ },
445
+ {
446
+ state: {id: 'battery.totalDischarge', name: 'Total Discharge', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
447
+ register: {reg: 37782, type: dataType.uint32, gain: 100}
448
+ },
449
+ {
450
+ state: { id: 'battery.currentDayChargeCapacity', name: 'Current Day Charge Capacity', type: 'number', unit: 'kWh', role: 'value.power.consumption' },
451
+ register: { reg: 37784, type: dataType.uint32, gain: 100 }
452
+ },
453
+ {
454
+ state: { id: 'battery.currentDayDischargeCapacity', name: 'Current Day Discharge Capacity', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'TBD' },
455
+ register: { reg: 37786, type: dataType.uint32, gain: 100 }
456
+ }
457
+ ]
458
+ }
459
+ ];
460
+ this.postUpdateHooks = [
461
+ {
462
+ refresh : dataRefreshRate.low,
463
+ state: {id: 'derived.dailyInputYield', name: 'Input Yield Today', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'daily yield displayed on the portal'},
464
+ fn : (path) => {
465
+ const disCharge = this.stateCache.get(path+'battery.currentDayDischargeCapacity')?.value;
466
+ const charge = this.stateCache.get(path+'battery.currentDayChargeCapacity')?.value;
467
+ const inputYield = Math.round((this.stateCache.get(path+'dailyEnergyYield')?.value + charge - disCharge)*100)/100;
468
+ this.stateCache.set(path+'derived.dailyInputYield', inputYield, {type: 'number'});
469
+ }
470
+ }
471
+ ];
472
+ this.processHooks = [
473
+ {
474
+ refresh : dataRefreshRate.high,
475
+ states : [
476
+ {id: 'collected.houseConsumption', name: 'House Consumption', type: 'number', unit: 'kW', role: 'value.power', desc: ''},
477
+ {id: 'collected.activePower', name: 'Active power', type: 'number', unit: 'kW', role: 'value.power.active', desc: 'Power currently used'},
478
+ {id: 'collected.inputPower', name: 'Input power' , type: 'number', unit: 'kW', role: 'value.power', desc: 'Power from solar'},
479
+ {id: 'collected.inputPowerWithEfficiencyLoss', name: 'input power with efficiency loss' , type: 'number', unit: 'kW', role: 'value.power', desc: ''},
480
+ {id: 'collected.chargeDischargePower', name: 'Charge/Discharge power', desc: '(>0 charging, <0 discharging)', type: 'number', unit: 'kW', role: 'value.power'},
481
+ ],
482
+ fn : (inverters) => {
483
+ let sum = 0;
484
+ let inPower = 0;
485
+ let inPowerEff = 0;
486
+ let chargeDischarge = 0;
487
+ for (const inverter of inverters) {
488
+ sum += this.stateCache.get(inverter.path+'.activePower')?.value;
489
+ inPower += this.stateCache.get(inverter.path+'.inputPower')?.value;
490
+ inPowerEff += this.stateCache.get(inverter.path+'.derived.inputPowerWithEfficiencyLoss')?.value;
491
+ chargeDischarge += this.stateCache.get(inverter.path+'.battery.chargeDischargePower')?.value;
492
+ }
493
+ //this.adapter.log.debug('++++ collected.inputPower '+inPower);
494
+ this.stateCache.set('collected.inputPower',inPower,{type: 'number', renew : true});
495
+ this.stateCache.set('collected.inputPowerWithEfficiencyLoss',inPowerEff,{type: 'number'});
496
+ this.stateCache.set('collected.activePower',sum,{type: 'number', renew : true});
497
+ sum -= this.stateCache.get('meter.activePower')?.value;
498
+ this.stateCache.set('collected.houseConsumption',sum,{type: 'number'});
499
+ this.stateCache.set('collected.chargeDischargePower',chargeDischarge,{type: 'number'});
500
+ }
501
+
502
+ },
503
+ {
504
+ refresh : dataRefreshRate.low,
505
+ states : [
506
+ {id: 'collected.dailyEnergyYield', name: 'Daily Energy Yield', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'daily output yield of the inverters'},
507
+ {id: 'collected.dailyInputYield', name: 'Input Yield Today', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'daily yield displayed on the portal'},
508
+ {id: 'collected.accumulatedEnergyYield', name: 'Accumulated Energy Yield', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
509
+ {id: 'collected.consumptionSum', name: 'Consumption Sum', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
510
+ {id: 'collected.gridExportStart', name: 'Grid Export Start Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
511
+ {id: 'collected.gridImportStart', name: 'Grid Export Start Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
512
+ {id: 'collected.consumptionStart', name: 'Consumption Start Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
513
+ {id: 'collected.gridExportToday', name: 'Grid Export Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
514
+ {id: 'collected.gridImportToday', name: 'Grid Import Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
515
+ {id: 'collected.consumptionToday', name: 'Consumption Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
516
+ {id: 'collected.totalCharge', name: 'Total Charge of Battery', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
517
+ {id: 'collected.totalDischarge', name: 'Total Discharge of Battery', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
518
+ {id: 'collected.currentDayChargeCapacity', name: 'Current Day Charge Capacity of Battery', type: 'number', unit: 'kWh', role: 'value.power.consumption' },
519
+ {id: 'collected.currentDayDischargeCapacity', name: 'Current Day Discharge Capacity of Battery', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'TBD' },
520
+ {id: 'collected.SOC', name: 'State of battery capacity', type: 'number', unit: '%', role: 'value.battery', desc: 'SOC'},
521
+ {id: 'collected.ratedCapacity', name: 'Rated of battery Capacity', type: 'number', unit: 'Wh', role: 'value.capacity'}
522
+ ],
523
+ fn : (inverters) => {
524
+ let inYield = 0;
525
+ let outYield = 0;
526
+ let enYield = 0;
527
+ let charge = 0;
528
+ let disCharge = 0;
529
+ let totalDisCharge = 0;
530
+ let totalCharge = 0;
531
+ let ratedCap = 0;
532
+ let load = 0;
533
+ for (const inverter of inverters) {
534
+ outYield += this.stateCache.get(inverter.path+'.dailyEnergyYield')?.value;
535
+ inYield += this.stateCache.get(inverter.path+'.derived.dailyInputYield')?.value;
536
+ enYield += this.stateCache.get(inverter.path+'.accumulatedEnergyYield')?.value;
537
+ charge += this.stateCache.get(inverter.path+'.battery.currentDayChargeCapacity')?.value;
538
+ disCharge += this.stateCache.get(inverter.path+'.battery.currentDayDischargeCapacity')?.value;
539
+ totalCharge += this.stateCache.get(inverter.path+'.battery.totalCharge')?.value;
540
+ totalDisCharge += this.stateCache.get(inverter.path+'.battery.totalDischarge')?.value;
541
+ if (this.stateCache.get(inverter.path+'.battery.ratedCapacity')?.value > 0) {
542
+ load += this.stateCache.get(inverter.path+'.battery.ratedCapacity')?.value * this.stateCache.get(inverter.path+'.battery.SOC')?.value;
543
+ ratedCap += this.stateCache.get(inverter.path+'.battery.ratedCapacity')?.value;
544
+ }
545
+ }
546
+ this.stateCache.set('collected.dailyEnergyYield',outYield, {type: 'number'});
547
+ this.stateCache.set('collected.dailyInputYield',inYield, {type: 'number'});
548
+ this.stateCache.set('collected.accumulatedEnergyYield',enYield, {type: 'number'});
549
+ const conSum = enYield + this.stateCache.get('meter.reverseActiveEnergy')?.value - this.stateCache.get('meter.positiveActiveEnergy')?.value;
550
+ this.stateCache.set('collected.consumptionSum',conSum, {type: 'number'});
551
+ // compute export and import today
552
+ this.stateCache.set('collected.gridExportToday',this.stateCache.get('meter.positiveActiveEnergy')?.value -
553
+ this.stateCache.get('collected.gridExportStart')?.value, {type: 'number'});
554
+ this.stateCache.set('collected.gridImportToday',this.stateCache.get('meter.reverseActiveEnergy')?.value -
555
+ this.stateCache.get('collected.gridImportStart')?.value, {type: 'number'});
556
+ // compute consumption today
557
+ this.stateCache.set('collected.consumptionSum',this.stateCache.get('collected.accumulatedEnergyYield')?.value +
558
+ this.stateCache.get('meter.reverseActiveEnergy')?.value -
559
+ this.stateCache.get('meter.positiveActiveEnergy')?.value, {type: 'number'});
560
+ this.stateCache.set('collected.consumptionToday', this.stateCache.get('collected.consumptionSum')?.value -
561
+ this.stateCache.get('collected.consumptionStart')?.value, {type: 'number'});
562
+ //compute battery
563
+ this.stateCache.set('collected.totalCharge',totalCharge, {type: 'number'});
564
+ this.stateCache.set('collected.totalDischarge',totalDisCharge, {type: 'number'});
565
+ this.stateCache.set('collected.currentDayChargeCapacity',charge, {type: 'number'});
566
+ this.stateCache.set('collected.currentDayDischargeCapacity',disCharge, {type: 'number'});
567
+ this.stateCache.set('collected.ratedCapacity',ratedCap, {type: 'number'});
568
+ this.stateCache.set('collected.SOC',Math.round(load/ratedCap), {type: 'number'});
569
+ }
570
+
571
+
572
+ }
573
+ ];
574
+ }
575
+
576
+
577
+ async _initState(path, state) {
578
+ //this.adapter.log.debug('[_initStat] path+id '+path+state.id);
579
+ await this.adapter.extendObjectAsync(path+state.id, {
580
+ type: 'state',
581
+ common: {
582
+ name: state.name,
583
+ type: state.type,
584
+ role: state.role,
585
+ unit: state.unit,
586
+ desc: state.desc,
587
+ read: true,
588
+ write: false
589
+ },
590
+ native: {}
591
+ });
592
+ }
593
+
594
+
595
+ _fromArray(data,address,field) {
596
+ //nullish coalescing Operator (??)
597
+ const len = field.register.length ?? dataType.size(field.register.type);
598
+ const pos = field.register.reg - address;
599
+ return dataType.convert(data.slice(pos,pos+len),field.register.type);
600
+ }
601
+
602
+ async storeStates() {
603
+ for (const stateEntry of this.stateCache.values()) {
604
+ if (stateEntry.stored) continue;
605
+ if (stateEntry.value !== null) {
606
+ try {
607
+ stateEntry.stored = true;
608
+ await this.adapter.setStateAsync(stateEntry.id, {val: stateEntry.value , ack: true}); //fire and forget
609
+ this.adapter.log.debug(`Fetched ${stateEntry.id}, val=${stateEntry.value}`);
610
+ } catch (err) {
611
+ stateEntry.stored = false;
612
+ this.adapter.log.warn(`Error while fetching ${stateEntry.id}, val=${stateEntry.value} err=${err.message}`);
613
+ }
614
+ }
615
+ }
616
+ }
617
+
618
+ getStatePath(type) {
619
+ let path = '';
620
+ if (type !== deviceType.meter) path = this.inverterInfo.path+'.';
621
+ return path;
622
+ }
623
+
624
+ async processRegister(reg,data) {
625
+ const path = this.getStatePath(reg.type);
626
+ if (reg.states) {
627
+ for(const field of reg.states) {
628
+ const state = field.state;
629
+
630
+ if (!reg['initState'+this.inverterInfo.index]) {
631
+ await this._initState(path,state);
632
+ }
633
+
634
+ if (field.register) {
635
+ let value = this._fromArray(data,reg.address,field);
636
+ if (value !== null) {
637
+ if (field.register.gain) {
638
+ value /= field.register.gain;
639
+ }
640
+ if (field.mapper) {
641
+ value = await field.mapper(value);
642
+ }
643
+ this.stateCache.set(path+state.id, value, { renew : field?.storeAlways} );
644
+ }
645
+ }
646
+ }
647
+ reg['initState'+this.inverterInfo.index] = true;
648
+ }
649
+ //Einschubfunktion
650
+ if (reg.postHook) reg.postHook(path);
651
+ }
652
+
653
+ async updateStates(inverter,modbusClient,refreshRate,duration) {
654
+ const start = new Date().getTime();
655
+ //this.inverterInfo = this.adapter.getInverterInfo(deviceID);
656
+ this.inverterInfo = inverter;
657
+ //The number of Registers reads
658
+ let readRegisters = 0;
659
+ for (const reg of this.registerFields) {
660
+ if (duration) {
661
+ if (new Date().getTime() - start > (duration - 3000)) {
662
+ this.adapter.log.debug('Duration: '+Math.round(duration/1000)+' used time: '+ (new Date().getTime() - start)/1000);
663
+ break;
664
+ }
665
+ }
666
+ if (!reg.states || reg.states.length == 0) continue; //no states ?!
667
+ if (!dataRefreshRate.compare(refreshRate,reg.refresh)) continue; //refreshrate unequal
668
+ if (reg.type == deviceType.meter && this.inverterInfo.meter == false) continue; //meter
669
+ //refresh rate low or empty
670
+ const lastread = reg['lastread'+this.inverterInfo.index];
671
+ if ( refreshRate !== dataRefreshRate.high) {
672
+ if (lastread) {
673
+ if (!reg.refresh) continue;
674
+ if ((start - lastread) < 60000) {
675
+ this.adapter.log.debug('Last Update :'+(start - lastread));
676
+ continue;
677
+ }
678
+ }
679
+ }
680
+ //this.adapter.log.debug(JSON.stringify(reg));
681
+ try {
682
+ this.adapter.log.debug('Try to read data from id/address ' + modbusClient.id + '/' + reg.address);
683
+ const data = await modbusClient.readHoldingRegisters(reg.address, reg.length);
684
+ reg['lastread'+this.inverterInfo.index] = new Date().getTime();
685
+ //this.adapter.log.debug("Data " + reg.info+':'+data);
686
+ await this.processRegister(reg,data);
687
+ readRegisters++;
688
+ } catch (err) {
689
+ this.adapter.log.warn(`Error while reading from ${modbusClient.ipAddress}: [${reg.address}|${reg.length}] id: ${modbusClient.id} '' with : ${err.message}`);
690
+ //this.adapter.log.warn('err.code '+err.modbusCode?);
691
+ if (err.code == 'EHOSTUNREACH' || err.modbusCode == 6) break; // modbus is busy : 6
692
+ //break;
693
+ }
694
+ }
695
+ //Einschubfunktionen
696
+ await this.runPostUpdateHooks(refreshRate);
697
+ this.storeStates(); //fire and forget
698
+ return readRegisters;
699
+ }
700
+
701
+
702
+ async runPostUpdateHooks(refreshRate) {
703
+ const path = this.getStatePath(deviceType.inverter);
704
+
705
+ for (const hook of this.postUpdateHooks) {
706
+ if (dataRefreshRate.compare(refreshRate,hook.refresh)) {
707
+ const state = hook.state;
708
+ if (!hook['initState'+this.inverterInfo.index]) {
709
+ /*
710
+ if (state.id == 'derived.dailyInputYield' ) {
711
+ this.adapter.log.debug('*** [runPostUpdateHooks modbusID '+JSON.stringify(this.inverterInfo));
712
+ this.adapter.log.debug('*** [runPostUpdateHooks] path '+path+state.id+' modbusID ');
713
+ }
714
+ */
715
+ await this._initState(path,state);
716
+ }
717
+ hook.fn(path);
718
+ hook['initState'+this.inverterInfo.index] = true;
719
+ }
720
+ }
721
+ }
722
+
723
+
724
+ async runProcessHooks(refreshRate) {
725
+ for (const hook of this.processHooks) {
726
+ if (dataRefreshRate.compare(refreshRate,hook.refresh)) {
727
+ for (const state of hook.states) {
728
+ if (!hook['initState'+this.inverterInfo.index]) {
729
+ await this._initState('',state);
730
+ }
731
+ hook.fn(this.adapter.inverters);
732
+ }
733
+ hook['initState'+this.inverterInfo.index] = true;
734
+ }
735
+ }
736
+ this.storeStates(); //fire and forget
737
+ }
738
+
739
+ async _loadStates() {
740
+ let value = await this.adapter.getStateAsync('collected.gridExportStart');
741
+ this.stateCache.set('collected.gridExportStart',value?.val, {type : 'number', stored : true });
742
+ value = await this.adapter.getStateAsync('collected.gridImportStart');
743
+ this.stateCache.set('collected.gridImportStart',value?.val, {type : 'number', stored : true });
744
+ value = await this.adapter.getStateAsync('collected.consumptionStart');
745
+ this.stateCache.set('collected.consumptionStart',value?.val, {type : 'number', stored : true });
746
+ }
747
+
748
+ // one minute before midnight - perform housekeeping actions
749
+ async mitnightProcess () {
750
+ // copy current export/import kWh - used to compute daily import/export in kWh
751
+ this.stateCache.set('collected.gridExportStart',this.stateCache.get('meter.positiveActiveEnergy')?.value, {type : 'number'});
752
+ this.stateCache.set('collected.gridImportStart',this.stateCache.get('meter.reverseActiveEnergy')?.value, {type : 'number'});
753
+ // copy consumption Sum to Start for the next day
754
+ this.stateCache.set('collected.consumptionStart',this.stateCache.get('collected.consumptionSum')?.value, {type : 'number'});
755
+ this.storeStates(); //fire and forget
756
+ }
757
+
758
+ }
759
+
760
+ module.exports = Registers;
761
+