iobroker.sun2000 0.1.2 → 0.2.0

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 CHANGED
@@ -14,11 +14,11 @@
14
14
 
15
15
  Read register data from Huawei SUN2000 inverter and LUNA2000 battery using Modbus TCP.
16
16
 
17
+ Feel free to follow the discussions in the german [iobroker forum](https://forum.iobroker.net/topic/71768/test-adapter-sun2000-v0-1-x-huawei-wechselrichter)
18
+
17
19
  Modbus interface definition (Issue 5, 2023-02-16):
18
20
  https://forum.iobroker.net/assets/uploads/files/1699119419919-solar-inverter-modbus-interface-definitions-v5.pdf
19
21
 
20
- The development of this adapter was inspired by discussions from the forum thread https://forum.iobroker.net/topic/53005/huawei-sun2000-iobroker-via-js-script-funktioniert and the iobroker javascript https://github.com/ChrisBCH/SunLuna2000_iobroker.
21
-
22
22
  ## Supported hardware
23
23
 
24
24
  * HUAWEI Inverter (SUN2000 Serie) M1
@@ -26,13 +26,21 @@ The development of this adapter was inspired by discussions from the forum threa
26
26
  * HUAWEI Luna2000 Battery
27
27
  * HUAWEI Smart Power Sensor DTSU666-H or DDSU666-H
28
28
 
29
+ [Huawei product information](https://solar.huawei.com/en/professionals/all-products?residential-smart-pv)
30
+
29
31
  ## Feature list
30
32
 
31
33
  * Maximum 5 inverters (master/slave) can be processed, each with a battery module (max. 30kWh).
32
- * Live data such as input power, output power, charging/discharging power and the grid consumption are read out at a fixed interval.
34
+ * Real-time values such as input power, output power, charging/discharging power and the grid consumption are read out at a fixed interval.
33
35
  * States are only written for changed data from the inverter. This relieves the burden on the iobroker instance.
34
36
  * The states “inputPower” or “activePower” in the “collected” path can be monitored with a “was updated” trigger element. Because these states are always written within the set interval.
35
37
 
38
+ ## Settings
39
+
40
+ * `address`: Inverter IP address
41
+ * `port`: Inverter modbus port (default: 502)
42
+ * `modbusIds`: inverter IDs, separated with "," (default: 1, max. 5 inverters)
43
+ * `updateInterval`: Fast update interval (default: 20 sec, smallest 5 seconds per inverter)
36
44
 
37
45
  ## Configure inverters
38
46
 
@@ -47,12 +55,12 @@ You may also need a password to connect to the inverters own WLAN: `Changeme`
47
55
  After login on the inverter go to `Settings` (Einstellungen) > `Communication configuration` (Kommunikationskonfiguration) > `Dongle parameter settings` (Dongle‐Parametereinstellungen) > `Modbus TCP` > Activate the `connection without restriction` (Verbindung uneingeschränkt aktivieren). You can also enter the Modbus comm address at the same time read out.
48
56
  If you use two inverters, then connect to the second inverter and read the communication address there too.
49
57
 
50
- ## Settings
58
+ [How activate 'Modbus TCP' - from huawei forum](https://forum.huawei.com/enterprise/en/modbus-tcp-guide/thread/789585-100027)
59
+
60
+ ## Inspiration
61
+
62
+ The development of this adapter was inspired by discussions from the forum thread https://forum.iobroker.net/topic/53005/huawei-sun2000-iobroker-via-js-script-funktioniert and the iobroker javascript https://github.com/ChrisBCH/SunLuna2000_iobroker.
51
63
 
52
- * `address`: Inverter IP address
53
- * `port`: Inverter modbus port (default: 502)
54
- * `modbusIds`: inverter IDs, separated with "," (default: 1, max. 5 inverters)
55
- * `updateInterval`: Fast update interval (default: 20 sec, smallest 5 seconds per inverter)
56
64
 
57
65
  ## Changelog
58
66
 
@@ -60,6 +68,18 @@ If you use two inverters, then connect to the second inverter and read the commu
60
68
  Placeholder for the next version (at the beginning of the line):
61
69
  ### **WORK IN PROGRESS**
62
70
  -->
71
+ ### 0.2.0 (2024-01-24)
72
+ * [Add sun2000 to latest](https://github.com/ioBroker/ioBroker.repositories/pull/3219)
73
+ * improve error handling (#34)
74
+ * add simple optimizer info
75
+ * Riemann sum of input power with energy loss for new state `dailySolarYield`
76
+ * try to recreate the `yield today` from the fusion portal
77
+
78
+ ### 0.1.3 (2024-01-17)
79
+ * display the data from PV strings (#27)
80
+ * optimize the timing of interval loop
81
+ * improved handling of read timeouts from more then 2 inverters
82
+
63
83
  ### 0.1.2 (2024-01-12)
64
84
  * fix: no Data if interval less 20 sec (#24)
65
85
  * prepare collected values more precisely
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "sun2000",
4
- "version": "0.1.2",
4
+ "version": "0.2.0",
5
5
  "news": {
6
+ "0.2.0": {
7
+ "en": "improve error handling (#34)\nadd simple optimizer info \nRiemann sum of input power with energy loss for new state `dailySolarYield`\ntry to recreate the `yield today` from the fusion portal",
8
+ "de": "fehlerbehandlung verbessern (#34)\neinfache optimierer-info hinzufügen\nRiemann Summe der Eingangsleistung mit Energieverlust für neuen Zustand `dailySolarYield `\nversuchen, den `yield heute` aus dem fusionsportal wiederherzustellen",
9
+ "ru": "улучшить обработку ошибок (#34)\nдобавить простую информацию о оптимизации\nRiemann Сумма входной мощности с потерей энергии для нового состояния `dailySolarYield \"\nпопытаться воссоздать «урожай сегодня» с портала синтеза",
10
+ "pt": "melhorar o manuseio de erros (#34)\nadicionar informações de otimização simples\nRiemann soma de energia de entrada com perda de energia para novo estado `dailySolarYield \"\ntentar recriar o `yield hoje` do portal de fusão",
11
+ "nl": "verbetering van de foutafhandeling (#34)\nvoeg eenvoudige optimalisatie info toe\nRiemann-som van het ingangsvermogen met energieverlies voor nieuwe toestand Wat\nproberen om de te recreëren van vandaag van de fusie portal",
12
+ "fr": "améliorer la gestion des erreurs (#34)\najouter des informations d'optimisation simples\nRiemann somme de puissance d'entrée avec perte d'énergie pour le nouvel état `dailySolarYield \"\nessayer de recréer le « rendement aujourd'hui » du portail de fusion",
13
+ "it": "migliorare la gestione degli errori (#34)\naggiungere semplici informazioni di ottimizzazione\nRiemann somma di potenza di ingresso con perdita di energia per nuovo stato `dailySolarYield #\ncercare di ricreare il `yield oggi` dal portale fusion",
14
+ "es": "mejorar el manejo de errores (#34)\nañadir información de optimización simple\nRiemann suma de poder de entrada con pérdida de energía para el nuevo estado `dailySolarYield `\ntratar de recrear el `yield hoy' del portal de fusión",
15
+ "pl": "poprawić obsługę błędów (# 34)\ndodaj proste informacje o optymalizacji\nRiemann suma mocy wejściowej z utratą energii dla nowego stanu \"dailySolarYeld '\nspróbuj odtworzyć \"wydajność dzisiaj\" z portalu syntezy jądrowej",
16
+ "uk": "поліпшення обробки помилок (#34)\nдодати просту оптимізацію\nСума вхідної енергії з втратою енергії для нового стану `dailySolarYield й\nнамагатися відтворити `yield сьогодні` з порталу fusion",
17
+ "zh-cn": "改进错误处理(# 34)\n添加简单的优化信息\nRiemann 输入功率总和, `\n尝试从聚变门户重现“今天”"
18
+ },
19
+ "0.1.3": {
20
+ "en": "display the data from PV strings (#27)\noptimize the timing of interval loop\nimproved handling of read timeouts from more then 2 inverters",
21
+ "de": "die Daten von PV-Strings anzeigen (#27)\noptimieren sie das timing der intervallschleife\nverbesserte handhabung von lesezeitausgängen von mehr als 2 wechselrichtern",
22
+ "ru": "отобразить данные из строк PV (#27)\nоптимизировать время интервального цикла\nулучшенная обработка считываемых таймаутов от более чем 2 инверторов",
23
+ "pt": "exibir os dados de strings PV (#27)\notimizar o tempo de loop de intervalo\nmelhor manuseio de leitura timeouts de mais, em seguida, 2 inversores",
24
+ "nl": "weergave van de gegevens van PV strings (#27)\noptimaliseren van de timing van intervallus\nverbeterde behandeling van leestijd-outs van meer dan 2 inverters",
25
+ "fr": "afficher les données des chaînes PV (#27)\noptimiser le timing de la boucle d'intervalle\namélioration de la gestion des temps de lecture de plus de 2 onduleurs",
26
+ "it": "visualizzare i dati dalle stringhe PV (#27)\nottimizzare la tempistica del loop di intervallo\nmigliore gestione dei timeout di lettura da più di 2 inverter",
27
+ "es": "mostrar los datos de cadenas PV (#27)\noptimizar el tiempo del bucle de intervalo\nmejor manejo de tiempo de lectura de más de 2 inversores",
28
+ "pl": "wyświetla dane z łańcuchów fotowoltaicznych (# 27)\noptymalizacja czasu pętli interwałowej\nulepszona obsługa timeout odczytu z więcej niż 2 inwerterów",
29
+ "uk": "відображення даних з ПВ-рядок (#27)\nоптимізуйте частимізацію інтервалної петлі\nполіпшена обробка часу читання з більш ніж 2 інверторів",
30
+ "zh-cn": "显示 PV 字符串中的数据 (# 27)\n优化间隔循环的时间\n改进对超过2个反转器的断读处理"
31
+ },
6
32
  "0.1.2": {
7
33
  "en": "fix: no Data if interval less 20 sec (#24)\nprepare collected values more precisely\nexpand up to 5 inverters #18\nfix problems with multiple inverters",
8
34
  "de": "fix: keine Daten, wenn Intervall weniger 20 sec (#24)\ndie gesammelten werte genauer\nbis zu 5 wechselrichtern #18 erweitern\nprobleme mit mehreren wechselrichtern beheben",
@@ -16,79 +42,36 @@
16
42
  "uk": "виправити: немає даних, якщо інтервал менше 20 сек (#24)\nпідготовка зібраних значень точно\nрозширити до 5 інверторів #18\nвиправити проблеми з декількома інверторами",
17
43
  "zh-cn": "固定: 如果间隔小于20秒(# 24) 则无数据\n更准确地编制收集的数值\n扩展至5个反转器 # 18\n解决多个反转器的问题"
18
44
  },
19
- "0.1.2-alpha.3": {
20
- "en": "fix: wrong deploying date",
21
- "de": "fix: falsches bereitstellungsdatum",
22
- "ru": "исправить: неправильная дата развертывания",
23
- "pt": "correção: data de implantação errada",
24
- "nl": "fix: verkeerde datum voor ingebruikname",
25
- "fr": "correction : mauvaise date de déploiement",
26
- "it": "fix: la data di distribuzione sbagliata",
27
- "es": "fijado: fecha de despliegue incorrecta",
28
- "pl": "fix: nieprawidłowa data rozmieszczenia",
29
- "uk": "виправити: неправильна дата розгортання",
30
- "zh-cn": "固定: 部署日期错误"
31
- },
32
- "0.1.2-alpha.2": {
33
- "en": "fix: no Data if interval less 20 sec (#24)",
34
- "de": "fix: keine Daten, wenn Intervall weniger 20 sec (#24)",
35
- "ru": "исправление: нет данных, если интервал менее 20 секунд (#24)",
36
- "pt": "correção: não Dados se intervalo menos 20 segundos (#24)",
37
- "nl": "fix: geen Gegevens indien interval minder 20 sec (#24)",
38
- "fr": "correction : pas de données si l'intervalle est inférieur à 20 secondes (#24)",
39
- "it": "correzione: no Dati se intervallo meno 20 sec (#24)",
40
- "es": "fijado: no Datos si intervalo menos 20 segundos (#24)",
41
- "pl": "fix: no Data if interval less 20 sec (# 24)",
42
- "uk": "виправити: немає даних, якщо інтервал менше 20 сек (#24)",
43
- "zh-cn": "固定: 如果间隔小于20秒(# 24) 则无数据"
44
- },
45
- "0.1.2-alpha.1": {
46
- "en": "deploy npm package",
47
- "de": "npm paket bereitstellen",
48
- "ru": "развернуть пакет npm",
49
- "pt": "implementar pacote npm",
50
- "nl": "npm-pakket implementeren",
51
- "fr": "déployer le paquet npm",
52
- "it": "distribuire il pacchetto npm",
53
- "es": "paquete npm",
54
- "pl": "rozmieszczenie pakietu npm",
55
- "uk": "пакет npm",
56
- "zh-cn": "部署 npm 软件包"
57
- },
58
- "0.1.2-alpha.0": {
59
- "en": "prepare collected values more precisely\nexpand up to 5 inverters #18\nfix problems with multiple inverters",
60
- "de": "die gesammelten werte genauer\nbis zu 5 wechselrichtern #18 erweitern\nprobleme mit mehreren wechselrichtern beheben",
61
- "ru": "готовить собранные значения точнее\n#18\nисправить проблемы с несколькими инверторами",
62
- "pt": "preparar valores coletados mais precisamente\nexpandir até 5 inversores #18\ncorrigir problemas com vários inversores",
63
- "nl": "verzamelde waarden nauwkeuriger voorbereiden\nuit te breiden tot 5 omvormers #18\nproblemen oplossen met meerdere inverters",
64
- "fr": "préparer plus précisément les valeurs collectées\nétendre jusqu'à 5 onduleurs #18\nrésoudre les problèmes avec plusieurs onduleurs",
65
- "it": "preparare i valori raccolti più precisamente\nespandere fino a 5 inverter #18\nrisolvere problemi con più inverter",
66
- "es": "preparar los valores recogidos con mayor precisión\nampliar hasta 5 inversores #18\nsolucionar problemas con múltiples inversores",
67
- "pl": "dokładniej przygotować zebrane wartości\nrozszerzyć do 5 inwerterów # 18\nrozwiązać problemy z wieloma inwerterami",
68
- "uk": "підготовка зібраних значень точно\nрозширити до 5 інверторів #18\nвиправити проблеми з декількома інверторами",
69
- "zh-cn": "更准确地编制收集的数值\n扩展至5个反转器 # 18\n解决多个反转器的问题"
70
- },
71
45
  "0.1.1": {
72
46
  "en": "fix some collected values",
73
- "de": "Korrektur einiger der gesammelten Werte (collected values)"
47
+ "de": "Korrektur einiger der gesammelten Werte (collected values)",
48
+ "ru": "исправить некоторые собранные значения",
49
+ "pt": "corrigir alguns valores coletados",
50
+ "nl": "sommige verzamelde waarden vast te stellen",
51
+ "fr": "fixer certaines valeurs collectées",
52
+ "it": "correggere alcuni valori raccolti",
53
+ "es": "fijar algunos valores recogidos",
54
+ "pl": "naprawić niektóre zebrane wartości",
55
+ "uk": "виправити деякі зібрані значення",
56
+ "zh-cn": "固定一些收集的值"
74
57
  }
75
58
  },
76
59
  "titleLang": {
77
- "en": "sun2000",
78
- "de": "sun2000",
79
- "ru": "солнце2000",
80
- "pt": "sol2000",
81
- "nl": "zon2000",
82
- "fr": "soleil2000",
83
- "it": "sole2000",
84
- "es": "sol2000",
85
- "pl": "słońce2000",
86
- "uk": "sun2000",
87
- "zh-cn": "太阳2000"
60
+ "en": "Huawei sun2000 inverters",
61
+ "de": "Huawei sun2000 Wechselrichtern",
62
+ "ru": "Huawei sun2000 inverters",
63
+ "pt": "Huawei sun2000 inverters",
64
+ "nl": "Huawei sun2000 inverters",
65
+ "fr": "Huawei sun2000 inverters",
66
+ "it": "Huawei sun2000 inverters",
67
+ "es": "Huawei sun2000 inverters",
68
+ "pl": "Huawei sun2000 inverters",
69
+ "uk": "Huawei sun2000 inverters",
70
+ "zh-cn": "Huawei sun2000 inverters"
88
71
  },
89
72
  "desc": {
90
- "en": "Read data from Huawei SUN2000 inverter and LUNA2000 battery using Modbus TCP\n",
91
- "de": "Lesen Sie die Daten von Huawei SUN2000 Wechselrichter und LUNA2000 Akku mit Modbus TCP\n",
73
+ "en": "Read data from Huawei SUN2000 inverters and LUNA2000 battery using Modbus TCP\n",
74
+ "de": "Auslesen von Huawei SUN2000 Wechselrichtern und LUNA2000 Akkus über Modbus TCP\n",
92
75
  "ru": "Прочитайте данные от Huawei SUN2000 inverter и LUNA2000 батареи с помощью Modbus TCP\n",
93
76
  "pt": "Leia dados do inversor Huawei SUN2000 e da bateria LUNA2000 usando Modbus TCP\n",
94
77
  "nl": "Lees gegevens van Huawei SUN2000 inverter en LUNA2000 batterij met Modbus TCP\n",
@@ -186,7 +169,7 @@
186
169
  "common": {
187
170
  "name": {
188
171
  "en": "Inverter IP",
189
- "de": "Inverter IP"
172
+ "de": "Wechselrichter IP"
190
173
  },
191
174
  "type": "string",
192
175
  "role": "indicator.ip",
@@ -215,8 +198,8 @@
215
198
  "type": "state",
216
199
  "common": {
217
200
  "name": {
218
- "en": "Secondary Modbus inverter ID",
219
- "de": "Modbus ID des 2ten Wechselrichters"
201
+ "en": "Modbus IDs of inverters",
202
+ "de": "Modbus IDs der Wechselrichters"
220
203
  },
221
204
  "type": "string",
222
205
  "role": "indicator.id",
@@ -240,6 +223,22 @@
240
223
  "desc": "modbus update interval",
241
224
  "unit": "sec"
242
225
  }
226
+ },
227
+ {
228
+ "_id": "info.JSONhealth",
229
+ "type": "state",
230
+ "common": {
231
+ "name": {
232
+ "en": "health information",
233
+ "de": "Gesundheitsinformation"
234
+ },
235
+ "type": "string",
236
+ "role": "indicator.alarm.health",
237
+ "read": true,
238
+ "write": false,
239
+ "desc": "",
240
+ "unit": ""
241
+ }
243
242
  }
244
243
  ]
245
244
  }
@@ -22,7 +22,6 @@ class ModbusConnect extends DeviceInterface {
22
22
  constructor(adapterInstance,ip,port) {
23
23
  super(ip,port);
24
24
  this.adapter = adapterInstance;
25
- this.lastErrno = 0;
26
25
  this._id = 0;
27
26
  }
28
27
 
@@ -35,15 +34,11 @@ class ModbusConnect extends DeviceInterface {
35
34
  }
36
35
 
37
36
  close() {
38
- return new Promise((resolve,reject) => {
39
- if (this.isOpen()) {
40
- this.client.close((err,data) => {
41
- if (err) reject(err);
42
- resolve(data);
43
- } );
44
- } else {
37
+ return new Promise((resolve) => {
38
+ this.client.close(() => {
39
+ //if (err) reject(err);
45
40
  resolve({});
46
- }
41
+ } );
47
42
  });
48
43
  }
49
44
 
@@ -70,21 +65,22 @@ class ModbusConnect extends DeviceInterface {
70
65
  }
71
66
 
72
67
 
73
- async _destroy() {
74
- return new Promise((resolve,reject) => {
75
- this.client.destroy((err,data) => {
76
- if (err) reject(err);
77
- resolve(data);
68
+ _destroy() {
69
+ return new Promise((resolve) => {
70
+ this.client.destroy(() => {
71
+ //if (err) reject(err);
72
+ resolve({});
78
73
  });
79
74
  });
80
75
  }
81
76
 
82
77
  async _checkError(err) {
78
+ this.adapter.log.debug('modbus client error: '+JSON.stringify(err));
79
+ await this.close();
83
80
  if (err.modbusCode == null) {
81
+ this.adapter.log.debug('modbus client destroyed');
84
82
  //https://github.com/yaacov/node-modbus-serial/issues/96
85
- //this.adapter.log.debug('Client destroy!');
86
83
  //await this._destroy();
87
- //this.adapter.log.debug('Client destroy!');
88
84
  await this._create();
89
85
  }
90
86
  }
@@ -93,7 +89,7 @@ class ModbusConnect extends DeviceInterface {
93
89
  async connect(repeatCounter = 0) {
94
90
  try {
95
91
  this.adapter.log.info('Open Connection...');
96
- await this.close();
92
+ this.isOpen() && await this.close();
97
93
  await this.client.setTimeout(5000);
98
94
  await this.client.connectTcpRTUBuffered(this.ipAddress, { port: this.port} ); //hang
99
95
  await this.delay(1000);
@@ -113,7 +109,6 @@ class ModbusConnect extends DeviceInterface {
113
109
  async readHoldingRegisters(address, length) {
114
110
  try {
115
111
  await this.open();
116
- //this.adapter.log.debug('client.setID: '+this._id);
117
112
  await this.client.setID(this._id);
118
113
  const data = await this.client.readHoldingRegisters(address, length);
119
114
  return data.data;
package/lib/register.js CHANGED
@@ -1,39 +1,14 @@
1
1
 
2
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
- }
3
+ const {StateMap,RiemannSum} = require(__dirname + '/tools.js');
31
4
 
32
5
  class Registers {
33
6
  constructor(adapterInstance) {
34
7
  this.adapter = adapterInstance;
35
8
  this.stateCache = new StateMap();
36
-
9
+ for (const inverter of this.adapter.inverters) {
10
+ inverter.solarSum = new RiemannSum();
11
+ }
37
12
  this._loadStates();
38
13
 
39
14
  //https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md
@@ -75,6 +50,9 @@ class Registers {
75
50
  },
76
51
  {
77
52
  state: {id: 'derived.inputPowerWithEfficiencyLoss', name: 'input power with efficiency loss', type: 'number', unit: 'kW', role: 'value.power', desc: ''}
53
+ },
54
+ {
55
+ 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'}
78
56
  }
79
57
  ],
80
58
  postHook: (path) => {
@@ -95,6 +73,9 @@ class Registers {
95
73
  }
96
74
  //inputPower_with_efficiency_loss
97
75
  this.stateCache.set(path+'derived.inputPowerWithEfficiencyLoss', inPowerEff, {type: 'number'});
76
+
77
+ this.inverterInfo.solarSum.add(inPowerEff); //riemannSum
78
+ this.stateCache.set(path+'derived.dailySolarYield', this.inverterInfo.solarSum.sum, {type: 'number'});
98
79
  }
99
80
  },
100
81
  {
@@ -151,6 +132,10 @@ class Registers {
151
132
  state: {id: 'info.ratedPower', name: 'Rated power', type: 'number', unit: 'kW', role: 'value.power'},
152
133
  register: {reg: 30073, type: dataType.int32, gain:1000}
153
134
  },
135
+ {
136
+ state: {id: 'info.numberPVStrings', name: 'Number of PV Strings', type: 'number', unit: '', role: 'value'},
137
+ register: {reg: 30071, type: dataType.uint16}
138
+ },
154
139
  {
155
140
  state: {id: 'info.numberMPPTrackers', name: 'Number of MPP trackers', type: 'number', unit: '', role: 'value'},
156
141
  register: {reg: 30072, type: dataType.uint16}
@@ -326,11 +311,49 @@ class Registers {
326
311
  {
327
312
  state: {id: 'dailyEnergyYield', name: 'Daily Energy Yield', type: 'number', unit: 'kWh', role: 'value.power.produced'},
328
313
  register: {reg: 32114, type: dataType.uint32, gain: 100}
329
- }]
314
+ }
315
+ ],
316
+ preHook: (path,reg) => {
317
+ const noPVString = this.stateCache.get(path+'info.numberPVStrings')?.value;
318
+ if (noPVString > 0) {
319
+ if (!stringFieldsTemplate.generated) stringFieldsTemplate.generated = 0;
320
+ if (stringFieldsTemplate.generated < noPVString) {
321
+ for (let i = stringFieldsTemplate.generated; i < noPVString; i++) {
322
+ //clonen
323
+ //const statePV = Object.assign({},stringFieldsTemplate.states[0]);
324
+ const statePV = JSON.parse(JSON.stringify(stringFieldsTemplate.states[0]));
325
+ const stateCu = JSON.parse(JSON.stringify(stringFieldsTemplate.states[1]));
326
+ const statePo = JSON.parse(JSON.stringify(stringFieldsTemplate.states[2]));
327
+ statePV.state.id = 'string.PV'+(i+1)+'Voltage';
328
+ statePV.register.reg = (stringFieldsTemplate.states[0].register?.reg ?? 0)+ (i*2);
329
+ statePV.register.type = stringFieldsTemplate.states[0].register?.type; //types are not copied?!
330
+ stateCu.state.id = 'string.PV'+(i+1)+'Current';
331
+ stateCu.register.reg = (stringFieldsTemplate.states[1].register?.reg ?? 0)+ (i*2);
332
+ stateCu.register.type = stringFieldsTemplate.states[1].register?.type;
333
+ statePo.state.id = 'string.PV'+(i+1)+'Power';
334
+ reg.states.push(statePV);
335
+ reg.states.push(stateCu);
336
+ reg.states.push(statePo);
337
+ }
338
+ }
339
+ stringFieldsTemplate.generated = noPVString;
340
+ }
341
+ },
342
+ postHook: (path) => {
343
+ const noPVString = this.stateCache.get(path+'info.numberPVStrings')?.value;
344
+ if (noPVString > 0) {
345
+ for (let i = 1; i <= noPVString; i++) {
346
+ const voltage = this.stateCache.get(path+'string.PV'+i+'Voltage')?.value;
347
+ const current = this.stateCache.get(path+'string.PV'+i+'Current')?.value;
348
+ this.stateCache.set(path+'string.PV'+i+'Power',Math.round(voltage*current),{type: 'number'});
349
+ }
350
+ }
351
+ }
352
+
330
353
  },
331
354
  {
332
355
  address : 37100,
333
- length : 114,
356
+ length : 36,
334
357
  info : 'meter info',
335
358
  refresh : dataRefreshRate.low,
336
359
  type : deviceType.meter,
@@ -409,6 +432,25 @@ class Registers {
409
432
  {
410
433
  state: {id: 'meter.activePowerL3', name: 'Active Power L3', type: 'number', unit: 'A', role: 'value.current'},
411
434
  register: {reg: 37136, type: dataType.int32}
435
+ }
436
+ ]
437
+ },
438
+ {
439
+ address : 37200,
440
+ length : 3,
441
+ info : 'optimizer info (static info)',
442
+ type : deviceType.inverter,
443
+ states: [{
444
+ state: {id: 'optimizer.optimizerTotalNumber', name: 'Optimizer Total Number', type: 'number', unit: '', role: 'value'},
445
+ register: {reg: 37200, type: dataType.int16}
446
+ },
447
+ {
448
+ state: {id: 'optimizer.optimizerOnlineNumber', name: 'Optimizer Online Number', type: 'number', unit: '', role: 'value'},
449
+ register: {reg: 37201, type: dataType.int16}
450
+ },
451
+ {
452
+ state: {id: 'optimizer.optimizerFeatureData', name: 'Optimizer Feature Data', type: 'number', unit: '', role: 'value'},
453
+ register: {reg: 37202, type: dataType.int16}
412
454
  }]
413
455
  },
414
456
  {
@@ -457,19 +499,39 @@ class Registers {
457
499
  ]
458
500
  }
459
501
  ];
502
+ //Vorlage für die StringsRegiter
503
+ const stringFieldsTemplate = {
504
+ states : [
505
+ {
506
+ state: {id: 'string.PV1Voltage', name: 'string voltage', type: 'number', unit: 'V', role: 'value.voltage'},
507
+ register: {reg: 32016, type: dataType.int16, length: 1, gain: 10}
508
+ },
509
+ {
510
+ state: {id: 'string.PV1Current', name: 'string current', type: 'number', unit: 'A', role: 'value.current'},
511
+ register: {reg: 32017, type: dataType.int16, length: 1, gain: 100}
512
+ },
513
+ {
514
+ state: {id: 'string.PV1Power', name: 'string power', type: 'number', unit: 'W', role: 'value.power'}
515
+ }
516
+ ]
517
+ };
518
+
460
519
  this.postUpdateHooks = [
461
520
  {
462
521
  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'},
522
+ state: {id: 'derived.dailyInputYield', name: 'Portal Yield Today', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'Try to recreate the yield from the portal'},
464
523
  fn : (path) => {
465
524
  const disCharge = this.stateCache.get(path+'battery.currentDayDischargeCapacity')?.value;
466
525
  const charge = this.stateCache.get(path+'battery.currentDayChargeCapacity')?.value;
467
- const inputYield = Math.round((this.stateCache.get(path+'dailyEnergyYield')?.value + charge - disCharge)*100)/100;
526
+ let inputYield = Math.round((this.stateCache.get(path+'dailyEnergyYield')?.value + charge - disCharge)*100)/100;
527
+ const energyLoss = new Date().getHours()*this.inverterInfo.energyLoss;//+disCharge*0.05;
528
+ inputYield -= energyLoss;
529
+ if (inputYield < 0) inputYield=0;
468
530
  this.stateCache.set(path+'derived.dailyInputYield', inputYield, {type: 'number'});
469
531
  }
470
532
  }
471
533
  ];
472
- this.processHooks = [
534
+ this.postProcessHooks = [
473
535
  {
474
536
  refresh : dataRefreshRate.high,
475
537
  states : [
@@ -504,7 +566,8 @@ class Registers {
504
566
  refresh : dataRefreshRate.low,
505
567
  states : [
506
568
  {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'},
569
+ {id: 'collected.dailyInputYield', name: 'Daily Portal Yield', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'Try to recreate the yield from the portal'},
570
+ {id: 'collected.dailySolarYield', name: 'Daily Solar Yield', type: 'number', unit: 'kWh', role: 'value.power.consumption', desc: 'Riemann sum of input power with efficiency loss'},
508
571
  {id: 'collected.accumulatedEnergyYield', name: 'Accumulated Energy Yield', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
509
572
  {id: 'collected.consumptionSum', name: 'Consumption Sum', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
510
573
  {id: 'collected.gridExportStart', name: 'Grid Export Start Today', type: 'number', unit: 'kWh', role: 'value.power.consumption'},
@@ -521,7 +584,8 @@ class Registers {
521
584
  {id: 'collected.ratedCapacity', name: 'Rated of battery Capacity', type: 'number', unit: 'Wh', role: 'value.capacity'}
522
585
  ],
523
586
  fn : (inverters) => {
524
- let inYield = 0;
587
+ let inYield = 0; //deprecated
588
+ let solarYield = 0;
525
589
  let outYield = 0;
526
590
  let enYield = 0;
527
591
  let charge = 0;
@@ -532,7 +596,8 @@ class Registers {
532
596
  let load = 0;
533
597
  for (const inverter of inverters) {
534
598
  outYield += this.stateCache.get(inverter.path+'.dailyEnergyYield')?.value;
535
- inYield += this.stateCache.get(inverter.path+'.derived.dailyInputYield')?.value;
599
+ inYield += this.stateCache.get(inverter.path+'.derived.dailyInputYield')?.value; //deprecated
600
+ solarYield += this.stateCache.get(inverter.path+'.derived.dailySolarYield')?.value;
536
601
  enYield += this.stateCache.get(inverter.path+'.accumulatedEnergyYield')?.value;
537
602
  charge += this.stateCache.get(inverter.path+'.battery.currentDayChargeCapacity')?.value;
538
603
  disCharge += this.stateCache.get(inverter.path+'.battery.currentDayDischargeCapacity')?.value;
@@ -544,7 +609,8 @@ class Registers {
544
609
  }
545
610
  }
546
611
  this.stateCache.set('collected.dailyEnergyYield',outYield, {type: 'number'});
547
- this.stateCache.set('collected.dailyInputYield',inYield, {type: 'number'});
612
+ this.stateCache.set('collected.dailyInputYield',inYield, {type: 'number'}); //deprecated
613
+ this.stateCache.set('collected.dailySolarYield',solarYield, {type: 'number'});
548
614
  this.stateCache.set('collected.accumulatedEnergyYield',enYield, {type: 'number'});
549
615
  const conSum = enYield + this.stateCache.get('meter.reverseActiveEnergy')?.value - this.stateCache.get('meter.positiveActiveEnergy')?.value;
550
616
  this.stateCache.set('collected.consumptionSum',conSum, {type: 'number'});
@@ -623,6 +689,7 @@ class Registers {
623
689
 
624
690
  async processRegister(reg,data) {
625
691
  const path = this.getStatePath(reg.type);
692
+ if (reg.preHook) reg.preHook(path,reg);
626
693
  if (reg.states) {
627
694
  for(const field of reg.states) {
628
695
  const state = field.state;
@@ -658,7 +725,7 @@ class Registers {
658
725
  let readRegisters = 0;
659
726
  for (const reg of this.registerFields) {
660
727
  if (duration) {
661
- if (new Date().getTime() - start > (duration - 3000)) {
728
+ if (new Date().getTime() - start > (duration - 1000)) {
662
729
  this.adapter.log.debug('Duration: '+Math.round(duration/1000)+' used time: '+ (new Date().getTime() - start)/1000);
663
730
  break;
664
731
  }
@@ -671,8 +738,8 @@ class Registers {
671
738
  if ( refreshRate !== dataRefreshRate.high) {
672
739
  if (lastread) {
673
740
  if (!reg.refresh) continue;
674
- if ((start - lastread) < 60000) {
675
- this.adapter.log.debug('Last Update :'+(start - lastread));
741
+ if ((start - lastread) < this.adapter.settings.lowIntervall) {
742
+ this.adapter.log.debug('Last read :'+(start - lastread));
676
743
  continue;
677
744
  }
678
745
  }
@@ -706,12 +773,6 @@ class Registers {
706
773
  if (dataRefreshRate.compare(refreshRate,hook.refresh)) {
707
774
  const state = hook.state;
708
775
  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
776
  await this._initState(path,state);
716
777
  }
717
778
  hook.fn(path);
@@ -720,9 +781,8 @@ class Registers {
720
781
  }
721
782
  }
722
783
 
723
-
724
784
  async runProcessHooks(refreshRate) {
725
- for (const hook of this.processHooks) {
785
+ for (const hook of this.postProcessHooks) {
726
786
  if (dataRefreshRate.compare(refreshRate,hook.refresh)) {
727
787
  for (const state of hook.states) {
728
788
  if (!hook['initState'+this.inverterInfo.index]) {
@@ -737,12 +797,54 @@ class Registers {
737
797
  }
738
798
 
739
799
  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 });
800
+ let state = await this.adapter.getStateAsync('collected.gridExportStart');
801
+ this.stateCache.set('collected.gridExportStart',state?.val, {type : 'number', stored : true });
802
+ state = await this.adapter.getStateAsync('collected.gridImportStart');
803
+ this.stateCache.set('collected.gridImportStart',state?.val, {type : 'number', stored : true });
804
+ state = await this.adapter.getStateAsync('collected.consumptionStart');
805
+ this.stateCache.set('collected.consumptionStart',state?.val, {type : 'number', stored : true });
806
+ for (const inverter of this.adapter.inverters) {
807
+ state = await this.adapter.getStateAsync(inverter.path+'.derived.dailySolarYield');
808
+ state?.val && inverter.solarSum.setStart(state.val,state.ts);
809
+ }
810
+ }
811
+
812
+ ChechReadError(timeShift) {
813
+ const now = new Date();
814
+ for (const inverter of this.adapter.inverters) {
815
+ for (const [i, reg] of this.registerFields.entries()) {
816
+ if (reg.type == deviceType.meter && inverter.meter == false) continue; //not meter
817
+ if (reg.states && reg.refresh) {
818
+ const lastread = reg['lastread'+inverter.index];
819
+ const ret = {
820
+ errno : 0,
821
+ address : reg.address,
822
+ info : reg.info,
823
+ inverter : inverter.index,
824
+ modbusID : inverter.modbusId,
825
+ tc : now.getTime
826
+ };
827
+
828
+ if (lastread) {
829
+ ret.lastread = lastread;
830
+ } else {
831
+ ret.lastread = 0;
832
+ }
833
+
834
+ if (now.getTime()-ret.lastread > timeShift) {
835
+ if (reg.lastread == 0 && i == 0) {
836
+ ret.errno = 101;
837
+ ret.message = 'Can\'t read data from inverter! Please check the configuration.';
838
+ } else {
839
+ ret.errno = 102;
840
+ ret.message = 'Not all data can be read! Please inspect the sun2000 logs.';
841
+ }
842
+ return ret ;
843
+ }
844
+ }
845
+ }
846
+ }
847
+ return {message: 'No problems detected'};
746
848
  }
747
849
 
748
850
  // one minute before midnight - perform housekeeping actions
package/lib/tools.js ADDED
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ class StateMap {
4
+ constructor () {
5
+ this.stateMap = new Map();
6
+ }
7
+
8
+ get(id) {
9
+ return this.stateMap.get(id);
10
+ }
11
+
12
+ set(id, value, options) {
13
+ if (options?.type == 'number' && isNaN(value)) return;
14
+ if (value !== null) {
15
+ if (options?.renew || this.get(id)?.value !== value) {
16
+ if (options?.type == 'number') {
17
+ value = Math.round((value + Number.EPSILON) * 1000) / 1000; //3rd behind
18
+ }
19
+ if (options?.stored ) {
20
+ this.stateMap.set(id, {id: id, value: value, stored: options.stored});
21
+ } else {
22
+ this.stateMap.set(id, {id: id, value: value});
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ values () {
29
+ return this.stateMap.values();
30
+ }
31
+
32
+ }
33
+
34
+ class RiemannSum {
35
+ constructor (autoResetAtMitnight = true) {
36
+ this._resetAtMitnight = autoResetAtMitnight;
37
+ this.reset();
38
+ }
39
+
40
+ add (newValue) {
41
+ //Obersumme bilden
42
+ const now = new Date();
43
+ if (this._resetAtMitnight) {
44
+ const night = new Date(
45
+ now.getFullYear(),
46
+ now.getMonth(),
47
+ now.getDate(), // today, ...
48
+ 0, 0, 0 // ...at 00:00:00 hours
49
+ );
50
+ // @ts-ignore
51
+ if (this._lastDate.getTime() <= night.getTime()) {
52
+ this.reset();
53
+ }
54
+ }
55
+ // @ts-ignore
56
+ this._sum += newValue * (now.getTime() - this._lastDate.getTime())/3600/1000; //Hour/Sekunden
57
+ this._lastDate = now;
58
+ }
59
+
60
+ reset() {
61
+ this._sum = 0;
62
+ this._lastDate = new Date();
63
+ }
64
+
65
+ get sum() {
66
+ return this._sum;
67
+ }
68
+
69
+ setStart(sum, ts) {
70
+ this._sum = sum;
71
+ this._lastDate = new Date(ts);
72
+ }
73
+ }
74
+
75
+
76
+ module.exports = {
77
+ StateMap,
78
+ RiemannSum
79
+ };
package/main.js CHANGED
@@ -25,14 +25,16 @@ class Sun2000 extends utils.Adapter {
25
25
  name: 'sun2000',
26
26
  });
27
27
 
28
- this.lastTimeUpdated = 0;
29
- this.lastStateUpdated = 0;
28
+ this.lastTimeUpdated = new Date().getTime();
29
+ this.lastStateUpdatedHigh = 0;
30
+ this.lastStateUpdatedLow = 0;
30
31
  this.isConnected = false;
31
32
  this.inverters = [];
32
33
  this.settings = {
33
- intervall : 20000,
34
+ highIntervall : 20000,
35
+ lowIntervall : 60000,
34
36
  address : '',
35
- port : 520,
37
+ port : 520
36
38
  };
37
39
 
38
40
  this.on('ready', this.onReady.bind(this));
@@ -42,19 +44,11 @@ class Sun2000 extends utils.Adapter {
42
44
  this.on('unload', this.onUnload.bind(this));
43
45
  }
44
46
 
45
- getInverterInfo(id) {
46
- /*
47
- const inverter = this.inverters.find((item) => item.modbusId == id);
48
- return inverter;
49
- */
50
- return this.inverters[id];
51
- }
52
-
53
47
  async initPath() {
54
48
  await this.extendObjectAsync('info', {
55
49
  type: 'channel',
56
50
  common: {
57
- name: 'info',
51
+ name: 'channel info',
58
52
  role: 'info'
59
53
  },
60
54
  native: {}
@@ -76,16 +70,14 @@ class Sun2000 extends utils.Adapter {
76
70
  await this.extendObjectAsync('meter', {
77
71
  type: 'device',
78
72
  common: {
79
- name: 'meter',
80
- role: 'info'
73
+ name: 'device meter'
81
74
  },
82
75
  native: {}
83
76
  });
84
77
  await this.extendObjectAsync('collected', {
85
78
  type: 'channel',
86
79
  common: {
87
- name: 'collected',
88
- role: 'info'
80
+ name: 'channel collected'
89
81
  },
90
82
  native: {}
91
83
  });
@@ -93,21 +85,19 @@ class Sun2000 extends utils.Adapter {
93
85
  await this.extendObjectAsync('inverter', {
94
86
  type: 'device',
95
87
  common: {
96
- name: 'meter',
97
- role: 'info'
88
+ name: 'device inverter'
98
89
  },
99
90
  native: {}
100
91
  });
101
92
 
102
- //ES6 use a for (const [index, item] of array.entries()) of loop
103
- //for (const [i, item] of this.conf.entries()) {
93
+ //ES6 use a for (const [index, item] of array.entries()) of loop
104
94
  for (const [i, item] of this.inverters.entries()) {
105
95
  const path = 'inverter.'+String(i);
106
96
  item.path = path;
107
97
  await this.extendObjectAsync(path, {
108
98
  type: 'channel',
109
99
  common: {
110
- name: 'modbus'+i,
100
+ name: 'channel modbus'+i,
111
101
  role: 'indicator'
112
102
  },
113
103
  native: {}
@@ -116,7 +106,15 @@ class Sun2000 extends utils.Adapter {
116
106
  await this.extendObjectAsync(path+'.grid', {
117
107
  type: 'channel',
118
108
  common: {
119
- name: 'grid',
109
+ name: 'channel grid'
110
+ },
111
+ native: {}
112
+ });
113
+
114
+ await this.extendObjectAsync(path+'.info', {
115
+ type: 'channel',
116
+ common: {
117
+ name: 'channel info',
120
118
  role: 'info'
121
119
  },
122
120
  native: {}
@@ -125,8 +123,15 @@ class Sun2000 extends utils.Adapter {
125
123
  await this.extendObjectAsync(path+'.battery', {
126
124
  type: 'channel',
127
125
  common: {
128
- name: 'battery',
129
- role: 'info'
126
+ name: 'channel battery'
127
+ },
128
+ native: {}
129
+ });
130
+
131
+ await this.extendObjectAsync(path+'.string', {
132
+ type: 'channel',
133
+ common: {
134
+ name: 'channel string'
130
135
  },
131
136
  native: {}
132
137
  });
@@ -134,8 +139,15 @@ class Sun2000 extends utils.Adapter {
134
139
  await this.extendObjectAsync(path+'.derived', {
135
140
  type: 'channel',
136
141
  common: {
137
- name: 'derived',
138
- role: 'indicator'
142
+ name: 'channel derived'
143
+ },
144
+ native: {}
145
+ });
146
+
147
+ await this.extendObjectAsync(path+'.optimizer', {
148
+ type: 'channel',
149
+ common: {
150
+ name: 'channel optimizer'
139
151
  },
140
152
  native: {}
141
153
  });
@@ -144,8 +156,6 @@ class Sun2000 extends utils.Adapter {
144
156
  }
145
157
 
146
158
  async InitProcess() {
147
- this.state = new Registers(this);
148
- this.modbusClient = new ModbusConnect(this,this.settings.address,this.settings.port);
149
159
  try {
150
160
  await this.initPath();
151
161
  /*
@@ -154,6 +164,8 @@ class Sun2000 extends utils.Adapter {
154
164
  } catch (err) {
155
165
  this.log.warn(err);
156
166
  }
167
+ this.modbusClient = new ModbusConnect(this,this.settings.address,this.settings.port);
168
+ this.state = new Registers(this);
157
169
  this.dataPolling();
158
170
  this.runWatchDog();
159
171
  this.atMidnight();
@@ -210,21 +222,33 @@ class Sun2000 extends utils.Adapter {
210
222
  runWatchDog() {
211
223
  this.watchDogHandle && this.clearInterval(this.watchDogHandle);
212
224
  this.watchDogHandle = this.setInterval( () => {
213
- if (!this.lastTimeUpdated) this.lastUpdated = 0;
214
- if (this.lastTimeUpdated > 0) {
215
- const sinceLastUpdate = new Date().getTime() - this.lastTimeUpdated; //ms
216
- this.log.debug('Watchdog: time to last update '+sinceLastUpdate/1000+' sec');
217
- const lastIsConnected = this.isConnected;
218
- this.isConnected = this.lastStateUpdated > 0 && sinceLastUpdate < this.settings.intervall*2;
219
- this.log.debug('lastIsConncted '+lastIsConnected+' isConnectetd '+this.isConnected+' lastStateupdated '+this.lastStateUpdated);
220
-
221
- if (this.isConnected !== lastIsConnected ) this.setState('info.connection', this.isConnected, true);
222
- if (sinceLastUpdate > this.settings.intervall*10) {
223
- this.log.warn('watchdog: restart Adapter...');
224
- this.restart();
225
+ const sinceLastUpdate = new Date().getTime() - this.lastTimeUpdated; //ms
226
+ this.log.debug('Watchdog: time of last update '+sinceLastUpdate/1000+' sec');
227
+ const lastIsConnected = this.isConnected;
228
+ this.isConnected = this.lastStateUpdatedHigh > 0 && sinceLastUpdate < this.settings.highIntervall*3;
229
+ if (this.isConnected !== lastIsConnected ) this.setState('info.connection', this.isConnected, true);
230
+ if (!this.isConnected) {
231
+ this.setStateAsync('info.JSONhealth', {val: '{errno:1, message: "Can\'t connect to inverter"}', ack: true});
232
+ } else {
233
+ if (this.alreadyRunWatchDog) {
234
+ const ret = this.state.ChechReadError(this.settings.lowIntervall*2);
235
+ if (ret.errno) this.log.warn(ret.message);
236
+ this.setStateAsync('info.JSONhealth', {val: JSON.stringify(ret), ack: true});
237
+ } else {
238
+ this.alreadyRunWatchDog = true;
225
239
  }
226
240
  }
227
- },30000);
241
+
242
+ this.lastStateUpdatedLow = 0;
243
+ this.lastStateUpdatedHigh = 0;
244
+
245
+ if (sinceLastUpdate > this.settings.highIntervall*10) {
246
+ this.setStateAsync('info.JSONhealth', {val: '{errno:2, message: "Internal loop error"}', ack: true});
247
+ this.log.warn('watchdog: restart Adapter...');
248
+ this.restart();
249
+ }
250
+
251
+ },this.settings.lowIntervall);
228
252
  }
229
253
 
230
254
 
@@ -237,21 +261,25 @@ class Sun2000 extends utils.Adapter {
237
261
  await this.setStateAsync('info.ip', {val: this.config.address, ack: true});
238
262
  await this.setStateAsync('info.port', {val: this.config.port, ack: true});
239
263
  await this.setStateAsync('info.modbusIds', {val: this.config.modbusIds, ack: true});
264
+ await this.setStateAsync('info.JSONhealth', {val: '{message : "Information is collected"}', ack: true});
240
265
 
241
266
  // Load user settings
242
267
  if (this.config.address !== '' || this.config.port > 0 || this.config.updateInterval > 0 ) {
243
- this.settings.intervall = this.config.updateInterval*1000; //ms
268
+ this.settings.highIntervall = this.config.updateInterval*1000; //ms
244
269
  this.settings.address = this.config.address;
245
270
  this.settings.port = this.config.port;
246
271
  this.settings.modbusIds = this.config.modbusIds.split(',').map((n) => {return Number(n);});
247
272
 
248
273
  if (this.settings.modbusIds.length > 0 && this.settings.modbusIds.length < 6) {
249
- if (this.settings.intervall < 5000*this.settings.modbusIds.length ) {
250
- this.settings.intervall = 5000*this.settings.modbusIds.length;
274
+ if (this.settings.highIntervall < 5000*this.settings.modbusIds.length ) {
275
+ this.settings.highIntervall = 5000*this.settings.modbusIds.length;
251
276
  }
252
- await this.setStateAsync('info.modbusUpdateInterval', {val: this.settings.intervall/1000, ack: true});
277
+ if (this.settings.highIntervall >= this.settings.lowIntervall) {
278
+ this.settings.lowIntervall = this.settings.highIntervall;
279
+ }
280
+ await this.setStateAsync('info.modbusUpdateInterval', {val: this.settings.highIntervall/1000, ack: true});
253
281
  for (const [i,id] of this.settings.modbusIds.entries()) {
254
- this.inverters.push({index: i, modbusId: id, meter: (i==0)});
282
+ this.inverters.push({index: i, modbusId: id, energyLoss: 0.008, meter: (i==0)}); //own energy consumption of inverter 8 W
255
283
  }
256
284
  await this.InitProcess();
257
285
  } else {
@@ -265,52 +293,43 @@ class Sun2000 extends utils.Adapter {
265
293
  }
266
294
 
267
295
  async dataPolling() {
268
- function timeLeft(target) {
269
- const left = target - new Date().getTime();
296
+
297
+ function timeLeft(target,factor =1) {
298
+ const left = Math.round((target - new Date().getTime())*factor);
270
299
  if (left < 0) return 0;
271
300
  return left;
272
301
  }
273
302
 
274
303
  const start = new Date().getTime();
275
-
276
- this.log.debug('### DataPolling START <> '+ Math.round((start-this.lastTimeUpdated)/1000)+' sec ###');
277
- if (this.lastTimeUpdated > 0 && (start-this.lastTimeUpdated)/1000 > this.settings.intervall/1000 + 1) {
278
- this.log.warn('time intervall '+(start-this.lastTimeUpdated)/1000+' sec');
304
+ this.log.debug('### DataPolling START '+ Math.round((start-this.lastTimeUpdated)/1000)+' sec ###');
305
+ if (this.lastTimeUpdated > 0 && (start-this.lastTimeUpdated)/1000 > this.settings.highIntervall/1000 + 1) {
306
+ this.log.info('time intervall '+(start-this.lastTimeUpdated)/1000+' sec');
279
307
  }
280
308
  this.lastTimeUpdated = start;
281
- let stateUpdated = 0;
282
-
283
- const nextLoop = this.settings.intervall - start % (this.settings.intervall) + start;
309
+ const nextLoop = this.settings.highIntervall - start % (this.settings.highIntervall) + start;
284
310
 
285
311
  //High Loop
286
312
  for (const item of this.inverters) {
287
313
  this.modbusClient.setID(item.modbusId);
288
- //this.log.info('### Left Time '+timeLeft/1000);
289
- stateUpdated += await this.state.updateStates(item,this.modbusClient,dataRefreshRate.high,timeLeft(nextLoop));
314
+ this.lastStateUpdatedHigh += await this.state.updateStates(item,this.modbusClient,dataRefreshRate.high,timeLeft(nextLoop));
290
315
  }
316
+ await this.state.runProcessHooks(dataRefreshRate.high);
291
317
 
292
- if (timeLeft(nextLoop) > 2000) {
293
- await this.state.runProcessHooks(dataRefreshRate.high);
294
-
318
+ if (timeLeft(nextLoop) > 500) {
295
319
  //Low Loop
296
- for (const item of this.inverters) {
320
+ for (const [i,item] of this.inverters.entries()) {
297
321
  this.modbusClient.setID(item.modbusId);
298
- //this.log.info('### Left Time '+timeLeft/1000);
299
- stateUpdated += await this.state.updateStates(item,this.modbusClient,dataRefreshRate.low,timeLeft(nextLoop));
300
- }
301
- if (timeLeft(nextLoop) > 1000) {
302
- await this.state.runProcessHooks(dataRefreshRate.low);
322
+ //this.log.debug('+++++ Loop: '+i+' Left Time: '+timeLeft(nextLoop,(i+1)/this.inverters.length)+' Faktor '+((i+1)/this.inverters.length));
323
+ this.lastStateUpdatedLow += await this.state.updateStates(item,this.modbusClient,dataRefreshRate.low,timeLeft(nextLoop,(i+1)/this.inverters.length));
303
324
  }
304
325
  }
305
-
306
- this.lastStateUpdated = stateUpdated;
326
+ await this.state.runProcessHooks(dataRefreshRate.low);
307
327
 
308
328
  if (this.pollingTimer) this.clearTimeout(this.pollingTimer);
309
329
  this.pollingTimer = this.setTimeout(() => {
310
330
  this.dataPolling(); //recursiv
311
331
  }, timeLeft(nextLoop));
312
332
  this.log.debug('### DataPolling STOP ###');
313
- //this.state.mitnightProcess();
314
333
  }
315
334
 
316
335
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.sun2000",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "sun2000",
5
5
  "author": {
6
6
  "name": "bolliy",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@iobroker/adapter-core": "^3.0.4",
26
- "modbus-serial": "^8.0.13"
26
+ "modbus-serial": "^8.0.16"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@alcalzone/release-script": "^3.6.0",