iobroker.laundrylens 0.4.37 → 0.4.38
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 +6 -0
- package/io-package.json +14 -14
- package/main.js +267 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,6 +132,12 @@ This also works inside the conditional `[...]` blocks: `[🌡️ Outside: {state
|
|
|
132
132
|
|
|
133
133
|
### **WORK IN PROGRESS**
|
|
134
134
|
|
|
135
|
+
### 0.4.38 (2026-10-04)
|
|
136
|
+
- 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
|
+
- 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)
|
|
138
|
+
- New writable actions, each acting on the oldest pending cycle: `feedbackConfirm` (button - same as the admin tab's "Correct – confirm"), `feedbackCorrectProgram` (dropdown of program names, kept in sync the same way `programOverride`'s own dropdown is - writing a name corrects and confirms, same as the admin tab's "Wrong program" flow), `feedbackDelete` (button - discards the cycle without confirming it)
|
|
139
|
+
- Covered by a new test file (`tests/test_vis_feedback_actions.js`, 17 cases) plus an extension to `test_english_only.js`'s existing state/migration-coverage invariant check (the ten new state ids are brand new, so correctly added to the "nothing to migrate" allow-list rather than the migrations object)
|
|
140
|
+
|
|
135
141
|
### 0.4.37 (2026-10-03)
|
|
136
142
|
- Fix: three test files added in recent releases (`test_show_probable_program.js`, `test_cycle_finish_exception_guard.js`, `test_cycle_boundary_reset.js`) called the bare global `setTimeout()`/`setInterval()` directly in their fake-adapter mocks - the same mistake made and fixed before in other test files, missed again when writing these new ones. Brought in line with the native-timer-alias pattern used everywhere else in this suite (review checker E5004/E5005)
|
|
137
143
|
- Fix: `@iobroker/testing` devDependency bumped to `^6.3.0` (current; was `^6.2.2`)
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "laundrylens",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.38",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.4.38": {
|
|
7
|
+
"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
|
+
"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).",
|
|
9
|
+
"ru": "Новое: процесс контроля обучения (Lernkontrolle) теперь можно выполнять из VIS, а не только через вкладку администратора. Новые датапоинты только для чтения: pendingFeedbackCount, pendingFeedback (JSON-список всех неподтверждённых циклов, сначала самые старые) и feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (удобные поля для самого старого). Новые доступные для записи действия, каждое воздействует на самый старый ожидающий цикл: feedbackConfirm (кнопка, подтверждает), feedbackCorrectProgram (выпадающий список названий программ - запись исправляет и подтверждает), feedbackDelete (кнопка, отклоняет без подтверждения).",
|
|
10
|
+
"pt": "Novo: o fluxo de revisao de aprendizado (Lernkontrolle) agora pode ser controlado pelo VIS, nao apenas pela aba de administracao. Novos data points somente leitura: pendingFeedbackCount, pendingFeedback (lista JSON de todos os ciclos nao confirmados, do mais antigo ao mais novo) e feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (campos de conveniencia para o mais antigo). Novas acoes graváveis, cada uma atuando no ciclo pendente mais antigo: feedbackConfirm (botao, confirma), feedbackCorrectProgram (lista suspensa de nomes de programas - escrever um corrige e confirma), feedbackDelete (botao, descarta sem confirmar).",
|
|
11
|
+
"nl": "Nieuw: de Lernkontrolle (leercontrole/feedback) kan nu ook vanuit VIS worden bediend, niet alleen via het admin-tabblad. Nieuwe alleen-lezen datapoints: pendingFeedbackCount, pendingFeedback (JSON-lijst van alle onbevestigde cycli, oudste eerst) en feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (gemaksvelden voor de oudste). Nieuwe schrijfbare acties, elk werkend op de oudste openstaande cyclus: feedbackConfirm (knop, bevestigt deze), feedbackCorrectProgram (dropdown met programmanamen - schrijven corrigeert en bevestigt), feedbackDelete (knop, verwerpt zonder te bevestigen).",
|
|
12
|
+
"fr": "Nouveau : le flux de controle d'apprentissage (Lernkontrolle) peut desormais etre pilote depuis VIS, pas seulement via l'onglet admin. Nouveaux points de donnees en lecture seule : pendingFeedbackCount, pendingFeedback (liste JSON de tous les cycles non confirmes, du plus ancien au plus recent) et feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (champs pratiques pour le plus ancien). Nouvelles actions inscriptibles, agissant chacune sur le cycle en attente le plus ancien : feedbackConfirm (bouton, le confirme), feedbackCorrectProgram (liste deroulante de noms de programmes - ecrire une valeur corrige et confirme), feedbackDelete (bouton, l'ecarte sans confirmer).",
|
|
13
|
+
"it": "Novita: il flusso di controllo dell'apprendimento (Lernkontrolle) puo ora essere gestito da VIS, non solo tramite la scheda admin. Nuovi data point di sola lettura: pendingFeedbackCount, pendingFeedback (elenco JSON di tutti i cicli non confermati, dal piu vecchio) e feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (campi comodi per il piu vecchio). Nuove azioni scrivibili, ciascuna agisce sul ciclo in sospeso piu vecchio: feedbackConfirm (pulsante, lo conferma), feedbackCorrectProgram (menu a tendina dei nomi dei programmi - scrivere un valore corregge e conferma), feedbackDelete (pulsante, lo scarta senza confermare).",
|
|
14
|
+
"es": "Novedad: el flujo de control de aprendizaje (Lernkontrolle) ahora se puede manejar desde VIS, no solo desde la pestana de administracion. Nuevos data points de solo lectura: pendingFeedbackCount, pendingFeedback (lista JSON de todos los ciclos no confirmados, el mas antiguo primero) y feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (campos de conveniencia para el mas antiguo). Nuevas acciones escribibles, cada una actuando sobre el ciclo pendiente mas antiguo: feedbackConfirm (boton, lo confirma), feedbackCorrectProgram (lista desplegable de nombres de programas - escribir uno corrige y confirma), feedbackDelete (boton, lo descarta sin confirmar).",
|
|
15
|
+
"pl": "Nowosc: proces kontroli uczenia (Lernkontrolle) mozna teraz obslugiwac z poziomu VIS, a nie tylko przez zakladke administratora. Nowe datapointy tylko do odczytu: pendingFeedbackCount, pendingFeedback (lista JSON wszystkich niepotwierdzonych cykli, od najstarszego) oraz feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (wygodne pola dla najstarszego). Nowe zapisywalne akcje, kazda dziala na najstarszym oczekujacym cyklu: feedbackConfirm (przycisk, potwierdza go), feedbackCorrectProgram (lista rozwijana nazw programow - wpisanie poprawia i potwierdza), feedbackDelete (przycisk, odrzuca bez potwierdzania).",
|
|
16
|
+
"uk": "Нове: процес контролю навчання (Lernkontrolle) тепер можна виконувати з VIS, а не лише через вкладку адміністратора. Нові датапоінти лише для читання: pendingFeedbackCount, pendingFeedback (JSON-список усіх непідтверджених циклів, спочатку найстаріші) та feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence (зручні поля для найстарішого). Нові записувані дії, кожна діє на найстаріший очікуючий цикл: feedbackConfirm (кнопка, підтверджує його), feedbackCorrectProgram (випадний список назв програм - запис виправляє і підтверджує), feedbackDelete (кнопка, відхиляє без підтвердження).",
|
|
17
|
+
"zh-cn": "新增:学习检查(Lernkontrolle)流程现在也可以从 VIS 操作,而不仅限于管理标签页。新增只读数据点:pendingFeedbackCount、pendingFeedback(所有未确认周期的 JSON 列表,最旧的在前)以及 feedbackCycleId/feedbackProgram/feedbackDuration/feedbackEnergy/feedbackConfidence(针对最旧周期的便捷字段)。新增可写操作,均作用于最旧的待处理周期:feedbackConfirm(按钮,确认该周期)、feedbackCorrectProgram(程序名称下拉列表——写入即可更正并确认)、feedbackDelete(按钮,不确认直接丢弃该周期)。"
|
|
18
|
+
},
|
|
6
19
|
"0.4.37": {
|
|
7
20
|
"en": "Fix: three test files added in recent releases (test_show_probable_program.js, test_cycle_finish_exception_guard.js, test_cycle_boundary_reset.js) called the bare global setTimeout()/setInterval() directly in their fake-adapter mocks, which the review checker's plain-timer-usage check flags - brought in line with the native-timer-alias pattern already used everywhere else in this test suite. Fix: bumped the @iobroker/testing devDependency to the current version (6.3.0).",
|
|
8
21
|
"de": "Fix: drei in juengeren Releases hinzugefuegte Testdateien (test_show_probable_program.js, test_cycle_finish_exception_guard.js, test_cycle_boundary_reset.js) riefen in ihren Fake-Adapter-Mocks das nackte globale setTimeout()/setInterval() direkt auf, was die Pruefung des Review-Checkers auf rohe Timer-Aufrufe meldet - an das in dieser Testsuite ueberall sonst verwendete native-Timer-Alias-Muster angeglichen. Fix: die devDependency @iobroker/testing auf die aktuelle Version (6.3.0) angehoben.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
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.",
|
|
81
94
|
"uk": "Додаткове виправлення до 0.4.31: сама лише перезагрузка не очищала вже застарілий датапоінт стану (наприклад, застряглий на \"Running\"), навіть із встановленим 0.4.31. onReady() перезаписував датапоінти state/stateText/running/program/phase лише коли спрацьовувала гілка відновлення (датчик наразі активний, або збережений цикл потрібно відновити/завершити) - якщо жодне з цього не було правдою, звичайний випадок, нічого не змінювалося. Тепер запуск кожного пристрою безумовно синхронізує ці датапоінти з фактичним, визначеним станом менеджера, тому перезапуск завжди негайно очищує будь-яке застарile значення, незалежно від причини.",
|
|
82
95
|
"zh-cn": "0.4.31 的后续修复:即使安装了 0.4.31,仅重启也无法清除已经过期的状态数据点(例如卡在「Running」)。此前 onReady() 只有在触发恢复分支时(传感器当前处于活动状态,或需要恢复/结束已保存的周期)才会重写 state/stateText/running/program/phase 数据点——如果两者都不成立(这是常见情况),则完全不做任何更改。现在每台设备启动时都会无条件将这些数据点与管理器实际解析出的状态同步,因此重启总能立即清除任何过期的值,无论原因是什么。"
|
|
83
|
-
},
|
|
84
|
-
"0.4.31": {
|
|
85
|
-
"en": "Fix: a real bug report - the admin tab showed the dryer as off while the state data point still read \"Running\". Root cause: the dryer's anti-crease \"power drop\" quick-finish path (which force-ends a cycle 45s after a sudden power drop, to react faster than waiting for the full off-delay) set the in-memory state to off directly but never notified the rest of the adapter about it - so the state/stateText/running/program/phase data points stayed frozen on their last value from before the drop, even though the live status the admin tab reads was already correct. Fixed by firing the same notification the normal end-of-cycle transition already fires. If you're seeing this right now, restarting the adapter clears the stuck values immediately without waiting for the next cycle.",
|
|
86
|
-
"de": "Fix: ein realer Fehlerbericht - der Admin-Tab zeigte den Trockner als aus an, waehrend der Status-Datenpunkt noch \"Running\" anzeigte. Ursache: der Anti-Knitter-Schnellabschluss des Trockners (der einen Zyklus 45 Sekunden nach einem ploetzlichen Leistungsabfall zwangsbeendet, um schneller zu reagieren als die volle Ausschaltverzoegerung abzuwarten) setzte den internen Status direkt auf aus, benachrichtigte aber nie den Rest des Adapters darueber - daher blieben die Datenpunkte state/stateText/running/program/phase auf ihrem letzten Wert vor dem Abfall eingefroren, obwohl der Live-Status, den der Admin-Tab liest, bereits korrekt war. Behoben, indem dieselbe Benachrichtigung ausgeloest wird, die der normale Zyklusende-Uebergang ohnehin schon ausloest. Wer das gerade sieht: ein Neustart des Adapters raeumt die haengengebliebenen Werte sofort auf, ohne auf den naechsten Zyklus warten zu muessen.",
|
|
87
|
-
"ru": "Исправление: реальный отчёт об ошибке - вкладка администратора показывала сушилку выключенной, тогда как датапоинт состояния всё ещё показывал \"Running\". Причина: путь быстрого завершения цикла сушилки при защите от сминания (\"power drop\" - принудительно завершает цикл через 45 секунд после резкого падения мощности, чтобы реагировать быстрее полной задержки отключения) устанавливал внутреннее состояние в off напрямую, но никогда не уведомлял остальную часть адаптера об этом - поэтому датапоинты state/stateText/running/program/phase оставались замороженными на последнем значении до падения, хотя живой статус, который читает вкладка администратора, уже был корректен. Исправлено вызовом того же уведомления, которое уже вызывает обычный переход в конце цикла. Если вы видите это прямо сейчас, перезапуск адаптера немедленно устраняет зависшие значения без ожидания следующего цикла.",
|
|
88
|
-
"pt": "Correcao: um relato real de bug - a aba de administracao mostrava a secadora como desligada enquanto o data point de estado ainda mostrava \"Running\". Causa raiz: o caminho de finalizacao rapida por queda de energia (anti-amarrotamento) da secadora (que forca o fim de um ciclo 45s apos uma queda subita de energia, para reagir mais rapido do que esperar o atraso total de desligamento) definia o estado interno como desligado diretamente, mas nunca notificava o restante do adaptador sobre isso - entao os data points state/stateText/running/program/phase ficavam congelados no ultimo valor antes da queda, mesmo que o status ao vivo lido pela aba de administracao ja estivesse correto. Corrigido disparando a mesma notificacao que a transicao normal de fim de ciclo ja dispara. Se voce esta vendo isso agora, reiniciar o adaptador limpa os valores presos imediatamente, sem esperar pelo proximo ciclo.",
|
|
89
|
-
"nl": "Fix: een echte bugmelding - het admin-tabblad toonde de droger als uit terwijl het state-datapoint nog \"Running\" liet zien. Oorzaak: het kreukbeschermings-\"stroomval\"-snelafsluitpad van de droger (dat een cyclus 45s na een plotselinge stroomval geforceerd beeindigt om sneller te reageren dan de volledige uitschakelvertraging) zette de interne status direct op uit, maar informeerde de rest van de adapter daar nooit over - waardoor de datapoints state/stateText/running/program/phase bevroren bleven op hun laatste waarde van voor de val, terwijl de live status die het admin-tabblad leest al correct was. Opgelost door dezelfde melding te versturen die de normale cyclus-einde-overgang al verstuurt. Zie je dit nu, dan lost een herstart van de adapter de vastzittende waarden meteen op, zonder op de volgende cyclus te hoeven wachten.",
|
|
90
|
-
"fr": "Correction : un vrai signalement de bug - l'onglet admin affichait le secheur comme eteint alors que le data point d'etat indiquait encore \"Running\". Cause : le chemin de fin rapide anti-froissage du secheur (qui force la fin d'un cycle 45s apres une chute soudaine de puissance, pour reagir plus vite que d'attendre le delai d'arret complet) mettait l'etat interne a off directement, mais ne notifiait jamais le reste de l'adaptateur - les data points state/stateText/running/program/phase restaient donc figes sur leur derniere valeur d'avant la chute, alors que le statut en direct lu par l'onglet admin etait deja correct. Corrige en declenchant la meme notification que la transition normale de fin de cycle declenche deja. Si vous voyez ce cas maintenant, redemarrer l'adaptateur efface immediatement les valeurs bloquees sans attendre le prochain cycle.",
|
|
91
|
-
"it": "Correzione: una segnalazione reale di bug - la scheda admin mostrava l'asciugatrice come spenta mentre il data point di stato mostrava ancora \"Running\". Causa: il percorso di chiusura rapida anti-piega dell'asciugatrice (che forza la fine di un ciclo 45s dopo un calo improvviso di potenza, per reagire piu velocemente rispetto ad attendere il ritardo di spegnimento completo) impostava lo stato interno su off direttamente, ma non notificava mai il resto dell'adattatore - quindi i data point state/stateText/running/program/phase restavano bloccati sull'ultimo valore prima del calo, anche se lo stato live letto dalla scheda admin era gia corretto. Risolto attivando la stessa notifica gia attivata dalla normale transizione di fine ciclo. Se lo state vedendo ora, riavviare l'adattatore risolve subito i valori bloccati senza attendere il ciclo successivo.",
|
|
92
|
-
"es": "Correccion: un informe real de error - la pestana de administracion mostraba la secadora como apagada mientras el data point de estado aun mostraba \"Running\". Causa raiz: el camino de finalizacion rapida anti-arrugas de la secadora (que fuerza el fin de un ciclo 45s despues de una caida repentina de potencia, para reaccionar mas rapido que esperar el retraso de apagado completo) establecia el estado interno en apagado directamente, pero nunca notificaba al resto del adaptador - por lo que los data points state/stateText/running/program/phase quedaban congelados en su ultimo valor antes de la caida, aunque el estado en vivo que lee la pestana de administracion ya era correcto. Corregido disparando la misma notificacion que ya dispara la transicion normal de fin de ciclo. Si esta viendo esto ahora mismo, reiniciar el adaptador limpia los valores atascados de inmediato, sin esperar al proximo ciclo.",
|
|
93
|
-
"pl": "Poprawka: prawdziwe zgloszenie bledu - zakladka administratora pokazywala suszarke jako wylaczona, podczas gdy datapoint stanu nadal pokazywal \"Running\". Przyczyna: sciezka szybkiego zakonczenia cyklu suszarki przy ochronie przed gnieceniem (\"power drop\" - wymusza zakonczenie cyklu 45s po nagłym spadku mocy, aby zareagowac szybciej niz czekajac na pelne opoznienie wylaczenia) ustawiala stan wewnetrzny na wylaczony bezposrednio, ale nigdy nie powiadamiala o tym reszty adaptera - dlatego datapointy state/stateText/running/program/phase pozostawaly zamrozone na ostatniej wartosci sprzed spadku, mimo ze status na zywo odczytywany przez zakladke administratora byl juz poprawny. Naprawiono, wywolujac to samo powiadomienie, ktore wywoluje juz normalne przejscie konca cyklu. Jesli widzisz to teraz, ponowne uruchomienie adaptera natychmiast czysci utkniete wartosci bez czekania na kolejny cykl.",
|
|
94
|
-
"uk": "Виправлення: реальне повідомлення про помилку - вкладка адміністратора показувала сушарку вимкненою, тоді як датапоінт стану все ще показував \"Running\". Причина: шлях швидкого завершення циклу сушарки при захисті від зминання (\"power drop\" - примусово завершує цикл через 45 секунд після різкого падіння потужності, щоб реагувати швидше за повне очікування затримки вимкнення) встановлював внутрішній стан на вимкнено напряму, але ніколи не повідомляв про це решту адаптера - тому датапоінти state/stateText/running/program/phase залишалися замороженими на останньому значенні до падіння, хоча живий статус, який читає вкладка адміністратора, вже був коректним. Виправлено викликом того самого сповіщення, яке вже викликає звичайний перехід у кінці циклу. Якщо ви бачите це зараз, перезапуск адаптера негайно очищує застряглі значення без очікування наступного циклу.",
|
|
95
|
-
"zh-cn": "修复:一个真实的问题反馈——管理标签页显示烘干机已关闭,但状态数据点仍显示「Running」。根本原因:烘干机的防皱「功率骤降」快速结束路径(在功率突然下降 45 秒后强制结束周期,以比等待完整关闭延迟更快地做出反应)直接将内存中的状态设为关闭,但从未通知适配器的其余部分——因此 state/stateText/running/program/phase 数据点一直冻结在骤降前的最后一个值,尽管管理标签页读取的实时状态早已正确。修复方法是触发与正常周期结束转换相同的通知。如果你现在正遇到这个问题,重启适配器即可立即清除卡住的值,无需等待下一个周期。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/main.js
CHANGED
|
@@ -378,6 +378,11 @@ class WashdataAdapter extends utils.Adapter {
|
|
|
378
378
|
// Subscribe to writable states
|
|
379
379
|
await this.subscribeStatesAsync(`${deviceCfg.deviceId}.programOverride`);
|
|
380
380
|
await this.subscribeStatesAsync(`${deviceCfg.deviceId}.forceFinish`);
|
|
381
|
+
await this.subscribeStatesAsync(`${deviceCfg.deviceId}.feedbackConfirm`);
|
|
382
|
+
await this.subscribeStatesAsync(
|
|
383
|
+
`${deviceCfg.deviceId}.feedbackCorrectProgram`,
|
|
384
|
+
);
|
|
385
|
+
await this.subscribeStatesAsync(`${deviceCfg.deviceId}.feedbackDelete`);
|
|
381
386
|
|
|
382
387
|
// Disable ghost protection if the sensor is currently drawing power
|
|
383
388
|
try {
|
|
@@ -664,6 +669,36 @@ class WashdataAdapter extends utils.Adapter {
|
|
|
664
669
|
}
|
|
665
670
|
return;
|
|
666
671
|
}
|
|
672
|
+
if (id === `${this.namespace}.${deviceId}.feedbackConfirm`) {
|
|
673
|
+
if (state.val === true) {
|
|
674
|
+
this._handleFeedbackConfirm(deviceId, mgr).catch((err) =>
|
|
675
|
+
this.log.error(`${deviceId}: feedbackConfirm: ${err.message}`),
|
|
676
|
+
);
|
|
677
|
+
this.setState(`${deviceId}.feedbackConfirm`, false, true);
|
|
678
|
+
}
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (id === `${this.namespace}.${deviceId}.feedbackCorrectProgram`) {
|
|
682
|
+
if (state.val) {
|
|
683
|
+
this._handleFeedbackCorrectProgram(deviceId, mgr, state.val).catch(
|
|
684
|
+
(err) =>
|
|
685
|
+
this.log.error(
|
|
686
|
+
`${deviceId}: feedbackCorrectProgram: ${err.message}`,
|
|
687
|
+
),
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
this.setState(`${deviceId}.feedbackCorrectProgram`, "", true);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (id === `${this.namespace}.${deviceId}.feedbackDelete`) {
|
|
694
|
+
if (state.val === true) {
|
|
695
|
+
this._handleFeedbackDelete(deviceId, mgr).catch((err) =>
|
|
696
|
+
this.log.error(`${deviceId}: feedbackDelete: ${err.message}`),
|
|
697
|
+
);
|
|
698
|
+
this.setState(`${deviceId}.feedbackDelete`, false, true);
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
667
702
|
}
|
|
668
703
|
}
|
|
669
704
|
|
|
@@ -760,6 +795,84 @@ class WashdataAdapter extends utils.Adapter {
|
|
|
760
795
|
mgr.detector.reset();
|
|
761
796
|
}
|
|
762
797
|
|
|
798
|
+
// Returns the oldest cycle still needing feedback for this device, or
|
|
799
|
+
// null if there isn't one - the single target the three feedback*
|
|
800
|
+
// writable data points below act on, same cycle _updateNeedsFeedback()
|
|
801
|
+
// exposes via feedbackCycleId/feedbackProgram/etc.
|
|
802
|
+
_oldestPendingCycle(mgr) {
|
|
803
|
+
const open = mgr
|
|
804
|
+
.getCycleHistory()
|
|
805
|
+
.filter((c) => !c.confirmed)
|
|
806
|
+
.sort((a, b) => a.startTime - b.startTime);
|
|
807
|
+
return open[0] || null;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// VIS-friendly equivalent of the admin tab's confirmCycle sendTo
|
|
811
|
+
// handler (see onMessage() below), acting on the oldest pending cycle.
|
|
812
|
+
async _handleFeedbackConfirm(deviceId, mgr) {
|
|
813
|
+
const cycle = this._oldestPendingCycle(mgr);
|
|
814
|
+
if (!cycle) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
cycle.confirmed = true;
|
|
818
|
+
if (cycle.profileId) {
|
|
819
|
+
mgr.profileStore.learnFromCycle(cycle.profileId, [], cycle.durationMs);
|
|
820
|
+
await mgr.profileStore.save();
|
|
821
|
+
}
|
|
822
|
+
await mgr._saveState();
|
|
823
|
+
await this._updateNeedsFeedback(deviceId, mgr);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// VIS-friendly equivalent of correctCycle, resolving the written
|
|
827
|
+
// program name back to a profile id the way programOverride's own
|
|
828
|
+
// handler (_handleProgramOverride) already does.
|
|
829
|
+
async _handleFeedbackCorrectProgram(deviceId, mgr, programName) {
|
|
830
|
+
const cycle = this._oldestPendingCycle(mgr);
|
|
831
|
+
if (!cycle) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const profile = mgr.profileStore
|
|
835
|
+
.getAllProfiles()
|
|
836
|
+
.find((p) => p.name === programName);
|
|
837
|
+
if (!profile) {
|
|
838
|
+
this.log.warn(
|
|
839
|
+
`${deviceId}: feedbackCorrectProgram: unknown program "${programName}"`,
|
|
840
|
+
);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
cycle.matchedProfile = profile.name;
|
|
844
|
+
cycle.profileId = profile.id;
|
|
845
|
+
cycle.confirmed = true;
|
|
846
|
+
cycle.corrected = true;
|
|
847
|
+
const traceForLearn = mgr.getTrace(cycle.id);
|
|
848
|
+
const tracePoints = traceForLearn
|
|
849
|
+
? traceForLearn.points.map((p) => ({ ts: p.ts, watts: p.watts }))
|
|
850
|
+
: [];
|
|
851
|
+
mgr.profileStore.learnFromCycle(profile.id, tracePoints, cycle.durationMs);
|
|
852
|
+
await mgr.profileStore.save();
|
|
853
|
+
await mgr._saveState();
|
|
854
|
+
await this._updateNeedsFeedback(deviceId, mgr);
|
|
855
|
+
await this._updateOverrideStates(deviceId, mgr);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// VIS-friendly equivalent of deleteCycle, discarding the oldest
|
|
859
|
+
// pending cycle without confirming or correcting it.
|
|
860
|
+
async _handleFeedbackDelete(deviceId, mgr) {
|
|
861
|
+
const cycle = this._oldestPendingCycle(mgr);
|
|
862
|
+
if (!cycle) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const idx = mgr.cycleHistory.findIndex((c) => c.id === cycle.id);
|
|
866
|
+
if (idx === -1) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
mgr.cycleHistory.splice(idx, 1);
|
|
870
|
+
mgr.traceStore.deleteTrace(cycle.id);
|
|
871
|
+
await mgr._saveState();
|
|
872
|
+
await mgr.traceStore.save();
|
|
873
|
+
await this._updateNeedsFeedback(deviceId, mgr);
|
|
874
|
+
}
|
|
875
|
+
|
|
763
876
|
async onMessage(obj) {
|
|
764
877
|
if (!obj || !obj.command) {
|
|
765
878
|
return;
|
|
@@ -1419,16 +1532,76 @@ class WashdataAdapter extends utils.Adapter {
|
|
|
1419
1532
|
JSON.stringify(profiles.map((p) => p.name)),
|
|
1420
1533
|
true,
|
|
1421
1534
|
);
|
|
1535
|
+
// feedbackCorrectProgram's own dropdown - same program list, also
|
|
1536
|
+
// without 'auto' (correcting a past, already-finished cycle to
|
|
1537
|
+
// "automatic" makes no sense the way it does for programOverride)
|
|
1538
|
+
await this.extendObjectAsync(`${deviceId}.feedbackCorrectProgram`, {
|
|
1539
|
+
common: {
|
|
1540
|
+
states: profiles.reduce((o, p) => {
|
|
1541
|
+
o[p.name] = p.name;
|
|
1542
|
+
return o;
|
|
1543
|
+
}, {}),
|
|
1544
|
+
},
|
|
1545
|
+
});
|
|
1422
1546
|
}
|
|
1423
1547
|
|
|
1424
|
-
// ── Update "feedback needed" indicator
|
|
1425
|
-
//
|
|
1426
|
-
//
|
|
1427
|
-
//
|
|
1428
|
-
//
|
|
1548
|
+
// ── Update "feedback needed" indicator + VIS-friendly pending-
|
|
1549
|
+
// feedback data points ──────────────────────────────────
|
|
1550
|
+
// Mirrors admin/tab_m.html's renderFeedback()/updateFeedbackBadge()
|
|
1551
|
+
// exactly (cycles with !confirmed, oldest first) - that used to be
|
|
1552
|
+
// the only place this was ever computed, entirely client-side via
|
|
1553
|
+
// sendTo, which is why none of this was ever exposed as data points
|
|
1554
|
+
// that a VIS dashboard (or any other automation) could read or act
|
|
1555
|
+
// on directly.
|
|
1429
1556
|
async _updateNeedsFeedback(deviceId, mgr) {
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1557
|
+
const open = mgr
|
|
1558
|
+
.getCycleHistory()
|
|
1559
|
+
.filter((c) => !c.confirmed)
|
|
1560
|
+
.sort((a, b) => a.startTime - b.startTime);
|
|
1561
|
+
|
|
1562
|
+
this.setState(`${deviceId}.needsFeedback`, open.length > 0, true);
|
|
1563
|
+
this.setState(`${deviceId}.pendingFeedbackCount`, open.length, true);
|
|
1564
|
+
this.setState(
|
|
1565
|
+
`${deviceId}.pendingFeedback`,
|
|
1566
|
+
JSON.stringify(
|
|
1567
|
+
open.map((c) => ({
|
|
1568
|
+
id: c.id,
|
|
1569
|
+
startTime: c.startTime,
|
|
1570
|
+
program: c.matchedProfile || "",
|
|
1571
|
+
durationMin: Math.round((c.durationMs || 0) / 60000),
|
|
1572
|
+
energyWh: Math.round((c.energyWh || 0) * 100) / 100,
|
|
1573
|
+
confidencePct: Math.round((c.confidence || 0) * 100),
|
|
1574
|
+
})),
|
|
1575
|
+
),
|
|
1576
|
+
true,
|
|
1577
|
+
);
|
|
1578
|
+
|
|
1579
|
+
// Convenience fields for the single oldest pending cycle - the one
|
|
1580
|
+
// feedbackConfirm/feedbackCorrectProgram/feedbackDelete act on - so
|
|
1581
|
+
// a simple VIS text/button widget doesn't need to parse the JSON
|
|
1582
|
+
// array above just to show "what am I confirming right now".
|
|
1583
|
+
const oldest = open[0];
|
|
1584
|
+
this.setState(`${deviceId}.feedbackCycleId`, oldest ? oldest.id : "", true);
|
|
1585
|
+
this.setState(
|
|
1586
|
+
`${deviceId}.feedbackProgram`,
|
|
1587
|
+
oldest ? oldest.matchedProfile || "" : "",
|
|
1588
|
+
true,
|
|
1589
|
+
);
|
|
1590
|
+
this.setState(
|
|
1591
|
+
`${deviceId}.feedbackDuration`,
|
|
1592
|
+
oldest ? Math.round((oldest.durationMs || 0) / 60000) : 0,
|
|
1593
|
+
true,
|
|
1594
|
+
);
|
|
1595
|
+
this.setState(
|
|
1596
|
+
`${deviceId}.feedbackEnergy`,
|
|
1597
|
+
oldest ? Math.round((oldest.energyWh || 0) * 100) / 100 : 0,
|
|
1598
|
+
true,
|
|
1599
|
+
);
|
|
1600
|
+
this.setState(
|
|
1601
|
+
`${deviceId}.feedbackConfidence`,
|
|
1602
|
+
oldest ? Math.round((oldest.confidence || 0) * 100) : 0,
|
|
1603
|
+
true,
|
|
1604
|
+
);
|
|
1432
1605
|
}
|
|
1433
1606
|
|
|
1434
1607
|
// ── Callbacks ────────────────────────────────────────────────
|
|
@@ -2240,6 +2413,67 @@ class WashdataAdapter extends utils.Adapter {
|
|
|
2240
2413
|
def: false,
|
|
2241
2414
|
write: false,
|
|
2242
2415
|
},
|
|
2416
|
+
// Lernkontrolle (feedback) - VIS-friendly data points so the
|
|
2417
|
+
// confirm/correct/delete flow doesn't require the admin tab
|
|
2418
|
+
{
|
|
2419
|
+
id: "pendingFeedbackCount",
|
|
2420
|
+
name: "Cycles needing feedback",
|
|
2421
|
+
type: "number",
|
|
2422
|
+
role: "value",
|
|
2423
|
+
def: 0,
|
|
2424
|
+
write: false,
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
id: "pendingFeedback",
|
|
2428
|
+
name: "Cycles needing feedback (JSON)",
|
|
2429
|
+
type: "string",
|
|
2430
|
+
role: "json",
|
|
2431
|
+
def: "[]",
|
|
2432
|
+
write: false,
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
id: "feedbackCycleId",
|
|
2436
|
+
name: "Oldest pending cycle ID",
|
|
2437
|
+
type: "string",
|
|
2438
|
+
role: "text",
|
|
2439
|
+
def: "",
|
|
2440
|
+
write: false,
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
id: "feedbackProgram",
|
|
2444
|
+
name: "Oldest pending cycle: detected program",
|
|
2445
|
+
type: "string",
|
|
2446
|
+
role: "text",
|
|
2447
|
+
def: "",
|
|
2448
|
+
write: false,
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
id: "feedbackDuration",
|
|
2452
|
+
name: "Oldest pending cycle: duration",
|
|
2453
|
+
type: "number",
|
|
2454
|
+
role: "value",
|
|
2455
|
+
def: 0,
|
|
2456
|
+
write: false,
|
|
2457
|
+
unit: "min",
|
|
2458
|
+
},
|
|
2459
|
+
{
|
|
2460
|
+
id: "feedbackEnergy",
|
|
2461
|
+
name: "Oldest pending cycle: consumption",
|
|
2462
|
+
type: "number",
|
|
2463
|
+
role: "value",
|
|
2464
|
+
def: 0,
|
|
2465
|
+
write: false,
|
|
2466
|
+
unit: "Wh",
|
|
2467
|
+
},
|
|
2468
|
+
{
|
|
2469
|
+
id: "feedbackConfidence",
|
|
2470
|
+
name: "Oldest pending cycle: confidence",
|
|
2471
|
+
type: "number",
|
|
2472
|
+
role: "value",
|
|
2473
|
+
def: 0,
|
|
2474
|
+
write: false,
|
|
2475
|
+
unit: "%",
|
|
2476
|
+
},
|
|
2243
2477
|
// Zusatz-Info
|
|
2244
2478
|
{
|
|
2245
2479
|
id: "phase",
|
|
@@ -2315,6 +2549,32 @@ class WashdataAdapter extends utils.Adapter {
|
|
|
2315
2549
|
write: true,
|
|
2316
2550
|
read: false,
|
|
2317
2551
|
},
|
|
2552
|
+
{
|
|
2553
|
+
id: "feedbackConfirm",
|
|
2554
|
+
name: "Confirm oldest pending cycle",
|
|
2555
|
+
type: "boolean",
|
|
2556
|
+
role: "button",
|
|
2557
|
+
def: false,
|
|
2558
|
+
write: true,
|
|
2559
|
+
read: false,
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
id: "feedbackCorrectProgram",
|
|
2563
|
+
name: "Correct oldest pending cycle to this program, then confirm",
|
|
2564
|
+
type: "string",
|
|
2565
|
+
role: "text",
|
|
2566
|
+
def: "",
|
|
2567
|
+
write: true,
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
id: "feedbackDelete",
|
|
2571
|
+
name: "Delete oldest pending cycle without confirming",
|
|
2572
|
+
type: "boolean",
|
|
2573
|
+
role: "button",
|
|
2574
|
+
def: false,
|
|
2575
|
+
write: true,
|
|
2576
|
+
read: false,
|
|
2577
|
+
},
|
|
2318
2578
|
];
|
|
2319
2579
|
|
|
2320
2580
|
for (const s of states) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.laundrylens",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.38",
|
|
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",
|