iobroker.idm-multitalent_002 2.2.3 → 2.2.5

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
@@ -76,6 +76,10 @@ Example screenshots of objects:
76
76
  ![Status](resources/ioBrokerAdapter-Status.jpg)
77
77
 
78
78
  ## Changelog
79
+ ### 2.2.4 (2026-10-10)
80
+ * (zloe) fixed the same "marked read-only by mistake" bug for `Waermepumpe.Maximaltemperatur` on EVR752101 (found by cross-checking all six firmware definitions against each other, same pattern as the Heizkreis B fix below)
81
+ * (zloe) added a permanent automated test (`lib/idm_datablocks.test.js`) that cross-checks every bundled firmware definition against the others - same statename + register ("function") id must agree on `writable`/`length`/`factor` - so this class of bug is caught by CI from now on instead of needing a one-off manual check. A handful of genuine, still-unconfirmed differences (`Waermepumpe.MinimaleLaufzeit` writable, and three `length` mismatches around `Heizkreis-A.Absenkfaktor`/`Heizung.PufferVorhanden`/`Heizung.SchichttrennplatteVorhanden`) are documented as known exceptions in that test rather than guessed at - see the test file for why
82
+
79
83
  ### 2.2.3 (2026-10-10)
80
84
  * (zloe) fixed: "Heizkreis B" operating mode (Betriebsart) could not be changed on the S_H726100 and idm750100 control versions - it was incorrectly marked read-only in the data block definition (`writable: false` instead of `true`), while reading always worked fine. Found by cross-checking all six firmware definitions against each other: every other control version already had `function: 19` (same register, same min/max) correctly set to `writable: true`
81
85
 
@@ -112,98 +116,7 @@ Example screenshots of objects:
112
116
  ### 2.0.0 (2026-09-10)
