iobroker.laundrylens 0.4.39 → 0.4.41

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.41 (2026-10-08)
136
+ - Fix: reported live - a cycle running noticeably longer than its historical average plateaued at ~99% progress / near-zero remaining time for a long stretch instead of correcting (a wash cycle showed 98% progress/2min remaining a full 34 minutes before it actually finished - confirmed from the debug log: still well inside an overrunning rinse phase, with spinning not even starting until 29 minutes later). Root cause: once the plain duration-average estimate (or, within a phase, that phase's typical duration) runs out, it has nothing more to contribute - but the live energy-based estimate (which tracks this specific run's actual pace, not a historical average) was still only partially trusted, as little as 30% weight for a consistent/low-CV profile
137
+ - Fixed by fully trusting the energy-based estimate once the whole-cycle average has run out (no phase data available), and leaning much more heavily on it (30/70 instead of 70/30) once the current phase has overrun its own typical duration - so an unusually long run corrects toward reality instead of sitting stuck near 0% remaining
138
+ - Covered by two new regression tests extending `tests/test_time_estimate.js` (one per branch)
139
+
140
+ ### 0.4.40 (2026-10-07)
141
+ - 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
142
+ - 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)
143
+ - Covered by a new regression test extending `tests/test_time_estimate.js`
144
+
135
145
  ### 0.4.39 (2026-10-06)
136
146
  - 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
