iobroker.sun2000 2.5.0 → 2.5.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.
- package/README.md +11 -9
- package/admin/jsonConfig.json5 +13 -13
- package/io-package.json +24 -24
- package/lib/controls/inverter_service_queue.js +1 -1
- package/lib/drivers/driver_base.js +2 -1
- package/lib/drivers/driver_emma.js +8 -10
- package/lib/drivers/driver_inverter.js +4 -1
- package/lib/modbus/modbus_server.js +1 -1
- package/lib/register.js +5 -14
- package/lib/statistics/consumptionBreakdown.js +1 -1
- package/lib/statistics/statistics.js +16 -11
- package/lib/tools.js +9 -2
- package/main.js +7 -6
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -69,8 +69,18 @@ The sun2000 adapter calculates how much of your self-generated solar energy is a
|
|
|
69
69
|
Placeholder for the next version (at the beginning of the line):
|
|
70
70
|
### **WORK IN PROGRESS**
|
|
71
71
|
-->
|
|
72
|
+
### 2.5.1 (2026-06-29)
|
|
73
|
+
- (bolliy) fix: update service queue logic ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))
|
|
74
|
+
- (bolliy) statistics fix: adjust reset handling logic to treat significant drops in value as potential resets
|
|
75
|
+
|
|
72
76
|
### 2.5.0 (2026-06-09)
|
|
73
|
-
* statistics: added live power chart (statistics.jsonLive)
|
|
77
|
+
* (bolliy) statistics: added live power chart (statistics.jsonLive)
|
|
78
|
+
* (bolliy) statistics: consumption breakdown — breakdown values are now subtracted from the total `consumption` entry so the lower chart panel shows the remainder separately from the breakdown series
|
|
79
|
+
* (bolliy) statistics: `xAxisFormatter` for the live chart only labels full-hour ticks to avoid clutter
|
|
80
|
+
* (bolliy) statistics: tooltip formatter refactored — `formatTooltipValue(unit, negative, decimals)` helper used consistently across all series
|
|
81
|
+
* (bolliy) statistics: if no battery is present, the charts are generated without battery information (SOC, charge, discharge).
|
|
82
|
+
* (bolliy) fix emma: update register addresses of meter.activePowerL1-L3 ([#282](https://github.com/bolliy/ioBroker.sun2000/issues/282))
|
|
83
|
+
* (bolliy) requires node.js >= 22
|
|
74
84
|
|
|
75
85
|
### 2.4.5 (2026-05-14)
|
|
76
86
|
* statistics fix: return weekly range up to current Monday
|
|
@@ -86,14 +96,6 @@ The sun2000 adapter calculates how much of your self-generated solar energy is a
|
|
|
86
96
|
* statistics: computed values `selfSufficiency` and `selfConsumption` calculated automatically in all time-series states
|
|
87
97
|
* statistics: data placeholders (`%%solarYield%%`, `%%selfSufficiency%%` etc.) and negated variants (`%%gridExportNeg%%` etc.) for mirrored chart layouts
|
|
88
98
|
|
|
89
|
-
### 2.4.2 (2026-04-04)
|
|
90
|
-
* fix test-and-release: deploy with 24.x
|
|
91
|
-
* statistics: flexcharts integration — built-in Apache ECharts configuration with bar and line chart support
|
|
92
|
-
* statistics: day-break visualization with alternating shaded areas for hourly charts
|
|
93
|
-
* statistics: per chart-type templates (`statistics.flexCharts.template.hourly` etc.) for full ECharts customization including functions
|
|
94
|
-
* statistics: data placeholders (`%%solarYield%%`, `%%gridExport%%` etc.) allow complete chart layout control via template states
|
|
95
|
-
* statistics: chart output states (`statistics.flexCharts.jsonOutput.hourly` etc.) updated automatically each hour
|
|
96
|
-
|
|
97
99
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
98
100
|
|
|
99
101
|
## License
|
package/admin/jsonConfig.json5
CHANGED
|
@@ -389,19 +389,6 @@
|
|
|
389
389
|
"type": "panel",
|
|
390
390
|
"label": "Statistics",
|
|
391
391
|
"items": {
|
|
392
|
-
"staticLink1": {
|
|
393
|
-
"type": "staticLink",
|
|
394
|
-
"text": "Information and settings regarding the statistics explained in the wiki",
|
|
395
|
-
"href": "https://github.com/bolliy/ioBroker.sun2000/wiki/Statistk-(statistics)",
|
|
396
|
-
"button": true,
|
|
397
|
-
"icon": "info",
|
|
398
|
-
"newLine": true,
|
|
399
|
-
"xs": 12,
|
|
400
|
-
"sm": 12,
|
|
401
|
-
"md": 6,
|
|
402
|
-
"lg": 3,
|
|
403
|
-
"xl": 3
|
|
404
|
-
},
|
|
405
392
|
"stat_liveInterval": {
|
|
406
393
|
"type": "number",
|
|
407
394
|
"label": "Live chart interval (min)",
|
|
@@ -414,6 +401,19 @@
|
|
|
414
401
|
"md": 6,
|
|
415
402
|
"lg": 4,
|
|
416
403
|
"xl": 4
|
|
404
|
+
},
|
|
405
|
+
"staticLink1": {
|
|
406
|
+
"type": "staticLink",
|
|
407
|
+
"text": "Information and settings regarding the statistics explained in the wiki",
|
|
408
|
+
"href": "https://github.com/bolliy/ioBroker.sun2000/wiki/Statistk-(statistics)",
|
|
409
|
+
"button": true,
|
|
410
|
+
"icon": "info",
|
|
411
|
+
"newLine": true,
|
|
412
|
+
"xs": 12,
|
|
413
|
+
"sm": 12,
|
|
414
|
+
"md": 6,
|
|
415
|
+
"lg": 3,
|
|
416
|
+
"xl": 3
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "sun2000",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.5.1": {
|
|
7
|
+
"en": "fix: update service queue logic ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nstatistics fix: adjust reset handling logic to treat significant drops in value as potential resets",
|
|
8
|
+
"de": "fix: update service queue logic ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nstatistics fix: anpassung der reset-handling-logik, um signifikante wertverluste als potenzielle resets zu behandeln",
|
|
9
|
+
"ru": "исправить: обновить логику очереди обслуживания ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nисправление статистики: настройте логику обработки сбросов, чтобы рассматривать значительное снижение стоимости как потенциальные сбросы",
|
|
10
|
+
"pt": "fix: update service fila logic ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nstatistics fix: ajuste a lógica de manipulação de reset para tratar quedas significativas no valor como resets potenciais",
|
|
11
|
+
"nl": "fix: update service wachtrij logica ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nstatistieken fix: aanpassen reset handling logica om significante dalingen in waarde te behandelen als potentiële ressets",
|
|
12
|
+
"fr": "correction: mise à jour de la logique de la file d'attente de service ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\ncorrection statistique: ajuster la logique de réinitialisation pour traiter les baisses significatives de valeur comme des réinitialisations potentielles",
|
|
13
|
+
"it": "correzione: aggiornamento della coda di servizio logica ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nstatistiche fix: regolare la gestione del reset logica per trattare gocce significative di valore come potenziali reset",
|
|
14
|
+
"es": "fix: update service queue logic ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283))\nestadística fijado: ajuste la lógica de manejo de reajuste para tratar gotas significativas en valor como posibles reajustes",
|
|
15
|
+
"pl": "fix: update service queue logic ([# 283] (https: / / github.com / bolliy / ioBroker.sun2000 / discussions / 283))\nstatystyki fix: dostosować logikę obsługi reset w celu traktowania znaczących spadku wartości jako potencjalnych reset",
|
|
16
|
+
"uk": "виправити: оновити логіку черги ([#283](https://github.com/bolliy/ioBroker.sun2000/discussions/283)))\nвиправлення статистичних даних: регулюйте логіку обробки скидання для обробки значних крапель у значення як потенційні скидання",
|
|
17
|
+
"zh-cn": "fix:更新服务队列逻辑([#283] (https://github.com/bolliy/ioBroker.sun2000/discussions/283))\n统计修补:调整重置处理逻辑,将价值大幅下降视为潜在重置"
|
|
18
|
+
},
|
|
6
19
|
"2.5.0": {
|
|
7
20
|
"en": "statistics: added live power chart (statistics.jsonLive)\nrequires node.js >= 22",
|
|
8
21
|
"de": "statistiken: Live Power Chart hinzugefügt (statistics.jsonLive)\nrequires node.js >= 22",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "fix: kolejność przypisywania bitów poprawionych alarmów JSON\nw języku angielskim:\nstatystyki: Zagregowane historyczne zbierane punkty danych w skróty czasowe (np. godzinowe, dzienne, miesięczne, roczne). Dane są przechowywane w ścieżce \"statystyki\" jako JSON.",
|
|
81
94
|
"uk": "фіксувати: порядок відведення біту виправлено тривоги Сонце\nновий стан `inverter.x.emma.activeAlarmSN` і `inverter.x.emma.HistoricalAlarmSN` : emma тривоги [#226](https://github.com/bolliy/ioBroker.sun2000/products/226)\nстатистика: Агрегати історичних зібраних точок даних на часові суми (наприклад, час, щоденно, щомісяця, рік). Дані зберігаються на шляху `statistics` як JSON.",
|
|
82
95
|
"zh-cn": "固定: 修改提醒的位任务顺序 贾森\n新状态`inverter.x.emma.active AlarmSN ' 和`inverter.x.emma. Historical AlarmSN ' : emma警报[# 226](https://github.com/bolliy/ioBroker.sun2000/issues/226)\n统计: 将历史收集的数据点汇总为时间摘要(如小时、每日、每月、每年)。 数据作为JSON储存在路径`统计'中."
|
|
83
|
-
},
|
|
84
|
-
"2.3.7": {
|
|
85
|
-
"en": "deleted deprecated state `collected.usableSurplusPower`",
|
|
86
|
-
"de": "gelöschter deprecated state `collect.usableSurplusPower `",
|
|
87
|
-
"ru": "удаленное устаревшее состояние 'collected.usableSurplusPower пункт",
|
|
88
|
-
"pt": "estado desactualizado excluído `colected.usableSurplusPower `",
|
|
89
|
-
"nl": "verwijderde verouderde staat Wat",
|
|
90
|
-
"fr": "état obsolète supprimé `collected.usableSurplusPower \"",
|
|
91
|
-
"it": "cancellato stato deprecato `colletto.usableSurplusPower #",
|
|
92
|
-
"es": "borrado estado deprecatado `collected.usableSurplusPower `",
|
|
93
|
-
"pl": "usunięty stan zdepregatowany \"collected.usableSurplusPower '",
|
|
94
|
-
"uk": "вилучена депресована держава `collected.usableSurplusPower й",
|
|
95
|
-
"zh-cn": "删除已贬值状态“ 已收集。 可使用 SurplusPower `"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"de": "Gerät angeschlossen"
|
|
287
287
|
},
|
|
288
288
|
"type": "boolean",
|
|
289
|
-
"role": "
|
|
289
|
+
"role": "sensor",
|
|
290
290
|
"read": true,
|
|
291
291
|
"write": false,
|
|
292
292
|
"desc": "Is the device connected?"
|
|
@@ -301,7 +301,7 @@
|
|
|
301
301
|
"de": "Geräte IP"
|
|
302
302
|
},
|
|
303
303
|
"type": "string",
|
|
304
|
-
"role": "
|
|
304
|
+
"role": "text",
|
|
305
305
|
"read": true,
|
|
306
306
|
"write": false,
|
|
307
307
|
"desc": "Device IP address"
|
|
@@ -316,7 +316,7 @@
|
|
|
316
316
|
"de": "Modbusport"
|
|
317
317
|
},
|
|
318
318
|
"type": "number",
|
|
319
|
-
"role": "
|
|
319
|
+
"role": "value",
|
|
320
320
|
"read": true,
|
|
321
321
|
"write": false,
|
|
322
322
|
"desc": "Device modbus port"
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
"de": "Modbus IDs der Geräte"
|
|
332
332
|
},
|
|
333
333
|
"type": "string",
|
|
334
|
-
"role": "
|
|
334
|
+
"role": "text",
|
|
335
335
|
"read": true,
|
|
336
336
|
"write": false,
|
|
337
337
|
"desc": "device modbus IDs"
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
"de": "modbus Update Intervall"
|
|
347
347
|
},
|
|
348
348
|
"type": "number",
|
|
349
|
-
"role": "
|
|
349
|
+
"role": "time.interval",
|
|
350
350
|
"read": true,
|
|
351
351
|
"write": false,
|
|
352
352
|
"desc": "modbus update interval",
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
"de": "modbus timeout"
|
|
363
363
|
},
|
|
364
364
|
"type": "number",
|
|
365
|
-
"role": "
|
|
365
|
+
"role": "time.timeout",
|
|
366
366
|
"read": true,
|
|
367
367
|
"write": false,
|
|
368
368
|
"desc": "modbus timeout",
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
"de": "modbus delay"
|
|
379
379
|
},
|
|
380
380
|
"type": "number",
|
|
381
|
-
"role": "
|
|
381
|
+
"role": "time.span",
|
|
382
382
|
"read": true,
|
|
383
383
|
"write": false,
|
|
384
384
|
"desc": "delay between modbus requests",
|
|
@@ -394,7 +394,7 @@
|
|
|
394
394
|
"de": "modbus connect delay"
|
|
395
395
|
},
|
|
396
396
|
"type": "number",
|
|
397
|
-
"role": "
|
|
397
|
+
"role": "time.span",
|
|
398
398
|
"read": true,
|
|
399
399
|
"write": false,
|
|
400
400
|
"desc": "delay after modbus connected",
|
|
@@ -410,7 +410,7 @@
|
|
|
410
410
|
"de": "Gesundheitsinformation"
|
|
411
411
|
},
|
|
412
412
|
"type": "string",
|
|
413
|
-
"role": "
|
|
413
|
+
"role": "json",
|
|
414
414
|
"read": true,
|
|
415
415
|
"write": false,
|
|
416
416
|
"desc": "",
|
|
@@ -426,7 +426,7 @@
|
|
|
426
426
|
"de": "modbus Server aktiv"
|
|
427
427
|
},
|
|
428
428
|
"type": "boolean",
|
|
429
|
-
"role": "
|
|
429
|
+
"role": "sensor",
|
|
430
430
|
"read": true,
|
|
431
431
|
"write": false,
|
|
432
432
|
"desc": "",
|
|
@@ -12,7 +12,7 @@ class ServiceQueueMap {
|
|
|
12
12
|
this._eventMap = new Map();
|
|
13
13
|
this._initialized = false;
|
|
14
14
|
this._name = 'inverter control';
|
|
15
|
-
this._emmaAvailable = this.adapter.devices.find(d => d.driverClass == driverClasses.emma);
|
|
15
|
+
//this._emmaAvailable = this.adapter.devices.find(d => d.driverClass == driverClasses.emma);
|
|
16
16
|
|
|
17
17
|
this.serviceFields = [
|
|
18
18
|
{
|
|
@@ -9,6 +9,7 @@ class DriverBase {
|
|
|
9
9
|
this.adapter = stateInstance.adapter;
|
|
10
10
|
this.stateCache = stateInstance.stateCache;
|
|
11
11
|
this.deviceInfo = device;
|
|
12
|
+
|
|
12
13
|
this._modbusClient = null; //NEW!!
|
|
13
14
|
//https://wiki.selfhtml.org/wiki/JavaScript/Operatoren/Nullish_Coalescing_Operator
|
|
14
15
|
//https://stackoverflow.com/questions/2851404/what-does-options-options-mean-in-javascript
|
|
@@ -77,7 +78,7 @@ class DriverBase {
|
|
|
77
78
|
* @returns {boolean} True if the device is in test mode, false otherwise
|
|
78
79
|
*/
|
|
79
80
|
isTestMode() {
|
|
80
|
-
return false;
|
|
81
|
+
return this.deviceInfo.testMode ?? false;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
logHoldingRegisters(startAddr, length) {
|
|
@@ -10,6 +10,10 @@ class Emma extends DriverBase {
|
|
|
10
10
|
...options,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
if (this.isTestMode()) {
|
|
14
|
+
this._modbusId = 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
this.control = new ServiceQueueMap(this.adapter, this.deviceInfo); //emma service queue
|
|
14
18
|
|
|
15
19
|
//https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md
|
|
@@ -590,6 +594,10 @@ class Emma extends DriverBase {
|
|
|
590
594
|
this.log.debug(`### PostHook indentification for Inverter Sun2000 - ret: ${JSON.stringify(ret)}`);
|
|
591
595
|
//check if inverter sun2000 already exists
|
|
592
596
|
const inverterExist = this.adapter.devices.findIndex(x => x.driverClass === driverClasses.inverter);
|
|
597
|
+
//TestMode
|
|
598
|
+
if (this.isTestMode() && ret.length === 0) {
|
|
599
|
+
ret.push({ slave_id: 1 });
|
|
600
|
+
}
|
|
593
601
|
for (const [i, inverter] of ret.entries()) {
|
|
594
602
|
this.log.info(`${this._name} identify subdevice an inverter sun2000: OID=${inverter.obj_id}, modbus id: ${inverter.slave_id}`);
|
|
595
603
|
//unless inverters are configured, add them
|
|
@@ -804,16 +812,6 @@ class Emma extends DriverBase {
|
|
|
804
812
|
|
|
805
813
|
this.registerFields.push.apply(this.registerFields, newFields);
|
|
806
814
|
}
|
|
807
|
-
|
|
808
|
-
// Override
|
|
809
|
-
/**
|
|
810
|
-
* Checks if the device is in test mode
|
|
811
|
-
* @returns {boolean} True if the device is in test mode, false otherwise
|
|
812
|
-
* A device is considered in test mode if its modbus ID is not equal to 0.
|
|
813
|
-
*/
|
|
814
|
-
isTestMode() {
|
|
815
|
-
return this.modbusId !== 0;
|
|
816
|
-
}
|
|
817
815
|
}
|
|
818
816
|
|
|
819
817
|
class EmmaCharger extends DriverBase {
|
|
@@ -103,7 +103,7 @@ class InverterSun2000 extends DriverBase {
|
|
|
103
103
|
this.activePowerSum.setStart(state?.val, state?.ts);
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
|
-
|
|
106
|
+
/*
|
|
107
107
|
this.chargeSumHelper = new RiemannSum();
|
|
108
108
|
this.adapter.getState(`${this.deviceInfo.path}.battery.currentDayChargeCapacity`, (err, state) => {
|
|
109
109
|
if (!err && state) {
|
|
@@ -116,6 +116,7 @@ class InverterSun2000 extends DriverBase {
|
|
|
116
116
|
this.dischargeSumHelper.setStart(state?.val, state?.ts, false);
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
|
+
*/
|
|
119
120
|
|
|
120
121
|
this.control = new ServiceQueueMap(this.adapter, this.deviceInfo);
|
|
121
122
|
|
|
@@ -235,6 +236,7 @@ class InverterSun2000 extends DriverBase {
|
|
|
235
236
|
],
|
|
236
237
|
//Check if the address field is active
|
|
237
238
|
checkIfActive: () => this._batteryExists(),
|
|
239
|
+
/*
|
|
238
240
|
postHook: path => {
|
|
239
241
|
//calculate daily charge and discharge capacity
|
|
240
242
|
const chargeDischargePower = this.stateCache.get(`${path}battery.chargeDischargePower`)?.value ?? 0;
|
|
@@ -264,6 +266,7 @@ class InverterSun2000 extends DriverBase {
|
|
|
264
266
|
this.stateCache.set(`${path}battery.statistics.currentDayChargeCapacity`, this.chargeSumHelper.sum, { type: 'number', stored: true }); //stored for statistics
|
|
265
267
|
this.stateCache.set(`${path}battery.statistics.currentDayDischargeCapacity`, this.dischargeSumHelper.sum, { type: 'number', stored: true }); //stored for statistics
|
|
266
268
|
},
|
|
269
|
+
*/
|
|
267
270
|
},
|
|
268
271
|
{
|
|
269
272
|
address: 32064,
|
package/lib/register.js
CHANGED
|
@@ -22,6 +22,7 @@ class Registers {
|
|
|
22
22
|
|
|
23
23
|
this.statistics = new statistics(adapterInstance, this.stateCache);
|
|
24
24
|
|
|
25
|
+
/*
|
|
25
26
|
for (const device of this.adapter.devices) {
|
|
26
27
|
//DriverInfo Instance or Sdongle
|
|
27
28
|
const handler = getDriverHandler(device.driverClass);
|
|
@@ -29,6 +30,7 @@ class Registers {
|
|
|
29
30
|
device.instance = new handler(this, device);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
*/
|
|
32
34
|
|
|
33
35
|
//Upgrade to v2.3.7 - deleted deprecated states
|
|
34
36
|
if (
|
|
@@ -363,16 +365,6 @@ class Registers {
|
|
|
363
365
|
role: 'value.power.produced',
|
|
364
366
|
desc: 'Riemann sum of external power',
|
|
365
367
|
},
|
|
366
|
-
/*
|
|
367
|
-
{
|
|
368
|
-
id: 'collected.dailyActiveEnergy',
|
|
369
|
-
name: 'Active Energy today',
|
|
370
|
-
type: 'number',
|
|
371
|
-
unit: 'kWh',
|
|
372
|
-
role: 'value.power.produced',
|
|
373
|
-
desc: 'Amount of Riemann sum of sum of active power',
|
|
374
|
-
},
|
|
375
|
-
*/
|
|
376
368
|
],
|
|
377
369
|
fn: inverters => {
|
|
378
370
|
let inYield = 0;
|
|
@@ -510,15 +502,14 @@ class Registers {
|
|
|
510
502
|
|
|
511
503
|
async storeStates() {
|
|
512
504
|
for (const stateEntry of this.stateCache.values()) {
|
|
513
|
-
//if (stateEntry?.storeType === storeType.never) continue;
|
|
514
505
|
if (stateEntry.stored) {
|
|
515
506
|
continue;
|
|
516
507
|
}
|
|
517
|
-
//if (stateEntry?.storeType !== storeType.always && stateEntry.stored) continue;
|
|
518
508
|
if (stateEntry.value !== null) {
|
|
519
509
|
try {
|
|
520
510
|
stateEntry.stored = true;
|
|
521
|
-
|
|
511
|
+
const valueToStore = typeof stateEntry.value === 'number' ? this.stateCache.round(stateEntry.value, 3) : stateEntry.value;
|
|
512
|
+
await this.adapter.setState(stateEntry.id, { val: valueToStore, ack: true });
|
|
522
513
|
this.adapter.logger.debug(`Fetched ${stateEntry.id}, val=${stateEntry.value}`);
|
|
523
514
|
} catch (err) {
|
|
524
515
|
stateEntry.stored = false;
|
|
@@ -543,7 +534,7 @@ class Registers {
|
|
|
543
534
|
* @returns {Promise<number>} - Returns a promise that resolves to the number of states updated.
|
|
544
535
|
*/
|
|
545
536
|
async updateStates(device, modbusClient, refreshRate, duration) {
|
|
546
|
-
//this.adapter.log.debug(
|
|
537
|
+
//this.adapter.log.debug(`### DeviceInfo: ${device.index} ${JSON.stringify(device.instance?.info)}`);
|
|
547
538
|
if (!device.instance) {
|
|
548
539
|
const handler = getDriverHandler(device.driverClass);
|
|
549
540
|
if (handler) {
|
|
@@ -170,7 +170,7 @@ class ConsumptionBreakdown {
|
|
|
170
170
|
if (state && state?.val != null) {
|
|
171
171
|
const raw = Number(state.val);
|
|
172
172
|
if (!isNaN(raw)) {
|
|
173
|
-
this.stateCache.set(cacheKey, raw / bd.gain || 1, { type: 'number', stored: true });
|
|
173
|
+
this.stateCache.set(cacheKey, raw / (bd.gain || 1), { type: 'number', stored: true });
|
|
174
174
|
} else {
|
|
175
175
|
this.adapter.logger.debug(`consumptionBreakdown: '${bd.targetPath}' value is not a number: ${state.val}`);
|
|
176
176
|
}
|
|
@@ -317,29 +317,33 @@ class statistics {
|
|
|
317
317
|
for (const stat of effectiveStats) {
|
|
318
318
|
if (stat.type === statisticsType.computed) continue;
|
|
319
319
|
|
|
320
|
-
const source = Math.round((Number(this.stateCache.get(stat.sourceId)?.value ?? 0) + Number.EPSILON) * 1000) / 1000;
|
|
320
|
+
//const source = Math.round((Number(this.stateCache.get(stat.sourceId)?.value ?? 0) + Number.EPSILON) * 1000) / 1000;
|
|
321
|
+
const source = this.stateCache.get(stat.sourceId)?.value;
|
|
321
322
|
if (source === null || source === undefined) {
|
|
322
323
|
if (!stat._isBreakdown) this.adapter.logger.debug(`Source state ${stat.sourceId} not found statistic hook`);
|
|
323
324
|
continue;
|
|
324
325
|
}
|
|
325
326
|
let value = source;
|
|
326
327
|
if (stat.type === statisticsType.delta || stat.type === statisticsType.deltaReset) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
328
|
+
if (last[stat.targetPath]?.total === undefined) {
|
|
329
|
+
this.adapter.logger.debug(`No total value for ${stat.targetPath} in last entry, delta set to 0`);
|
|
330
|
+
value = 0;
|
|
330
331
|
} else {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
value
|
|
332
|
+
const lastTotal = Number(last[stat.targetPath]?.total ?? 0);
|
|
333
|
+
if (stat.type === statisticsType.deltaReset) {
|
|
334
|
+
// Handle resets: If the new value is significantly lower than the last total value, a reset has likely occurred. Otherwise, the difference relative to the last total value is treated as consumption.
|
|
335
|
+
if (value > lastTotal * 0.8) {
|
|
336
|
+
value -= lastTotal;
|
|
337
|
+
}
|
|
334
338
|
} else {
|
|
335
339
|
value -= lastTotal;
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
342
|
}
|
|
339
|
-
|
|
343
|
+
value = Math.round((Number(value) + Number.EPSILON) * 1000) / 1000;
|
|
340
344
|
entry[stat.targetPath] = { value: Number(value.toFixed(3)) };
|
|
341
345
|
if (stat.type === statisticsType.delta || stat.type === statisticsType.deltaReset) {
|
|
342
|
-
entry[stat.targetPath].total = Number(source.toFixed(
|
|
346
|
+
entry[stat.targetPath].total = Number(source.toFixed(5));
|
|
343
347
|
}
|
|
344
348
|
entry[stat.targetPath].unit = stat.unit || 'kWh';
|
|
345
349
|
}
|
|
@@ -585,7 +589,6 @@ class statistics {
|
|
|
585
589
|
for (const stat of this._effectiveLiveStats()) {
|
|
586
590
|
// Only convert energy (kWh) deltas — leave SOC (%) and computed (%) as-is
|
|
587
591
|
if (stat.type !== statisticsType.deltaReset && stat.type !== statisticsType.delta) continue;
|
|
588
|
-
//if ((stat.unit || 'kWh') !== 'kWh') continue; //??
|
|
589
592
|
if (last[stat.targetPath] === undefined) continue;
|
|
590
593
|
|
|
591
594
|
const kw = Math.round(last[stat.targetPath].value * convFactor * 1000) / 1000;
|
|
@@ -843,7 +846,9 @@ class statistics {
|
|
|
843
846
|
await this._initializeLiveTimer();
|
|
844
847
|
this._initializeTask();
|
|
845
848
|
|
|
846
|
-
this.adapter.subscribeStates(`${this._path}
|
|
849
|
+
this.adapter.subscribeStates(`${this._path}.flexCharts.template*`);
|
|
850
|
+
this.adapter.subscribeStates(`${this._path}.dataDef*`);
|
|
851
|
+
|
|
847
852
|
this._initialized = true;
|
|
848
853
|
}
|
|
849
854
|
|
package/lib/tools.js
CHANGED
|
@@ -57,6 +57,7 @@ class Logging {
|
|
|
57
57
|
class StateMap {
|
|
58
58
|
constructor() {
|
|
59
59
|
this.stateMap = new Map();
|
|
60
|
+
this._mapDecimalPlaces = 10; //internal
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
round(num, decimalPlaces = 0) {
|
|
@@ -65,7 +66,12 @@ class StateMap {
|
|
|
65
66
|
return Math.round(n) / p;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
get(id) {
|
|
69
|
+
get(id, decimalPlaces = -1) {
|
|
70
|
+
const mapItem = this.stateMap.get(id);
|
|
71
|
+
if (decimalPlaces >= 0 && mapItem?.type === 'number') {
|
|
72
|
+
return { ...mapItem, value: this.round(mapItem.value, decimalPlaces) };
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
return this.stateMap.get(id);
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -78,7 +84,7 @@ class StateMap {
|
|
|
78
84
|
if (value !== null) {
|
|
79
85
|
if (options?.type == 'number') {
|
|
80
86
|
//value = Math.round((value + Number.EPSILON) * 1000) / 1000; //3rd behind
|
|
81
|
-
value = this.round(value,
|
|
87
|
+
value = this.round(Number(value), this._mapDecimalPlaces);
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
const existing = this.get(id); //existing entry
|
|
@@ -86,6 +92,7 @@ class StateMap {
|
|
|
86
92
|
const mapOptions = {
|
|
87
93
|
id: id,
|
|
88
94
|
value: value,
|
|
95
|
+
type: options?.type ? options.type : existing?.type ? existing.type : typeof value,
|
|
89
96
|
stored: existing?.stored ? existing.stored : false,
|
|
90
97
|
};
|
|
91
98
|
if (options?.renew || existing?.value !== value) {
|
package/main.js
CHANGED
|
@@ -556,9 +556,9 @@ class Sun2000 extends utils.Adapter {
|
|
|
556
556
|
this.devices.push({
|
|
557
557
|
index: 0,
|
|
558
558
|
duration: 0,
|
|
559
|
-
//modbusId: 1, --> testMode
|
|
560
559
|
modbusId: 0,
|
|
561
560
|
meter: true,
|
|
561
|
+
//testMode: true,
|
|
562
562
|
driverClass: driverClasses.emma,
|
|
563
563
|
});
|
|
564
564
|
}
|
|
@@ -685,10 +685,10 @@ class Sun2000 extends utils.Adapter {
|
|
|
685
685
|
this.pollingTimer && this.clearTimeout(this.pollingTimer);
|
|
686
686
|
this.mitnightTimer && this.clearTimeout(this.mitnightTimer);
|
|
687
687
|
this.watchDogHandle && this.clearInterval(this.watchDogHandle);
|
|
688
|
-
this.modbusClient && this.modbusClient.close();
|
|
689
688
|
if (typeof this.state.destroy === 'function') {
|
|
690
689
|
this.state.destroy();
|
|
691
690
|
}
|
|
691
|
+
this.modbusClient && this.modbusClient.close();
|
|
692
692
|
this.setState('info.connection', false, true);
|
|
693
693
|
callback();
|
|
694
694
|
} catch {
|
|
@@ -707,19 +707,20 @@ class Sun2000 extends utils.Adapter {
|
|
|
707
707
|
if (state) {
|
|
708
708
|
// The state was changed
|
|
709
709
|
if (state.ack) {
|
|
710
|
-
//this.logger.
|
|
710
|
+
//this.logger.warn(`state ${id} was changed but the ack flag was set. Therefore, no processing takes place!`);
|
|
711
711
|
return;
|
|
712
712
|
}
|
|
713
713
|
const idArray = id.split('.');
|
|
714
714
|
// sun2000.0.inverter.0.control
|
|
715
715
|
if (idArray[2] == 'inverter') {
|
|
716
|
-
const
|
|
717
|
-
|
|
716
|
+
const inverter = this.devices.find(d => d.driverClass == driverClasses.inverter && d.index == Number(idArray[3]));
|
|
717
|
+
//const control = this.devices[Number(idArray[3])].instance.control;
|
|
718
|
+
if (inverter && inverter.instance.control) {
|
|
718
719
|
let serviceId = idArray[5];
|
|
719
720
|
for (let i = 6; i < idArray.length; i++) {
|
|
720
721
|
serviceId += `.${idArray[i]}`;
|
|
721
722
|
}
|
|
722
|
-
control.set(serviceId, state);
|
|
723
|
+
inverter.instance.control.set(serviceId, state);
|
|
723
724
|
}
|
|
724
725
|
//this.log.info(`### state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
725
726
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.sun2000",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "sun2000",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "bolliy",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"node": ">= 22"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@iobroker/adapter-core": "^3.
|
|
30
|
+
"@iobroker/adapter-core": "^3.4.1",
|
|
31
|
+
"javascript-stringify": "^2.1.0",
|
|
31
32
|
"modbus-serial": "^8.0.25",
|
|
32
33
|
"suncalc2": "^1.8.1",
|
|
33
|
-
"tcp-port-used": "^1.0.
|
|
34
|
-
"javascript-stringify": "^2.1.0"
|
|
34
|
+
"tcp-port-used": "^1.0.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@alcalzone/release-script": "^5.2.1",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@iobroker/eslint-config": "^2.3.4",
|
|
43
43
|
"@iobroker/testing": "^5.2.2",
|
|
44
44
|
"@tsconfig/node22": "^22.0.5",
|
|
45
|
-
"@types/node": "^22.19.
|
|
45
|
+
"@types/node": "^22.19.21",
|
|
46
46
|
"typescript": "~6.0.3"
|
|
47
47
|
},
|
|
48
48
|
"main": "main.js",
|