iobroker.laundrylens 0.4.38 → 0.4.40

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
@@ -132,6 +132,16 @@ This also works inside the conditional `[...]` blocks: `[🌡️ Outside: {state
132
132
 
133
133
  ### **WORK IN PROGRESS**
134
134
 
135
+ ### 0.4.40 (2026-10-07)
136
+ - Fix: reported live (notification history showing a predicted finish time creeping from 12:30 to 14:06 over the course of a single wash cycle, while `cycleProgress` stayed roughly accurate - over an hour off by the end) - the predicted finish time could drift later and later with every update. Root cause: whenever the recent power trace's variance exceeds a threshold (e.g. a washer motor cycling on/off during agitation), the remaining-time estimate gets "locked" to the last trusted value, to avoid jumpy/unstable predictions from noisy readings. The locked value was a frozen millisecond *duration* though, which never shrank while locked - so the countdown effectively stopped entirely for as long as the noisy condition persisted, while real wall-clock time kept passing, making `now + remaining` (the predicted finish time) creep later and later
137
+ - Fixed by decrementing the locked value by the real elapsed time since it was last touched, instead of freezing it as a constant - it now keeps counting down correctly even while locked, while still avoiding jumpy re-estimates from noisy readings (the original intent)
138
+ - Covered by a new regression test extending `tests/test_time_estimate.js`
139
+
140
+ ### 0.4.39 (2026-10-06)
141
+ - Fix: reported live - `elapsedTime` stayed frozen (often at a stale value left over from a previous cycle) for the entire "detecting..." period of a new cycle, only starting to update once a program was actually confirmed (observed: stuck at 11342 while only ~15 minutes into a new washer cycle; for a dryer, jumped from frozen to 1096 the moment the program was recognized). Root cause: `elapsedTime` is just `Date.now()` minus the cycle's start time, with no real dependency on program detection - but the `onTimeUpdate` callback that wrote it (via `_onTime()`) returned early, skipping the write entirely, whenever `WashDataManager._updateTimeEstimate()` reported no program/bestCandidate yet
142
+ - Fixed by extracting the computation into a shared `_updateElapsedTime()` helper, now called unconditionally at the top of `onTimeUpdate` - before its "no program detected" early return - as well as from `_onTime()`'s normal path, so it updates from the moment a cycle starts running regardless of detection status
143
+ - Covered by a new regression test (`tests/test_elapsed_time_before_detection.js`)
144
+
135
145
  ### 0.4.38 (2026-10-04)
136
146
  - New: the Lernkontrolle (learning review/feedback) flow can now be driven from a VIS dashboard, not just the admin tab - previously only reachable via sendTo commands, invisible to VIS, which can only read/write data points
137
147
  - New read-only data points: `pendingFeedbackCount`, `pendingFeedback` (JSON array of all unconfirmed cycles, oldest first), and `feedbackCycleId`/`feedbackProgram`/`feedbackDuration`/`feedbackEnergy`/`feedbackConfidence` (convenience fields for the oldest pending cycle, so a simple VIS text widget doesn't need to parse JSON)
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "laundrylens",
4
- "version": "0.4.38",
4
+ "version": "0.4.40",
5
5
  "news": {
6
+ "0.4.40": {
7
+ "en": "Fix: the predicted finish time ('Fertig um HH:MM') could drift later and later with every notification over the course of a single cycle - observed over an hour off by the end of a wash. Root cause: whenever the recent power trace got too noisy (e.g. a washer's motor cycling on/off during agitation), the remaining-time estimate gets 'locked' to avoid jumpy predictions - but the locked value was a frozen duration that never shrank while locked, even though real time kept passing. The countdown effectively stopped while locked, so the predicted finish time (now + frozen remaining) crept later the longer the noisy condition persisted. Now decrements by the real elapsed time while locked, so it keeps counting down correctly.",
8
+ "de": "Fix: Die vorhergesagte Fertigstellungszeit ('Fertig um HH:MM') konnte sich mit jeder Benachrichtigung im Laufe eines Zyklus immer weiter nach hinten verschieben - beobachtet mit ueber einer Stunde Abweichung zum Ende eines Waschgangs. Ursache: Sobald die juengste Leistungsaufnahme zu unruhig wurde (z. B. ein Motor, der bei der Waschbewegung staendig an-/abschaltet), wird die Restzeitschaetzung 'gesperrt', um sprunghafte Vorhersagen zu vermeiden - der gesperrte Wert war aber eine eingefrorene Dauer, die waehrend der Sperre nie schrumpfte, obwohl real Zeit verging. Der Countdown blieb waehrend der Sperre praktisch stehen, wodurch die vorhergesagte Fertigstellungszeit (jetzt + eingefrorene Restzeit) immer spaeter rutschte, je laenger der unruhige Zustand anhielt. Zaehlt jetzt waehrend der Sperre um die tatsaechlich vergangene Zeit weiter herunter.",
9
+ "ru": "Исправление: прогнозируемое время завершения ('Готово в ЧЧ:ММ') могло сдвигаться всё дальше и дальше с каждым уведомлением в течение одного цикла - наблюдалось отклонение более часа к концу стирки. Причина: как только недавние показания мощности становились слишком шумными (например, мотор стиральной машины циклически включается/выключается при встряхивании), оценка оставшегося времени 'блокируется' во избежание скачкообразных прогнозов - но заблокированное значение было замороженной длительностью, которая не уменьшалась во время блокировки, хотя реальное время продолжало идти. Обратный отсчёт фактически останавливался во время блокировки, поэтому прогнозируемое время завершения (сейчас + замороженный остаток) сдвигалось всё позже, чем дольше сохранялось шумное состояние. Теперь во время блокировки значение уменьшается на реально прошедшее время.",
10
+ "pt": "Correcao: o horario previsto de conclusao ('Pronto as HH:MM') podia atrasar cada vez mais a cada notificacao ao longo de um ciclo - observado com mais de uma hora de desvio ao final de uma lavagem. Causa raiz: sempre que a leitura recente de potencia ficava muito ruidosa (ex. o motor da lavadora ligando/desligando durante a agitacao), a estimativa de tempo restante era 'travada' para evitar previsoes instaveis - mas o valor travado era uma duracao congelada que nunca diminuia enquanto travado, mesmo com o tempo real passando. A contagem regressiva praticamente parava enquanto travado, entao o horario previsto de conclusao (agora + restante congelado) ia atrasando cada vez mais quanto mais a condicao ruidosa persistisse. Agora diminui pelo tempo real decorrido enquanto travado.",
11
+ "nl": "Fix: de voorspelde eindtijd ('Klaar om HH:MM') kon met elke melding steeds verder opschuiven tijdens een cyclus - waargenomen met meer dan een uur afwijking tegen het einde van een wasbeurt. Oorzaak: zodra het recente vermogensverloop te ruizig werd (bijv. een wasmachinemotor die tijdens de beweging steeds aan/uit schakelt), wordt de resterende-tijdschatting 'vergrendeld' om schokkerige voorspellingen te voorkomen - maar de vergrendelde waarde was een bevroren duur die tijdens het vergrendelen nooit kromp, terwijl de echte tijd wel doorliep. Het aftellen stopte feitelijk tijdens het vergrendelen, waardoor de voorspelde eindtijd (nu + bevroren resterende tijd) steeds later opschoof naarmate de ruizige toestand langer aanhield. Telt nu tijdens het vergrendelen gewoon verder af met de echt verstreken tijd.",
12
+ "fr": "Correction : l'heure de fin predite ('Termine a HH:MM') pouvait se decaler de plus en plus a chaque notification au cours d'un cycle - ecart observe de plus d'une heure a la fin d'un lavage. Cause : des que les releves de puissance recents devenaient trop bruites (ex. le moteur d'un lave-linge qui s'allume/s'eteint sans cesse pendant le brassage), l'estimation du temps restant est 'verrouillee' pour eviter des previsions erratiques - mais la valeur verrouillee etait une duree figee qui ne diminuait jamais pendant le verrouillage, alors que le temps reel continuait de s'ecouler. Le compte a rebours s'arretait donc en pratique pendant le verrouillage, ce qui faisait reculer l'heure de fin predite (maintenant + temps restant fige) de plus en plus tant que l'etat bruite persistait. Decompte desormais correctement le temps reel ecoule pendant le verrouillage.",
13
+ "it": "Correzione: l'orario di fine previsto ('Pronto alle HH:MM') poteva slittare sempre piu avanti a ogni notifica nel corso di un ciclo - osservato uno scarto di oltre un'ora verso la fine di un lavaggio. Causa: non appena le letture di potenza recenti diventavano troppo rumorose (es. il motore della lavatrice che si accende/spegne continuamente durante l'agitazione), la stima del tempo rimanente viene 'bloccata' per evitare previsioni instabili - ma il valore bloccato era una durata congelata che non si riduceva mai durante il blocco, pur continuando a scorrere il tempo reale. Il conto alla rovescia si fermava di fatto durante il blocco, facendo slittare sempre piu avanti l'orario di fine previsto (ora + tempo rimanente congelato) quanto piu a lungo persisteva la condizione rumorosa. Ora si riduce in base al tempo reale trascorso durante il blocco.",
14
+ "es": "Correccion: la hora prevista de finalizacion ('Listo a las HH:MM') podia retrasarse cada vez mas con cada notificacion a lo largo de un ciclo - se observo una desviacion de mas de una hora al final de un lavado. Causa raiz: en cuanto las lecturas recientes de potencia se volvian demasiado ruidosas (p. ej. el motor de la lavadora encendiendose/apagandose durante la agitacion), la estimacion de tiempo restante se 'bloquea' para evitar predicciones erraticas - pero el valor bloqueado era una duracion congelada que nunca disminuia mientras estaba bloqueada, aunque el tiempo real seguia transcurriendo. La cuenta regresiva practicamente se detenia mientras estaba bloqueada, por lo que la hora prevista de finalizacion (ahora + tiempo restante congelado) se retrasaba cada vez mas cuanto mas persistia la condicion ruidosa. Ahora disminuye segun el tiempo real transcurrido mientras esta bloqueada.",
15
+ "pl": "Poprawka: przewidywany czas zakonczenia ('Gotowe o HH:MM') mogl przesuwac sie coraz dalej z kazdym powiadomieniem w trakcie cyklu - zaobserwowano odchylenie ponad godziny pod koniec prania. Przyczyna: gdy ostatnie odczyty mocy stawaly sie zbyt niestabilne (np. silnik pralki wlaczajacy/wylaczajacy sie podczas prania), szacowany pozostaly czas zostaje 'zablokowany', aby uniknac skokowych prognoz - ale zablokowana wartosc byla zamrozonym czasem trwania, ktory nigdy nie malal podczas blokady, mimo ze realny czas uplywal. Odliczanie w praktyce zatrzymywalo sie podczas blokady, przez co przewidywany czas zakonczenia (teraz + zamrozony pozostaly czas) przesuwal sie coraz pozniej, im dluzej utrzymywal sie niestabilny stan. Teraz podczas blokady odejmowany jest rzeczywiscie uplyniety czas.",
16
+ "uk": "Виправлення: прогнозований час завершення ('Готово о ГГ:ХХ') міг зсуватися все далі й далі з кожним сповіщенням протягом одного циклу - зафіксовано відхилення понад годину до кінця прання. Причина: щойно останні показання потужності ставали занадто шумними (наприклад, двигун пральної машини циклічно вмикається/вимикається під час перемішування), оцінка часу, що залишився, 'блокується', щоб уникнути стрибкоподібних прогнозів - але заблокероване значення було замороженою тривалістю, яка не зменшувалася під час блокування, хоча реальний час продовжував минати. Відлік фактично зупинявся під час блокування, через що прогнозований час завершення (зараз + заморожений залишок) зсувався все пізніше, чим довше тривав шумний стан. Тепер під час блокування значення зменшується на реально витрачений час.",
17
+ "zh-cn": "修复:预计完成时间(「将于 HH:MM 完成」)可能会随着单个周期内的每次通知而越来越晚——曾观察到在一次洗涤结束时偏差超过一小时。根本原因:一旦近期功率读数变得过于嘈杂(例如洗衣机电机在搅动过程中反复开关),剩余时间估算会被「锁定」以避免预测跳动——但锁定的值是一个冻结的时长,在锁定期间从不缩短,尽管现实时间仍在流逝。锁定期间倒计时实际上完全停止,因此嘈杂状态持续越久,预计完成时间(当前时间 + 冻结的剩余时间)就越往后推移。现在锁定期间会按实际经过的时间正确递减。"
18
+ },
19
+ "0.4.39": {
20
+ "en": "Fix: elapsedTime stayed frozen (often at a stale value left over from a previous cycle) for the entire \"detecting...\" period of a new cycle, only starting to update once a program was actually confirmed. elapsedTime is just Date.now() minus the cycle's start time though, with no real dependency on program detection - the callback that writes it was simply skipped entirely whenever no program had been recognized yet. Now updates from the moment a cycle starts running, regardless of detection status.",
21
+ "de": "Fix: elapsedTime blieb waehrend der gesamten \"detecting...\"-Phase eines neuen Zyklus eingefroren (oft auf einem veralteten Wert vom vorherigen Zyklus) und aktualisierte sich erst, sobald ein Programm tatsaechlich bestaetigt war. elapsedTime ist aber nur Date.now() minus Zyklusstartzeit, ohne echte Abhaengigkeit von der Programmerkennung - der Callback, der es schreibt, wurde schlicht komplett uebersprungen, solange noch kein Programm erkannt war. Aktualisiert sich jetzt ab dem Moment, in dem ein Zyklus zu laufen beginnt, unabhaengig vom Erkennungsstatus.",
22
+ "ru": "Исправление: elapsedTime оставался замороженным (часто на устаревшем значении с предыдущего цикла) на протяжении всего периода \"detecting...\" нового цикла и начинал обновляться только после фактического подтверждения программы. Однако elapsedTime - это просто Date.now() минус время начала цикла, без реальной зависимости от распознавания программы - колбэк, который его записывал, просто полностью пропускался, пока программа ещё не была распознана. Теперь обновляется с момента начала работы цикла, независимо от статуса распознавания.",
23
+ "pt": "Correcao: elapsedTime permanecia congelado (muitas vezes num valor obsoleto deixado do ciclo anterior) durante todo o periodo \"detecting...\" de um novo ciclo, so comecando a atualizar quando um programa era realmente confirmado. No entanto, elapsedTime e apenas Date.now() menos o horario de inicio do ciclo, sem dependencia real da deteccao de programa - o callback que o escrevia era simplesmente ignorado por completo enquanto nenhum programa tivesse sido reconhecido ainda. Agora atualiza a partir do momento em que um ciclo comeca a rodar, independente do status de deteccao.",
24
+ "nl": "Fix: elapsedTime bleef bevroren (vaak op een verouderde waarde van een vorige cyclus) gedurende de hele \"detecting...\"-periode van een nieuwe cyclus en werd pas bijgewerkt zodra een programma daadwerkelijk bevestigd was. elapsedTime is echter gewoon Date.now() min de startijd van de cyclus, zonder echte afhankelijkheid van programmaherkenning - de callback die het schreef werd simpelweg volledig overgeslagen zolang nog geen programma herkend was. Wordt nu bijgewerkt vanaf het moment dat een cyclus begint te lopen, ongeacht de herkenningsstatus.",
25
+ "fr": "Correction : elapsedTime restait fige (souvent sur une valeur obsolete laissee par un cycle precedent) pendant toute la periode \"detecting...\" d'un nouveau cycle, ne commencant a se mettre a jour qu'une fois un programme reellement confirme. Pourtant, elapsedTime n'est que Date.now() moins l'heure de debut du cycle, sans veritable dependance a la detection du programme - le callback qui l'ecrivait etait simplement totalement ignore tant qu'aucun programme n'avait encore ete reconnu. Se met desormais a jour des l'instant ou un cycle commence a tourner, quel que soit l'etat de detection.",
26
+ "it": "Correzione: elapsedTime restava bloccato (spesso su un valore obsoleto lasciato da un ciclo precedente) per l'intero periodo \"detecting...\" di un nuovo ciclo, iniziando ad aggiornarsi solo una volta che un programma veniva effettivamente confermato. Tuttavia elapsedTime e solo Date.now() meno l'orario di inizio del ciclo, senza una reale dipendenza dal rilevamento del programma - il callback che lo scriveva veniva semplicemente saltato del tutto finche non era stato riconosciuto alcun programma. Ora si aggiorna dal momento in cui un ciclo inizia a funzionare, indipendentemente dallo stato di rilevamento.",
27
+ "es": "Correccion: elapsedTime permanecia congelado (a menudo en un valor obsoleto de un ciclo anterior) durante todo el periodo \"detecting...\" de un nuevo ciclo, y solo empezaba a actualizarse una vez que se confirmaba realmente un programa. Sin embargo, elapsedTime es solo Date.now() menos la hora de inicio del ciclo, sin una dependencia real de la deteccion del programa - el callback que lo escribia simplemente se saltaba por completo mientras no se hubiera reconocido ningun programa todavia. Ahora se actualiza desde el momento en que un ciclo empieza a ejecutarse, independientemente del estado de deteccion.",
28
+ "pl": "Poprawka: elapsedTime pozostawal zamrozony (czesto na nieaktualnej wartosci pozostalej z poprzedniego cyklu) przez caly okres \"detecting...\" nowego cyklu i zaczynal sie aktualizowac dopiero po faktycznym potwierdzeniu programu. Jednak elapsedTime to po prostu Date.now() minus czas rozpoczecia cyklu, bez rzeczywistej zaleznosci od wykrywania programu - wywolanie zwrotne, ktore je zapisywalo, bylo po prostu calkowicie pomijane, dopoki zaden program nie zostal jeszcze rozpoznany. Teraz aktualizuje sie od momentu rozpoczecia dzialania cyklu, niezaleznie od statusu wykrywania.",
29
+ "uk": "Виправлення: elapsedTime залишався замороженим (часто на застарілому значенні з попереднього циклу) протягом усього періоду \"detecting...\" нового циклу і починав оновлюватися лише після фактичного підтвердження програми. Проте elapsedTime - це просто Date.now() мінус час початку циклу, без реальної залежності від розпізнавання програми - зворотний виклик, який його записував, просто повністю пропускався, доки програму ще не було розпізнано. Тепер оновлюється з моменту початку роботи циклу, незалежно від статусу розпізнавання.",
30
+ "zh-cn": "修复:在新周期的整个「detecting...」(检测中)阶段,elapsedTime 会一直冻结(通常停留在上一个周期遗留的过期值上),只有在程序被真正确认后才开始更新。然而 elapsedTime 其实只是 Date.now() 减去周期开始时间,与程序检测并无真正关联——写入它的回调函数在尚未识别出程序时会被完全跳过。现在从周期开始运行的那一刻起就会更新,与检测状态无关。"
31
+ },
6
32
  "0.4.38": {
7
33
  "en": "New: the Lernkontrolle (learning review/feedback) flow can now be driven from VIS, not just the admin tab. New read-only data points: pendingFeedbackCount, pendingFeedback (JSON list of all unconfirmed cycles, oldest first), and feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (convenience fields for the oldest one). New writable actions, each acting on the oldest pending cycle: feedbackConfirm (button, confirms it), feedbackCorrectProgram (dropdown of program names - writing one corrects and confirms), feedbackDelete (button, discards it without confirming).",
8
34
  "de": "Neu: Die Lernkontrolle kann jetzt auch aus VIS heraus bedient werden, nicht nur ueber den Admin-Tab. Neue schreibgeschuetzte Datenpunkte: pendingFeedbackCount, pendingFeedback (JSON-Liste aller unbestaetigten Zyklen, aelteste zuerst) sowie feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (Komfortfelder fuer den aeltesten). Neue schreibbare Aktionen, die jeweils auf den aeltesten offenen Zyklus wirken: feedbackConfirm (Button, bestaetigt ihn), feedbackCorrectProgram (Dropdown mit Programmnamen - Eintrag schreiben korrigiert und bestaetigt), feedbackDelete (Button, verwirft ihn ohne Bestaetigung).",
@@ -67,32 +93,6 @@
67
93
  "pl": "Poprawka: availablePrograms mógł pokazywać [], mimo że urządzenie wyraźnie miało zapisane programy (widoczne we własnej liście Programy zakładki administratora) - datapoint był zapisywany tylko przy określonych akcjach w zakładce administratora (utworzenie/usunięcie/zmiana nazwy programu, wyczyszczenie wszystkich danych), nigdy przy starcie ani po automatycznym nauczeniu się programu z potwierdzonego cyklu. Poprawka: needsFeedback pozostawał false, mimo że w zakładce Lernkontrolle administratora widoczny był cykl oczekujący na potwierdzenie - datapoint był zadeklarowany, ale nigdzie faktycznie nie był zapisywany; zakładka administratora obliczała własną liczbę oczekujących cykli całkowicie po stronie klienta. Oba są teraz synchronizowane przy starcie adaptera, po każdym zakończonym cyklu oraz (dla needsFeedback) po potwierdzeniu lub poprawieniu cyklu.",
68
94
  "uk": "Виправлення: availablePrograms міг показувати [], хоча пристрій явно мав збережені програми (видимі у власному списку «Програми» вкладки адміністратора) - датапоінт записувався лише при певних діях у вкладці адміністратора (створення/видалення/перейменування програми, очищення всіх даних), ніколи під час запуску або після автоматичного навчання програми з підтвердженого циклу. Виправлення: needsFeedback залишався false, хоча на вкладці «Контроль навчання» адміністратора було явно видно цикл, що очікує підтвердження - датапоінт був оголошений, але ніде фактично не записувався; вкладка адміністратора обчислювала власну кількість очікуючих циклів повністю на стороні клієнта. Обидва тепер синхронізуються під час запуску адаптера, після кожного завершеного циклу та (для needsFeedback) після підтвердження чи виправлення циклу.",
69
95
  "zh-cn": "修复:即使设备明显已保存程序(在管理标签页自己的「程序」列表中可见),availablePrograms 仍可能显示为 []——该数据点此前只在特定的管理标签页操作中写入(创建/删除/重命名程序、清除所有数据),从不在启动时或某个程序通过已确认的周期自动学习后写入。修复:即使管理标签页的「学习检查」标签页中明显有一个周期待确认,needsFeedback 仍保持 false——该数据点已声明但从未在任何地方真正写入过;管理标签页完全在客户端自行计算待处理数量。现在两者都会在适配器启动时、每次周期结束后,以及(对于 needsFeedback)在确认或更正某个周期后保持同步。"
70
- },
71
- "0.4.33": {
72
- "en": "Fix: a new cycle could show a leftover probable-program guess from the *previous*, already-finished cycle for several minutes (e.g. \"~30 Speed (76%)\" right at cycle start) - the internal best-candidate field wasn't cleared when a new cycle started. Fix: cycleProgress stayed stuck at its last value (often 100%) after a cycle finished instead of resetting to 0. Fix: an exception during cycle-finish post-processing (notably the post-hoc phase analysis for washing machines/dishwashers) could silently block the live status data points from updating. New option \"Show probable program before confirmation\": optionally surfaces the live best-candidate guess into the program/programText/confidence data points (prefixed with \"≈\"), not just in the admin tab's own preview. New option \"Text data points without emoji\": optionally omits the emoji from phaseText/stateText, for dashboards or text-to-speech.",
73
- "de": "Fix: ein neuer Zyklus konnte minutenlang eine Restvermutung des *vorherigen*, bereits beendeten Zyklus anzeigen (z. B. \"~30 Speed (76%)\" direkt beim Start) - das interne Bestkandidat-Feld wurde beim Start eines neuen Zyklus nicht zurückgesetzt. Fix: cycleProgress blieb nach Zyklusende auf dem letzten Wert (oft 100%) hängen, statt auf 0 zurückgesetzt zu werden. Fix: eine Exception bei der Nachbearbeitung am Zyklusende (insbesondere die Post-hoc-Phasenanalyse für Waschmaschine/Spülmaschine) konnte die Aktualisierung der Live-Status-Datenpunkte stillschweigend blockieren. Neue Option \"Wahrscheinliches Programm vor Bestätigung anzeigen\": zeigt optional die Live-Bestkandidat-Vermutung in den Datenpunkten program/programText/confidence an (mit \"≈\"-Präfix), nicht nur in der Admin-Tab-eigenen Vorschau. Neue Option \"Text-Datenpunkte ohne Emoji\": lässt optional das Emoji in phaseText/stateText weg, für Dashboards oder Sprachausgabe.",
74
- "ru": "Исправление: новый цикл мог несколько минут показывать остаточную вероятную программу от *предыдущего*, уже завершённого цикла (например, \"~30 Speed (76%)\" прямо в начале цикла) - внутреннее поле лучшего кандидата не сбрасывалось при старте нового цикла. Исправление: cycleProgress оставался на последнем значении (часто 100%) после завершения цикла вместо сброса на 0. Исправление: исключение при постобработке в конце цикла (в частности, постфактум-анализ фаз для стиральных/посудомоечных машин) могло незаметно блокировать обновление датапоинтов живого статуса. Новая опция \"Показывать вероятную программу до подтверждения\": опционально выводит текущую догадку лучшего кандидата в датапоинты program/programText/confidence (с префиксом \"≈\"), а не только в собственном превью вкладки администратора. Новая опция \"Текстовые датапоинты без эмодзи\": опционально убирает эмодзи из phaseText/stateText для дашбордов или синтеза речи.",
75
- "pt": "Correção: um novo ciclo podia mostrar por vários minutos um palpite de programa provável restante do ciclo *anterior*, já finalizado (ex. \"~30 Speed (76%)\" logo no início do ciclo) - o campo interno de melhor candidato não era limpo ao iniciar um novo ciclo. Correção: cycleProgress ficava preso no último valor (frequentemente 100%) após o fim de um ciclo, em vez de voltar a 0. Correção: uma exceção durante o pós-processamento de fim de ciclo (notadamente a análise pós-fato de fases para máquinas de lavar/lava-louças) podia bloquear silenciosamente a atualização dos data points de status ao vivo. Nova opção \"Mostrar programa provável antes da confirmação\": exibe opcionalmente o palpite de melhor candidato ao vivo nos data points program/programText/confidence (prefixado com \"≈\"), não apenas na prévia da própria aba de administração. Nova opção \"Data points de texto sem emoji\": omite opcionalmente o emoji de phaseText/stateText, para painéis ou texto para fala.",
76
- "nl": "Fix: een nieuwe cyclus kon enkele minuten lang een overgebleven waarschijnlijk-programma-gok van de *vorige*, al afgeronde cyclus tonen (bijv. \"~30 Speed (76%)\" meteen bij de start) - het interne beste-kandidaat-veld werd niet gewist bij de start van een nieuwe cyclus. Fix: cycleProgress bleef na afloop van een cyclus op de laatste waarde (vaak 100%) hangen in plaats van terug te gaan naar 0. Fix: een uitzondering tijdens de nabewerking aan het einde van een cyclus (met name de post-hoc fase-analyse voor wasmachines/vaatwassers) kon de update van de live-statusdatapoints stilzwijgend blokkeren. Nieuwe optie \"Waarschijnlijk programma tonen voor bevestiging\": toont optioneel de live beste-kandidaat-gok in de datapoints program/programText/confidence (met \"≈\"-voorvoegsel), niet alleen in de eigen preview van het admin-tabblad. Nieuwe optie \"Tekst-datapoints zonder emoji\": laat optioneel de emoji weg uit phaseText/stateText, voor dashboards of tekst-naar-spraak.",
77
- "fr": "Correction : un nouveau cycle pouvait afficher pendant plusieurs minutes une estimation de programme probable restante du cycle *précédent*, déjà terminé (ex. \"~30 Speed (76%)\" dès le début du cycle) - le champ interne de meilleur candidat n'était pas réinitialisé au démarrage d'un nouveau cycle. Correction : cycleProgress restait bloqué sur sa dernière valeur (souvent 100 %) après la fin d'un cycle au lieu de revenir à 0. Correction : une exception pendant le post-traitement de fin de cycle (notamment l'analyse a posteriori des phases pour lave-linge/lave-vaisselle) pouvait bloquer silencieusement la mise à jour des points de données de statut en direct. Nouvelle option \"Afficher le programme probable avant confirmation\" : affiche en option l'estimation du meilleur candidat en direct dans les points de données program/programText/confidence (précédée de \"≈\"), pas seulement dans l'aperçu propre à l'onglet admin. Nouvelle option \"Points de données texte sans emoji\" : omet en option l'emoji de phaseText/stateText, pour les tableaux de bord ou la synthèse vocale.",
78
- "it": "Correzione: un nuovo ciclo poteva mostrare per diversi minuti una stima di programma probabile residua del ciclo *precedente*, già terminato (es. \"~30 Speed (76%)\" subito all'inizio del ciclo) - il campo interno del miglior candidato non veniva azzerato all'avvio di un nuovo ciclo. Correzione: cycleProgress rimaneva bloccato sull'ultimo valore (spesso 100%) dopo la fine di un ciclo invece di tornare a 0. Correzione: un'eccezione durante la post-elaborazione di fine ciclo (in particolare l'analisi post-hoc delle fasi per lavatrici/lavastoviglie) poteva bloccare silenziosamente l'aggiornamento dei data point di stato live. Nuova opzione \"Mostra il programma probabile prima della conferma\": mostra opzionalmente la stima del miglior candidato live nei data point program/programText/confidence (con prefisso \"≈\"), non solo nell'anteprima della scheda admin. Nuova opzione \"Data point di testo senza emoji\": omette opzionalmente l'emoji da phaseText/stateText, per dashboard o sintesi vocale.",
79
- "es": "Corrección: un nuevo ciclo podía mostrar durante varios minutos una suposición de programa probable sobrante del ciclo *anterior*, ya finalizado (p. ej. \"~30 Speed (76%)\" justo al inicio del ciclo) - el campo interno de mejor candidato no se reiniciaba al comenzar un nuevo ciclo. Corrección: cycleProgress se quedaba atascado en su último valor (a menudo 100%) tras finalizar un ciclo, en lugar de volver a 0. Corrección: una excepción durante el post-procesamiento al final del ciclo (en particular el análisis posterior de fases para lavadoras/lavavajillas) podía bloquear silenciosamente la actualización de los data points de estado en vivo. Nueva opción \"Mostrar programa probable antes de la confirmación\": muestra opcionalmente la suposición en vivo del mejor candidato en los data points program/programText/confidence (con prefijo \"≈\"), no solo en la vista previa propia de la pestaña de administración. Nueva opción \"Data points de texto sin emoji\": omite opcionalmente el emoji de phaseText/stateText, para paneles o texto a voz.",
80
- "pl": "Poprawka: nowy cykl mógł przez kilka minut pokazywać pozostałość przypuszczenia programu z *poprzedniego*, już zakończonego cyklu (np. \"~30 Speed (76%)\" zaraz na starcie cyklu) - wewnętrzne pole najlepszego kandydata nie było czyszczone przy starcie nowego cyklu. Poprawka: cycleProgress pozostawał na ostatniej wartości (często 100%) po zakończeniu cyklu zamiast wrócić do 0. Poprawka: wyjątek podczas przetwarzania końcowego cyklu (zwłaszcza analiza post-hoc faz dla pralek/zmywarek) mógł po cichu blokować aktualizację datapointów statusu na żywo. Nowa opcja \"Pokaż prawdopodobny program przed potwierdzeniem\": opcjonalnie pokazuje bieżące przypuszczenie najlepszego kandydata w datapointach program/programText/confidence (z przedrostkiem \"≈\"), nie tylko we własnym podglądzie zakładki administratora. Nowa opcja \"Datapointy tekstowe bez emoji\": opcjonalnie pomija emoji w phaseText/stateText, na potrzeby dashboardów lub syntezy mowy.",
81
- "uk": "Виправлення: новий цикл міг кілька хвилин показувати залишкове припущення ймовірної програми з *попереднього*, уже завершеного циклу (наприклад, \"~30 Speed (76%)\" одразу на старті циклу) - внутрішнє поле найкращого кандидата не скидалося під час старту нового циклу. Виправлення: cycleProgress залишався на останньому значенні (часто 100%) після завершення циклу замість скидання до 0. Виправлення: виняток під час постобробки наприкінці циклу (зокрема постфактум-аналіз фаз для пральних/посудомийних машин) міг непомітно блокувати оновлення датапоінтів живого статусу. Нова опція \"Показувати ймовірну програму до підтвердження\": опціонально виводить поточне припущення найкращого кандидата в датапоінти program/programText/confidence (з префіксом \"≈\"), а не лише у власному прев'ю вкладки адміністратора. Нова опція \"Текстові датапоінти без емодзі\": опціонально прибирає емодзі з phaseText/stateText для дашбордів або синтезу мовлення.",
82
- "zh-cn": "修复:新周期可能会在最初几分钟内显示来自*上一个*已结束周期残留的可能程序猜测(例如周期刚开始就显示「~30 Speed (76%)」)——内部的最佳候选字段在新周期开始时未被清空。修复:周期结束后 cycleProgress 停留在最后的值(通常是 100%)而不会重置为 0。修复:周期结束后处理过程中的异常(尤其是洗衣机/洗碗机的事后阶段分析)可能会悄悄阻止实时状态数据点的更新。新增选项「确认前显示可能的程序」:可选择将实时最佳候选猜测显示到 program/programText/confidence 数据点中(带「≈」前缀),而不仅仅是管理标签页自身的预览。新增选项「文本数据点不含表情符号」:可选择在 phaseText/stateText 中省略表情符号,便于仪表盘或文本转语音使用。"
83
- },
84
- "0.4.32": {
85
- "en": "Follow-up fix to 0.4.31: a restart alone did not clear an already-stale state data point (e.g. stuck on \"Running\"), even with 0.4.31 installed. onReady() only re-writes the state/stateText/running/program/phase data points when a restore branch fires (sensor currently active, or a saved cycle needs resuming/finishing) - with neither true, the common case, nothing touched them at all. Now every device's startup unconditionally re-syncs these data points with the manager's actual resolved state, so a restart always clears any stale value immediately, regardless of cause.",
86
- "de": "Folgefix zu 0.4.31: ein reiner Neustart raeumte einen bereits haengengebliebenen Status-Datenpunkt (z.B. festgefahren auf \"Running\") nicht auf, selbst mit installiertem 0.4.31. onReady() schrieb die Datenpunkte state/stateText/running/program/phase bisher nur neu, wenn ein Restore-Zweig griff (Sensor aktuell aktiv, oder ein gespeicherter Zyklus muss fortgesetzt/beendet werden) - traf keins von beidem zu, der Normalfall, wurde nichts angefasst. Jetzt synchronisiert jeder Geraete-Start diese Datenpunkte bedingungslos mit dem tatsaechlichen, aufgeloesten Zustand des Managers, sodass ein Neustart jeden haengengebliebenen Wert sofort beseitigt, unabhaengig von der Ursache.",
87
- "ru": "Дополнительное исправление к 0.4.31: одна лишь перезагрузка не устраняла уже зависший датапоинт состояния (например, застрявший на \"Running\"), даже при установленном 0.4.31. onReady() перезаписывал датапоинты state/stateText/running/program/phase только при срабатывании ветки восстановления (датчик сейчас активен либо сохранённый цикл нужно возобновить/завершить) - если ни то, ни другое не верно (обычный случай), ничего не менялось. Теперь при каждом запуске устройства эти датапоинты безусловно синхронизируются с фактическим состоянием менеджера, поэтому перезапуск всегда немедленно устраняет любое зависшее значение, независимо от причины.",
88
- "pt": "Correcao de acompanhamento a 0.4.29: reiniciar sozinho nao limpava um data point de estado ja obsoleto (por exemplo, preso em \"Running\"), mesmo com a 0.4.31 instalada. O onReady() so reescrevia os data points state/stateText/running/program/phase quando um ramo de restauracao era acionado (sensor atualmente ativo, ou um ciclo salvo precisa ser retomado/finalizado) - sem nenhum dos dois, o caso comum, nada era alterado. Agora a inicializacao de cada dispositivo sincroniza incondicionalmente esses data points com o estado real resolvido do gerenciador, entao reiniciar sempre limpa qualquer valor obsoleto imediatamente, independente da causa.",
89
- "nl": "Vervolgfix op 0.4.31: een herstart alleen wiste een reeds verouderd state-datapoint (bijv. vastgelopen op \"Running\") niet, zelfs met 0.4.31 geinstalleerd. onReady() herschreef de datapoints state/stateText/running/program/phase alleen wanneer een herstel-tak werd geactiveerd (sensor momenteel actief, of een opgeslagen cyclus moet worden hervat/afgerond) - was geen van beide waar, het normale geval, dan werd er niets aangepast. Nu synchroniseert elke apparaatstart deze datapoints onvoorwaardelijk met de daadwerkelijk vastgestelde status van de manager, zodat een herstart elke verouderde waarde altijd meteen opheldert, ongeacht de oorzaak.",
90
- "fr": "Correction complementaire a 0.4.31 : un simple redemarrage ne nettoyait pas un data point d'etat deja obsolete (par ex. bloque sur \"Running\"), meme avec la 0.4.31 installee. onReady() ne reecrivait les data points state/stateText/running/program/phase que lorsqu'une branche de restauration se declenchait (capteur actuellement actif, ou un cycle enregistre doit etre repris/termine) - si aucun des deux n'etait vrai, le cas courant, rien n'etait modifie. Desormais, chaque demarrage d'appareil resynchronise inconditionnellement ces data points avec l'etat reel resolu du gestionnaire, de sorte qu'un redemarrage efface toujours immediatement toute valeur obsolete, quelle qu'en soit la cause.",
91
- "it": "Correzione di follow-up alla 0.4.31: un semplice riavvio non ripuliva un data point di stato gia bloccato (ad es. fermo su \"Running\"), anche con la 0.4.31 installata. onReady() riscriveva i data point state/stateText/running/program/phase solo quando si attivava un ramo di ripristino (sensore attualmente attivo, oppure un ciclo salvato deve essere ripreso/terminato) - se nessuno dei due era vero, il caso comune, non veniva modificato nulla. Ora l'avvio di ogni dispositivo risincronizza incondizionatamente questi data point con lo stato effettivo risolto del manager, cosi un riavvio ripulisce sempre immediatamente qualsiasi valore bloccato, indipendentemente dalla causa.",
92
- "es": "Correccion de seguimiento a 0.4.31: reiniciar por si solo no limpiaba un data point de estado ya obsoleto (por ejemplo, atascado en \"Running\"), incluso con 0.4.31 instalado. onReady() solo reescribia los data points state/stateText/running/program/phase cuando se activaba una rama de restauracion (sensor actualmente activo, o un ciclo guardado necesita reanudarse/finalizarse) - si ninguno de los dos era cierto, el caso comun, no se tocaba nada. Ahora el arranque de cada dispositivo resincroniza incondicionalmente estos data points con el estado real resuelto del gestor, por lo que reiniciar siempre limpia de inmediato cualquier valor atascado, sin importar la causa.",
93
- "pl": "Poprawka uzupelniajaca do 0.4.31: samo ponowne uruchomienie nie czyscilo juz nieaktualnego datapointu stanu (np. utknietego na \"Running\"), nawet po zainstalowaniu 0.4.31. onReady() ponownie zapisywal datapointy state/stateText/running/program/phase tylko wtedy, gdy uruchamiala sie galaz przywracania (czujnik aktualnie aktywny lub zapisany cykl wymaga wznowienia/zakonczenia) - jesli zadne z tych warunkow nie bylo spelnione, czyli w typowym przypadku, nic nie bylo zmieniane. Teraz uruchomienie kazdego urzadzenia bezwarunkowo synchronizuje te datapointy z faktycznym, rozwiazanym stanem menedzera, dzieki czemu ponowne uruchomienie zawsze natychmiast czysci kazda utkniete wartosc, niezaleznie od przyczyny.",
94
- "uk": "Додаткове виправлення до 0.4.31: сама лише перезагрузка не очищала вже застарілий датапоінт стану (наприклад, застряглий на \"Running\"), навіть із встановленим 0.4.31. onReady() перезаписував датапоінти state/stateText/running/program/phase лише коли спрацьовувала гілка відновлення (датчик наразі активний, або збережений цикл потрібно відновити/завершити) - якщо жодне з цього не було правдою, звичайний випадок, нічого не змінювалося. Тепер запуск кожного пристрою безумовно синхронізує ці датапоінти з фактичним, визначеним станом менеджера, тому перезапуск завжди негайно очищує будь-яке застарile значення, незалежно від причини.",
95
- "zh-cn": "0.4.31 的后续修复:即使安装了 0.4.31,仅重启也无法清除已经过期的状态数据点(例如卡在「Running」)。此前 onReady() 只有在触发恢复分支时(传感器当前处于活动状态,或需要恢复/结束已保存的周期)才会重写 state/stateText/running/program/phase 数据点——如果两者都不成立(这是常见情况),则完全不做任何更改。现在每台设备启动时都会无条件将这些数据点与管理器实际解析出的状态同步,因此重启总能立即清除任何过期的值,无论原因是什么。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -72,6 +72,7 @@ class WashDataManager {
72
72
  this._peakConfidence = 0;
73
73
  this._programLocked = false;
74
74
  this._lockedRemaining = null;
75
+ this._lockedRemainingTs = null;
75
76
 
76
77
  this._progressResetTimer = null;
77
78
  this._lastPowerVal = null;
@@ -529,6 +530,7 @@ class WashDataManager {
529
530
  this._peakConfidence = 0;
530
531
  this._programLocked = false;
531
532
  this._lockedRemaining = null;
533
+ this._lockedRemainingTs = null;
532
534
  this._dryerDropTriggered = false;
533
535
  this._dryerHighStart = null;
534
536
  this._bestCandidate = null;
@@ -901,6 +903,7 @@ class WashDataManager {
901
903
  this._programLocked = false;
902
904
  this._unmatchCount = 0;
903
905
  this._lockedRemaining = null;
906
+ this._lockedRemainingTs = null;
904
907
  if (typeof this.callbacks.onProgramChange === "function") {
905
908
  this.callbacks.onProgramChange("detecting...", 0);
906
909
  }
@@ -1061,10 +1064,27 @@ class WashDataManager {
1061
1064
 
1062
1065
  let remainingMs;
1063
1066
  if (variance > VARIANCE_LOCK_W && this._lockedRemaining !== null) {
1064
- remainingMs = this._lockedRemaining;
1067
+ // Noisy readings mean the freshly blended estimate isn't trusted
1068
+ // this round, but real time still passes regardless - decrement
1069
+ // the locked value by exactly how much time has elapsed since it
1070
+ // was last touched, rather than freezing it as a constant
1071
+ // duration. Previously this stayed a fixed number forever while
1072
+ // locked, so "remaining" (and therefore the predicted finish
1073
+ // time shown to the user) stopped counting down entirely for as
1074
+ // long as the variance stayed high - observed live as a washer's
1075
+ // predicted finish time drifting later with every update,
1076
+ // eventually over an hour off, during a long noisy (e.g.
1077
+ // agitation-heavy) stretch of a cycle.
1078
+ const sinceLockMs = this._lockedRemainingTs
1079
+ ? now - this._lockedRemainingTs
1080
+ : 0;
1081
+ remainingMs = Math.max(0, this._lockedRemaining - sinceLockMs);
1082
+ this._lockedRemaining = remainingMs;
1083
+ this._lockedRemainingTs = now;
1065
1084
  } else {
1066
1085
  remainingMs = Math.max(0, blendedRemainingMs);
1067
1086
  this._lockedRemaining = remainingMs;
1087
+ this._lockedRemainingTs = now;
1068
1088
  }
1069
1089
 
1070
1090
  // Recompute progress from the SAME blended remaining-time estimate,
@@ -1265,6 +1285,7 @@ class WashDataManager {
1265
1285
  this._unmatchCount = 0;
1266
1286
  this._peakConfidence = 0;
1267
1287
  this._lockedRemaining = null;
1288
+ this._lockedRemainingTs = null;
1268
1289
 
1269
1290
  this.adapter.log.info(
1270
1291
  `${this._name} ${cycleStatus}: ${cycle.matchedProfile}, ` +
package/main.js CHANGED
@@ -347,6 +347,7 @@ class WashdataAdapter extends utils.Adapter {
347
347
  onProgramChange: (program, confidence) =>
348
348
  this._onProgram(deviceCfg.deviceId, program, confidence),
349
349
  onTimeUpdate: (remaining, total, pct) => {
350
+ this._updateElapsedTime(deviceCfg.deviceId);
350
351
  if (remaining === null) {
351
352
  // No program detected – reset remaining time and progress
352
353
  this.setState(`${deviceCfg.deviceId}.timeRemaining`, 0, true);
@@ -1711,6 +1712,22 @@ class WashdataAdapter extends utils.Adapter {
1711
1712
  }
1712
1713
  }
1713
1714
 
1715
+ // elapsedTime is purely Date.now() - cycleStartTime - meaningful and
1716
+ // available from the moment RUNNING starts, with no actual dependency
1717
+ // on whether a program has been detected yet. Called both from
1718
+ // onTimeUpdate()'s "no program detected" early-return branch and from
1719
+ // _onTime()'s normal path, so it keeps ticking up throughout the
1720
+ // entire "detecting..." period instead of only once a program is
1721
+ // confirmed - found live (reported stuck at a stale value, e.g. left
1722
+ // over from a previous, longer cycle, until recognition caught up).
1723
+ _updateElapsedTime(deviceId) {
1724
+ const mgr = this.managers[deviceId];
1725
+ if (mgr && mgr.cycleStartTime) {
1726
+ const elapsedSec = Math.round((Date.now() - mgr.cycleStartTime) / 1000);
1727
+ this.setState(`${deviceId}.elapsedTime`, elapsedSec, true);
1728
+ }
1729
+ }
1730
+
1714
1731
  _onTime(
1715
1732
  deviceId,
1716
1733
  remainingSeconds,
@@ -1771,12 +1788,7 @@ class WashdataAdapter extends utils.Adapter {
1771
1788
  ).catch(() => {});
1772
1789
  }
1773
1790
  this.setState(`${deviceId}.timeRemaining`, remainingSeconds ?? 0, true);
1774
- // Elapsed time from the manager
1775
- const mgr2 = this.managers[deviceId];
1776
- if (mgr2 && mgr2.cycleStartTime) {
1777
- const elapsedSec = Math.round((Date.now() - mgr2.cycleStartTime) / 1000);
1778
- this.setState(`${deviceId}.elapsedTime`, elapsedSec, true);
1779
- }
1791
+ this._updateElapsedTime(deviceId);
1780
1792
  this.setState(`${deviceId}.totalDuration`, totalSeconds ?? 0, true);
1781
1793
  // Only overwrite progress if > 0 (prevents a reset if the profile
1782
1794
  // briefly doesn't match) - UNLESS forceWrite is set, which is how
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.laundrylens",
3
- "version": "0.4.38",
3
+ "version": "0.4.40",
4
4
  "description": "ioBroker adapter that detects washing machine and dryer cycles via smart plug power measurement, matches them against learned programs and estimates remaining time",
5
5
  "author": {
6
6
  "name": "backfisch88",