iobroker.laundrylens 0.4.33 → 0.4.35

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,15 @@ This also works inside the conditional `[...]` blocks: `[🌡️ Outside: {state
132
132
 
133
133
  ### **WORK IN PROGRESS**
134
134
 
135
+ ### 0.4.35 (2026-10-03)
136
+ - Fix: reported live - `phaseText` could get stuck showing an earlier phase (e.g. "Aufheizen"/heating) while `phase` had correctly moved on (e.g. to "dryer_drying"). Root cause: `phase` was already written on every `_onTime()` tick (which fires frequently throughout a running cycle), but `phaseText` (the human-readable, localized, emoji-carrying label) was only ever written in `_onManagerState()` - which only runs on actual state transitions (off/starting/running/paused/ending), not on every phase change within a single long "running" period. Both data points now update together on every tick
137
+ - Covered by a new regression test (`tests/test_phase_text_tick_update.js`)
138
+
139
+ ### 0.4.34 (2026-10-02)
140
+ - Fix: reported live with screenshots - `availablePrograms` could show `[]` for a device that clearly had saved programs visible in the admin tab's own "Programme" list, while a second device correctly showed all of its programs. Root cause: the data point (and `programOverride`'s dropdown states) was only ever written from five specific admin-tab actions (create/delete/rename a program, "clear all data") - never at startup, and never after a program got auto-learned from a confirmed cycle. A device whose programs were all auto-learned, with none of those five actions performed since the last restart, kept showing an empty list indefinitely even though `profileStore` had loaded the real programs from disk
141
+ - Fix: `needsFeedback` stayed `false` even with a cycle visibly pending confirmation in the admin tab's "Lernkontrolle" tab (complete with its own "1" badge). Root cause: the data point was declared in `io-package.json` but never actually written anywhere - the admin tab computes its own pending-cycle count entirely client-side (`updateFeedbackBadge()` in `tab_m.html`, counting cycles with `!confirmed`), so the data point itself never reflected it
142
+ - Both are now re-synced (a) unconditionally at the end of every device's startup, (b) after every finished cycle, and (c) - for `needsFeedback` - after a cycle is confirmed or corrected via the admin tab, mirroring the admin tab's own counting logic exactly
143
+
135
144
  ### 0.4.33 (2026-10-02)
136
145
  - Fix: reported live with logs + a screenshot - a brand new cycle could show a leftover best-candidate guess from the *previous*, already-finished cycle for several minutes (e.g. "~30 Speed (76%)" right at the start of a new washing-machine cycle, matching exactly what the previous cycle had matched), before that new cycle's own matching had run even once. Root cause: `_onDetectorState()`'s `STARTING` case reset about a dozen per-cycle fields but not `_bestCandidate`, the field driving both the admin tab's live preview and, at the next confirmed transition, the persisted `program`/`programText`/`confidence` data points. The `STARTING` transition is now the single, authoritative reset point for every per-cycle field (also added the phase-tracking fields as defensive redundancy) - a new cycle can no longer show anything left over from the one before it
137
146
  - Fix: `cycleProgress` stayed stuck at its last value (often 100%) after a cycle finished, instead of resetting to 0. The reset call reused `_onTime()`, which has a guard that only writes `cycleProgress` when `progressPct > 0` (meant to ignore a brief non-match mid-cycle) - the deliberate reset-to-0 call was silently swallowed by that same guard. `_onTime()` now takes a `forceWrite` parameter that the reset call uses to bypass the guard
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "laundrylens",
4
- "version": "0.4.33",
4
+ "version": "0.4.35",
5
5
  "news": {
6
+ "0.4.35": {
7
+ "en": "Fix: phaseText could get stuck showing an earlier phase (e.g. \"Aufheizen\"/heating) while phase correctly moved on (e.g. to \"dryer_drying\") - phase was already updated on every tick, but phaseText (the human-readable, localized label) was only ever written at actual state transitions, not on every phase change within a single running cycle. Both now update together on every tick.",
8
+ "de": "Fix: phaseText konnte auf einer älteren Phase hängen bleiben (z. B. \"Aufheizen\"), während phase korrekt weiterlief (z. B. zu \"dryer_drying\") - phase wurde bereits bei jedem Tick aktualisiert, phaseText (das menschenlesbare, übersetzte Label) aber nur bei tatsächlichen Zustandsübergängen, nicht bei jedem Phasenwechsel innerhalb eines laufenden Zyklus. Beide werden jetzt gemeinsam bei jedem Tick aktualisiert.",
9
+ "ru": "Исправление: phaseText мог зависать на более ранней фазе (например, \"Aufheizen\"/нагрев), в то время как phase корректно переходил дальше (например, к \"dryer_drying\") - phase уже обновлялся при каждом тике, а phaseText (человекочитаемая, локализованная метка) записывался только при фактических переходах состояния, а не при каждой смене фазы внутри одного работающего цикла. Теперь оба обновляются вместе при каждом тике.",
10
+ "pt": "Correção: phaseText podia ficar preso mostrando uma fase anterior (ex. \"Aufheizen\"/aquecendo) enquanto phase avançava corretamente (ex. para \"dryer_drying\") - phase já era atualizado a cada tick, mas phaseText (o rótulo legível e localizado) só era escrito em transições de estado reais, não a cada mudança de fase dentro de um único ciclo em execução. Ambos agora são atualizados juntos a cada tick.",
11
+ "nl": "Fix: phaseText kon blijven hangen op een eerdere fase (bijv. \"Aufheizen\"/verwarmen) terwijl phase correct verderging (bijv. naar \"dryer_drying\") - phase werd al bij elke tick bijgewerkt, maar phaseText (het leesbare, gelokaliseerde label) werd alleen geschreven bij daadwerkelijke statusovergangen, niet bij elke fasewijziging binnen één lopende cyclus. Beide worden nu samen bij elke tick bijgewerkt.",
12
+ "fr": "Correction : phaseText pouvait rester bloqué sur une phase antérieure (ex. \"Aufheizen\"/chauffage) alors que phase progressait correctement (ex. vers \"dryer_drying\") - phase était déjà mis à jour à chaque tick, mais phaseText (l'étiquette lisible et localisée) n'était écrit qu'aux transitions d'état réelles, pas à chaque changement de phase au sein d'un même cycle en cours. Les deux sont désormais mis à jour ensemble à chaque tick.",
13
+ "it": "Correzione: phaseText poteva restare bloccato su una fase precedente (es. \"Aufheizen\"/riscaldamento) mentre phase avanzava correttamente (es. a \"dryer_drying\") - phase veniva già aggiornato a ogni tick, ma phaseText (l'etichetta leggibile e localizzata) veniva scritto solo alle transizioni di stato reali, non a ogni cambio di fase all'interno di un singolo ciclo in corso. Ora entrambi si aggiornano insieme a ogni tick.",
14
+ "es": "Corrección: phaseText podía quedarse atascado mostrando una fase anterior (p. ej. \"Aufheizen\"/calentando) mientras phase avanzaba correctamente (p. ej. a \"dryer_drying\") - phase ya se actualizaba en cada tick, pero phaseText (la etiqueta legible y localizada) solo se escribía en transiciones de estado reales, no en cada cambio de fase dentro de un mismo ciclo en ejecución. Ambos se actualizan ahora juntos en cada tick.",
15
+ "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.",
16
+ "uk": "Виправлення: phaseText міг застрягати на попередній фазі (наприклад, \"Aufheizen\"/нагрівання), тоді як phase коректно просувався далі (наприклад, до \"dryer_drying\") - phase вже оновлювався при кожному тіку, а phaseText (зрозуміла, локалізована мітка) записувався лише при фактичних переходах стану, а не при кожній зміні фази в межах одного робочого циклу. Тепер обидва оновлюються разом при кожному тіку.",
17
+ "zh-cn": "修复:phaseText 可能会卡在较早的阶段(例如「Aufheizen」/加热),而 phase 却正确地推进(例如到「dryer_drying」)——phase 早已在每次心跳时更新,但 phaseText(人类可读的本地化标签)此前只在实际状态转换时才写入,而不是在单次运行周期内每次阶段变化时都写入。现在两者会在每次心跳时一起更新。"
18
+ },
19
+ "0.4.34": {
20
+ "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.",
21
+ "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.",
22
+ "ru": "Исправление: availablePrograms мог показывать [], хотя у устройства явно были сохранённые программы (видимые в собственном списке «Программы» админ-вкладки) - датапоинт записывался только при определённых действиях в админ-вкладке (создание/удаление/переименование программы, очистка всех данных), но никогда при запуске или после автоматического обучения программе из подтверждённого цикла. Исправление: needsFeedback оставался false, хотя на вкладке «Контроль обучения» админ-панели был явно виден цикл, ожидающий подтверждения - датапоинт был объявлен, но нигде фактически не записывался; админ-вкладка вычисляла своё количество ожидающих циклов полностью на стороне клиента. Теперь оба синхронизируются при запуске адаптера, после каждого завершённого цикла и (для needsFeedback) после подтверждения или исправления цикла.",
23
+ "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.",
24
+ "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.",
25
+ "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.",
26
+ "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.",
27
+ "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.",
28
+ "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.",
29
+ "uk": "Виправлення: availablePrograms міг показувати [], хоча пристрій явно мав збережені програми (видимі у власному списку «Програми» вкладки адміністратора) - датапоінт записувався лише при певних діях у вкладці адміністратора (створення/видалення/перейменування програми, очищення всіх даних), ніколи під час запуску або після автоматичного навчання програми з підтвердженого циклу. Виправлення: needsFeedback залишався false, хоча на вкладці «Контроль навчання» адміністратора було явно видно цикл, що очікує підтвердження - датапоінт був оголошений, але ніде фактично не записувався; вкладка адміністратора обчислювала власну кількість очікуючих циклів повністю на стороні клієнта. Обидва тепер синхронізуються під час запуску адаптера, після кожного завершеного циклу та (для needsFeedback) після підтвердження чи виправлення циклу.",
30
+ "zh-cn": "修复:即使设备明显已保存程序(在管理标签页自己的「程序」列表中可见),availablePrograms 仍可能显示为 []——该数据点此前只在特定的管理标签页操作中写入(创建/删除/重命名程序、清除所有数据),从不在启动时或某个程序通过已确认的周期自动学习后写入。修复:即使管理标签页的「学习检查」标签页中明显有一个周期待确认,needsFeedback 仍保持 false——该数据点已声明但从未在任何地方真正写入过;管理标签页完全在客户端自行计算待处理数量。现在两者都会在适配器启动时、每次周期结束后,以及(对于 needsFeedback)在确认或更正某个周期后保持同步。"
31
+ },
6
32
  "0.4.33": {
7
33
  "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.",
8
34
  "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.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Poprawki zaleznosci/higieny repozytorium zgloszone przez przeglad repozytorium ioBroker: zaktualizowano @iobroker/testing do aktualnego minimum (6.2.2). Adres e-mail autora typu noreply z GitHub (nieakceptowany przez checker przegladu) zastapiono prawdziwym adresem kontaktowym w package.json, io-package.json oraz w liniach praw autorskich README/LICENSE. Timery falszywego adaptera w jednym pliku testowym przebudowano tak, aby uzywaly tego samego wzorca aliasow natywnych setTimeout/setInterval, ktory jest juz uzywany wszedzie indziej w zestawie testow, dzieki czemu kontrola checkera dotyczaca golych wywolan timerow juz blednie tego nie zglasza (mock i tak jedynie przekazuje wywolanie do prawdziwego timera pod nazwa adapter.setTimeout - zachowanie sie nie zmienia).",
68
94
  "uk": "Виправлення залежностей і гігієни репозиторію, виявлені під час перегляду репозиторію ioBroker: @iobroker/testing оновлено до поточного мінімуму (6.2.2). Адресу електронної пошти автора GitHub noreply (яку не приймає чекер перегляду) замінено на справжню контактну адресу в package.json, io-package.json та рядках авторських прав README/LICENSE. Таймери фіктивного адаптера в одному тестовому файлі переведено на той самий шаблон нативних псевдонімів setTimeout/setInterval, який уже використовується всюди в решті набору тестів, щоб перевірка чекера на використання простих таймерів більше не позначала це помилково (мок і так лише передає виклик до справжнього таймера під назвою adapter.setTimeout - поведінка не змінюється).",
69
95
  "zh-cn": "根据 ioBroker 仓库审查报告修复的依赖/仓库卫生问题:将 @iobroker/testing 升级到当前要求的最低版本(6.2.2)。将审查检查器不接受的 GitHub noreply 作者邮箱,替换为 package.json、io-package.json 以及 README/LICENSE 版权行中的真实联系邮箱。将某测试文件中伪造适配器的计时器改为使用测试套件其余部分早已统一采用的原生 setTimeout/setInterval 别名写法,使检查器对「直接使用原生计时器」的检测不再误报该文件(该模拟函数本来就只是以 adapter.setTimeout 之名转发给真实计时器——行为未发生变化)。"
70
- },
71
- "0.4.27": {
72
- "en": "Fix: the notification-target dropdown showed a hardcoded German placeholder (\"Alle (broadcast)\") for every notification adapter except Telegram/email, regardless of the configured system language - now uses the existing translation everywhere. Fix: a Telegram user's display name (which any user messaging the configured bot can set arbitrarily) was written into the admin tab's dropdown options without HTML-escaping - now escaped before being inserted into the page.",
73
- "de": "Fix: Das Dropdown für das Benachrichtigungsziel zeigte bei jedem Benachrichtigungs-Adapter außer Telegram/E-Mail einen fest codierten deutschen Platzhalter (\"Alle (broadcast)\") an, unabhängig von der eingestellten Systemsprache - nutzt jetzt überall die vorhandene Übersetzung. Fix: Der Anzeigename eines Telegram-Nutzers (den jeder Nutzer, der dem konfigurierten Bot schreibt, beliebig setzen kann) wurde ohne HTML-Escaping in die Dropdown-Optionen des Admin-Tabs geschrieben - wird jetzt vor dem Einfügen escaped.",
74
- "ru": "Исправление: выпадающий список цели уведомления показывал жёстко заданный немецкий текст-заполнитель (\"Alle (broadcast)\") для любого адаптера уведомлений, кроме Telegram/email, независимо от настроенного системного языка - теперь везде используется существующий перевод. Исправление: отображаемое имя пользователя Telegram (которое может произвольно задать любой пользователь, написавший настроенному боту) записывалось в опции выпадающего списка вкладки администратора без экранирования HTML - теперь экранируется перед вставкой.",
75
- "pt": "Correção: o menu suspenso do destino da notificação mostrava um texto de espaço reservado alemão fixo (\"Alle (broadcast)\") para qualquer adaptador de notificação exceto Telegram/email, independentemente do idioma do sistema configurado - agora usa a tradução existente em todos os casos. Correção: o nome de exibição de um usuário do Telegram (que qualquer usuário que enviar mensagem ao bot configurado pode definir livremente) era escrito nas opções do menu suspenso da aba de administração sem escapar HTML - agora é escapado antes de ser inserido.",
76
- "nl": "Fix: de vervolgkeuzelijst voor het notificatiedoel toonde een hardgecodeerde Duitse plaatshoudertekst (\"Alle (broadcast)\") voor elke notificatie-adapter behalve Telegram/e-mail, ongeacht de ingestelde systeemtaal - gebruikt nu overal de bestaande vertaling. Fix: de weergavenaam van een Telegram-gebruiker (die elke gebruiker die de geconfigureerde bot een bericht stuurt willekeurig kan instellen) werd zonder HTML-escaping in de dropdown-opties van het admin-tabblad geschreven - wordt nu geëscaped voordat het wordt ingevoegd.",
77
- "fr": "Correction : le menu déroulant de la cible de notification affichait un texte de remplacement allemand codé en dur (\"Alle (broadcast)\") pour tout adaptateur de notification autre que Telegram/e-mail, quelle que soit la langue système configurée - utilise désormais la traduction existante partout. Correction : le nom d'affichage d'un utilisateur Telegram (que tout utilisateur écrivant au bot configuré peut définir arbitrairement) était écrit dans les options du menu déroulant de l'onglet admin sans échappement HTML - il est désormais échappé avant insertion.",
78
- "it": "Correzione: il menu a tendina della destinazione di notifica mostrava un testo segnaposto tedesco fisso (\"Alle (broadcast)\") per qualsiasi adattatore di notifica tranne Telegram/email, indipendentemente dalla lingua di sistema configurata - ora usa ovunque la traduzione esistente. Correzione: il nome visualizzato di un utente Telegram (che qualsiasi utente che scrive al bot configurato può impostare arbitrariamente) veniva scritto nelle opzioni del menu a tendina della scheda admin senza escaping HTML - ora viene sottoposto a escaping prima dell'inserimento.",
79
- "es": "Corrección: el menú desplegable del destino de notificación mostraba un texto de marcador de posición en alemán fijo (\"Alle (broadcast)\") para cualquier adaptador de notificación excepto Telegram/correo electrónico, sin importar el idioma del sistema configurado - ahora usa la traducción existente en todos los casos. Corrección: el nombre de visualización de un usuario de Telegram (que cualquier usuario que escriba al bot configurado puede establecer libremente) se escribía en las opciones del menú desplegable de la pestaña de administración sin escapar HTML - ahora se escapa antes de insertarse.",
80
- "pl": "Poprawka: rozwijana lista celu powiadomienia pokazywała na sztywno zakodowany niemiecki tekst zastępczy (\"Alle (broadcast)\") dla każdego adaptera powiadomień poza Telegram/e-mail, niezależnie od skonfigurowanego języka systemu - teraz wszędzie używa istniejącego tłumaczenia. Poprawka: wyświetlana nazwa użytkownika Telegram (którą może dowolnie ustawić każdy użytkownik piszący do skonfigurowanego bota) była zapisywana w opcjach rozwijanej listy zakładki administratora bez ucieczki HTML - teraz jest escapowana przed wstawieniem.",
81
- "uk": "Виправлення: спадний список цілі сповіщення показував жорстко закодований німецький текст-заповнювач (\"Alle (broadcast)\") для будь-якого адаптера сповіщень, крім Telegram/email, незалежно від налаштованої системної мови - тепер скрізь використовується наявний переклад. Виправлення: відображуване ім'я користувача Telegram (яке може довільно встановити будь-який користувач, що пише налаштованому боту) записувалося в опції спадного списку вкладки адміністратора без екранування HTML - тепер екранується перед вставкою.",
82
- "zh-cn": "修复:通知目标下拉菜单在除 Telegram/邮件之外的所有通知适配器中都显示硬编码的德语占位文本(\"Alle (broadcast)\"),与所配置的系统语言无关——现在统一使用现有的翻译。修复:Telegram 用户的显示名称(任何给已配置的机器人发消息的用户都可以任意设置)此前未经 HTML 转义就被写入管理标签页的下拉选项——现在会在插入前进行转义处理。"
83
- },
84
- "0.4.26": {
85
- "en": "Fix: a program correctly detected early in a cycle could later revert to \"detecting...\" near the end, if it had been confirmed at a moderate confidence (60-74%) rather than a very high one. The protection against this (locking a confirmed program so a later run of unmatched readings can't wipe it) used to only activate if some individual reading happened to also reach 75% confidence on its own - now it activates immediately on every confirmation, regardless of the confirming confidence. A high-confidence, persistent override to a genuinely different program is still possible and unaffected.",
86
- "de": "Fix: Ein früh im Zyklus korrekt erkanntes Programm konnte gegen Ende wieder auf „erkenne...\" zurückfallen, wenn es mit moderater statt sehr hoher Konfidenz (60-74%) bestätigt worden war. Der Schutz davor (ein bestätigtes Programm sperren, damit es eine spätere Serie von Nicht-Treffern nicht löschen kann) griff bisher nur, wenn zufällig auch eine einzelne Messung 75% Konfidenz erreichte - jetzt greift er sofort bei jeder Bestätigung, unabhängig von der bestätigenden Konfidenz. Ein hochsicherer, dauerhafter Wechsel zu einem tatsächlich anderen Programm bleibt weiterhin möglich und unverändert.",
87
- "ru": "Исправление: программа, правильно определённая в начале цикла, могла позже снова показать «определение...» ближе к концу, если она была подтверждена с умеренной, а не очень высокой уверенностью (60-74%). Защита от этого (блокировка подтверждённой программы, чтобы более поздняя серия несовпадений не могла её стереть) раньше активировалась только если какое-то отдельное измерение случайно достигало 75% уверенности - теперь она активируется сразу при каждом подтверждении, независимо от подтверждающей уверенности.",
88
- "pt": "Correção: um programa corretamente detectado no início de um ciclo podia voltar a mostrar \"detectando...\" perto do fim, se tivesse sido confirmado com confiança moderada (60-74%) em vez de muito alta. A proteção contra isso (bloquear um programa confirmado para que uma série posterior de leituras sem correspondência não o apague) antes só era ativada se alguma leitura individual também atingisse 75% de confiança - agora é ativada imediatamente em cada confirmação, independentemente da confiança confirmadora.",
89
- "nl": "Fix: een programma dat vroeg in een cyclus correct werd gedetecteerd, kon later tegen het einde weer \"detecteren...\" tonen, als het was bevestigd met een gematigde in plaats van zeer hoge betrouwbaarheid (60-74%). De bescherming hiertegen (een bevestigd programma vergrendelen zodat een latere reeks niet-overeenkomende metingen het niet kan wissen) werd voorheen alleen geactiveerd als een individuele meting toevallig ook 75% betrouwbaarheid haalde - nu wordt deze direct geactiveerd bij elke bevestiging, ongeacht de bevestigende betrouwbaarheid.",
90
- "fr": "Correction : un programme correctement détecté tôt dans un cycle pouvait revenir à « détection... » vers la fin, s'il avait été confirmé avec une confiance modérée (60-74 %) plutôt que très élevée. La protection contre cela (verrouiller un programme confirmé pour qu'une série ultérieure de lectures non concordantes ne puisse pas l'effacer) ne s'activait auparavant que si une lecture individuelle atteignait aussi 75 % de confiance par hasard - elle s'active désormais immédiatement à chaque confirmation, quelle que soit la confiance de confirmation.",
91
- "it": "Correzione: un programma rilevato correttamente all'inizio di un ciclo poteva tornare a \"rilevamento...\" verso la fine, se era stato confermato con una confidenza moderata (60-74%) anziché molto alta. La protezione contro questo (bloccare un programma confermato affinché una successiva serie di letture non corrispondenti non possa cancellarlo) prima si attivava solo se una singola lettura raggiungeva casualmente anche il 75% di confidenza - ora si attiva immediatamente ad ogni conferma, indipendentemente dalla confidenza di conferma.",
92
- "es": "Corrección: un programa correctamente detectado al principio de un ciclo podía volver a mostrar \"detectando...\" cerca del final, si se había confirmado con una confianza moderada (60-74%) en lugar de muy alta. La protección contra esto (bloquear un programa confirmado para que una serie posterior de lecturas sin coincidencia no pueda borrarlo) antes solo se activaba si alguna lectura individual también alcanzaba el 75% de confianza por sí sola - ahora se activa inmediatamente en cada confirmación, sin importar la confianza confirmante.",
93
- "pl": "Poprawka: program poprawnie wykryty na początku cyklu mógł później, bliżej końca, ponownie pokazać \"wykrywanie...\", jeśli został potwierdzony z umiarkowaną, a nie bardzo wysoką pewnością (60-74%). Zabezpieczenie przed tym (blokowanie potwierdzonego programu, aby późniejsza seria niedopasowanych odczytów nie mogła go skasować) wcześniej aktywowało się tylko wtedy, gdy pojedynczy odczyt przypadkowo osiągnął też 75% pewności - teraz aktywuje się natychmiast przy każdym potwierdzeniu, niezależnie od potwierdzającej pewności.",
94
- "uk": "Виправлення: програма, правильно визначена на початку циклу, могла пізніше знову показати «визначення...» ближче до кінця, якщо вона була підтверджена з помірною, а не дуже високою впевненістю (60-74%). Захист від цього (блокування підтвердженої програми, щоб пізніша серія невідповідних показань не могла її стерти) раніше активувався лише якщо якесь окреме показання випадково також досягало 75% впевненості - тепер він активується одразу під час кожного підтвердження, незалежно від підтверджувальної впевненості.",
95
- "zh-cn": "修复:一个在周期早期被正确检测到的程序,如果是以中等置信度(60-74%)而非非常高的置信度被确认的,后期可能会重新显示为\"检测中...\"。此前,防止这种情况的保护机制(锁定已确认的程序,使后续一连串不匹配的读数无法将其清除)只有在某次单独读数恰好也达到 75% 置信度时才会启用——现在无论确认时的置信度如何,每次确认都会立即启用该保护。切换到确实不同程序的高置信度、持续性覆盖机制不受影响,仍然可用。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/main.js CHANGED
@@ -608,6 +608,22 @@ class WashdataAdapter extends utils.Adapter {
608
608
  manager.currentState,
609
609
  manager.getStatus(),
610
610
  );
611
+
612
+ // Same reasoning as above, for two more data points found live to
613
+ // have the same "only updated from specific admin-tab actions"
614
+ // gap: availablePrograms/programOverride's states (only ever
615
+ // written from the createProfile/deleteProfile/renameProfile/
616
+ // clearAllData sendTo handlers - never at startup, so a device
617
+ // with profiles already on disk showed an empty [] until one of
618
+ // those actions happened to run) and needsFeedback (declared as a
619
+ // data point but never written anywhere at all - the admin tab's
620
+ // own "Lernkontrolle" badge computes its pending-cycle count
621
+ // entirely client-side from getCycles(), so the data point never
622
+ // reflected it). Both are now also re-synced after every finished
623
+ // cycle and (for needsFeedback) after a cycle is confirmed/
624
+ // corrected - see _onCycleFinished()/confirmCycle/correctCycle.
625
+ await this._updateOverrideStates(deviceCfg.deviceId, manager);
626
+ await this._updateNeedsFeedback(deviceCfg.deviceId, manager);
611
627
  }
612
628
 
613
629
  this.setState("info.connection", true, true);
@@ -851,6 +867,7 @@ class WashdataAdapter extends utils.Adapter {
851
867
  );
852
868
  await mgr.profileStore.save();
853
869
  }
870
+ await this._updateNeedsFeedback(obj.message.deviceId, mgr);
854
871
  respond({ ok: true });
855
872
  break;
856
873
  }
@@ -885,6 +902,8 @@ class WashdataAdapter extends utils.Adapter {
885
902
  );
886
903
  await mgr.profileStore.save();
887
904
  await mgr._saveState(); // Persist the cycle change
905
+ await this._updateNeedsFeedback(obj.message.deviceId, mgr);
906
+ await this._updateOverrideStates(obj.message.deviceId, mgr);
888
907
  respond({ ok: true });
889
908
  break;
890
909
  }
@@ -1392,6 +1411,16 @@ class WashdataAdapter extends utils.Adapter {
1392
1411
  );
1393
1412
  }
1394
1413
 
1414
+ // ── Update "feedback needed" indicator ──────────────────
1415
+ // Mirrors admin/tab_m.html's updateFeedbackBadge() exactly (cycles
1416
+ // with !confirmed) - that is the only place this count was ever
1417
+ // computed before, entirely client-side, which is why the
1418
+ // needsFeedback data point itself was never actually written.
1419
+ async _updateNeedsFeedback(deviceId, mgr) {
1420
+ const needsFeedback = mgr.getCycleHistory().some((c) => !c.confirmed);
1421
+ this.setState(`${deviceId}.needsFeedback`, needsFeedback, true);
1422
+ }
1423
+
1395
1424
  // ── Callbacks ────────────────────────────────────────────────
1396
1425
  _onManagerState(deviceId, state, status) {
1397
1426
  const prevState = this._lastState && this._lastState[deviceId];
@@ -1511,10 +1540,29 @@ class WashdataAdapter extends utils.Adapter {
1511
1540
  if (mgrPh) {
1512
1541
  const st = mgrPh.getStatus();
1513
1542
  if (st && st.phase) {
1514
- const phaseText = st.phase
1543
+ const phaseKey = st.phase
1515
1544
  .replace(/^[\u{1F300}-\u{1FFFF}\u{2600}-\u{27BF}]\s*/u, "")
1516
1545
  .trim();
1517
- this.setState(`${deviceId}.phase`, phaseText, true);
1546
+ this.setState(`${deviceId}.phase`, phaseKey, true);
1547
+ // phaseText was only ever written in _onManagerState() - which
1548
+ // only fires on actual state transitions (off/starting/running/
1549
+ // paused/ending), not on every phase change within a single
1550
+ // long "running" period. phase (above) was already correctly
1551
+ // updated on every tick, which is exactly why it could show a
1552
+ // newer phase (e.g. "dryer_drying") while phaseText stayed
1553
+ // frozen on an older one from earlier in the same cycle (e.g.
1554
+ // "Aufheizen"/heating) - found live.
1555
+ const devCfgTime = this._getDeviceConfig().find(
1556
+ (d) => d.deviceId === deviceId,
1557
+ );
1558
+ const langTime =
1559
+ (this._deviceLangCache && this._deviceLangCache[deviceId]) || "en";
1560
+ const noEmojiTime = devCfgTime ? !!devCfgTime.noEmoji : false;
1561
+ this.setState(
1562
+ `${deviceId}.phaseText`,
1563
+ getPhaseText(phaseKey, langTime, noEmojiTime),
1564
+ true,
1565
+ );
1518
1566
  }
1519
1567
  }
1520
1568
  // Only check update messages every 60s AND only when a program is really recognized
@@ -1592,6 +1640,15 @@ class WashdataAdapter extends utils.Adapter {
1592
1640
  // Reset the program override
1593
1641
  this.setState(`${deviceId}.programOverride`, "auto", true);
1594
1642
 
1643
+ if (mgrPh) {
1644
+ // A freshly finished cycle may be unconfirmed (needs feedback) and/or
1645
+ // may have caused profileStore.learnFromCycle() to auto-learn a brand
1646
+ // new program - both found live to otherwise only ever update from
1647
+ // specific admin-tab actions, never automatically here.
1648
+ await this._updateNeedsFeedback(deviceId, mgrPh);
1649
+ await this._updateOverrideStates(deviceId, mgrPh);
1650
+ }
1651
+
1595
1652
  // Send notification
1596
1653
  await this._sendNotification(deviceId, cycle);
1597
1654
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.laundrylens",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
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",