iobroker.miele-local 0.3.45 → 0.3.47

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
@@ -237,6 +237,16 @@ engineering work of the projects `MieleRESTServer` (akappner),
237
237
  ### **WORK IN PROGRESS**
238
238
  -->
239
239
 
240
+ ### 0.3.47
241
+ - (SmarthomeElektroniker) A command under `control.*` that cannot be executed (control disabled, appliance not connected, HTTP error, connection error) is now acknowledged (`val=false`, `ack=true`) with the reason in the state comment `c` and a matching quality `q`. Until now it stayed unacknowledged - one `control.powerOn` stood there for 52 days
242
+ - (SmarthomeElektroniker) Unacknowledged commands left over from before a restart are closed on start and are **not** sent to the appliance
243
+ - (SmarthomeElektroniker) `remainingMinutes`/`remainingHHMM` are 0 while the appliance is off, finished or not connected (status 1/7/255), and no end time is calculated. A G5840 reported a remaining time of 0:01 while switched off
244
+ - (SmarthomeElektroniker) A lost connection to an appliance and its return are logged once at info level (until now only at debug)
245
+ - (SmarthomeElektroniker) The IP fallback search ("Search the subnet for the new IP if a device drops out") also runs in the background when mDNS discovery is off - exactly the Docker bridge setup it is meant for. It only probes appliances that currently fail, and searches every /24 network that has a missing appliance, not only the network of the first configured IP
246
+
247
+ ### 0.3.46
248
+ - (SmarthomeElektroniker) Only the running appliance is polled at the short interval ("Poll interval (active)"); idle appliances keep the idle interval. Until now all appliances were polled at the short interval as soon as one was running - fewer requests to the Miele modules, which serve only one connection at a time
249
+
240
250
  ### 0.3.45
241
251
  - (SmarthomeElektroniker) The last German state ID is gone: `eco.quelle` is now `eco.source`, its value is always English. Existing installations are migrated on start
242
252
  - (SmarthomeElektroniker) `statusText`, `programText`, `programPhaseText`, `programTypeText` and `dryingStepText` follow the "German names" option - with the option off they are English (until now they were always German)
package/README_de.md CHANGED
@@ -245,6 +245,16 @@ Reverse-Engineering-Arbeit der Projekte `MieleRESTServer` (akappner),
245
245
  ### **WORK IN PROGRESS**
246
246
  -->
247
247
 
248
+ ### 0.3.47
249
+ - Ein Befehl unter `control.*`, der nicht ausgeführt werden kann (Steuerung aus, Gerät nicht verbunden, HTTP-Fehler, Verbindungsfehler), wird jetzt bestätigt (`val=false`, `ack=true`), der Grund steht im Kommentar `c` des Datenpunkts, dazu eine passende Qualität `q`. Bisher blieb er unbestätigt stehen – ein `control.powerOn` stand so 52 Tage da.
250
+ - Unbestätigte Befehle von vor einem Neustart werden beim Start geschlossen und **nicht** an das Gerät gesendet.
251
+ - `remainingMinutes`/`remainingHHMM` sind 0, solange das Gerät aus, fertig oder nicht verbunden ist (Status 1/7/255), und es wird kein Programmende berechnet. Eine G5840 meldete ausgeschaltet eine Restzeit von 0:01.
252
+ - Verbindungsverlust zu einem Gerät und seine Rückkehr stehen je einmal im Log (Stufe info, bisher nur debug).
253
+ - Die IP-Suche bei Ausfall („Bei Ausfall die neue IP im Subnetz suchen“) läuft auch dann im Hintergrund, wenn die mDNS-Suche aus ist – also genau im Docker-Bridge-Betrieb, für den sie gedacht ist. Sie fragt nur Geräte, deren Abfrage gerade scheitert, und durchsucht jedes /24-Netz mit einem fehlenden Gerät, nicht nur das Netz der ersten eingetragenen IP.
254
+
255
+ ### 0.3.46
256
+ - Nur das laufende Gerät wird im kurzen Takt abgefragt („Abfrageintervall (aktiv)“), Geräte in Ruhe behalten den Ruhetakt. Bisher wurden alle Geräte im kurzen Takt abgefragt, sobald eines lief – weniger Anfragen an die Miele-Module, die nur eine Verbindung gleichzeitig bedienen.
257
+
248
258
  ### 0.3.45
249
259
  - Die letzte deutsche Datenpunkt-ID ist weg: `eco.quelle` heißt jetzt `eco.source`, der Wert ist immer englisch. Vorhandene Installationen ziehen beim Start um.
250
260
  - `statusText`, `programText`, `programPhaseText`, `programTypeText` und `dryingStepText` folgen der Option „Deutsche Namen“ - ist sie aus, sind die Texte englisch (bisher immer deutsch).
