iobroker.sigenergy 2.3.2 → 2.3.4

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
@@ -153,6 +153,12 @@ Status and power readings for the DC charger.
153
153
 
154
154
  ## Changelog
155
155
 
156
+ ### 2.3.4 (2026-06-12)
157
+ - (ssbingo) fix: correct protocol level detection — use proper register quantities for probes, descend from V2.9 to V2.6, distinguish transport errors from device exceptions to avoid false pre-V2.6 report
158
+
159
+ ### 2.3.3 (2026-06-11)
160
+ - (ssbingo) fix: suppress repeated register read warnings after first occurrence for plant/inverter/acCharger/dcCharger/pss/pid; subsequent failures log at debug level only
161
+
156
162
  ### 2.3.2 (2026-06-10)
157
163
  - (ssbingo) fix: show 'pre-V2.6' instead of 'unknown' when device responds but has no extended plant registers; add per-probe debug log with Modbus exception message
158
164
 
package/io-package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "sigenergy",
4
- "version": "2.3.2",
4
+ "version": "2.3.4",
5
5
  "news": {
6
- "2.3.2": {
7
- "en": "(ssbingo) fix: show 'pre-V2.6' instead of 'unknown' when device responds but has no extended plant registers; add per-probe debug log with Modbus exception message",
8
- "de": "(ssbingo) fix: 'pre-V2.6' statt 'unknown' wenn Gerät antwortet aber keine erweiterten Plant-Register hat; Debug-Log pro Probe mit Modbus-Exception-Meldung ergänzt",
9
- "ru": "(ssbingo) fix: отображать 'pre-V2.6' вместо 'unknown' если устройство отвечает, но не имеет расширенных регистров plant; добавлено отладочное сообщение на каждый зонд с кодом Modbus-исключения",
10
- "pt": "(ssbingo) fix: mostrar 'pre-V2.6' em vez de 'unknown' quando o dispositivo responde mas não tem registos plant estendidos; log de debug por sonda com mensagem de excepção Modbus",
11
- "nl": "(ssbingo) fix: 'pre-V2.6' tonen in plaats van 'unknown' als het apparaat reageert maar geen uitgebreide plant-registers heeft; debug-log per probe met Modbus-uitzonderingsmelding",
12
- "fr": "(ssbingo) fix: afficher 'pre-V2.6' au lieu de 'unknown' si l'appareil répond mais n'a pas de registres plant étendus ; log de débogage par sonde avec message d'exception Modbus",
13
- "it": "(ssbingo) fix: mostrare 'pre-V2.6' invece di 'unknown' quando il dispositivo risponde ma non ha registri plant estesi; log di debug per sonda con messaggio di eccezione Modbus",
14
- "es": "(ssbingo) fix: mostrar 'pre-V2.6' en lugar de 'unknown' cuando el dispositivo responde pero no tiene registros plant extendidos; log de depuración por sonda con mensaje de excepción Modbus",
15
- "pl": "(ssbingo) fix: pokazywać 'pre-V2.6' zamiast 'unknown' gdy urządzenie odpowiada ale nie ma rozszerzonych rejestrów plant; log debugowania na sondę z komunikatem wyjątku Modbus",
16
- "uk": "(ssbingo) fix: відображати 'pre-V2.6' замість 'unknown' коли пристрій відповідає, але не має розширених регістрів plant; журнал налагодження на зонд з повідомленням про виняток Modbus",
17
- "zh-cn": "(ssbingo) fix: 当设备响应但无扩展 plant 寄存器时显示 'pre-V2.6' 而非 'unknown';为每个探针添加含 Modbus 异常信息的调试日志"
6
+ "2.3.4": {
7
+ "en": "(ssbingo) fix: correct protocol level detection use proper register quantities for probes, descend from V2.9 to V2.6, distinguish transport errors from device exceptions to avoid false pre-V2.6 report",
8
+ "de": "(ssbingo) fix: Protokollversionserkennung korrigiert korrekte Registermengen für Probes, absteigende Reihenfolge V2.9→V2.6, Transportfehler von Geräteantworten unterschieden um fehlerhafte pre-V2.6-Meldung zu vermeiden",
9
+ "ru": "(ssbingo) fix: исправлено определение уровня протокола правильное количество регистров для проб, порядок убывания V2.9→V2.6, разграничение транспортных ошибок и ответов устройства для исключения ложного сообщения pre-V2.6",
10
+ "pt": "(ssbingo) fix: correção da deteção do nível de protocolo quantidades corretas de registos para sondas, ordem decrescente V2.9→V2.6, distinção entre erros de transporte e exceções do dispositivo para evitar falso relatório pre-V2.6",
11
+ "nl": "(ssbingo) fix: correcte protocolniveaudetectie juiste registerhoeveelheden voor probes, aflopende volgorde V2.9→V2.6, onderscheid transportfouten van apparaatreacties om vals pre-V2.6-rapport te voorkomen",
12
+ "fr": "(ssbingo) fix: correction de la détection du niveau de protocole quantités de registres correctes pour les sondes, ordre décroissant V2.9→V2.6, distinction des erreurs de transport des exceptions du périphérique pour éviter un rapport pre-V2.6 erroné",
13
+ "it": "(ssbingo) fix: correzione del rilevamento del livello di protocollo quantità di registri corrette per le probe, ordine decrescente V2.9→V2.6, distinzione errori di trasporto da eccezioni del dispositivo per evitare falso rapporto pre-V2.6",
14
+ "es": "(ssbingo) fix: corrección de la detección del nivel de protocolo cantidades correctas de registros para sondas, orden descendente V2.9→V2.6, distinción de errores de transporte de excepciones del dispositivo para evitar informe falso pre-V2.6",
15
+ "pl": "(ssbingo) fix: poprawka wykrywania poziomu protokołu właściwe ilości rejestrów dla sond, kolejność malejąca V2.9→V2.6, rozróżnienie błędów transportu od wyjątków urządzenia w celu uniknięcia fałszywego raportu pre-V2.6",
16
+ "uk": "(ssbingo) fix: виправлення визначення рівня протоколу правильна кількість регістрів для проб, порядок спадання V2.9→V2.6, розрізнення транспортних помилок від винятків пристрою для уникнення хибного звіту pre-V2.6",
17
+ "zh-cn": "(ssbingo) fix: 修正协议版本检测 使用正确的寄存器数量探测,从 V2.9 降序到 V2.6,区分传输错误和设备异常,避免误报 pre-V2.6"
18
18
  }
19
19
  },
20
20
  "titleLang": {
package/lib/registers.js CHANGED
@@ -1252,7 +1252,7 @@ const PLANT_READ_REGISTERS = [
1252
1252
  desc: 'Total oil generator energy (V2.8)',
1253
1253
  feature: 'enableCumulativeEnergy',
1254
1254
  },
1255
- // V2.8 — separate FC04 group (gap > 4 from 30087)
1255
+ // V2.9 — separate FC04 group (gap > 4 from 30087)
1256
1256
  {
1257
1257
  addr: 30272,
1258
1258
  qty: 2,
@@ -1261,7 +1261,7 @@ const PLANT_READ_REGISTERS = [
1261
1261
  gain: 100,
1262
1262
  unit: 'kWh',
1263
1263
  role: 'value.energy',
1264
- desc: 'PV total daily generation (V2.8)',
1264
+ desc: 'PV total daily generation (V2.9)',
1265
1265
  },
1266
1266
  {
1267
1267
  addr: 30274,
@@ -1271,9 +1271,9 @@ const PLANT_READ_REGISTERS = [
1271
1271
  gain: 100,
1272
1272
  unit: 'kWh',
1273
1273
  role: 'value.energy',
1274
- desc: 'PV total generation previous day (V2.8)',
1274
+ desc: 'PV total generation previous day (V2.9)',
1275
1275
  },
1276
- // V2.9
1276
+ // V2.8
1277
1277
  {
1278
1278
  addr: 30276,
1279
1279
  qty: 1,
@@ -1282,7 +1282,7 @@ const PLANT_READ_REGISTERS = [
1282
1282
  gain: 100,
1283
1283
  unit: 'Hz',
1284
1284
  role: 'value.frequency',
1285
- desc: 'Grid code rated frequency (V2.9)',
1285
+ desc: 'Grid code rated frequency (V2.8)',
1286
1286
  },
1287
1287
  {
1288
1288
  addr: 30277,
@@ -1292,7 +1292,7 @@ const PLANT_READ_REGISTERS = [
1292
1292
  gain: 100,
1293
1293
  unit: 'V',
1294
1294
  role: 'value.voltage',
1295
- desc: 'Grid code rated voltage (V2.9)',
1295
+ desc: 'Grid code rated voltage (V2.8)',
1296
1296
  },
1297
1297
  {
1298
1298
  addr: 30279,
@@ -1312,7 +1312,7 @@ const PLANT_READ_REGISTERS = [
1312
1312
  gain: null,
1313
1313
  unit: '',
1314
1314
  role: 'value',
1315
- desc: 'Merged alarm 6 (V2.9)',
1315
+ desc: 'Merged alarm 6 (V2.8)',
1316
1316
  },
1317
1317
  {
1318
1318
  addr: 30281,
@@ -1322,7 +1322,7 @@ const PLANT_READ_REGISTERS = [
1322
1322
  gain: null,
1323
1323
  unit: '',
1324
1324
  role: 'value',
1325
- desc: 'Merged alarm 7 (V2.9)',
1325
+ desc: 'Merged alarm 7 (V2.8)',
1326
1326
  },
1327
1327
  {
1328
1328
  addr: 30282,
@@ -1332,7 +1332,7 @@ const PLANT_READ_REGISTERS = [
1332
1332
  gain: 1000,
1333
1333
  unit: 'kW',
1334
1334
  role: 'value.power',
1335
- desc: 'General load power (V2.9)',
1335
+ desc: 'General load power (V2.8)',
1336
1336
  },
1337
1337
  {
1338
1338
  addr: 30284,
@@ -1342,7 +1342,7 @@ const PLANT_READ_REGISTERS = [
1342
1342
  gain: 1000,
1343
1343
  unit: 'kW',
1344
1344
  role: 'value.power',
1345
- desc: 'Total load power (V2.9)',
1345
+ desc: 'Total load power (V2.8)',
1346
1346
  },
1347
1347
  {
1348
1348
  addr: 30286,
package/main.js CHANGED
@@ -62,6 +62,12 @@ class Sigenergy extends utils.Adapter {
62
62
  this._controlRegistersRead = false;
63
63
  this._protocolDetected = false;
64
64
  this._essPreheatingUnsupported = false;
65
+ this._plantUnsupportedGroups = new Set();
66
+ this._inverterUnsupportedGroups = new Set();
67
+ this._acChargerUnsupportedGroups = new Set();
68
+ this._dcChargerUnsupportedGroups = new Set();
69
+ this._pssUnsupportedGroups = new Set();
70
+ this._pidUnsupportedGroups = new Set();
65
71
 
66
72
  this.on('ready', this.onReady.bind(this));
67
73
  this.on('stateChange', this.onStateChange.bind(this));
@@ -249,8 +255,9 @@ class Sigenergy extends utils.Adapter {
249
255
  this._controlRegistersRead = true;
250
256
  }
251
257
  if (!this._protocolDetected) {
252
- await this._detectProtocolLevel();
253
- this._protocolDetected = true;
258
+ // Only latch the flag if detection was conclusive (no transport
259
+ // errors). Otherwise retry on the next poll cycle.
260
+ this._protocolDetected = await this._detectProtocolLevel();
254
261
  }
255
262
 
256
263
  const _t0 = Date.now();
@@ -305,13 +312,13 @@ class Sigenergy extends utils.Adapter {
305
312
  await this._updatePvStringPowers();
306
313
 
307
314
  await this.setStateAsync('info.connection', { val: true, ack: true });
308
- // Successful cycle reset dedup flags so next outage logs at ERROR level again.
315
+ // Successful cycle reset dedup flags so next outage logs at ERROR level again.
309
316
  this._pollErrorKey = null;
310
317
  this._pollWarnLogged = false;
311
318
  this.log.debug(`[poll] cycle completed in ${Date.now() - _pollStart} ms`);
312
319
  } catch (err) {
313
- // Deduplicate: first occurrence ERROR (visible + Sentry-worthy).
314
- // Identical subsequent errors DEBUG, so a broken network does not flood the log.
320
+ // Deduplicate: first occurrence ERROR (visible + Sentry-worthy).
321
+ // Identical subsequent errors DEBUG, so a broken network does not flood the log.
315
322
  const key = `poll:${err.code || err.message}`;
316
323
  if (this._pollErrorKey !== key) {
317
324
  this.log.error(`Poll error: ${err.message}`);
@@ -353,7 +360,12 @@ class Sigenergy extends utils.Adapter {
353
360
  await this._processReadGroup(group, raw, 'plant');
354
361
  await this._sleep(100);
355
362
  } catch (err) {
356
- this.log.warn(`Plant register read error at ${group.startAddr}: ${err.message}`);
363
+ if (!this._plantUnsupportedGroups.has(group.startAddr)) {
364
+ this._plantUnsupportedGroups.add(group.startAddr);
365
+ this.log.warn(`Plant register read error at ${group.startAddr}: ${err.message}`);
366
+ } else {
367
+ this.log.debug(`[plant] read error at ${group.startAddr}: ${err.message}`);
368
+ }
357
369
  }
358
370
  }
359
371
  }
@@ -379,7 +391,12 @@ class Sigenergy extends utils.Adapter {
379
391
  await this._processReadGroup(group, raw, 'inverter');
380
392
  await this._sleep(100);
381
393
  } catch (err) {
382
- this.log.warn(`Inverter register read error at ${group.startAddr}: ${err.message}`);
394
+ if (!this._inverterUnsupportedGroups.has(group.startAddr)) {
395
+ this._inverterUnsupportedGroups.add(group.startAddr);
396
+ this.log.warn(`Inverter register read error at ${group.startAddr}: ${err.message}`);
397
+ } else {
398
+ this.log.debug(`[inverter] read error at ${group.startAddr}: ${err.message}`);
399
+ }
383
400
  }
384
401
  }
385
402
  }
@@ -402,7 +419,12 @@ class Sigenergy extends utils.Adapter {
402
419
  await this._processReadGroup(group, raw, 'acCharger');
403
420
  await this._sleep(100);
404
421
  } catch (err) {
405
- this.log.warn(`AC Charger register read error: ${err.message}`);
422
+ if (!this._acChargerUnsupportedGroups.has(group.startAddr)) {
423
+ this._acChargerUnsupportedGroups.add(group.startAddr);
424
+ this.log.warn(`AC Charger register read error at ${group.startAddr}: ${err.message}`);
425
+ } else {
426
+ this.log.debug(`[acCharger] read error at ${group.startAddr}: ${err.message}`);
427
+ }
406
428
  }
407
429
  }
408
430
  }
@@ -425,7 +447,12 @@ class Sigenergy extends utils.Adapter {
425
447
  await this._processReadGroup(group, raw, 'dcCharger');
426
448
  await this._sleep(100);
427
449
  } catch (err) {
428
- this.log.warn(`DC Charger register read error: ${err.message}`);
450
+ if (!this._dcChargerUnsupportedGroups.has(group.startAddr)) {
451
+ this._dcChargerUnsupportedGroups.add(group.startAddr);
452
+ this.log.warn(`DC Charger register read error at ${group.startAddr}: ${err.message}`);
453
+ } else {
454
+ this.log.debug(`[dcCharger] read error at ${group.startAddr}: ${err.message}`);
455
+ }
429
456
  }
430
457
  }
431
458
  }
@@ -445,7 +472,12 @@ class Sigenergy extends utils.Adapter {
445
472
  await this._processReadGroup(group, raw, 'pss');
446
473
  await this._sleep(100);
447
474
  } catch (err) {
448
- this.log.warn(`PSS register read error: ${err.message}`);
475
+ if (!this._pssUnsupportedGroups.has(group.startAddr)) {
476
+ this._pssUnsupportedGroups.add(group.startAddr);
477
+ this.log.warn(`PSS register read error at ${group.startAddr}: ${err.message}`);
478
+ } else {
479
+ this.log.debug(`[pss] read error at ${group.startAddr}: ${err.message}`);
480
+ }
449
481
  }
450
482
  }
451
483
  }
@@ -465,7 +497,12 @@ class Sigenergy extends utils.Adapter {
465
497
  await this._processReadGroup(group, raw, 'pid');
466
498
  await this._sleep(100);
467
499
  } catch (err) {
468
- this.log.warn(`PID register read error: ${err.message}`);
500
+ if (!this._pidUnsupportedGroups.has(group.startAddr)) {
501
+ this._pidUnsupportedGroups.add(group.startAddr);
502
+ this.log.warn(`PID register read error at ${group.startAddr}: ${err.message}`);
503
+ } else {
504
+ this.log.debug(`[pid] read error at ${group.startAddr}: ${err.message}`);
505
+ }
469
506
  }
470
507
  }
471
508
  }
@@ -506,26 +543,61 @@ class Sigenergy extends utils.Adapter {
506
543
  }
507
544
  }
508
545
 
546
+ /**
547
+ * Detect the Modbus protocol level supported by the device.
548
+ *
549
+ * Design notes (root cause of the former "pre-V2.6" misdetection):
550
+ * 1. Sigenergy firmware rejects reads that do not cover a complete data
551
+ * point. Probing addr 30088 (U64, qty 4) with qty=1 returns Modbus
552
+ * exception 0x02 (ILLEGAL DATA ADDRESS, "combination of reference
553
+ * number and transfer length is invalid"). Every probe therefore
554
+ * reads the FULL quantity of its data point.
555
+ * 2. Probes run in DESCENDING order and stop at the first success, so a
556
+ * single optional/legacy register can no longer cap the result.
557
+ * 3. Probe registers are valid for both "Hybrid Inv." and "PV Inv."
558
+ * (per protocol doc V2.9, table 5-1) and match the official version
559
+ * history: 30272 added in V2.9, 30276 in V2.8, 30194 in V2.7,
560
+ * 30088 in V2.6.
561
+ * 4. A Modbus exception (1/2) means "register not implemented" — that is
562
+ * a conclusive "no". A transport error/timeout is INCONCLUSIVE: it is
563
+ * retried once, and if detection stays inconclusive the whole run is
564
+ * repeated on the next poll cycle instead of reporting pre-V2.6.
565
+ *
566
+ * @returns {Promise<boolean>} true if detection was conclusive
567
+ */
509
568
  async _detectProtocolLevel() {
510
569
  const plantId = this.config.plantId || 247;
511
570
  const inverterId = this.config.inverterId || 1;
512
571
 
513
572
  const probes = [
514
- { addr: 30088, level: 'V2.6' },
515
- { addr: 30200, level: 'V2.7' },
516
- { addr: 30228, level: 'V2.8' },
517
- { addr: 30286, level: 'V2.9' },
573
+ { addr: 30272, qty: 2, level: 'V2.9' }, // PV total daily generation (U32)
574
+ { addr: 30276, qty: 1, level: 'V2.8' }, // [Grid code] Rated frequency (U16)
575
+ { addr: 30194, qty: 2, level: 'V2.7' }, // Third party inverter active power (S32)
576
+ { addr: 30088, qty: 4, level: 'V2.6' }, // Plant PV total generation (U64)
518
577
  ];
519
578
 
520
579
  let detectedLevel = null;
580
+ let inconclusive = false;
521
581
  for (const probe of probes) {
522
- try {
523
- await this.modbus.readInputRegisters(plantId, probe.addr, 1);
582
+ if (this._stopped) {
583
+ return false;
584
+ }
585
+ const result = await this._probeRegister(plantId, probe);
586
+ if (result === 'supported') {
524
587
  detectedLevel = probe.level;
525
- } catch (err) {
526
- this.log.debug(`[protocolProbe] addr=${probe.addr} (${probe.level}): ${err.message}`);
527
588
  break;
528
589
  }
590
+ if (result === 'error') {
591
+ inconclusive = true;
592
+ }
593
+ await this._sleep(100);
594
+ }
595
+
596
+ if (!detectedLevel && inconclusive) {
597
+ this.log.warn(
598
+ 'Protocol level detection inconclusive (communication errors during probing) — will retry on next poll cycle',
599
+ );
600
+ return false;
529
601
  }
530
602
 
531
603
  let firmwareVersion = 'unknown';
@@ -539,6 +611,48 @@ class Sigenergy extends utils.Adapter {
539
611
  const levelStr = detectedLevel ? `>=${detectedLevel}` : 'pre-V2.6';
540
612
  this.log.info(`Detected protocol level: ${levelStr} (firmware: ${firmwareVersion})`);
541
613
  await this.setStateAsync('info.protocolLevel', { val: levelStr, ack: true });
614
+ return true;
615
+ }
616
+
617
+ /**
618
+ * Probe a single register (full data point) on the device.
619
+ *
620
+ * @param {number} slaveId - Modbus slave id
621
+ * @param {{addr: number, qty: number, level: string}} probe - probe definition
622
+ * @returns {Promise<'supported'|'unsupported'|'error'>} probe result
623
+ */
624
+ async _probeRegister(slaveId, probe) {
625
+ for (let attempt = 1; attempt <= 2; attempt++) {
626
+ if (this._stopped) {
627
+ return 'error';
628
+ }
629
+ try {
630
+ await this.modbus.readInputRegisters(slaveId, probe.addr, probe.qty);
631
+ this.log.debug(`[protocolProbe] addr=${probe.addr} qty=${probe.qty} (${probe.level}): supported`);
632
+ return 'supported';
633
+ } catch (err) {
634
+ // modbus-serial sets err.modbusCode on Modbus exception responses
635
+ // (1 = ILLEGAL FUNCTION, 2 = ILLEGAL DATA ADDRESS, ...). An
636
+ // exception is an answer FROM the device => register not
637
+ // implemented on this firmware. Anything else (timeout, socket
638
+ // error) is a transport problem and proves nothing.
639
+ if (err && err.modbusCode !== undefined) {
640
+ this.log.debug(
641
+ `[protocolProbe] addr=${probe.addr} qty=${probe.qty} (${probe.level}): ` +
642
+ `not supported (Modbus exception ${err.modbusCode})`,
643
+ );
644
+ return 'unsupported';
645
+ }
646
+ this.log.debug(
647
+ `[protocolProbe] addr=${probe.addr} qty=${probe.qty} (${probe.level}): ` +
648
+ `transport error on attempt ${attempt}/2: ${err.message}`,
649
+ );
650
+ if (attempt < 2) {
651
+ await this._sleep(300);
652
+ }
653
+ }
654
+ }
655
+ return 'error';
542
656
  }
543
657
 
544
658
  async _readControlRegisters() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.sigenergy",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "ioBroker adapter for Sigenergy solar energy systems via Modbus TCP/RTU",
5
5
  "author": {
6
6
  "name": "ssbingo",