137
147
  - 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
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "laundrylens",
4
- "version": "0.4.39",
4
+ "version": "0.4.41",
5
5
  "news": {
6
+ "0.4.41": {
7
+ "en": "Fix: a cycle running noticeably longer than its historical average plateaued at ~99% progress / near-zero remaining time for a long stretch instead of correcting - reported live: a wash cycle showed 98% progress/2min remaining a full 34 minutes before it actually finished. Root cause: once the plain duration-average estimate (or, within a phase, that phase's typical duration) has run out, it has nothing more to contribute, but the live energy-based estimate (which tracks this specific run's actual pace) was still only partially trusted - as little as 30% weight for a consistent profile. Now fully (or much more heavily, in the per-phase case) trusted once the average-based estimate has nothing left to say, so the prediction corrects toward reality instead of sitting stuck near 0.",
8
+ "de": "Fix: Ein Zyklus, der merklich laenger lief als sein historischer Durchschnitt, blieb lange bei ~99% Fortschritt bzw. nahezu null Restzeit haengen, statt sich zu korrigieren - live gemeldet: ein Waschgang zeigte 98% Fortschritt/2min Restzeit ganze 34 Minuten vor dem tatsaechlichen Ende. Ursache: Sobald die reine Durchschnittsdauer-Schaetzung (bzw. innerhalb einer Phase deren typische Dauer) aufgebraucht ist, hat sie nichts mehr beizutragen - die live-energiebasierte Schaetzung (die das tatsaechliche Tempo dieses konkreten Laufs verfolgt) wurde aber weiterhin nur teilweise vertraut, bei einem konsistenten Profil nur mit bis zu 30% Gewicht. Wird jetzt voll (bzw. im Phasen-Fall deutlich staerker) vertraut, sobald die durchschnittsbasierte Schaetzung nichts mehr zu sagen hat, sodass sich die Vorhersage zur Realitaet hin korrigiert statt nahe 0 haengen zu bleiben.",
9
+ "ru": "Исправление: цикл, работавший заметно дольше своего исторического среднего, долго оставался на ~99% прогресса / почти нулевом оставшемся времени вместо коррекции - сообщено в реальном использовании: цикл стирки показывал 98% прогресса/2 мин до конца за целых 34 минуты до фактического завершения. Причина: как только простая оценка на основе средней длительности (или, внутри фазы, типичной длительности этой фазы) исчерпывается, ей больше нечего предложить, но живой оценке на основе энергопотребления (которая отслеживает реальный темп именно этого запуска) по-прежнему доверяли лишь частично - вплоть до 30% веса для стабильного профиля. Теперь ей доверяют полностью (или значительно сильнее, в случае отдельной фазы), как только оценке на основе среднего больше нечего сказать, так что прогноз корректируется в сторону реальности, а не застревает около 0.",
10
+ "pt": "Correcao: um ciclo que rodava visivelmente mais tempo que sua media historica estabilizava em ~99% de progresso / tempo restante quase zero por um longo periodo em vez de se corrigir - relatado ao vivo: um ciclo de lavagem mostrou 98% de progresso/2min restantes 34 minutos inteiros antes de realmente terminar. Causa raiz: assim que a estimativa pela media de duracao simples (ou, dentro de uma fase, a duracao tipica dessa fase) se esgota, ela nao tem mais nada a contribuir, mas a estimativa ao vivo baseada em energia (que acompanha o ritmo real desta execucao especifica) ainda era apenas parcialmente confiavel - ate apenas 30% de peso para um perfil consistente. Agora e totalmente (ou muito mais, no caso por fase) confiavel assim que a estimativa baseada na media nao tem mais nada a dizer, entao a previsao se corrige em direcao a realidade em vez de ficar presa perto de 0.",
11
+ "nl": "Fix: een cyclus die merkbaar langer liep dan zijn historische gemiddelde bleef lange tijd steken op ~99% voortgang / bijna nul resterende tijd in plaats van te corrigeren - live gemeld: een wascyclus toonde 98% voortgang/2min resterend een volle 34 minuten voordat hij daadwerkelijk klaar was. Oorzaak: zodra de simpele gemiddelde-duurschatting (of, binnen een fase, die fase se typische duur) is opgebruikt, heeft die niets meer bij te dragen, maar de live energiegebaseerde schatting (die het werkelijke tempo van deze specifieke run volgt) werd nog steeds maar gedeeltelijk vertrouwd - tot slechts 30% gewicht voor een consistent profiel. Wordt nu volledig (of veel sterker, in het geval per fase) vertrouwd zodra de gemiddelde-gebaseerde schatting niets meer te zeggen heeft, zodat de voorspelling richting de realiteit corrigeert in plaats van vast te blijven zitten bij 0.",
12
+ "fr": "Correction : un cycle fonctionnant nettement plus longtemps que sa moyenne historique restait bloque a ~99% de progression / temps restant quasi nul pendant une longue periode au lieu de se corriger - signale en direct : un cycle de lavage affichait 98% de progression/2min restantes 34 minutes entieres avant de reellement se terminer. Cause : des que l'estimation par simple moyenne de duree (ou, au sein d'une phase, la duree typique de cette phase) est epuisee, elle n'a plus rien a apporter, mais l'estimation en direct basee sur l'energie (qui suit le rythme reel de cette execution precise) n'etait encore que partiellement fiable - jusqu'a seulement 30% de poids pour un profil coherent. Desormais pleinement (ou bien plus fortement, dans le cas par phase) fiable des que l'estimation basee sur la moyenne n'a plus rien a dire, afin que la prediction se corrige vers la realite plutot que de rester bloquee pres de 0.",
13
+ "it": "Correzione: un ciclo che girava notevolmente piu a lungo della sua media storica si stabilizzava su ~99% di avanzamento / tempo residuo quasi nullo per un lungo periodo invece di correggersi - segnalato dal vivo: un ciclo di lavaggio mostrava 98% di avanzamento/2min residui ben 34 minuti prima di terminare realmente. Causa: non appena la semplice stima basata sulla durata media (o, all'interno di una fase, la durata tipica di quella fase) si esaurisce, non ha piu nulla da contribuire, ma la stima live basata sull'energia (che segue il ritmo reale di questa specifica esecuzione) era ancora solo parzialmente attendibile - fino a solo il 30% di peso per un profilo coerente. Ora e pienamente (o molto piu, nel caso per fase) attendibile non appena la stima basata sulla media non ha piu nulla da dire, cosi la previsione si corregge verso la realta invece di restare bloccata vicino a 0.",
14
+ "es": "Correccion: un ciclo que se ejecutaba notablemente mas tiempo que su promedio historico se estabilizaba en ~99% de progreso / tiempo restante casi cero durante un largo periodo en lugar de corregirse - informado en vivo: un ciclo de lavado mostro 98% de progreso/2min restantes 34 minutos enteros antes de terminar realmente. Causa raiz: en cuanto la simple estimacion por duracion promedio (o, dentro de una fase, la duracion tipica de esa fase) se agota, no tiene mas que aportar, pero la estimacion en vivo basada en energia (que sigue el ritmo real de esta ejecucion especifica) seguia siendo solo parcialmente confiable - hasta solo un 30% de peso para un perfil consistente. Ahora es totalmente (o mucho mas, en el caso por fase) confiable en cuanto la estimacion basada en el promedio no tiene mas que decir, de modo que la prediccion se corrige hacia la realidad en lugar de quedarse estancada cerca de 0.",
15
+ "pl": "Poprawka: cykl dzialajacy zauwazalnie dluzej niz jego historyczna srednia dlugo utrzymywal sie na ~99% postepu / niemal zerowym pozostalym czasie zamiast sie skorygowac - zgloszono na zywo: cykl prania pokazywal 98% postepu/2min pozostalo az 34 minuty przed faktycznym zakonczeniem. Przyczyna: gdy tylko prosta estymacja na podstawie sredniego czasu trwania (lub, w ramach fazy, typowego czasu trwania tej fazy) sie wyczerpie, nie ma juz nic do zaoferowania, ale zywej estymacji opartej na energii (ktora sledzi rzeczywiste tempo tego konkretnego przebiegu) nadal ufano tylko czesciowo - az do zaledwie 30% wagi dla spojnego profilu. Teraz jest w pelni (lub znacznie bardziej, w przypadku fazy) zaufana, gdy tylko estymacja oparta na sredniej nie ma juz nic do powiedzenia, dzieki czemu prognoza koryguje sie w strone rzeczywistosci zamiast utkwic blisko 0.",
16
+ "uk": "Виправлення: цикл, що працював помітно довше за свій історичний середній показник, довго залишався на ~99% прогресу / майже нульовому залишковому часі замість корекції - повідомлено наживо: цикл прання показував 98% прогресу/2хв залишилося за цілих 34 хвилини до фактичного завершення. Причина: щойно проста оцінка за середньою тривалістю (або, в межах фази, типова тривалість цієї фази) вичерпується, їй більше нічого запропонувати, але живій оцінці на основі енергоспоживання (яка відстежує реальний темп саме цього запуску) досі довіряли лише частково - аж до 30% ваги для стабільного профілю. Тепер їй довіряють повністю (або значно сильніше, у випадку окремої фази), щойно оцінці на основі середнього більше нічого сказати, тож прогноз коригується у бік реальності замість застрягання біля 0.",
17
+ "zh-cn": "修复:运行时间明显长于历史平均值的周期会长时间停留在约 99% 进度/接近零剩余时间,而不会自我修正——实际反馈案例:一次洗涤周期在实际完成前整整 34 分钟就显示 98% 进度/剩余 2 分钟。根本原因:一旦基于平均时长的简单估算(或在某个阶段内,该阶段的典型时长)耗尽,它就无法再提供更多信息,但基于能耗的实时估算(能追踪本次运行的实际节奏)此前仍只被部分信任——对于稳定的程序,权重最低只有 30%。现在只要基于平均值的估算已无话可说,就会完全信任(在按阶段的情况下则大幅提高信任度)能耗估算,使预测能够向现实靠拢,而不是卡在接近 0 的位置。"
18
+ },
19
+ "0.4.40": {
20
+ "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.",
21
+ "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.",
22
+ "ru": "Исправление: прогнозируемое время завершения ('Готово в ЧЧ:ММ') могло сдвигаться всё дальше и дальше с каждым уведомлением в течение одного цикла - наблюдалось отклонение более часа к концу стирки. Причина: как только недавние показания мощности становились слишком шумными (например, мотор стиральной машины циклически включается/выключается при встряхивании), оценка оставшегося времени 'блокируется' во избежание скачкообразных прогнозов - но заблокированное значение было замороженной длительностью, которая не уменьшалась во время блокировки, хотя реальное время продолжало идти. Обратный отсчёт фактически останавливался во время блокировки, поэтому прогнозируемое время завершения (сейчас + замороженный остаток) сдвигалось всё позже, чем дольше сохранялось шумное состояние. Теперь во время блокировки значение уменьшается на реально прошедшее время.",
23
+ "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.",
24
+ "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.",
25
+ "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.",
26
+ "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.",
27
+ "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.",
28
+ "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.",
29
+ "uk": "Виправлення: прогнозований час завершення ('Готово о ГГ:ХХ') міг зсуватися все далі й далі з кожним сповіщенням протягом одного циклу - зафіксовано відхилення понад годину до кінця прання. Причина: щойно останні показання потужності ставали занадто шумними (наприклад, двигун пральної машини циклічно вмикається/вимикається під час перемішування), оцінка часу, що залишився, 'блокується', щоб уникнути стрибкоподібних прогнозів - але заблокероване значення було замороженою тривалістю, яка не зменшувалася під час блокування, хоча реальний час продовжував минати. Відлік фактично зупинявся під час блокування, через що прогнозований час завершення (зараз + заморожений залишок) зсувався все пізніше, чим довше тривав шумний стан. Тепер під час блокування значення зменшується на реально витрачений час.",
30
+ "zh-cn": "修复:预计完成时间(「将于 HH:MM 完成」)可能会随着单个周期内的每次通知而越来越晚——曾观察到在一次洗涤结束时偏差超过一小时。根本原因:一旦近期功率读数变得过于嘈杂(例如洗衣机电机在搅动过程中反复开关),剩余时间估算会被「锁定」以避免预测跳动——但锁定的值是一个冻结的时长,在锁定期间从不缩短,尽管现实时间仍在流逝。锁定期间倒计时实际上完全停止,因此嘈杂状态持续越久,预计完成时间(当前时间 + 冻结的剩余时间)就越往后推移。现在锁定期间会按实际经过的时间正确递减。"
31
+ },
6
32
  "0.4.39": {
7
33
  "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.",
8
34
  "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.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Poprawka: phaseText mógł utknąć na wcześniejszej fazie (np. \"Aufheizen\"/nagrzewanie), podczas gdy phase poprawnie szedł dalej (np. do \"dryer_drying\") - phase był już aktualizowany przy każdym tyknięciu, ale phaseText (czytelna, zlokalizowana etykieta) był zapisywany tylko przy faktycznych przejściach stanu, a nie przy każdej zmianie fazy w obrębie jednego trwającego cyklu. Oba są teraz aktualizowane razem przy każdym tyknięciu.",
68
94
  "uk": "Виправлення: phaseText міг застрягати на попередній фазі (наприклад, \"Aufheizen\"/нагрівання), тоді як phase коректно просувався далі (наприклад, до \"dryer_drying\") - phase вже оновлювався при кожному тіку, а phaseText (зрозуміла, локалізована мітка) записувався лише при фактичних переходах стану, а не при кожній зміні фази в межах одного робочого циклу. Тепер обидва оновлюються разом при кожному тіку.",
69
95
  "zh-cn": "修复:phaseText 可能会卡在较早的阶段(例如「Aufheizen」/加热),而 phase 却正确地推进(例如到「dryer_drying」)——phase 早已在每次心跳时更新,但 phaseText(人类可读的本地化标签)此前只在实际状态转换时才写入,而不是在单次运行周期内每次阶段变化时都写入。现在两者会在每次心跳时一起更新。"
70
- },
71
- "0.4.34": {
72
- "en": "Fix: availablePrograms could show [] for a device that clearly had saved programs (visible in the admin tab's own Programme list) - it was only ever written from specific admin-tab actions (create/delete/rename a program, clear all data), never at startup or after a program got auto-learned from a confirmed cycle. Fix: needsFeedback stayed false even with a cycle visibly pending confirmation in the admin tab's Lernkontrolle tab - the data point was declared but never actually written anywhere; the admin tab computed its own pending-count entirely client-side. Both are now kept in sync at adapter startup, after every finished cycle, and (for needsFeedback) after a cycle is confirmed or corrected.",
73
- "de": "Fix: availablePrograms konnte [] anzeigen, obwohl ein Gerät eindeutig gespeicherte Programme hatte (sichtbar in der Admin-Tab-eigenen Programme-Liste) - der Datenpunkt wurde nur von bestimmten Admin-Tab-Aktionen geschrieben (Programm anlegen/löschen/umbenennen, alle Daten löschen), nie beim Start oder nachdem ein Programm aus einem bestätigten Zyklus automatisch gelernt wurde. Fix: needsFeedback blieb auf false, obwohl im Lernkontrolle-Tab des Admin-Tabs sichtbar ein Zyklus auf Bestätigung wartete - der Datenpunkt war deklariert, wurde aber nirgends tatsächlich beschrieben; der Admin-Tab berechnete seine eigene Anzahl offener Zyklen komplett clientseitig. Beide werden jetzt beim Adapterstart, nach jedem beendeten Zyklus und (bei needsFeedback) nach einer Bestätigung/Korrektur eines Zyklus synchron gehalten.",
74
- "ru": "Исправление: availablePrograms мог показывать [], хотя у устройства явно были сохранённые программы (видимые в собственном списке «Программы» админ-вкладки) - датапоинт записывался только при определённых действиях в админ-вкладке (создание/удаление/переименование программы, очистка всех данных), но никогда при запуске или после автоматического обучения программе из подтверждённого цикла. Исправление: needsFeedback оставался false, хотя на вкладке «Контроль обучения» админ-панели был явно виден цикл, ожидающий подтверждения - датапоинт был объявлен, но нигде фактически не записывался; админ-вкладка вычисляла своё количество ожидающих циклов полностью на стороне клиента. Теперь оба синхронизируются при запуске адаптера, после каждого завершённого цикла и (для needsFeedback) после подтверждения или исправления цикла.",
75
- "pt": "Correção: availablePrograms podia mostrar [] mesmo com um dispositivo claramente com programas salvos (visíveis na própria lista de Programas da aba de administração) - o data point só era escrito a partir de ações específicas da aba de administração (criar/excluir/renomear um programa, limpar todos os dados), nunca na inicialização ou após um programa ser aprendido automaticamente a partir de um ciclo confirmado. Correção: needsFeedback permanecia false mesmo com um ciclo visivelmente pendente de confirmação na aba Lernkontrolle da administração - o data point estava declarado, mas nunca era realmente escrito em lugar nenhum; a aba de administração calculava sua própria contagem pendente inteiramente no lado do cliente. Ambos agora são sincronizados na inicialização do adaptador, após cada ciclo finalizado e (para needsFeedback) após um ciclo ser confirmado ou corrigido.",
76
- "nl": "Fix: availablePrograms kon [] tonen voor een apparaat dat duidelijk opgeslagen programma's had (zichtbaar in de eigen Programma's-lijst van het admin-tabblad) - het datapoint werd alleen geschreven vanuit specifieke admin-tabbladacties (programma aanmaken/verwijderen/hernoemen, alle data wissen), nooit bij opstarten of nadat een programma automatisch was geleerd uit een bevestigde cyclus. Fix: needsFeedback bleef false terwijl er zichtbaar een cyclus wachtte op bevestiging in het Lernkontrolle-tabblad van de admin - het datapoint was gedeclareerd maar werd nergens daadwerkelijk geschreven; het admin-tabblad berekende zijn eigen aantal openstaande cycli volledig client-side. Beide worden nu gesynchroniseerd bij het opstarten van de adapter, na elke afgeronde cyclus en (voor needsFeedback) na het bevestigen of corrigeren van een cyclus.",
77
- "fr": "Correction : availablePrograms pouvait afficher [] pour un appareil ayant clairement des programmes enregistrés (visibles dans la propre liste Programmes de l'onglet admin) - le point de données n'était écrit que depuis des actions spécifiques de l'onglet admin (créer/supprimer/renommer un programme, effacer toutes les données), jamais au démarrage ni après qu'un programme ait été appris automatiquement à partir d'un cycle confirmé. Correction : needsFeedback restait à false alors qu'un cycle était visiblement en attente de confirmation dans l'onglet Lernkontrolle de l'admin - le point de données était déclaré mais jamais réellement écrit nulle part ; l'onglet admin calculait son propre nombre de cycles en attente entièrement côté client. Les deux sont désormais resynchronisés au démarrage de l'adaptateur, après chaque cycle terminé et (pour needsFeedback) après la confirmation ou la correction d'un cycle.",
78
- "it": "Correzione: availablePrograms poteva mostrare [] per un dispositivo che aveva chiaramente programmi salvati (visibili nella lista Programmi della scheda admin stessa) - il data point veniva scritto solo da azioni specifiche della scheda admin (creare/eliminare/rinominare un programma, cancellare tutti i dati), mai all'avvio o dopo che un programma veniva appreso automaticamente da un ciclo confermato. Correzione: needsFeedback restava false anche con un ciclo visibilmente in attesa di conferma nella scheda Lernkontrolle dell'admin - il data point era dichiarato ma non veniva mai effettivamente scritto da nessuna parte; la scheda admin calcolava il proprio conteggio in sospeso interamente lato client. Entrambi vengono ora sincronizzati all'avvio dell'adattatore, dopo ogni ciclo terminato e (per needsFeedback) dopo la conferma o correzione di un ciclo.",
79
- "es": "Corrección: availablePrograms podía mostrar [] para un dispositivo que claramente tenía programas guardados (visibles en la propia lista de Programas de la pestaña de administración) - el data point solo se escribía desde acciones específicas de la pestaña de administración (crear/eliminar/renombrar un programa, borrar todos los datos), nunca al iniciar ni después de que un programa se aprendiera automáticamente a partir de un ciclo confirmado. Corrección: needsFeedback permanecía en false aunque había visiblemente un ciclo pendiente de confirmación en la pestaña Lernkontrolle de administración - el data point estaba declarado pero nunca se escribía realmente en ningún sitio; la pestaña de administración calculaba su propio recuento pendiente completamente del lado del cliente. Ambos se sincronizan ahora al iniciar el adaptador, tras cada ciclo finalizado y (para needsFeedback) tras confirmar o corregir un ciclo.",
80
- "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.",
81
- "uk": "Виправлення: availablePrograms міг показувати [], хоча пристрій явно мав збережені програми (видимі у власному списку «Програми» вкладки адміністратора) - датапоінт записувався лише при певних діях у вкладці адміністратора (створення/видалення/перейменування програми, очищення всіх даних), ніколи під час запуску або після автоматичного навчання програми з підтвердженого циклу. Виправлення: needsFeedback залишався false, хоча на вкладці «Контроль навчання» адміністратора було явно видно цикл, що очікує підтвердження - датапоінт був оголошений, але ніде фактично не записувався; вкладка адміністратора обчислювала власну кількість очікуючих циклів повністю на стороні клієнта. Обидва тепер синхронізуються під час запуску адаптера, після кожного завершеного циклу та (для needsFeedback) після підтвердження чи виправлення циклу.",
82
- "zh-cn": "修复:即使设备明显已保存程序(在管理标签页自己的「程序」列表中可见),availablePrograms 仍可能显示为 []——该数据点此前只在特定的管理标签页操作中写入(创建/删除/重命名程序、清除所有数据),从不在启动时或某个程序通过已确认的周期自动学习后写入。修复:即使管理标签页的「学习检查」标签页中明显有一个周期待确认,needsFeedback 仍保持 false——该数据点已声明但从未在任何地方真正写入过;管理标签页完全在客户端自行计算待处理数量。现在两者都会在适配器启动时、每次周期结束后,以及(对于 needsFeedback)在确认或更正某个周期后保持同步。"
83
- },
84
- "0.4.33": {
85
- "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.",
86
- "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.",
87
- "ru": "Исправление: новый цикл мог несколько минут показывать остаточную вероятную программу от *предыдущего*, уже завершённого цикла (например, \"~30 Speed (76%)\" прямо в начале цикла) - внутреннее поле лучшего кандидата не сбрасывалось при старте нового цикла. Исправление: cycleProgress оставался на последнем значении (часто 100%) после завершения цикла вместо сброса на 0. Исправление: исключение при постобработке в конце цикла (в частности, постфактум-анализ фаз для стиральных/посудомоечных машин) могло незаметно блокировать обновление датапоинтов живого статуса. Новая опция \"Показывать вероятную программу до подтверждения\": опционально выводит текущую догадку лучшего кандидата в датапоинты program/programText/confidence (с префиксом \"≈\"), а не только в собственном превью вкладки администратора. Новая опция \"Текстовые датапоинты без эмодзи\": опционально убирает эмодзи из phaseText/stateText для дашбордов или синтеза речи.",
88
- "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.",
89
- "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.",
90
- "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.",
91
- "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.",
92
- "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.",
93
- "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.",
94
- "uk": "Виправлення: новий цикл міг кілька хвилин показувати залишкове припущення ймовірної програми з *попереднього*, уже завершеного циклу (наприклад, \"~30 Speed (76%)\" одразу на старті циклу) - внутрішнє поле найкращого кандидата не скидалося під час старту нового циклу. Виправлення: cycleProgress залишався на останньому значенні (часто 100%) після завершення циклу замість скидання до 0. Виправлення: виняток під час постобробки наприкінці циклу (зокрема постфактум-аналіз фаз для пральних/посудомийних машин) міг непомітно блокувати оновлення датапоінтів живого статусу. Нова опція \"Показувати ймовірну програму до підтвердження\": опціонально виводить поточне припущення найкращого кандидата в датапоінти program/programText/confidence (з префіксом \"≈\"), а не лише у власному прев'ю вкладки адміністратора. Нова опція \"Текстові датапоінти без емодзі\": опціонально прибирає емодзі з phaseText/stateText для дашбордів або синтезу мовлення.",
95
- "zh-cn": "修复:新周期可能会在最初几分钟内显示来自*上一个*已结束周期残留的可能程序猜测(例如周期刚开始就显示「~30 Speed (76%)」)——内部的最佳候选字段在新周期开始时未被清空。修复:周期结束后 cycleProgress 停留在最后的值(通常是 100%)而不会重置为 0。修复:周期结束后处理过程中的异常(尤其是洗衣机/洗碗机的事后阶段分析)可能会悄悄阻止实时状态数据点的更新。新增选项「确认前显示可能的程序」:可选择将实时最佳候选猜测显示到 program/programText/confidence 数据点中(带「≈」前缀),而不仅仅是管理标签页自身的预览。新增选项「文本数据点不含表情符号」:可选择在 phaseText/stateText 中省略表情符号,便于仪表盘或文本转语音使用。"
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
  }
@@ -978,6 +981,7 @@ class WashDataManager {
978
981
  // past cycles have data for the CURRENT phase specifically, and once
979
982
  // we actually know when that phase started (_phaseHistory's last entry).
980
983
  let phaseBasedRemainingMs = null;
984
+ let phaseOverrunning = false;
981
985
  if (
982
986
  profile.phaseStats &&
983
987
  this._stablePhase &&
@@ -1001,6 +1005,10 @@ class WashDataManager {
1001
1005
  0,
1002
1006
  typicalPhaseMs - elapsedInPhaseMs,
1003
1007
  );
1008
+ // Separately from the floored value above: has the current phase
1009
+ // already run past how long it usually takes? Used below to
1010
+ // decide how much to lean on the energy-based estimate instead.
1011
+ phaseOverrunning = elapsedInPhaseMs >= typicalPhaseMs;
1004
1012
  let futurePhasesMs = 0;
1005
1013
  for (let i = idx + 1; i < seq.length; i++) {
1006
1014
  futurePhasesMs += profile.phaseStats.durationMs[seq[i]] || 0;
@@ -1038,15 +1046,39 @@ class WashDataManager {
1038
1046
  // still capped from above by the phase safety net.
1039
1047
  let blendedRemainingMs;
1040
1048
  if (phaseBasedRemainingMs !== null) {
1041
- blendedRemainingMs =
1042
- energyBasedRemainingMs !== null
1043
- ? phaseBasedRemainingMs * 0.7 + energyBasedRemainingMs * 0.3
1044
- : phaseBasedRemainingMs;
1049
+ if (energyBasedRemainingMs === null) {
1050
+ blendedRemainingMs = phaseBasedRemainingMs;
1051
+ } else if (phaseOverrunning) {
1052
+ // The current phase has already run past how long it usually
1053
+ // takes - remainingInPhaseMs (above) is floored at 0 and has
1054
+ // nothing further to say about *this* phase, so the light 70/30
1055
+ // blend below would keep reporting "almost done" (just the
1056
+ // typical duration of whatever phases come next) no matter how
1057
+ // much longer this phase actually runs. Lean on the live
1058
+ // energy-based signal instead - it tracks the actual pace of
1059
+ // *this* run rather than a historical average. Found live: a
1060
+ // wash cycle still well inside an overrunning rinse phase
1061
+ // reported 98% progress / ~2min remaining a full 34 minutes
1062
+ // before it actually finished.
1063
+ blendedRemainingMs =
1064
+ phaseBasedRemainingMs * 0.3 + energyBasedRemainingMs * 0.7;
1065
+ } else {
1066
+ blendedRemainingMs =
1067
+ phaseBasedRemainingMs * 0.7 + energyBasedRemainingMs * 0.3;
1068
+ }
1045
1069
  } else {
1046
1070
  blendedRemainingMs = timeBasedRemainingMs;
1047
1071
  if (energyBasedRemainingMs !== null) {
1048
1072
  const cv = profile.durationCV || 0;
1049
- const energyWeight = Math.min(0.8, Math.max(0.3, 0.3 + cv * 1.25));
1073
+ // Same reasoning as the phase-overrun case above: once the whole
1074
+ // cycle has already run past its historical average
1075
+ // (timeBasedRemainingMs floored at 0), the clock-based estimate
1076
+ // has nothing more to contribute - trust the energy-based
1077
+ // projection fully rather than attenuating it by energyWeight.
1078
+ const energyWeight =
1079
+ timeBasedRemainingMs === 0
1080
+ ? 1
1081
+ : Math.min(0.8, Math.max(0.3, 0.3 + cv * 1.25));
1050
1082
  blendedRemainingMs =
1051
1083
  timeBasedRemainingMs * (1 - energyWeight) +
1052
1084
  energyBasedRemainingMs * energyWeight;
@@ -1061,10 +1093,27 @@ class WashDataManager {
1061
1093
 
1062
1094
  let remainingMs;
1063
1095
  if (variance > VARIANCE_LOCK_W && this._lockedRemaining !== null) {
1064
- remainingMs = this._lockedRemaining;
1096
+ // Noisy readings mean the freshly blended estimate isn't trusted
1097
+ // this round, but real time still passes regardless - decrement
1098
+ // the locked value by exactly how much time has elapsed since it
1099
+ // was last touched, rather than freezing it as a constant
1100
+ // duration. Previously this stayed a fixed number forever while
1101
+ // locked, so "remaining" (and therefore the predicted finish
1102
+ // time shown to the user) stopped counting down entirely for as
1103
+ // long as the variance stayed high - observed live as a washer's
1104
+ // predicted finish time drifting later with every update,
1105
+ // eventually over an hour off, during a long noisy (e.g.
1106
+ // agitation-heavy) stretch of a cycle.
1107
+ const sinceLockMs = this._lockedRemainingTs
1108
+ ? now - this._lockedRemainingTs
1109
+ : 0;
1110
+ remainingMs = Math.max(0, this._lockedRemaining - sinceLockMs);
1111
+ this._lockedRemaining = remainingMs;
1112
+ this._lockedRemainingTs = now;
1065
1113
  } else {
1066
1114
  remainingMs = Math.max(0, blendedRemainingMs);
1067
1115
  this._lockedRemaining = remainingMs;
1116
+ this._lockedRemainingTs = now;
1068
1117
  }
1069
1118
 
1070
1119
  // Recompute progress from the SAME blended remaining-time estimate,
@@ -1265,6 +1314,7 @@ class WashDataManager {
1265
1314
  this._unmatchCount = 0;
1266
1315
  this._peakConfidence = 0;
1267
1316
  this._lockedRemaining = null;
1317
+ this._lockedRemainingTs = null;
1268
1318
 
1269
1319
  this.adapter.log.info(
1270
1320
  `${this._name} ${cycleStatus}: ${cycle.matchedProfile}, ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.laundrylens",
3
- "version": "0.4.39",
3
+ "version": "0.4.41",
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",