package/io-package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "miele-local",
4
- "version": "0.3.45",
4
+ "version": "0.3.47",
5
5
  "titleLang": {
6
6
  "en": "Miele@local",
7
7
  "de": "Miele@local",
@@ -52,6 +52,32 @@
52
52
  "readme": "https://github.com/SmarthomeElektroniker/ioBroker.miele-local/blob/main/README.md",
53
53
  "loglevel": "info",
54
54
  "news": {
55
+ "0.3.47": {
56
+ "en": "Commands that cannot be executed are acknowledged with the reason (state comment and quality) instead of staying unacknowledged; pending commands from before a restart are closed without being sent. Remaining time is 0 while an appliance is off or finished. A lost and restored connection is logged once at info level. The IP fallback search also runs without mDNS and in every subnet with a missing appliance",
57
+ "de": "Nicht ausführbare Befehle werden mit Grund bestätigt (Kommentar und Qualität des Datenpunkts), statt unbestätigt stehen zu bleiben; offene Befehle von vor einem Neustart werden geschlossen, ohne sie zu senden. Restzeit ist 0, solange ein Gerät aus oder fertig ist. Verbindungsverlust und Wiederkehr stehen je einmal im Log (info). Die IP-Suche bei Ausfall läuft auch ohne mDNS und in jedem Subnetz mit einem fehlenden Gerät",
58
+ "ru": "Невыполнимые команды подтверждаются с указанием причины (комментарий и качество состояния), а не остаются неподтверждёнными; ожидающие команды до перезапуска закрываются без отправки. Оставшееся время равно 0, пока прибор выключен или завершил работу. Потеря и восстановление связи записываются в журнал один раз (info). Поиск IP при сбое работает и без mDNS, во всех подсетях с отсутствующим прибором",
59
+ "pt": "Comandos que não podem ser executados são confirmados com o motivo (comentário e qualidade do estado) em vez de ficarem por confirmar; comandos pendentes de antes de um reinício são fechados sem serem enviados. O tempo restante é 0 enquanto o aparelho está desligado ou concluído. Perda e recuperação da ligação são registadas uma vez (info). A procura de IP em caso de falha também funciona sem mDNS e em todas as sub-redes com um aparelho em falta",
60
+ "nl": "Opdrachten die niet uitgevoerd kunnen worden, worden met de reden bevestigd (commentaar en kwaliteit van de state) in plaats van onbevestigd te blijven; openstaande opdrachten van vóór een herstart worden gesloten zonder ze te verzenden. De resttijd is 0 zolang een apparaat uit of klaar is. Verbindingsverlies en herstel worden één keer gelogd (info). Het zoeken naar het IP-adres bij uitval werkt ook zonder mDNS en in elk subnet met een ontbrekend apparaat",
61
+ "fr": "Les commandes non exécutables sont acquittées avec la raison (commentaire et qualité de l'état) au lieu de rester non acquittées ; les commandes en attente d'avant un redémarrage sont fermées sans être envoyées. Le temps restant vaut 0 tant qu'un appareil est éteint ou terminé. La perte et le retour de la connexion sont journalisés une seule fois (info). La recherche d'IP en cas de panne fonctionne aussi sans mDNS et dans chaque sous-réseau où un appareil manque",
62
+ "it": "I comandi non eseguibili vengono confermati con il motivo (commento e qualità dello stato) invece di restare non confermati; i comandi in sospeso precedenti a un riavvio vengono chiusi senza essere inviati. Il tempo residuo è 0 finché l'elettrodomestico è spento o ha finito. Perdita e ripristino della connessione vengono registrati una sola volta (info). La ricerca dell'IP in caso di guasto funziona anche senza mDNS e in ogni sottorete con un elettrodomestico mancante",
63
+ "es": "Los comandos que no se pueden ejecutar se confirman con el motivo (comentario y calidad del estado) en lugar de quedar sin confirmar; los comandos pendientes de antes de un reinicio se cierran sin enviarse. El tiempo restante es 0 mientras el aparato está apagado o ha terminado. La pérdida y la recuperación de la conexión se registran una sola vez (info). La búsqueda de IP ante un fallo funciona también sin mDNS y en cada subred con un aparato ausente",
64
+ "pl": "Polecenia, których nie można wykonać, są potwierdzane z podaniem przyczyny (komentarz i jakość stanu), zamiast pozostawać niepotwierdzone; oczekujące polecenia sprzed restartu są zamykane bez wysyłania. Pozostały czas wynosi 0, gdy urządzenie jest wyłączone lub zakończyło pracę. Utrata i powrót połączenia są zapisywane w dzienniku jednorazowo (info). Wyszukiwanie IP po awarii działa także bez mDNS i w każdej podsieci z brakującym urządzeniem",
65
+ "uk": "Команди, які неможливо виконати, підтверджуються із зазначенням причини (коментар і якість стану), а не залишаються непідтвердженими; очікувані команди до перезапуску закриваються без надсилання. Залишковий час дорівнює 0, поки прилад вимкнений або завершив роботу. Втрата та відновлення зв'язку записуються в журнал один раз (info). Пошук IP при збої працює і без mDNS, у кожній підмережі з відсутнім приладом",
66
+ "zh-cn": "无法执行的命令会附带原因(状态注释和质量)确认,而不再保持未确认;重启前的待处理命令会被关闭且不发送。电器关闭或程序结束时剩余时间为 0。连接丢失与恢复只在日志中记录一次(info)。IP 故障搜索在没有 mDNS 时也会运行,并覆盖每个缺少电器的子网"
67
+ },
68
+ "0.3.46": {
69
+ "en": "Only the running appliance is polled at the short interval, idle appliances keep the idle interval - fewer requests to the Miele modules (until now all appliances were polled at the short interval as soon as one was running)",
70
+ "de": "Nur das laufende Gerät wird im kurzen Takt abgefragt, Geräte in Ruhe behalten den Ruhetakt - weniger Anfragen an die Miele-Module (bisher wurden alle Geräte im kurzen Takt abgefragt, sobald eines lief)",
71
+ "ru": "С коротким интервалом опрашивается только работающий прибор, приборы в покое сохраняют интервал покоя - меньше запросов к модулям Miele (раньше все приборы опрашивались с коротким интервалом, как только работал один)",
72
+ "pt": "Apenas o aparelho em funcionamento é consultado no intervalo curto, aparelhos inativos mantêm o intervalo de repouso - menos pedidos aos módulos Miele (até agora todos eram consultados no intervalo curto assim que um estava a funcionar)",
73
+ "nl": "Alleen het draaiende apparaat wordt met het korte interval opgevraagd, apparaten in rust houden het rustinterval - minder verzoeken aan de Miele-modules (tot nu toe werden alle apparaten kort opgevraagd zodra er één draaide)",
74
+ "fr": "Seul l'appareil en marche est interrogé à l'intervalle court, les appareils au repos gardent l'intervalle de repos - moins de requêtes vers les modules Miele (jusqu'ici tous étaient interrogés à l'intervalle court dès qu'un appareil fonctionnait)",
75
+ "it": "Solo l'elettrodomestico in funzione viene interrogato all'intervallo breve, quelli inattivi mantengono l'intervallo di riposo - meno richieste ai moduli Miele (finora tutti venivano interrogati all'intervallo breve appena uno era in funzione)",
76
+ "es": "Solo el aparato en marcha se consulta con el intervalo corto, los aparatos en reposo mantienen el intervalo de reposo - menos peticiones a los módulos Miele (hasta ahora todos se consultaban con el intervalo corto en cuanto uno funcionaba)",
77
+ "pl": "Tylko pracujące urządzenie jest odpytywane w krótkim interwale, urządzenia w spoczynku zachowują interwał spoczynku - mniej zapytań do modułów Miele (dotąd wszystkie były odpytywane krótko, gdy tylko jedno pracowało)",
78
+ "uk": "З коротким інтервалом опитується лише працюючий прилад, прилади у спокої зберігають інтервал спокою - менше запитів до модулів Miele (досі всі прилади опитувалися з коротким інтервалом, щойно працював один)",
79
+ "zh-cn": "只有正在运行的电器使用短间隔轮询,空闲电器保持空闲间隔 - 减少对 Miele 模块的请求(此前只要有一台在运行,所有电器都按短间隔轮询)"
80
+ },
55
81
  "0.3.45": {
56
82
  "en": "Only English IDs left: eco.quelle -> eco.source, eco.felderJson -> eco.fieldsJson; settings use English keys (collectorActive, leafStates, energyMeters ...), migrated once on start; text states follow the German names option; background loops never overlap",
57
83
  "de": "Nur noch englische IDs: eco.quelle -> eco.source, eco.felderJson -> eco.fieldsJson; Einstellungen mit englischen Namen (collectorActive, leafStates, energyMeters ...), beim Start einmalig übernommen; Textdatenpunkte folgen der Option „Deutsche Namen“; Hintergrundschleifen überlappen nicht mehr",
@@ -116,32 +142,6 @@
116
142
  "pl": "Poprawiono typy urządzeń (16 mikrofalówka, 67 piekarnik Dialog, 25 podgrzewacz naczyń, pralko-suszarka z programami pralki); nowe typy: robot sprzątający, piekarnik parowy kombi, piekarnik parowy z mikrofalą (418 programów), urządzenia profesjonalne. Podziękowania dla Home Assistant za tabele.",
117
143
  "uk": "Виправлено типи пристроїв (16 мікрохвильова піч, 67 піч Dialog, 25 підігрівач посуду, прально-сушильна машина з програмами пральної машини); нові типи: робот-пилосос, комбі-пароварка, пароварка з мікрохвильовою піччю (418 програм), професійні прилади. Подяка Home Assistant за таблиці.",
118
144
  "zh-cn": "修正设备类型(16 微波炉、67 Dialog 烤箱、25 暖碟机、洗干一体机使用洗衣机程序);新增类型:扫地机器人、蒸烤箱、微波蒸箱(418 个程序)、专业设备。感谢 Home Assistant 提供的表格。"
119
- },
120
- "0.3.40": {
121
- "en": "README: hints for a failing login (ad blocker), moving to another system and why mDNS may find nothing; clearer log message when no appliance is found (#12).",
122
- "de": "README: Hinweise bei scheiterndem Login (Werbeblocker), zum Umzug auf ein anderes System und warum mDNS nichts findet; deutlichere Logmeldung, wenn kein Gerät gefunden wird (#12).",
123
- "ru": "README: подсказки при неудачном входе (блокировщик рекламы), при переносе на другую систему и о том, почему mDNS ничего не находит; более понятное сообщение в журнале, если устройство не найдено (#12).",
124
- "pt": "README: dicas para um login falhado (bloqueador de anúncios), para mudar para outro sistema e porque o mDNS pode não encontrar nada; mensagem de registo mais clara quando nenhum aparelho é encontrado (#12).",
125
- "nl": "README: tips bij een mislukte login (adblocker), bij verhuizing naar een ander systeem en waarom mDNS niets vindt; duidelijkere logmelding als er geen apparaat wordt gevonden (#12).",
126
- "fr": "README : conseils en cas d'échec de connexion (bloqueur de publicités), pour migrer vers un autre système et pourquoi mDNS ne trouve rien ; message de journal plus clair lorsqu'aucun appareil n'est trouvé (#12).",
127
- "it": "README: suggerimenti per un login non riuscito (blocco pubblicità), per il trasferimento su un altro sistema e perché mDNS non trova nulla; messaggio di log più chiaro quando non viene trovato alcun apparecchio (#12).",
128
- "es": "README: consejos para un inicio de sesión fallido (bloqueador de anuncios), para migrar a otro sistema y por qué mDNS no encuentra nada; mensaje de registro más claro cuando no se encuentra ningún aparato (#12).",
129
- "pl": "README: wskazówki przy nieudanym logowaniu (bloker reklam), przy przenoszeniu na inny system i dlaczego mDNS nic nie znajduje; czytelniejszy komunikat w dzienniku, gdy nie znaleziono urządzenia (#12).",
130
- "uk": "README: підказки при невдалому вході (блокувальник реклами), при перенесенні на іншу систему та чому mDNS нічого не знаходить; зрозуміліше повідомлення в журналі, якщо пристрій не знайдено (#12).",
131
- "zh-cn": "README:登录失败(广告拦截器)、迁移到其他系统以及 mDNS 找不到设备原因的提示;未找到设备时日志信息更清晰 (#12)。"
132
- },
133
- "0.3.39": {
134
- "en": "Unknown program or phase IDs are shown as \"Programm 201\" instead of keeping the text of the previous program. Dishwasher program IDs of the G7771 added (#13).",
135
- "de": "Unbekannte Programm- oder Phasennummern erscheinen als „Programm 201“, statt den Text des vorigen Programms stehen zu lassen. Programmnummern des Geschirrspülers G7771 ergänzt (#13).",
136
- "ru": "Неизвестные номера программ или фаз отображаются как «Programm 201», а не сохраняют текст предыдущей программы. Добавлены номера программ посудомоечной машины G7771 (#13).",
137
- "pt": "IDs de programa ou fase desconhecidos aparecem como \"Programm 201\" em vez de manter o texto do programa anterior. Adicionados os IDs de programa da máquina de lavar loiça G7771 (#13).",
138
- "nl": "Onbekende programma- of fasenummers verschijnen als \"Programm 201\" in plaats van de tekst van het vorige programma te laten staan. Programmanummers van de vaatwasser G7771 toegevoegd (#13).",
139
- "fr": "Les numéros de programme ou de phase inconnus s'affichent comme « Programm 201 » au lieu de conserver le texte du programme précédent. Numéros de programme du lave-vaisselle G7771 ajoutés (#13).",
140
- "it": "I numeri di programma o di fase sconosciuti vengono mostrati come \"Programm 201\" invece di mantenere il testo del programma precedente. Aggiunti i numeri di programma della lavastoviglie G7771 (#13).",
141
- "es": "Los números de programa o fase desconocidos se muestran como \"Programm 201\" en lugar de conservar el texto del programa anterior. Añadidos los números de programa del lavavajillas G7771 (#13).",
142
- "pl": "Nieznane numery programów lub faz są wyświetlane jako „Programm 201” zamiast pozostawiać tekst poprzedniego programu. Dodano numery programów zmywarki G7771 (#13).",
143
- "uk": "Невідомі номери програм або фаз відображаються як «Programm 201», а не зберігають текст попередньої програми. Додано номери програм посудомийної машини G7771 (#13).",
144
- "zh-cn": "未知的程序或阶段编号显示为“Programm 201”,不再保留上一个程序的文本。新增 G7771 洗碗机的程序编号 (#13)。"
145
145
  }
146
146
  },
147
147
  "mode": "daemon",
package/lib/befehle.js ADDED
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * Steuerbefehle: ausfuehren oder sauber abschliessen (seit 0.3.47).
5
+ *
6
+ * Bis 0.3.46 blieb ein Befehl, der an der Freigabe, am Geraet oder an der Verbindung
7
+ * scheiterte, mit ack=false stehen. Am 16.10.2026 stand so ein control.powerOn = true seit
8
+ * 52 Tagen unbestaetigt da (gesetzt am 25.08., die Steuerung war damals aus). Wer den
9
+ * Datenpunkt liest, haelt das fuer einen Befehl, der noch unterwegs ist.
10
+ *
11
+ * Jetzt endet jeder Befehl mit val=false und ack=true. Wurde er nicht ausgefuehrt, steht der
12
+ * Grund im Kommentarfeld c, und die Qualitaet q ordnet ihn ein - sichtbar im Objektbrowser und
13
+ * fuer Skripte lesbar. Ein spaeterer erfolgreicher Befehl setzt q wieder auf 0.
14
+ */
15
+
16
+ /** ioBroker-Qualitaetscodes, die hier vorkommen. */
17
+ const Q = {
18
+ INSTANZ: 0x11, // general problem by instance
19
+ NICHT_VERBUNDEN: 0x42, // device not connected
20
+ GERAETEFEHLER: 0x44, // device reports error
21
+ };
22
+
23
+ /**
24
+ * Einen Befehl abschliessen, der nicht ausgefuehrt wurde.
25
+ *
26
+ * @param adapter Adapterinstanz (log, setStateAsync)
27
+ * @param {string} id vollstaendige ID des Steuer-Datenpunkts
28
+ * @param {string} sub Name des Befehls (fuers Log)
29
+ * @param {string} grund Fehlermeldung, englisch
30
+ * @param {number} q ioBroker-Qualitaetscode
31
+ */
32
+ async function abschliessen(adapter, id, sub, grund, q) {
33
+ adapter.log.warn(`Command '${sub}' not executed: ${grund}`);
34
+ try {
35
+ await adapter.setStateAsync(id, { val: false, ack: true, q, c: `not executed: ${grund}` });
36
+ } catch (e) {
37
+ adapter.log.debug(`${id}: could not acknowledge - ${e.message}`);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Einen Nutzerbefehl unter control.* verarbeiten.
43
+ *
44
+ * @param adapter Adapterinstanz (config, devices, log, setStateAsync, getObjectAsync)
45
+ * @param {{id: string, deviceId: string, sub: string, val: unknown}} befehl der Befehl
46
+ * @param {{senden: (dev: object, opcode: number) => Promise<number>, nachErfolg?: () => void}} wege
47
+ * senden schickt den Befehl und liefert den HTTP-Status; nachErfolg holt den neuen Zustand
48
+ */
49
+ async function verarbeiten(adapter, befehl, wege) {
50
+ const { id, deviceId, sub, val } = befehl;
51
+ const dev = adapter.devices && adapter.devices[deviceId];
52
+ if (!dev) {
53
+ return abschliessen(adapter, id, sub, 'device not connected', Q.NICHT_VERBUNDEN);
54
+ }
55
+ if (!adapter.config.allowControl) {
56
+ return abschliessen(adapter, id, sub, 'device control is disabled in the instance settings', Q.INSTANZ);
57
+ }
58
+ if (!val) {
59
+ // false loest nichts aus - nur bestaetigen, damit nichts offen bleibt.
60
+ await adapter.setStateAsync(id, { val: false, ack: true });
61
+ return;
62
+ }
63
+ const obj = await adapter.getObjectAsync(id);
64
+ const opcode = obj && obj.native ? obj.native.opcode : null;
65
+ if (opcode == null) {
66
+ return abschliessen(adapter, id, sub, 'unknown command', Q.INSTANZ);
67
+ }
68
+ let status;
69
+ try {
70
+ status = await wege.senden(dev, opcode);
71
+ } catch (e) {
72
+ return abschliessen(
73
+ adapter,
74
+ id,
75
+ sub,
76
+ `${deviceId} - ${e.message} (remote control might not be supported by the firmware)`,
77
+ Q.NICHT_VERBUNDEN,
78
+ );
79
+ }
80
+ if (status === 200 || status === 204) {
81
+ adapter.log.info(`Command '${sub}' sent to ${deviceId} (opcode 0x${opcode.toString(16)}).`);
82
+ await adapter.setStateAsync(id, { val: false, ack: true });
83
+ if (wege.nachErfolg) {
84
+ wege.nachErfolg();
85
+ }
86
+ return;
87
+ }
88
+ return abschliessen(
89
+ adapter,
90
+ id,
91
+ sub,
92
+ `HTTP ${status} from ${deviceId} (is MobileStart enabled on the device?)`,
93
+ Q.GERAETEFEHLER,
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Unbestaetigte Steuerbefehle beim Start schliessen - OHNE sie auszufuehren.
99
+ *
100
+ * Ein Befehl, der beim Start noch mit ack=false dasteht, stammt aus einer frueheren Laufzeit:
101
+ * gesetzt, waehrend der Adapter aus war, oder unter 0.3.46 und frueher gescheitert, ohne
102
+ * zurueckgesetzt zu werden. Ihn jetzt nachzuholen waere gefaehrlich - ein "Start" von vor
103
+ * Wochen gehoert nicht an eine Maschine, die inzwischen anders beladen ist.
104
+ *
105
+ * @param adapter Adapterinstanz (getStatesAsync, setStateAsync, log)
106
+ * @returns {Promise<number>} Zahl der geschlossenen Befehle
107
+ */
108
+ async function offeneSchliessen(adapter) {
109
+ let offen = {};
110
+ try {
111
+ offen = (await adapter.getStatesAsync('*.control.*')) || {};
112
+ } catch (e) {
113
+ adapter.log.debug(`Pending commands not checked: ${e.message}`);
114
+ return 0;
115
+ }
116
+ let n = 0;
117
+ for (const [id, st] of Object.entries(offen)) {
118
+ if (!st || st.ack !== false) {
119
+ continue;
120
+ }
121
+ const seit = st.ts ? new Date(st.ts).toISOString().slice(0, 10) : 'unknown';
122
+ adapter.log.info(`${id}: unacknowledged command from ${seit} discarded, not sent to the device`);
123
+ try {
124
+ await adapter.setStateAsync(id, {
125
+ val: false,
126
+ ack: true,
127
+ q: Q.INSTANZ,
128
+ c: `not executed: still pending at adapter start (set ${seit})`,
129
+ });
130
+ n++;
131
+ } catch (e) {
132
+ adapter.log.debug(`${id}: could not acknowledge - ${e.message}`);
133
+ }
134
+ }
135
+ return n;
136
+ }
137
+
138
+ module.exports = { Q, abschliessen, verarbeiten, offeneSchliessen };
package/lib/discovery.js CHANGED
@@ -191,4 +191,29 @@ async function scanSubnet(vorlageIp, log = () => {}, opt = {}) {
191
191
  return gefunden;
192
192
  }
193
193
 
194
- module.exports = { discover, SERVICE, scanSubnet, istMiele };
194
+ /**
195
+ * Eine Vorlage-Adresse je /24-Netz, in dem eine der Adressen liegt (Reihenfolge bleibt).
196
+ *
197
+ * Bis 0.3.46 suchte der Rueckfall nur im Netz der ERSTEN eingetragenen Adresse. Liegen die
198
+ * Geraete in verschiedenen Netzen - etwa waehrend eines Umzugs ins IoT-Netz -, blieb ein
199
+ * Geraet im zweiten Netz unauffindbar.
200
+ *
201
+ * @param {string[]} ips Adressen, deren Netze durchsucht werden sollen
202
+ * @returns {string[]} je Netz die erste passende Adresse
203
+ */
204
+ function subnetzVorlagen(ips) {
205
+ const netze = new Map();
206
+ for (const ip of ips || []) {
207
+ const teile = String(ip || '').split('.');
208
+ if (teile.length !== 4) {
209
+ continue;
210
+ }
211
+ const netz = teile.slice(0, 3).join('.');
212
+ if (!netze.has(netz)) {
213
+ netze.set(netz, ip);
214
+ }
215
+ }
216
+ return [...netze.values()];
217
+ }
218
+
219
+ module.exports = { discover, SERVICE, scanSubnet, istMiele, subnetzVorlagen };
package/lib/objects.js CHANGED
@@ -24,6 +24,34 @@ function timeToMinutes(arr) {
24
24
  return arr[0] * 60 + arr[1];
25
25
  }
26
26
 
27
+ /*
28
+ * Restzeit nur, wenn es ein Programm gibt (seit 0.3.47).
29
+ *
30
+ * Die Spuelmaschine G5840 meldete am 16.10.2026 im Zustand "Aus" (Status 1) eine Restzeit
31
+ * von [0, 1] - Rohwert des Geraets, im signierten GET /State nachgelesen. Durchgereicht
32
+ * stand im Datenpunkt "0:01", und daraus wurde ein Programmende eine Minute in der Zukunft
33
+ * berechnet, das nie kam. Ein ausgeschaltetes, fertiges oder nicht verbundenes Geraet hat
34
+ * keine Restzeit; die Anzeige des Panels ist dann ein Ueberbleibsel.
35
+ *
36
+ * Nicht betroffen sind 2 (An), 3 (programmiert) und 4 (wartet auf Start): Dort zeigt das
37
+ * Geraet die Dauer des gewaehlten Programms, und die ist eine echte Auskunft.
38
+ */
39
+ const OHNE_RESTZEIT = new Set([1, 7, 255]);
40
+
41
+ /**
42
+ * Setzt die Restzeit eines /State-Datensatzes auf 0, wenn das Geraet kein Programm hat.
43
+ * Gibt den Datensatz unveraendert zurueck, wenn nichts zu tun ist, sonst eine Kopie.
44
+ *
45
+ * @param {Record<string, unknown>} state Rohantwort von /State
46
+ * @returns {Record<string, unknown>} bereinigter Datensatz
47
+ */
48
+ function restzeitBereinigen(state) {
49
+ if (!state || !('RemainingTime' in state) || !OHNE_RESTZEIT.has(Number(state.Status))) {
50
+ return state;
51
+ }
52
+ return { ...state, RemainingTime: [0, 0] };
53
+ }
54
+
27
55
  /**
28
56
  * [h, min] → "H:MM"-String (wie mielecloudservice), null bei fehlend.
29
57
  *
@@ -850,6 +878,8 @@ module.exports = {
850
878
  fieldAllowed,
851
879
  nameFor,
852
880
  timeToMinutes,
881
+ restzeitBereinigen,
882
+ OHNE_RESTZEIT,
853
883
  temp,
854
884
  pathGet,
855
885
  TEMP_SENTINEL,
package/lib/takt.js ADDED
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * Abfragetakt je Geraet (seit 0.3.46).
5
+ *
6
+ * Bis 0.3.45 galt der kurze Takt fuer ALLE Geraete, sobald eines lief: Backte der Ofen, wurden
7
+ * Wasch- und Spuelmaschine im Minutentakt mitgefragt, obwohl sie aus waren. Ein XKM-Modul bedient
8
+ * nur eine Verbindung gleichzeitig - jede ueberfluessige Frage nimmt der Miele-App einen
9
+ * Zeitschlitz weg. Jetzt hat jedes Geraet seinen eigenen Termin: kurz, solange es laeuft, sonst
10
+ * der Ruhetakt. Die Schleife wacht zum fruehesten Termin auf und fragt nur, wer dran ist.
11
+ */
12
+
13
+ /** Kein Wecken in kuerzeren Abstaenden als diesem - auch wenn mehrere Termine dicht liegen. */
14
+ const MIN_WECKABSTAND_MS = 1000;
15
+
16
+ /**
17
+ * Abstand bis zur naechsten Abfrage eines Geraets.
18
+ *
19
+ * @param {{active?: boolean}} dev Geraeteeintrag
20
+ * @param {number} aktivMs Takt im Betrieb
21
+ * @param {number} ruheMs Takt in Ruhe
22
+ * @returns {number} Millisekunden
23
+ */
24
+ function abstand(dev, aktivMs, ruheMs) {
25
+ return dev && dev.active ? aktivMs : ruheMs;
26
+ }
27
+
28
+ /**
29
+ * Ist dieses Geraet jetzt dran? Ein Geraet ohne Termin (gerade gefunden) ist sofort dran.
30
+ *
31
+ * @param {{naechsteAbfrage?: number}} dev Geraeteeintrag
32
+ * @param {number} jetzt Zeitpunkt in ms
33
+ * @returns {boolean} true, wenn es jetzt abgefragt werden soll
34
+ */
35
+ function istFaellig(dev, jetzt) {
36
+ return (dev.naechsteAbfrage || 0) <= jetzt;
37
+ }
38
+
39
+ /**
40
+ * Wann die Schleife wieder aufwachen soll: zum fruehesten Termin aller Geraete.
41
+ *
42
+ * @param {Array<{naechsteAbfrage?: number}>} geraete alle Geraeteeintraege
43
+ * @param {number} jetzt Zeitpunkt in ms
44
+ * @param {number} ruheMs Abstand, wenn (noch) kein Geraet bekannt ist
45
+ * @returns {number} Millisekunden bis zum naechsten Durchgang
46
+ */
47
+ function weckAbstand(geraete, jetzt, ruheMs) {
48
+ if (!geraete.length) {
49
+ return ruheMs;
50
+ }
51
+ const fruehester = Math.min(...geraete.map(d => d.naechsteAbfrage || 0));
52
+ return Math.max(MIN_WECKABSTAND_MS, fruehester - jetzt);
53
+ }
54
+
55
+ module.exports = { abstand, istFaellig, weckAbstand, MIN_WECKABSTAND_MS };
package/main.js CHANGED
@@ -11,7 +11,9 @@ const utils = require('@iobroker/adapter-core');
11
11
  const paket = require('./package.json');
12
12
  const { MieleCrypto } = require('./lib/crypto');
13
13
  const { MieleDeviceApi } = require('./lib/api');
14
- const { discover, scanSubnet, istMiele } = require('./lib/discovery');
14
+ const { discover, scanSubnet, istMiele, subnetzVorlagen } = require('./lib/discovery');
15
+ const befehle = require('./lib/befehle');
16
+ const takt = require('./lib/takt');
15
17
  const cloud = require('./lib/cloud');
16
18
  const objdef = require('./lib/objects');
17
19
  const namen = require('./lib/names');
@@ -336,11 +338,22 @@ class MieleLocal extends utils.Adapter {
336
338
  }
337
339
 
338
340
  // Periodisches Re-Discovery im Hintergrund (z. B. für Geräte, die aus dem Standby aufwachen)
339
- if (this.config.autoDiscover !== false) {
341
+ /*
342
+ * Auch ohne mDNS, sobald IPs von Hand eingetragen sind und der IP-Rueckfall an ist.
343
+ *
344
+ * Bis 0.3.46 hing die ganze Hintergrundsuche an autoDiscover. Genau dort, wo der
345
+ * Rueckfall gebraucht wird - Docker-Bridge, mDNS aus, IPs von Hand -, lief er deshalb
346
+ * nur ein einziges Mal beim Start. Ein Geraet, das danach die Adresse wechselte oder
347
+ * beim Start nicht erreichbar war, kam bis zum naechsten Neustart nicht zurueck.
348
+ */
349
+ const handIps = (this.config.devices || []).some(e => (typeof e === 'string' ? e : e && e.ip));
350
+ if (this.config.autoDiscover !== false || (this.config.ipFallbackScan !== false && handIps)) {
340
351
  const discInterval = MieleLocal.intervallMs(this.config.autoDiscoverInterval, 10, 1, 60000);
341
352
  this.schleife('discoveryTimer', () => this.discoverDevices(), discInterval);
342
353
  }
343
354
 
355
+ // Erst Altlasten schliessen, dann abonnieren - siehe lib/befehle.js offeneSchliessen().
356
+ await befehle.offeneSchliessen(this);
344
357
  await this.subscribeStatesAsync('*.control.*');
345
358
 
346
359
  // Push-Listener + Enrollment
@@ -458,17 +471,39 @@ class MieleLocal extends utils.Adapter {
458
471
  * und Statistik bleiben also dieselben, nur die Adresse ist neu.
459
472
  */
460
473
  if (this.config.ipFallbackScan !== false && konfigurierte.length) {
461
- const erreichbar = await Promise.all(konfigurierte.map(ip => istMiele(ip, 1500)));
462
- const vermisst = konfigurierte.filter((_, i) => !erreichbar[i]);
474
+ // Nur nachsehen, wo die Abfrage gerade scheitert oder noch nie geklappt hat - ein
475
+ // laufendes Geraet bekommt keine zusaetzliche Anfrage (das Modul bedient nur eine
476
+ // Verbindung gleichzeitig).
477
+ const kandidaten = konfigurierte.filter(ip => {
478
+ if (this.alteIps && this.alteIps.has(ip)) {
479
+ return false; // Geraet laeuft schon unter seiner neuen Adresse
480
+ }
481
+ const d = Object.values(this.devices).find(x => x.ip === ip);
482
+ return !d || d.erreichbar === false;
483
+ });
484
+ const erreichbar = await Promise.all(kandidaten.map(ip => istMiele(ip, 1500)));
485
+ const vermisst = kandidaten.filter((_, i) => !erreichbar[i]);
463
486
  /*
464
- * Hoechstens alle 30 Minuten.
487
+ * Hoechstens alle 30 Minuten (mit Verlaengerung, siehe unten).
465
488
  *
466
489
  * Ist ein Geraet schlicht ausgeschaltet - der Backofen ist es die meiste Zeit -,
467
490
  * bleibt es "vermisst", und ohne diese Sperre liefe bei jedem Discovery-Lauf ein
468
491
  * Subnetz-Scan, der nichts Neues findet.
469
492
  */
493
+ /*
494
+ * Findet ein Scan nichts Neues, verdoppelt sich die Pause bis auf vier Stunden.
495
+ *
496
+ * Ein Geraet, das nach einem WLAN-Ausfall einfach nicht zurueckkommt (Waschmaschine,
497
+ * 16.10.2026), wuerde sonst die ganze Nacht alle 30 Minuten 254 Adressen abklopfen
498
+ * und zwei Logzeilen schreiben. Sobald alle wieder antworten oder eine neue Adresse
499
+ * auftaucht, gilt wieder die kurze Pause.
500
+ */
501
+ const pauseMs = this.scanPauseMs || 30 * 60 * 1000;
502
+ if (!vermisst.length) {
503
+ this.scanPauseMs = 30 * 60 * 1000;
504
+ }
470
505
  const seitLetztem = Date.now() - (this.letzterScan || 0);
471
- if (vermisst.length && seitLetztem < 30 * 60 * 1000) {
506
+ if (vermisst.length && seitLetztem < pauseMs) {
472
507
  this.log.debug(
473
508
  `${vermisst.length} device(s) not reachable, last subnet scan ` +
474
509
  `${Math.round(seitLetztem / 60000)} min ago - waiting`,
@@ -479,14 +514,24 @@ class MieleLocal extends utils.Adapter {
479
514
  `${vermisst.length} configured device(s) not responding ` +
480
515
  `(${vermisst.join(', ')}) - searching the subnet for the new address`,
481
516
  );
482
- const imNetz = await scanSubnet(konfigurierte[0], m => this.log.info(m));
517
+ // Jedes Subnetz, in dem ein Geraet fehlt - nicht nur das der ersten Adresse.
518
+ const imNetz = [];
519
+ for (const vorlage of subnetzVorlagen(vermisst)) {
520
+ imNetz.push(...(await scanSubnet(vorlage, m => this.log.info(m))));
521
+ }
522
+ let neue = 0;
483
523
  for (const ip of imNetz) {
484
524
  if (!seenIp.has(ip)) {
485
525
  found.push({ ip, techType: '', deviceType: null });
486
526
  seenIp.add(ip);
527
+ neue++;
487
528
  this.log.info(`New address found: ${ip}`);
488
529
  }
489
530
  }
531
+ this.scanPauseMs = neue ? 30 * 60 * 1000 : Math.min(4 * 60 * 60 * 1000, pauseMs * 2);
532
+ if (!neue) {
533
+ this.log.info(`No new address found - next subnet search in ${this.scanPauseMs / 60000} min`);
534
+ }
490
535
  }
491
536
  }
492
537
 
@@ -495,7 +540,8 @@ class MieleLocal extends utils.Adapter {
495
540
  }
496
541
 
497
542
  for (const f of found) {
498
- const alreadyKnown = Object.values(this.devices).some(d => d.ip === f.ip);
543
+ const alreadyKnown =
544
+ Object.values(this.devices).some(d => d.ip === f.ip) || (this.alteIps && this.alteIps.has(f.ip));
499
545
  if (!alreadyKnown) {
500
546
  try {
501
547
  await this.initDevice(f);
@@ -560,6 +606,15 @@ class MieleLocal extends utils.Adapter {
560
606
  // Vor allem anderen: was aus 0.3.37 noch unter deutschen IDs liegt, umziehen.
561
607
  await this.idsUmziehen(deviceId);
562
608
 
609
+ const vorher = this.devices[deviceId];
610
+ if (vorher && vorher.ip !== f.ip) {
611
+ // Gefunden ueber den IP-Rueckfall: dieselbe Seriennummer unter neuer Adresse.
612
+ this.alteIps = this.alteIps || new Set();
613
+ this.alteIps.add(vorher.ip);
614
+ this.log.info(
615
+ `${deviceId}: now at ${f.ip} (was ${vorher.ip}) - please update the IP on the 'Appliances' tab`,
616
+ );
617
+ }
563
618
  this.devices[deviceId] = { ip: f.ip, route, deviceType, api, active: false };
564
619
  /*
565
620
  * Die Sammlungsobjekte schon beim Verbinden anlegen, nicht erst am Zyklusende.
@@ -888,6 +943,9 @@ class MieleLocal extends utils.Adapter {
888
943
  if ('Status' in state && !(await this.statusPlausibel(deviceId, state.Status))) {
889
944
  return;
890
945
  }
946
+ // Restzeit eines ausgeschalteten/fertigen Geraets ist ein Ueberbleibsel - siehe
947
+ // objects.restzeitBereinigen. Vor dem Dekodieren, damit auch das Programmende stimmt.
948
+ state = objdef.restzeitBereinigen(state);
891
949
  // Fuer die Einschalt-Erkennung: was stand vorher da?
892
950
  const statusVorher =
893
951
  'Status' in state ? ((await this.getStateAsync(`${deviceId}.state.status`)) || {}).val : null;
@@ -2164,19 +2222,41 @@ class MieleLocal extends utils.Adapter {
2164
2222
  this._histCreated[deviceId] = true;
2165
2223
  }
2166
2224
 
2225
+ /**
2226
+ * Abstand bis zur naechsten Abfrage EINES Geraets: kurz, solange es laeuft, sonst der Ruhetakt.
2227
+ *
2228
+ * Bis 0.3.45 galt der kurze Takt fuer ALLE Geraete, sobald eines lief - backte der Ofen,
2229
+ * wurden Wasch- und Spuelmaschine im Minutentakt mitgefragt, obwohl sie aus waren. Ein
2230
+ * XKM-Modul bedient nur eine Verbindung; jede ueberfluessige Frage nimmt der Miele-App
2231
+ * einen Zeitschlitz weg.
2232
+ *
2233
+ * @param dev Geraeteeintrag aus this.devices
2234
+ */
2235
+ abstandFuer(dev) {
2236
+ return takt.abstand(
2237
+ dev,
2238
+ MieleLocal.intervallMs(this.config.activePollInterval, 5, 1),
2239
+ MieleLocal.intervallMs(this.config.pollInterval, 15, 1),
2240
+ );
2241
+ }
2242
+
2167
2243
  schedulePoll(immediate = false) {
2168
2244
  if (this.pollTimer) {
2169
2245
  this.clearTimeout(this.pollTimer);
2170
2246
  }
2171
- const anyActive = Object.values(this.devices).some(d => d.active);
2172
- const interval = anyActive
2173
- ? MieleLocal.intervallMs(this.config.activePollInterval, 5, 1)
2174
- : MieleLocal.intervallMs(this.config.pollInterval, 15, 1);
2247
+ // Geweckt wird zur naechsten faelligen Abfrage irgendeines Geraets; pollAll(true) fragt
2248
+ // dann nur die faelligen. Ein Geraet ohne Termin (neu gefunden) ist sofort faellig.
2249
+ const interval = takt.weckAbstand(
2250
+ Object.values(this.devices),
2251
+ Date.now(),
2252
+ MieleLocal.intervallMs(this.config.pollInterval, 15, 1),
2253
+ );
2175
2254
  const run = async () => {
2176
2255
  if (this.stopping) {
2177
2256
  return;
2178
2257
  }
2179
- await this.pollAll();
2258
+ // Ein sofortiger Durchgang (nach einem Schreibbefehl) fragt alle, sonst nur die faelligen.
2259
+ await this.pollAll(!immediate);
2180
2260
  this.schedulePoll(false);
2181
2261
  };
2182
2262
  const nextDelayMs = immediate ? 100 : interval;
@@ -2286,7 +2366,7 @@ class MieleLocal extends utils.Adapter {
2286
2366
  * stauten sich am Modul. Jetzt gibt es nie zwei Laeufe derselben Aufgabe gleichzeitig.
2287
2367
  *
2288
2368
  * @param {string} name Feld, in dem der Timer steht (fuer clearTimeout in onUnload)
2289
- * @param {() => any} aufgabe die Aufgabe; darf ein Promise liefern
2369
+ * @param {() => (void | Promise<void>)} aufgabe die Aufgabe; darf ein Promise liefern
2290
2370
  * @param {number} abstandMs Pause zwischen Ende eines Laufs und Beginn des naechsten
2291
2371
  */
2292
2372
  schleife(name, aufgabe, abstandMs) {
@@ -3180,9 +3260,20 @@ class MieleLocal extends utils.Adapter {
3180
3260
  return 1500;
3181
3261
  }
3182
3262
 
3183
- async pollAll() {
3263
+ /**
3264
+ * Alle Geraete abfragen - oder nur die, deren Termin gekommen ist (siehe abstandFuer()).
3265
+ *
3266
+ * @param nurFaellige true: Geraete mit spaeterem Termin auslassen
3267
+ */
3268
+ async pollAll(nurFaellige = false) {
3184
3269
  let ok = false;
3270
+ const jetzt = Date.now();
3185
3271
  for (const [deviceId, dev] of Object.entries(this.devices)) {
3272
+ if (nurFaellige && !takt.istFaellig(dev, jetzt)) {
3273
+ // Nicht dran - fuer info.connection zaehlt sein letzter Stand.
3274
+ ok = ok || dev.erreichbar === true;
3275
+ continue;
3276
+ }
3186
3277
  // Zweiter Anlauf, bevor ein Abruf als Fehler gilt. Das XKM-Modul der Geraete legt im
3187
3278
  // laufenden Betrieb sporadisch auf ("read ECONNRESET") oder antwortet kurz mit 404 -
3188
3279
  // am 23.08.2026 an der laufenden Waschmaschine mit 21 % der Abrufe gemessen, waehrend
@@ -3215,6 +3306,29 @@ class MieleLocal extends utils.Adapter {
3215
3306
  if (fehler) {
3216
3307
  await this.setStateAsync(`${deviceId}.info.connected`, { val: false, ack: true });
3217
3308
  }
3309
+ /*
3310
+ * Den Wechsel einmal melden, nicht jeden Fehlversuch.
3311
+ *
3312
+ * Bis 0.3.46 stand ein Ausfall nur im debug-Log - auf Stufe info sah man nichts,
3313
+ * waehrend die Waschmaschine am 16.10.2026 nach einem WLAN-Ausfall stundenlang
3314
+ * verschwunden blieb. Jede Abfrage einzeln auf info zu heben waere das Gegenteil:
3315
+ * alle zwei Minuten dieselbe Zeile. Also: eine Zeile beim Wegfall, eine bei der
3316
+ * Rueckkehr, dazwischen nur debug.
3317
+ */
3318
+ if (fehler && dev.erreichbar !== false) {
3319
+ this.log.info(
3320
+ `${deviceId} (${dev.ip}) not reachable: ${fehler.message} - ` +
3321
+ `still trying every ${Math.round(this.abstandFuer(dev) / 1000)} s`,
3322
+ );
3323
+ dev.wegSeit = Date.now();
3324
+ } else if (!fehler && dev.erreichbar === false) {
3325
+ const min = dev.wegSeit ? Math.round((Date.now() - dev.wegSeit) / 60000) : null;
3326
+ this.log.info(`${deviceId} (${dev.ip}) reachable again${min !== null ? ` after ${min} min` : ''}`);
3327
+ dev.wegSeit = null;
3328
+ }
3329
+ dev.erreichbar = !fehler;
3330
+ // Erst nach applyState: dev.active ist dann auf dem Stand dieser Abfrage.
3331
+ dev.naechsteAbfrage = Date.now() + this.abstandFuer(dev);
3218
3332
  await this.verbucheAbfrage(deviceId, fehler, wiederholt && !fehler);
3219
3333
  }
3220
3334
  await this.setStateAsync('info.connection', { val: ok, ack: true });
@@ -4170,52 +4284,28 @@ class MieleLocal extends utils.Adapter {
4170
4284
  if (idx < 0) {
4171
4285
  return;
4172
4286
  }
4173
- const deviceId = parts[idx - 1];
4174
- const sub = parts[idx + 1];
4175
- const dev = this.devices[deviceId];
4176
- if (!dev) {
4177
- return;
4178
- }
4179
- if (!this.config.allowControl) {
4180
- this.log.warn('Device control is disabled in instance settings.');
4181
- return;
4182
- }
4183
- if (!state.val) {
4184
- return;
4185
- } // nur bei true auslösen
4186
-
4187
- const obj = await this.getObjectAsync(id);
4188
- const opcode = obj && obj.native ? obj.native.opcode : null;
4189
- if (opcode == null) {
4190
- return;
4191
- }
4192
-
4193
- try {
4194
- const payload = buildUserRequest(opcode);
4195
- const status = await dev.api.put(
4196
- `Devices/${dev.route}/DOP2/${USER_REQ_UNIT}/${USER_REQ_LEAF}?idx1=0&idx2=0`,
4197
- payload,
4198
- );
4199
- if (status === 200 || status === 204) {
4200
- this.log.info(`Command '${sub}' sent to ${deviceId} (opcode 0x${opcode.toString(16)}).`);
4201
- await this.setStateAsync(id, { val: false, ack: true });
4202
- // Polling kurz pausieren, dann gezielt den neuen Zustand holen.
4203
- this.pausePollUntil = Date.now() + 2500;
4204
- if (this.pollTimer) {
4205
- this.clearTimeout(this.pollTimer);
4206
- }
4207
- this.setTimeout(async () => {
4208
- await this.pollAll();
4209
- this.schedulePoll(false);
4210
- }, 2600);
4211
- } else {
4212
- this.log.warn(`Command '${sub}' to ${deviceId}: HTTP ${status} (is MobileStart enabled on device?).`);
4213
- }
4214
- } catch (e) {
4215
- this.log.warn(
4216
- `Command '${sub}' to ${deviceId} failed: ${e.message} (remote control might not be supported by firmware).`,
4217
- );
4218
- }
4287
+ await befehle.verarbeiten(
4288
+ this,
4289
+ { id, deviceId: parts[idx - 1], sub: parts[idx + 1], val: state.val },
4290
+ {
4291
+ senden: (dev, opcode) =>
4292
+ dev.api.put(
4293
+ `Devices/${dev.route}/DOP2/${USER_REQ_UNIT}/${USER_REQ_LEAF}?idx1=0&idx2=0`,
4294
+ buildUserRequest(opcode),
4295
+ ),
4296
+ nachErfolg: () => {
4297
+ // Polling kurz pausieren, dann gezielt den neuen Zustand holen.
4298
+ this.pausePollUntil = Date.now() + 2500;
4299
+ if (this.pollTimer) {
4300
+ this.clearTimeout(this.pollTimer);
4301
+ }
4302
+ this.setTimeout(async () => {
4303
+ await this.pollAll();
4304
+ this.schedulePoll(false);
4305
+ }, 2600);
4306
+ },
4307
+ },
4308
+ );
4219
4309
  }
4220
4310
 
4221
4311
  startPush() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.miele-local",
3
- "version": "0.3.45",
3
+ "version": "0.3.47",
4
4
  "description": "Connect modern Miele@Home appliances locally without the internet (MieleH256/DOP2).",
5
5
  "author": "Immanuel <github@freitag.online>",
6
6
  "license": "MIT",