113
117
  * (zloe) for S_H726100 (currently the only firmware with every data block's actual wire length verified against real hardware), collect all settings data blocks in one multi-block request per settings turn instead of one block per poll cycle, with automatic re-asking/backoff for the control's typically-partial replies - a full settings refresh now takes seconds instead of roughly a minute. Every other supported firmware is completely unaffected and keeps requesting settings blocks one at a time (see the Architecture section)
114
118
 
115
- ### 1.3.10 (2026-09-09)
116
- * (zloe) fix: a retry on an already finely-tuned data block used to always jump its delay up by the full, coarse step (300ms) regardless of how small a correction was actually needed - now it corrects by the same (possibly already tiny) step that tuning had converged to, and only doubles that step towards the coarse ceiling if retries actually keep recurring
117
-
118
- ### 1.3.9 (2026-09-09)
119
- * (zloe) list every data block's current content delay in the full-coverage cycle log line, so a change in total cycle time can be traced back to which block(s) grew
120
- * (zloe) the adaptive per-block content delay's ease-down step now starts coarse (100ms) and halves each time it's used (down to a 1ms floor), instead of always easing by a flat 100ms - a block that's been stable for a while gets refined much more finely, converging near its true minimum safe delay instead of only ever landing on multiples of 100ms. A retry resets a block's step back to the coarse starting point
121
-
122
- ### 1.3.8 (2026-09-09)
123
- * (zloe) remove the "completed one full poll cycle (every sensor block + one settings block)" log line entirely - it fired every single sensor sweep (~14s on real hardware), far too often to be useful. Only the full-coverage cycle line (every sensor and settings block actually read at least once, confirmed correct against real production logs) remains
124
-
125
- ### 1.3.7 (2026-09-08)
126
- * (zloe) fix: the full-coverage cycle log line still fired far too often on real hardware - it was gated by the settings-block round-robin index advancing at *request* time, not by data actually being read, so a retry, a response-watchdog reset, or the periodic resync could let a "lap" complete without every block truly having been read. Now gated by actual received data instead
127
-
128
- ### 1.3.6 (2026-09-08)
129
- * (zloe) shorten the full-coverage cycle log line to `full-coverage cycle #N done in Xms` - same information (elapsed time, running total), just without the long parenthetical explanation
130
- * (zloe) fix three `npm run check` (typescript) errors surfaced by the typescript 7 / axios 1.20 / @types/sinon 22 updates, without changing any runtime behavior
131
-
132
- ### 1.3.5 (2026-09-08)
133
- * (zloe) hotfix: 1.3.4's CI run failed on Node 20 - `engines: ">=22"` and testing on Node 20 at the same time don't work together, npm install fails with EBADENGINE. Drop Node 20 from the CI test matrix again (keeping the >=22 requirement); this reintroduces one repository-checker item (E3025) that directly conflicts with another (E0028) - can't satisfy both
134
-
135
- ### 1.3.4 (2026-09-08)
136
- * (zloe) rename/rework the second cycle-timing log line: it's now explicitly about every data block (sensor and settings) having been read at least once, not just the settings side, and it now also reports a running total of how many full-coverage cycles have completed
137
- * (zloe) address most of the ioBroker repository checker's findings from #349: raise the minimum Node.js version to 22, bump the required admin/js-controller versions, add missing translations, trim the in-admin news list to real, published versions, switch built-in module imports to the `node:` form, add the missing release-script plugins, tidy up CI/dependabot config, and rewrite the README's installation section to stop suggesting a direct `npm install` (see below for the couple of checker items intentionally left alone)
138
-
139
- ### 1.3.3 (2026-09-08)
140
- * (zloe) shorten the recurring per-data-block request log line (data block 07) to one compact line with the same information
141
- * (zloe) also log how long a full settings cycle (every settings block once, not just the one per sweep) takes, once the next one starts
142
-
143
- ### 1.3.2 (2026-09-08)
144
- * (zloe) replace the per-data-block content delay's averaging with a proper hill-climb: it now only grows when a "not ready" retry was actually needed and eases back down after several clean cycles, converging on a sweet spot instead of only ever ratcheting upward
145
- * (zloe) lower the default/floor data-content delay from 1000ms to 650ms
146
- * (zloe) log how long one full poll cycle (every sensor block plus one settings block) actually takes, once the next cycle starts
147
-
148
- ### 1.3.1 (2026-09-08)
149
- * (zloe) log which firmware versions have a data block definition available, and which one (bundled or a custom override) was actually selected once the heat pump reports its version
150
- * (zloe) add estimated min/max write limits for every remaining writable S_H726100 field (temperature setpoints, cooling settings, two on/off flags, pump speed) - conservative, margin-padded ranges, not hardware-verified for every field, see lib/datablocks/README.md
151
- * (zloe) learn a per-data-block delay before requesting its content instead of one fixed guess for all of them, reducing "not ready" retries over time (see the Architecture section)
152
-
153
- ### 1.3.0 (2026-09-06)
154
- * (zloe) move the hardware data block definitions out of the code into one JSON file per firmware version (lib/datablocks/), validated at load time
155
- * (zloe) enforce configured min/max limits when writing a value to the heatpump, reverting the displayed value if a write is rejected
156
- * (zloe) support a directory of custom data block files (one per firmware version, matched by their own version field, not the filename) to override or add definitions without an adapter update - see lib/datablocks/README.md
157
- * (zloe) fix a bug where the per-version speed adjustment (e.g. idm722100's 75%) never actually took effect
158
- * (zloe) give every adapter instance its own protocol/connection state instead of a shared module-level singleton, fixing multi-instance ("compact mode") safety
159
- * (zloe) extract the TCP connection and request/response state machine out of main.js into its own class (lib/idm-session.js)
160
- * (zloe) add a short per-request response timeout and a frame-length cap, so a dropped reply or a garbled byte stream is recovered from in seconds instead of stalling
161
- * (zloe) translate the admin config screen and fill in missing changelog translations (previously English-only outside of a few fields)
162
-
163
- ### 1.2.9 (2026-09-05)
164
- * (zloe) switch npm deploy from the retired classic npm token to Trusted Publishing (OIDC) - both 1.2.7 and 1.2.8 failed to publish to npm because of this
165
-
166
- ### 1.2.8 (2026-09-05)
167
- * (zloe) fix repository checker issues (#298): add engines.node, German news translations, correct js-controller/admin dependency versions, add missing tier/licenseInformation, remove deprecated common.main/title, fix broken vscode schema link
168
- * (zloe) add .releaseconfig.json so io-package.json's version is kept in sync automatically on release
169
- * (zloe) fix broken `npm run lint` (missing @eslint/js dependency)
170
-
171
- ### 1.2.7 (2026-09-05)
172
- * (zloe) fix broken test/lint toolchain (incompatible chai/sinon-chai/chai-as-promised versions, outdated tsconfig moduleResolution)
173
- * (zloe) fix three duplicate `function` numbers in data block definitions that could have targeted the wrong register once made writable
174
- * (zloe) clear all pending communication timers on adapter unload, not just reconnect/resend
175
- * (zloe) remove unused imports, clear the default TCP server IP placeholder
176
- * (zloe) add real unit tests for idm-utils and the main.js communication state machine
177
-
178
- ### 1.2.6 (2024-09-21)
179
- * (zloe) fixed reconnect handling
180
-
181
- ### 1.2.5 (2024-01-21)
182
- * (zloe) further fixes in error handling
183
-
184
- ### 1.2.4 (2024-01-21)
185
- * (zloe) further improve logging and handling of transmission errors
186
-
187
- ### 1.2.3 (2024-01-21)
188
- * (zloe) improve handling of data transmission problems
189
-
190
- ### 1.2.2 (2024-01-21)
191
- * (zloe) fix handling of data transmission problems which lead to stopping requesting data
192
-
193
- ### 1.2.1 (2024-01-20)
194
- * (zloe) improving statistics and log messages
195
- * (zloe) fix data definition for idm722100
196
-
197
- ### 1.2.0 (2024-01-19)
198
- * (zloe) adding support for idm722100
199
-
200
- ### 1.1.1 (2023-11-04)
201
- * (zloe) optimizing protocol
202
- * (zloe) updated dependencies
203
-
204
- ### 1.1.0 (2023-11-02)
205
- * (zloe) initial version TERRA130601 - S_H726100 support
206
- * (zloe) updated dependencies
119
+ Older changes (versions 1.1.0 - 1.3.10) have been moved to [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
207
120
 
208
121
  ## Installation
209
122
  As the adapter is not (yet) listed in the official ioBroker repository, install it through the Admin UI rather than a direct npm command:
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "idm-multitalent_002",
4
- "version": "2.2.3",
4
+ "version": "2.2.5",
5
5
  "news": {
6
+ "2.2.5": {
7
+ "en": "fixed: \"Heizkreis-A.Absenkfaktor\" was declared 1 byte instead of 2 on S_H726100 - the following, previously unidentified \"padding\" byte was actually its own high byte (confirmed both by makki's original reference data and by live values read straight off Klaus's running heat pump); also fixed a copy/paste mix-up on idm701100 where \"Laufzeit Zirkulation\" and an unidentified field had their object names swapped, so the wrong one showed up under each name. Checked but intentionally left unchanged: \"Puffer vorhanden\"/\"Schichttrennplatte\" on idm701100/S_H726100 looked like they might need the same 1-byte fix per older reference documents, but live hardware captures confirmed the current 2-byte definition is actually the correct one for these two control versions",
8
+ "de": "behoben: \"Heizkreis-A.Absenkfaktor\" war bei S_H726100 fälschlich mit 1 statt 2 Byte definiert - das direkt danach liegende, bisher nicht identifizierte \"Padding\"-Byte war in Wirklichkeit sein eigenes High-Byte (bestätigt sowohl durch makkis Original-Referenzdaten als auch durch Live-Werte direkt von Klaus' laufender Wärmepumpe); außerdem einen Kopierfehler bei idm701100 behoben, bei dem \"Laufzeit Zirkulation\" und ein nicht identifiziertes Feld ihre Objektnamen vertauscht hatten, sodass unter jedem Namen der falsche Wert erschien. Geprüft, aber bewusst unverändert gelassen: \"Puffer vorhanden\"/\"Schichttrennplatte\" bei idm701100/S_H726100 sahen laut älteren Referenzdokumenten so aus, als bräuchten sie denselben 1-Byte-Fix, aber Live-Messungen an echter Hardware haben bestätigt, dass die aktuelle 2-Byte-Definition für diese beiden Steuerungsversionen tatsächlich richtig ist",
9
+ "ru": "исправлено: \"Heizkreis-A.Absenkfaktor\" на S_H726100 был объявлен как 1 байт вместо 2 - следующий за ним, ранее неопознанный \"padding\"-байт на самом деле был его собственным старшим байтом (подтверждено как исходными данными makki, так и живыми значениями прямо с работающего теплового насоса Клауса); также исправлена путаница из-за копирования в idm701100, где \"Laufzeit Zirkulation\" и неопознанное поле были перепутаны по именам объектов. Проверено, но намеренно оставлено без изменений: \"Puffer vorhanden\"/\"Schichttrennplatte\" на idm701100/S_H726100 выглядели так, будто нуждаются в том же исправлении на 1 байт по старым справочным документам, но живые данные с реального оборудования подтвердили, что текущее определение в 2 байта на самом деле верно для этих двух версий управления",
10
+ "pt": "corrigido: \"Heizkreis-A.Absenkfaktor\" estava declarado com 1 byte em vez de 2 no S_H726100 - o byte de \"padding\" seguinte, até então não identificado, era na verdade o seu próprio byte alto (confirmado tanto pelos dados de referência originais de makki quanto por valores em tempo real lidos direto da bomba de calor do Klaus); também corrigida uma confusão de copiar/colar no idm701100, em que \"Laufzeit Zirkulation\" e um campo não identificado tinham os nomes de objeto trocados. Verificado, mas deixado intencionalmente inalterado: \"Puffer vorhanden\"/\"Schichttrennplatte\" no idm701100/S_H726100 pareciam precisar da mesma correção de 1 byte segundo documentos de referência mais antigos, mas capturas em tempo real do hardware confirmaram que a definição atual de 2 bytes é, na verdade, a correta para essas duas versões de controle",
11
+ "nl": "opgelost: \"Heizkreis-A.Absenkfaktor\" was bij S_H726100 ten onrechte als 1 byte in plaats van 2 gedefinieerd - het daaropvolgende, voorheen niet-geïdentificeerde \"padding\"-byte was in werkelijkheid zijn eigen hoge byte (bevestigd door zowel makki's originele referentiegegevens als live waarden rechtstreeks van Klaus' draaiende warmtepomp); ook een kopieerfout bij idm701100 opgelost waarbij \"Laufzeit Zirkulation\" en een niet-geïdentificeerd veld hun objectnamen hadden verwisseld. Gecontroleerd maar opzettelijk ongewijzigd gelaten: \"Puffer vorhanden\"/\"Schichttrennplatte\" bij idm701100/S_H726100 leken volgens oudere referentiedocumenten dezelfde 1-byte-fix nodig te hebben, maar live hardwaremetingen bevestigden dat de huidige 2-byte-definitie voor deze twee besturingsversies daadwerkelijk correct is",
12
+ "fr": "corrigé : \"Heizkreis-A.Absenkfaktor\" était déclaré sur 1 octet au lieu de 2 sur S_H726100 - l'octet de \"padding\" suivant, jusqu'alors non identifié, était en réalité son propre octet de poids fort (confirmé à la fois par les données de référence originales de makki et par des valeurs lues en direct sur la pompe à chaleur de Klaus) ; correction également d'une confusion de copier/coller sur idm701100, où \"Laufzeit Zirkulation\" et un champ non identifié avaient leurs noms d'objet échangés. Vérifié mais volontairement laissé inchangé : \"Puffer vorhanden\"/\"Schichttrennplatte\" sur idm701100/S_H726100 semblaient nécessiter le même correctif d'1 octet selon d'anciens documents de référence, mais des captures en direct sur du matériel réel ont confirmé que la définition actuelle sur 2 octets est en fait correcte pour ces deux versions de régulation",
13
+ "it": "risolto: \"Heizkreis-A.Absenkfaktor\" era dichiarato come 1 byte invece di 2 su S_H726100 - il byte di \"padding\" successivo, fino ad ora non identificato, era in realtà il suo byte alto (confermato sia dai dati di riferimento originali di makki sia da valori letti in diretta dalla pompa di calore di Klaus); corretto anche un errore di copia/incolla su idm701100, dove \"Laufzeit Zirkulation\" e un campo non identificato avevano i nomi oggetto scambiati. Verificato ma volutamente lasciato invariato: \"Puffer vorhanden\"/\"Schichttrennplatte\" su idm701100/S_H726100 sembravano richiedere la stessa correzione a 1 byte secondo documenti di riferimento più vecchi, ma le acquisizioni live dall'hardware reale hanno confermato che l'attuale definizione a 2 byte è in realtà quella corretta per queste due versioni di controllo",
14
+ "es": "corregido: \"Heizkreis-A.Absenkfaktor\" estaba declarado con 1 byte en lugar de 2 en S_H726100 - el byte de \"padding\" siguiente, hasta ahora no identificado, era en realidad su propio byte alto (confirmado tanto por los datos de referencia originales de makki como por valores en vivo leídos directamente de la bomba de calor de Klaus); también se corrigió una confusión de copiar/pegar en idm701100, donde \"Laufzeit Zirkulation\" y un campo no identificado tenían sus nombres de objeto intercambiados. Comprobado pero dejado intencionalmente sin cambios: \"Puffer vorhanden\"/\"Schichttrennplatte\" en idm701100/S_H726100 parecían necesitar la misma corrección de 1 byte según documentos de referencia más antiguos, pero capturas en vivo de hardware real confirmaron que la definición actual de 2 bytes es, de hecho, la correcta para estas dos versiones de control",
15
+ "pl": "naprawiono: \"Heizkreis-A.Absenkfaktor\" był błędnie zadeklarowany jako 1 bajt zamiast 2 w S_H726100 - następujący po nim, wcześniej niezidentyfikowany bajt \"padding\" był w rzeczywistości jego własnym starszym bajtem (potwierdzone zarówno przez oryginalne dane referencyjne makki, jak i przez wartości odczytane na żywo z działającej pompy ciepła Klausa); naprawiono też pomyłkę powstałą przy kopiowaniu w idm701100, gdzie \"Laufzeit Zirkulation\" i niezidentyfikowane pole miały zamienione nazwy obiektów. Sprawdzono, ale celowo pozostawiono bez zmian: \"Puffer vorhanden\"/\"Schichttrennplatte\" w idm701100/S_H726100 wyglądały, jakby wymagały tej samej poprawki na 1 bajt według starszych dokumentów referencyjnych, jednak zrzuty na żywo z rzeczywistego sprzętu potwierdziły, że obecna definicja 2-bajtowa jest w rzeczywistości poprawna dla tych dwóch wersji sterowania",
16
+ "zh-cn": "修复:S_H726100 上 \"Heizkreis-A.Absenkfaktor\" 被错误地声明为 1 字节而非 2 字节——紧随其后的、此前未识别的\"填充\"字节实际上是它自己的高位字节(通过 makki 的原始参考数据以及直接从 Klaus 运行中的热泵读取的实时数值共同确认);同时修复了 idm701100 上的一个复制粘贴混淆问题,\"Laufzeit Zirkulation\" 与一个未识别字段的对象名称被互换了。已检查但有意保持不变:根据较旧的参考文档,idm701100/S_H726100 上的 \"Puffer vorhanden\"/\"Schichttrennplatte\" 看起来也需要同样的 1 字节修复,但来自真实硬件的实时采集数据确认,目前的 2 字节定义对这两个控制版本实际上才是正确的",
17
+ "uk": "виправлено: \"Heizkreis-A.Absenkfaktor\" на S_H726100 був оголошений як 1 байт замість 2 - наступний за ним, раніше невідомий \"padding\"-байт насправді був його власним старшим байтом (підтверджено як оригінальними довідковими даними makki, так і живими значеннями, зчитаними прямо з працюючого теплового насоса Клауса); також виправлено плутанину через копіювання в idm701100, де \"Laufzeit Zirkulation\" та невідоме поле мали переплутані імена об'єктів. Перевірено, але свідомо залишено без змін: \"Puffer vorhanden\"/\"Schichttrennplatte\" на idm701100/S_H726100 виглядали так, ніби потребують того самого виправлення на 1 байт згідно зі старішими довідковими документами, але живі дані з реального обладнання підтвердили, що поточне визначення в 2 байти насправді правильне для цих двох версій керування"
18
+ },
19
+ "2.2.4": {
20
+ "en": "fixed the same \"marked read-only by mistake\" bug for \"Waermepumpe.Maximaltemperatur\" on EVR752101 (found by cross-checking all six firmware definitions against each other, same pattern as the Heizkreis B fix in 2.2.3); added a permanent automated test that cross-checks every bundled firmware definition against the others so this class of bug gets caught by CI from now on, instead of needing a one-off manual check",
21
+ "de": "denselben \"versehentlich nur lesbar markiert\"-Fehler auch für \"Waermepumpe.Maximaltemperatur\" bei EVR752101 behoben (gefunden durch Quervergleich aller sechs Firmware-Definitionen, gleiches Muster wie der Heizkreis-B-Fix in 2.2.3); ein dauerhafter automatisierter Test hinzugefügt, der alle mitgelieferten Firmware-Definitionen gegeneinander prüft, damit diese Fehlerklasse ab sofort von der CI erkannt wird statt eine manuelle Einzelprüfung zu brauchen",
22
+ "ru": "исправлена та же ошибка \"случайно помечено только для чтения\" для \"Waermepumpe.Maximaltemperatur\" на EVR752101 (найдена путём сверки всех шести определений прошивок друг с другом, та же схема, что и исправление Heizkreis B в 2.2.3); добавлен постоянный автоматический тест, сверяющий все встроенные определения прошивок друг с другом, чтобы такие ошибки теперь выявлялись CI",
23
+ "pt": "corrigido o mesmo erro de \"marcado como somente leitura por engano\" para \"Waermepumpe.Maximaltemperatur\" no EVR752101 (encontrado ao comparar todas as seis definições de firmware entre si, mesmo padrão da correção do Heizkreis B na 2.2.3); adicionado um teste automatizado permanente que compara todas as definições de firmware incluídas entre si, para que esse tipo de erro seja detectado pela CI a partir de agora",
24
+ "nl": "dezelfde \"per ongeluk alleen-lezen\"-fout ook opgelost voor \"Waermepumpe.Maximaltemperatur\" op EVR752101 (gevonden door alle zes firmware-definities onderling te vergelijken, zelfde patroon als de Heizkreis B-fix in 2.2.3); een permanente geautomatiseerde test toegevoegd die alle meegeleverde firmware-definities onderling controleert, zodat dit type fout voortaan door CI wordt opgemerkt",
25
+ "fr": "correction du même bug \"marqué par erreur en lecture seule\" pour \"Waermepumpe.Maximaltemperatur\" sur EVR752101 (trouvé en comparant les six définitions de firmware entre elles, même schéma que le correctif Heizkreis B de la 2.2.3) ; ajout d'un test automatisé permanent qui compare toutes les définitions de firmware fournies entre elles, afin que ce type de bug soit désormais détecté par la CI",
26
+ "it": "corretto lo stesso bug \"erroneamente contrassegnato come sola lettura\" per \"Waermepumpe.Maximaltemperatur\" su EVR752101 (trovato confrontando tutte le sei definizioni firmware tra loro, stesso schema della correzione Heizkreis B nella 2.2.3); aggiunto un test automatico permanente che confronta tutte le definizioni firmware incluse tra loro, così questo tipo di bug viene ora rilevato dalla CI",
27
+ "es": "corregido el mismo error de \"marcado como solo lectura por error\" para \"Waermepumpe.Maximaltemperatur\" en EVR752101 (encontrado al comparar las seis definiciones de firmware entre sí, mismo patrón que la corrección de Heizkreis B en 2.2.3); se añadió una prueba automatizada permanente que compara todas las definiciones de firmware incluidas entre sí, para que este tipo de error ahora lo detecte la CI",
28
+ "pl": "naprawiono ten sam błąd \"omyłkowo oznaczone jako tylko do odczytu\" dla \"Waermepumpe.Maximaltemperatur\" w EVR752101 (znaleziony przez wzajemne porównanie wszystkich sześciu definicji firmware, ten sam wzorzec co poprawka Heizkreis B w 2.2.3); dodano stały zautomatyzowany test porównujący wszystkie dołączone definicje firmware ze sobą, aby ten rodzaj błędu był odtąd wykrywany przez CI",
29
+ "zh-cn": "修复了 EVR752101 上 \"Waermepumpe.Maximaltemperatur\" 同样的\"误标记为只读\"问题(通过交叉核对全部六个固件定义发现,与 2.2.3 中 Heizkreis B 的修复模式相同);新增了一个永久性自动化测试,用于交叉核对所有内置固件定义,从此由 CI 自动捕获此类错误",
30
+ "uk": "виправлено ту саму помилку \"помилково позначено лише для читання\" для \"Waermepumpe.Maximaltemperatur\" на EVR752101 (знайдено шляхом звірки всіх шести визначень прошивок одне з одним, той самий шаблон, що й виправлення Heizkreis B у 2.2.3); додано постійний автоматизований тест, який звіряє всі вбудовані визначення прошивок одне з одним, щоб цей клас помилок відтепер виявляла CI"
31
+ },
6
32
  "2.2.3": {
7
33
  "en": "fixed: \"Heizkreis B\" (heating circuit B) operating mode (Betriebsart) could not be changed on the S_H726100 and idm750100 control versions - it was incorrectly marked as read-only in the data block definition (writable:false instead of true), while reading it always worked fine. All other known control versions already had this correct; the field now matches circuit A/C/D on every firmware",
8
34
  "de": "behoben: Die Betriebsart von \"Heizkreis B\" ließ sich bei den Steuerungsversionen S_H726100 und idm750100 nicht ändern - sie war in der Datenblock-Definition fälschlich als nur lesbar markiert (writable:false statt true), Lesen funktionierte dabei immer einwandfrei. Bei allen anderen bekannten Steuerungsversionen war das bereits korrekt; das Feld verhält sich jetzt überall wie Heizkreis A/C/D",
@@ -824,7 +824,7 @@
824
824
  "description": "WP Maximaltemp.",
825
825
  "length": 2,
826
826
  "factor": 1,
827
- "writable": false,
827
+ "writable": true,
828
828
  "function": 37
829
829
  },
830
830
  {
@@ -336,20 +336,11 @@
336
336
  "statename": "Heizkreis-A.Absenkfaktor",
337
337
  "field": "faktor_A",
338
338
  "description": "Absenkfaktor A",
339
- "length": 1,
339
+ "length": 2,
340
340
  "factor": 1,
341
341
  "writable": false,
342
342
  "function": 50
343
343
  },
344
- {
345
- "statename": "",
346
- "field": "padding5",
347
- "description": "padding5",
348
- "length": 1,
349
- "factor": 1,
350
- "writable": false,
351
- "function": -1
352
- },
353
344
  {
354
345
  "statename": "Heizkreis-A.Schnellabsenkung",
355
346
  "field": "absenkA",
@@ -91,8 +91,8 @@
91
91
  "function": 55
92
92
  },
93
93
  {
94
- "statename": "Heizung.unbekannt",
95
- "field": "unbekannt",
94
+ "statename": "Heizung.LaufzeitZirkulation",
95
+ "field": "zirkzeit",
96
96
  "description": "Laufzeit Zirkulation",
97
97
  "length": 2,
98
98
  "factor": 1,
@@ -118,8 +118,8 @@
118
118
  "function": 56
119
119
  },
120
120
  {
121
- "statename": "Heizung.LaufzeitZirkulation",
122
- "field": "zirkzeit",
121
+ "statename": "Heizung.unbekannt",
122
+ "field": "unbekannt",
123
123
  "description": "unbekannt",
124
124
  "length": 2,
125
125
  "factor": 1,
@@ -119,7 +119,7 @@ describe('idm-protocol', () => {
119
119
 
120
120
  it('S_H726100', () => {
121
121
  expectParse('S_H726100', '03', '00002E001200010F00000001000B2701000100260200000000000000', 'padding:0; Warmwasser-Sollwert:46; Sommer-Winter-Umsch.:18; autom. WP Zuschaltung:1; WP Zuschaltzeit:15; Notbetrieb:0; Bad-Sommerbetrieb:0; Schichttrennplatte:1; min. Drehzahl HZK-Pumpe:9995; Puffer vorhanden:1; Laufzeit Zirkulation:1; gew. HGL-Temperatur:55; Funktion ext. Kontakt:0; Restzeit?:0; * add data: 000000');
122
- expectParse('S_H726100', '04', '0000010400150013000102002D00010012002D000100030A0001FA0005000014000101000003000300FA00FA000001000005000500010100001E001E00320032000A000A00100010006400640001010A000A0014001400', 'padding:0; Frostschutz:1; Kennlinie:4; Nenntemperatur:21; Spartemperatur:19; Art des Heizkreises:1; Betriebsart:2; Maximaltemperatur:45; Raumeinfluss:1; Betriebsart Warmwasser:0; Minimaltemperatur:18; Anteil Raumeinfluss:45; Raumeinfluss von:1; Raumeinfluss auf:0; mit Mischer A:3; padding2:10; passing3:1; padding4:250; Absenkfaktor A:5; padding5:0; Schnellabsenkung A:0; Konstanttemperatur A:20; mit Mischer C:1; mit Mischer D:1; Schnellabsenkung C:0; Schnellabsenkung D:0; Betriebsart C:3; Betriebsart D:3; padding6:250; Raumeinfluss auf C:0; padding7:250; Raumeinfluss auf D:0; padding8:256; Raumeinfluss C:0; Raumeinfluss D:0; Absenkfaktor C:5; Absenkfaktor D:5; Art des Heizkreises C:1; Art des Heizkreises D:1; Frostschutz C:0; Frostschutz D:0; Konstanttemperatur C:30; Konstanttemperatur D:30; Maximaltemperatur C:50; Maximaltemperatur D:50; Minimaltemperatur C:10; Minimaltemperatur D:10; Spartemperatur C:16; Spartemperatur D:16; Anteil Raumeinfluss C:100; Anteil Raumeinfluss D:100; Raumeinfluss von C:1; Raumeinfluss von D:1; Kennlinie C:10; Kennlinie D:10; Nenntemperatur C:20; Nenntemperatur D:20; ');
122
+ expectParse('S_H726100', '04', '0000010400150013000102002D00010012002D000100030A0001FA0005000014000101000003000300FA00FA000001000005000500010100001E001E00320032000A000A00100010006400640001010A000A0014001400', 'padding:0; Frostschutz:1; Kennlinie:4; Nenntemperatur:21; Spartemperatur:19; Art des Heizkreises:1; Betriebsart:2; Maximaltemperatur:45; Raumeinfluss:1; Betriebsart Warmwasser:0; Minimaltemperatur:18; Anteil Raumeinfluss:45; Raumeinfluss von:1; Raumeinfluss auf:0; mit Mischer A:3; padding2:10; passing3:1; padding4:250; Absenkfaktor A:5; Schnellabsenkung A:0; Konstanttemperatur A:20; mit Mischer C:1; mit Mischer D:1; Schnellabsenkung C:0; Schnellabsenkung D:0; Betriebsart C:3; Betriebsart D:3; padding6:250; Raumeinfluss auf C:0; padding7:250; Raumeinfluss auf D:0; padding8:256; Raumeinfluss C:0; Raumeinfluss D:0; Absenkfaktor C:5; Absenkfaktor D:5; Art des Heizkreises C:1; Art des Heizkreises D:1; Frostschutz C:0; Frostschutz D:0; Konstanttemperatur C:30; Konstanttemperatur D:30; Maximaltemperatur C:50; Maximaltemperatur D:50; Minimaltemperatur C:10; Minimaltemperatur D:10; Spartemperatur C:16; Spartemperatur D:16; Anteil Raumeinfluss C:100; Anteil Raumeinfluss D:100; Raumeinfluss von C:1; Raumeinfluss von D:1; Kennlinie C:10; Kennlinie D:10; Nenntemperatur C:20; Nenntemperatur D:20; ');
123
123
  expectParse('S_H726100', '05', '0000000132000A00140010000300000A00640002010500001E000600FA00', 'padding:0; Frostschutz B:0; Art HK B:1; Maximaltemp HK B:50; Kennlinie HK B:10; Nenntemp HK B:20; Spartemp HK B:16; Betriebsart HK B:3; Raumeinfluss HK B:0; Minimaltemp HK B:10; Anteil Raumeinfluss B:100; Raumeinfluss B von:2; Raumeinfluss B auf:1; Absenkfaktor B:5; HK B mit Mischer:0; Konstanttemp B:30; Schnellabsenkung B:6; * add data: 00FA00');
124
124
  expectParse('S_H726100', '06', '00000101040039000A000A00F1FFEEFF0002001E000000', 'padding:0; WP-Freigabe:1; wp?:1; WP Schaltdifferenz:4; WP Maximaltemp.:57; min. WP Laufzeit:10; min. WP Stehzeit:10; min. Solewarnung:-15; min. Solealarm:-18; min Soletemp:512; WP-Freigabe Kunde:0; Ueberhoehung Stromsperrung:30; Vorverlegezeit Stromsperrung:0; * add data: 00');
125
125
  expectParse('S_H726100', '07', '000000000000000000000000000000000000000000000000000001000000000000000000', 'padding:0; padding2:0; Relais1 Solepumpe:0; Relais2 WP Stufe 1:0; Relais3 Mischer A - auf:0; Relais4 Mischer A - zu:0; Relais5 WP Stufe 2:0; Relais6 Pumpe Heizkreis B:0; Relais7 Mischer B - auf:0; Relais8 Mischer B - zu:0; Relais9 Kühlventil:0; Relais10 HG-Mischer - auf:0; Relais11 HG-Mischer - zu:0; Relais12 Zirkulationspumpe:0; Relais13 Störmeldeausgang:0; Triac Plattentauscherpumpe:0; Triac Ladepumpe:0; Triac Pumpe Heizkreis A:0; Störung Übertemperatur:0; padding3:0; padding4:0; Telefonkontakt:1; * add data: 000000000000000000');
@@ -52,6 +52,126 @@ describe('idm_datablocks (data blocks loader)', () => {
52
52
  });
53
53
  });
54
54
 
55
+ // Catches transcription bugs like the one fixed in 2.2.3 (and again in 2.2.4): a field
56
+ // was accidentally copy-pasted with the wrong "writable" flag into exactly one of the six
57
+ // bundled files, so "Heizkreis B" could be read but never changed on that control version.
58
+ // A field is the "same" field across firmware files when it shares both a statename AND a
59
+ // function (register) id there - the protocol has no other way to say "this register is
60
+ // the same thing on every control version". writable/length/factor MUST then match: they
61
+ // describe how the register is framed on the wire and whether writes are even attempted,
62
+ // none of which plausibly differs by firmware revision for the same physical register.
63
+ // min/max are treated more leniently: today only S_H726100 (and partially EVR752101) has
64
+ // hand-verified limits (see README - "neither source documents official min/max limits"),
65
+ // so one side being undocumented (undefined) while the other has a real value is expected,
66
+ // not a bug. Two DIFFERENT non-undefined bounds for the same register would still fail.
67
+ describe('cross-firmware consistency of the bundled definitions', () => {
68
+ // Each entry: a (statename, function) pair that genuinely differs across firmware files
69
+ // as of 2026-10. One other that used to be listed here (Heizkreis-A.Absenkfaktor) was
70
+ // confirmed and fixed in 2.2.5: makki's original idm712/idm750 source data (Klaus found
71
+ // and shared both idm712-datatype.txt and idm750_Datatypes.csv - our bundled
72
+ // idm712100.json and idm750100.json already matched them with 0 discrepancies out of
73
+ // 120/142 fields each) said Absenkfaktor A is 2 bytes everywhere, and S_H726100's block
74
+ // 04 had an unexplained 1-byte "padding5" entry (function: -1, i.e. nobody had ever
75
+ // identified what that byte was for) sitting immediately AFTER a 1-byte Absenkfaktor A.
76
+ // That byte was never padding - it was Absenkfaktor's own second byte, misclassified as
77
+ // unknown filler. Widening Absenkfaktor to 2 bytes and removing the adjacent padding5
78
+ // entry keeps block 04's hardware-confirmed wireLength (87) exactly satisfied while
79
+ // every later field lands at the same offset as before. Klaus independently spotted the
80
+ // same fix by reading fresh values straight off his running adapter: with the old 1-byte
81
+ // field, Absenkfaktor A read 5 and the very next (phantom) padding5 read 0 - exactly the
82
+ // low/high byte pair you'd expect for a 2-byte value of 5, matching Absenkfaktor C/D
83
+ // (already 2 bytes) which also read 5 on his hardware.
84
+ //
85
+ // Heizung.PufferVorhanden and Heizung.SchichttrennplatteVorhanden were the INVERSE
86
+ // lesson, and are listed below rather than fixed: the same source documents say both are
87
+ // 1 byte, and idm712100/idm722100/idm750100/EVR752101 do use 1 byte - but shrinking them
88
+ // on idm701100 and S_H726100 (both currently 2 bytes) does NOT produce sane output. Live
89
+ // block 03 reads from Klaus's actual S_H726100 hardware, and this project's own
90
+ // regression-snapshot captures (lib/idm-protocol.test.js) for both idm701100 and
91
+ // S_H726100, decode to sensible values (booleans 0/1, small Laufzeit Zirkulation,
92
+ // plausible min. Drehzahl HZK-Pumpe) ONLY with the current 2-byte lengths; shrinking
93
+ // either field by 1 byte shifts every subsequent field in the block and turns all of
94
+ // them into nonsense (e.g. Puffer vorhanden reading 39, Laufzeit Zirkulation reading
95
+ // 2560). Unlike Absenkfaktor, no adjacent misclassified padding field was found that
96
+ // would absorb a 1-byte shrink here - so, exactly like Heizkreis-A.Absenkfaktor before it
97
+ // was understood, treat the source documents as describing idm712100/idm750100 correctly
98
+ // but NOT idm701100/S_H726100 for this specific pair of fields, until a live capture
99
+ // (not just another reference document) proves otherwise.
100
+ //
101
+ // Waermepumpe.MinimaleLaufzeit remains unresolved for a different reason: neither
102
+ // reference file has a "writable" column (they only cover Id/Block-Id/Beschreibung/
103
+ // Name/Groesse/Faktor), so they cannot adjudicate a writable-flag disagreement at all.
104
+ const KNOWN_UNRESOLVED_DIFFERENCES = new Set([
105
+ 'Waermepumpe.MinimaleLaufzeit/38', // writable: true on idm712100/idm722100, false elsewhere
106
+ 'Heizung.SchichttrennplatteVorhanden/55', // length: 2 on idm701100/S_H726100, 1 everywhere else (incl. source docs)
107
+ 'Heizung.PufferVorhanden/56', // length: 2 on idm701100/S_H726100, 1 everywhere else (incl. source docs)
108
+ ]);
109
+
110
+ it('keeps writable/length/factor identical for the same (statename, function) across every firmware', () => {
111
+ /** @type {Map<string, {file: string, writable: boolean, length: number, factor: number, min?: number, max?: number}[]>} */
112
+ const byKey = new Map();
113
+ for (const { file, data } of idm_datablocks.readVersionFilesFrom(idm_datablocks.BUNDLED_DIR)) {
114
+ for (const block of data.data_blocks) {
115
+ for (const field of block.definition) {
116
+ if (!field.statename || field.function === -1) continue; // padding/unidentified
117
+ const key = field.statename + '/' + field.function;
118
+ let list = byKey.get(key);
119
+ if (!list) {
120
+ list = [];
121
+ byKey.set(key, list);
122
+ }
123
+ list.push({ file, writable: field.writable, length: field.length, factor: field.factor, min: field.min, max: field.max });
124
+ }
125
+ }
126
+ }
127
+
128
+ const problems = [];
129
+ for (const [key, entries] of byKey) {
130
+ if (entries.length < 2 || KNOWN_UNRESOLVED_DIFFERENCES.has(key)) continue;
131
+
132
+ for (const attr of ['writable', 'length', 'factor']) {
133
+ const values = new Set(entries.map(e => e[attr]));
134
+ if (values.size > 1) {
135
+ problems.push(key + ': ' + attr + ' differs - ' + entries.map(e => e.file.split('/').pop() + '=' + e[attr]).join(', '));
136
+ }
137
+ }
138
+ for (const attr of ['min', 'max']) {
139
+ const defined = entries.map(e => e[attr]).filter(v => v !== undefined);
140
+ if (new Set(defined).size > 1) {
141
+ problems.push(key + ': ' + attr + ' disagrees (not just undocumented) - ' + entries.map(e => e.file.split('/').pop() + '=' + e[attr]).join(', '));
142
+ }
143
+ }
144
+ }
145
+
146
+ expect(problems, 'found inconsistent field definitions for what should be the same register - see KNOWN_UNRESOLVED_DIFFERENCES above if this is an intentional, confirmed difference').to.deep.equal([]);
147
+ });
148
+
149
+ it('fails loudly if a KNOWN_UNRESOLVED_DIFFERENCES entry stops being a real difference (so it gets removed, not forgotten)', () => {
150
+ /** @type {Map<string, Set<string>>} */
151
+ const byKey = new Map();
152
+ for (const { data } of idm_datablocks.readVersionFilesFrom(idm_datablocks.BUNDLED_DIR)) {
153
+ for (const block of data.data_blocks) {
154
+ for (const field of block.definition) {
155
+ if (!field.statename || field.function === -1) continue;
156
+ const key = field.statename + '/' + field.function;
157
+ if (!KNOWN_UNRESOLVED_DIFFERENCES.has(key)) continue;
158
+ let seen = byKey.get(key);
159
+ if (!seen) {
160
+ seen = new Set();
161
+ byKey.set(key, seen);
162
+ }
163
+ seen.add(JSON.stringify({ writable: field.writable, length: field.length, factor: field.factor }));
164
+ }
165
+ }
166
+ }
167
+ for (const key of KNOWN_UNRESOLVED_DIFFERENCES) {
168
+ const seen = byKey.get(key);
169
+ expect(seen, key + ' is listed as unresolved but no longer appears in the bundled files - remove it from the list').to.not.be.undefined;
170
+ expect(seen ? seen.size : 0, key + ' is listed as unresolved but all bundled files now agree - remove it from the list').to.be.greaterThan(1);
171
+ }
172
+ });
173
+ });
174
+
55
175
  describe('validateVersionFile', () => {
56
176
  it('rejects a non-object root', () => {
57
177
  expect(idm_datablocks.validateVersionFile(null)).to.not.be.empty;
package/main.js CHANGED
@@ -49,7 +49,7 @@ class IdmMultitalent002 extends utils.Adapter {
49
49
 
50
50
  statesCreated;
51
51
  statesSubscribed;
52
- version;
52
+ idmControlVersion;
53
53
  connectedToIDM;
54
54
  stateNameMap = new Map();
55
55
  // Last value actually confirmed for a state - either read from the heat pump, or a write
@@ -158,7 +158,7 @@ class IdmMultitalent002 extends utils.Adapter {
158
158
  // create the states
159
159
  async CreateStates() {
160
160
  this.log.debug('creating states');
161
- const dataBlocks = this.idm.getDataBlocks(this.version); // get the known data blocks for the connected version
161
+ const dataBlocks = this.idm.getDataBlocks(this.idmControlVersion); // get the known data blocks for the connected version
162
162
 
163
163
  if (!dataBlocks) {
164
164
  this.log.warn('no data blocks defined, no states will be created');
@@ -168,7 +168,7 @@ class IdmMultitalent002 extends utils.Adapter {
168
168
  // Wait for every state to actually be created before flagging statesCreated/statesSubscribed -
169
169
  // Array.prototype.forEach does not await its (async) callback, so this used to mark the states
170
170
  // as created while most of the setObjectNotExistsAsync calls were still in flight.
171
- await this.idm.mapStatenames(this.version, this.createIDMState.bind(this));
171
+ await this.idm.mapStatenames(this.idmControlVersion, this.createIDMState.bind(this));
172
172
  for (const element of dataBlocks) {
173
173
  const stateName = 'Data_block_' + idm_u.get_byte(element);
174
174
  await this.setObjectNotExistsAsync(stateName, {
@@ -253,7 +253,7 @@ class IdmMultitalent002 extends utils.Adapter {
253
253
  });
254
254
  },
255
255
  onVersion: (version) => {
256
- this.version = version;
256
+ this.idmControlVersion = version;
257
257
  if (this.idm.dataDefinitions.has(version)) {
258
258
  const sourceKind = this.idm.isCustomDefinition(version) ? 'custom override' : 'bundled';
259
259
  this.log.info('heat pump reports version "' + version + '" - using its ' + sourceKind + ' data block definition (' + this.idm.dataSources.get(version) + ')');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.idm-multitalent_002",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Read and write values of a iDM heatpump with multitalent.002 control.",
5
5
  "author": {
6
6
  "name": "zloe",
@@ -31,6 +31,7 @@
31
31
  "@alcalzone/release-script-plugin-license": "^5.2.2",
32
32
  "@eslint/js": "^10.0.1",
33
33
  "@iobroker/testing": "^6.2.2",
34
+ "@tsconfig/node22": "^22.0.6",
34
35
  "@types/gulp": "^4.0.18",
35
36
  "@types/node": "^22.20.4",
36
37
  "@types/proxyquire": "^1.3.31",