iobroker.laundrylens 0.4.13 → 0.4.15

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
@@ -105,6 +105,15 @@ The defaults are tuned for Siemens iQ appliances. Detection threshold is a trade
105
105
 
106
106
  ### **WORK IN PROGRESS**
107
107
 
108
+ ### 0.4.15 (2026-08-31)
109
+ - Fix: notification-language cache was module-level state (compact-mode leak risk between instances sharing a process) - moved to an instance field, same fix class already applied to ProfileStore's MIN_CONFIDENCE
110
+ - Fix: two devices accidentally sharing the same power sensor now log a warning instead of one silently losing updates
111
+ - Fix: `durationTolerance`/`matchIntervalMin`/`matchPersist`/`offDelayMin` normalization used a bare `||` default (same falsy-zero bug class fixed for other fields in 0.2.3) - aligned to the strict check
112
+ - Removed unused dead code (`msgBlockTimer`, never assigned anywhere)
113
+
114
+ ### 0.4.14 (2026-08-30)
115
+ - Replaced the low-resolution 128x128 adapter icon with a new, sharper 256x256 version
116
+
108
117
  ### 0.4.13 (2026-08-27)
109
118
  - Fix: the `running` state's name was still German ("Läuft") on existing installations, even though the code has said "Running" for a long time - it was missing from the state-name migration list added in 0.4.12 (found via a fresh object dump). Extended `tests/test_english_only.js` to check that every state id is covered by the migration, catching this kind of gap automatically going forward
110
119
 
package/admin/icon.png CHANGED
Binary file
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "laundrylens",
4
- "version": "0.4.13",
4
+ "version": "0.4.15",
5
5
  "news": {
6
+ "0.4.15": {
7
+ "en": "Fixes from a full adapter review: the notification-language cache was module-level state, which could leak between two instances of this adapter sharing one process under compact mode - moved to an instance field. Two devices accidentally configured with the same power sensor now log a warning instead of one silently stopping updates. Cleaned up a small inconsistency in default-value handling for a few settings, and removed unused dead code.",
8
+ "de": "Fixes aus einem vollständigen Adapter-Review: Der Sprach-Cache für Benachrichtigungen war modulweiter State, der unter Compact-Mode zwischen zwei Instanzen dieses Adapters im selben Prozess hätte überlaufen können - jetzt ein Instanz-Feld. Zwei Geräte, die versehentlich denselben Stromsensor nutzen, loggen jetzt eine Warnung statt dass eines stillschweigend keine Updates mehr bekommt. Kleine Inkonsistenz bei Default-Werten einiger Einstellungen bereinigt, toten Code entfernt.",
9
+ "ru": "Исправления по итогам полного ревью адаптера: кэш языка уведомлений был состоянием уровня модуля, что могло привести к утечке между двумя инстанциями этого адаптера в одном процессе при compact-режиме - перенесено в поле инстанции. Два устройства, случайно настроенных на один датчик мощности, теперь логируют предупреждение вместо того, чтобы одно из них молча переставало получать обновления. Устранена небольшая несогласованность в обработке значений по умолчанию, удалён неиспользуемый код.",
10
+ "pt": "Correções de uma revisão completa do adaptador: o cache de idioma de notificação era estado em nível de módulo, podendo vazar entre duas instâncias deste adaptador compartilhando um processo em modo compact - movido para um campo da instância. Dois dispositivos configurados acidentalmente com o mesmo sensor de energia agora registram um aviso em vez de um parar silenciosamente de receber atualizações. Corrigida uma pequena inconsistência no tratamento de valores padrão e removido código morto.",
11
+ "nl": "Fixes uit een volledige adapterreview: de taalcache voor meldingen was module-brede state, die onder compact mode kon lekken tussen twee instanties van deze adapter in hetzelfde proces - nu een instantieveld. Twee apparaten die per ongeluk dezelfde stroomsensor gebruiken, loggen nu een waarschuwing in plaats van dat er één stilletjes geen updates meer krijgt. Kleine inconsistentie bij standaardwaarden opgelost, ongebruikte code verwijderd.",
12
+ "fr": "Corrections issues d'une revue complète de l'adaptateur : le cache de langue des notifications était un état au niveau du module, pouvant fuiter entre deux instances de cet adaptateur partageant un processus en mode compact - déplacé vers un champ d'instance. Deux appareils configurés par erreur avec le même capteur de puissance enregistrent désormais un avertissement au lieu que l'un cesse silencieusement de recevoir des mises à jour. Petite incohérence corrigée dans les valeurs par défaut, code mort supprimé.",
13
+ "it": "Correzioni da una revisione completa dell'adattatore: la cache della lingua delle notifiche era stato a livello di modulo, che poteva perdersi tra due istanze di questo adattatore che condividono un processo in modalità compact - spostata in un campo dell'istanza. Due dispositivi configurati per errore con lo stesso sensore di potenza ora registrano un avviso invece che uno smetta silenziosamente di ricevere aggiornamenti. Risolta una piccola incoerenza nei valori predefiniti, rimosso codice inutilizzato.",
14
+ "es": "Correcciones de una revisión completa del adaptador: la caché de idioma de notificaciones era estado a nivel de módulo, que podía filtrarse entre dos instancias de este adaptador que comparten un proceso en modo compact - movida a un campo de instancia. Dos dispositivos configurados accidentalmente con el mismo sensor de energía ahora registran una advertencia en lugar de que uno deje de recibir actualizaciones silenciosamente. Corregida una pequeña inconsistencia en los valores predeterminados, eliminado código muerto.",
15
+ "pl": "Poprawki z pełnego przeglądu adaptera: pamięć podręczna języka powiadomień była stanem na poziomie modułu, co mogło powodować wyciek między dwiema instancjami tego adaptera współdzielącymi jeden proces w trybie compact - przeniesiono do pola instancji. Dwa urządzenia przypadkowo skonfigurowane z tym samym czujnikiem mocy teraz logują ostrzeżenie zamiast tego, by jedno z nich po cichu przestało otrzymywać aktualizacje. Naprawiono drobną niespójność w wartościach domyślnych, usunięto martwy kod.",
16
+ "uk": "Виправлення за результатами повного огляду адаптера: кеш мови сповіщень був станом рівня модуля, що могло призвести до витоку між двома інстанціями цього адаптера в одному процесі в режимі compact - перенесено в поле інстанції. Два пристрої, випадково налаштовані на один датчик потужності, тепер логують попередження замість того, щоб один із них тихо переставав отримувати оновлення. Виправлено дрібну невідповідність у значеннях за замовчуванням, видалено невикористовуваний код.",
17
+ "zh-cn": "全面审查适配器后的修复:通知语言缓存原为模块级状态,在 compact 模式下共享同一进程的两个适配器实例之间可能发生泄漏——现已改为实例字段。两个设备如果意外配置了相同的功率传感器,现在会记录警告,而不是其中一个默默停止接收更新。修复了部分设置默认值处理中的小的不一致,移除了未使用的死代码。"
18
+ },
19
+ "0.4.14": {
20
+ "en": "Replaced the low-resolution 128x128 adapter icon with a new, sharper 256x256 version.",
21
+ "de": "Das niedrig aufgelöste 128x128-Adapter-Icon durch eine neue, schärfere 256x256-Version ersetzt.",
22
+ "ru": "Заменена иконка адаптера с низким разрешением 128x128 на новую, более чёткую версию 256x256.",
23
+ "pt": "Substituído o ícone do adaptador de baixa resolução 128x128 por uma nova versão mais nítida de 256x256.",
24
+ "nl": "Het laagresolutie 128x128-adapterpictogram vervangen door een nieuwe, scherpere 256x256-versie.",
25
+ "fr": "Remplacement de l'icône basse résolution 128x128 de l'adaptateur par une nouvelle version plus nette en 256x256.",
26
+ "it": "Sostituita l'icona dell'adattatore a bassa risoluzione 128x128 con una nuova versione più nitida a 256x256.",
27
+ "es": "Se reemplazó el icono del adaptador de baja resolución 128x128 por una nueva versión más nítida de 256x256.",
28
+ "pl": "Zastąpiono ikonę adaptera o niskiej rozdzielczości 128x128 nową, ostrzejszą wersją 256x256.",
29
+ "uk": "Замінено іконку адаптера низької роздільної здатності 128x128 на нову, чіткішу версію 256x256.",
30
+ "zh-cn": "将低分辨率的 128x128 适配器图标替换为更清晰的新版 256x256 图标。"
31
+ },
6
32
  "0.4.13": {
7
33
  "en": "Fix: the \"running\" state's name was still German (\"Läuft\") on existing installations, even though the code has said \"Running\" for a long time - it was simply missing from the state-name migration list added in 0.4.12. Found via a fresh object dump. Added a test that checks every state id is covered by the migration, to catch this kind of gap automatically going forward.",
8
34
  "de": "Fix: Der Name des Status \"running\" war auf bestehenden Installationen noch Deutsch (\"Läuft\"), obwohl der Code schon lange \"Running\" sagt - er fehlte schlicht in der in 0.4.12 hinzugefügten State-Namen-Migrationsliste. Gefunden über einen frischen Object-Dump. Test ergänzt, der prüft, dass jede State-ID von der Migration abgedeckt ist, damit diese Art von Lücke künftig automatisch auffällt.",
@@ -67,32 +93,6 @@
67
93
  "pl": "Poprawka: w suszarkach doczesywanie przeciw gnieceniu po sygnale dźwiękowym mogło zostać błędnie rozpoznane jako rozpoczęcie nowego cyklu, nawet gdy zapisano wzorzec referencyjny, ponieważ ochrona blokady była powiązana z polem wyboru \"Ignoruj zapisany wzorzec przeciw gnieceniu\", a nie z istnieniem wzorca. Doprecyzowano etykietę i tekst pomocy tego pola wyboru, ponieważ łatwo było je opacznie zrozumieć. Aplikacja pokazuje teraz również podpowiedź po zapisaniu wzorca referencyjnego, jeśli to pole wyboru nadal trzeba wyłączyć, aby wzorzec był rzeczywiście używany.",
68
94
  "uk": "Виправлення: на сушарках залишковий рух проти зминання після сигналу міг помилково розпізнаватися як початок нового циклу, навіть якщо шаблон було збережено, оскільки захист блокування був прив'язаний до прапорця «Ігнорувати збережений шаблон захисту від зминання», а не до наявності самого шаблону. Уточнено підпис і текст довідки цього прапорця, оскільки їх було легко зрозуміти навпаки. Додаток тепер також показує підказку після збереження шаблону, якщо цей прапорець ще потрібно вимкнути, щоб шаблон дійсно використовувався.",
69
95
  "zh-cn": "修复:在烘干机上,提示音后的防皱回转动作可能被误判为新周期开始,即使已录制参考模式也是如此,因为锁定保护与\"忽略已保存的防皱模式\"复选框绑定,而不是与模式是否存在绑定。已澄清该复选框的标签和帮助文本,因为原文容易被理解反。保存参考模式后,如果仍需关闭该复选框才能真正使用该模式,应用现在也会显示提示。"
70
- },
71
- "0.4.8": {
72
- "en": "No functional changes. Migrated admin/i18n from the old per-language folder structure (i18n/<lang>/translations.json) to the short format (i18n/<lang>.json) - repository checker suggestion S5601.",
73
- "de": "Keine funktionalen Änderungen. admin/i18n von der alten Ordnerstruktur pro Sprache (i18n/<lang>/translations.json) auf das Kurzformat (i18n/<lang>.json) umgestellt - Repository-Checker-Vorschlag S5601.",
74
- "ru": "Функциональных изменений нет. admin/i18n переведён со старой структуры папок для каждого языка (i18n/<lang>/translations.json) на короткий формат (i18n/<lang>.json) - предложение repository checker S5601.",
75
- "pt": "Sem alterações funcionais. admin/i18n migrado da antiga estrutura de pastas por idioma (i18n/<lang>/translations.json) para o formato curto (i18n/<lang>.json) - sugestão do repository checker S5601.",
76
- "nl": "Geen functionele wijzigingen. admin/i18n gemigreerd van de oude mapstructuur per taal (i18n/<lang>/translations.json) naar het korte formaat (i18n/<lang>.json) - suggestie van de repository checker S5601.",
77
- "fr": "Aucun changement fonctionnel. admin/i18n migré de l'ancienne structure de dossiers par langue (i18n/<lang>/translations.json) vers le format court (i18n/<lang>.json) - suggestion du repository checker S5601.",
78
- "it": "Nessuna modifica funzionale. admin/i18n migrato dalla vecchia struttura di cartelle per lingua (i18n/<lang>/translations.json) al formato breve (i18n/<lang>.json) - suggerimento del repository checker S5601.",
79
- "es": "Sin cambios funcionales. admin/i18n migrado de la antigua estructura de carpetas por idioma (i18n/<lang>/translations.json) al formato corto (i18n/<lang>.json) - sugerencia del repository checker S5601.",
80
- "pl": "Brak zmian funkcjonalnych. admin/i18n przeniesiono ze starej struktury folderów dla każdego języka (i18n/<lang>/translations.json) na format krótki (i18n/<lang>.json) - sugestia repository checkera S5601.",
81
- "uk": "Функціональних змін немає. admin/i18n переведено зі старої структури папок для кожної мови (i18n/<lang>/translations.json) на короткий формат (i18n/<lang>.json) - пропозиція repository checker S5601.",
82
- "zh-cn": "无功能变更。将 admin/i18n 从旧的按语言分文件夹结构(i18n/<lang>/translations.json)迁移为简短格式(i18n/<lang>.json)——仓库检查器建议 S5601。"
83
- },
84
- "0.4.7": {
85
- "en": "Fix: on devices going through the generic phase-history branch (currently dryers), recorded phase timestamps could show wildly wrong values (often large negative numbers) because the internal phase-history buffer was never cleared between cycles and kept accumulating entries from every past cycle since adapter start. Fix: repository checker finding (invalid state role \"value.percent\" on cycleProgress, corrected to \"value\").",
86
- "de": "Fix: Bei Geräten, die über den generischen Phasenverlauf-Zweig laufen (aktuell Trockner), konnten aufgezeichnete Phasen-Zeitstempel völlig falsche Werte zeigen (oft große negative Zahlen), weil der interne Phasenverlauf-Puffer nie zwischen Zyklen geleert wurde und sich Einträge aus jedem früheren Zyklus seit Adapterstart ansammelten. Fix: Repository-Checker-Finding (ungültige State-Rolle „value.percent\" bei cycleProgress, korrigiert auf „value\").",
87
- "ru": "Исправление: на устройствах, использующих универсальную ветку истории фаз (сейчас это сушильные машины), записанные временные метки фаз могли содержать совершенно неверные значения (часто большие отрицательные числа), поскольку внутренний буфер истории фаз никогда не очищался между циклами и накапливал записи всех прошлых циклов с момента запуска адаптера. Исправление: замечание repository checker (недопустимая роль состояния \"value.percent\" у cycleProgress, исправлено на \"value\").",
88
- "pt": "Correção: em dispositivos que usam o ramo genérico de histórico de fases (atualmente secadoras), os carimbos de data/hora de fase registrados podiam mostrar valores completamente errados (frequentemente números negativos grandes), pois o buffer interno de histórico de fases nunca era limpo entre ciclos e acumulava entradas de todos os ciclos anteriores desde o início do adaptador. Correção: apontamento do repository checker (role de estado inválida \"value.percent\" em cycleProgress, corrigida para \"value\").",
89
- "nl": "Fix: bij apparaten die de generieke fasegeschiedenis-tak gebruiken (momenteel drogers) konden opgeslagen fase-tijdstempels compleet verkeerde waarden tonen (vaak grote negatieve getallen), omdat de interne fasegeschiedenis-buffer nooit werd geleegd tussen cycli en zich vulde met vermeldingen van elke vorige cyclus sinds de start van de adapter. Fix: bevinding van de repository checker (ongeldige state-rol \"value.percent\" bij cycleProgress, gecorrigeerd naar \"value\").",
90
- "fr": "Correction : sur les appareils utilisant la branche générique d'historique de phases (actuellement les sèche-linge), les horodatages de phase enregistrés pouvaient afficher des valeurs complètement erronées (souvent de grands nombres négatifs), car le buffer interne d'historique de phases n'était jamais réinitialisé entre les cycles et accumulait les entrées de tous les cycles précédents depuis le démarrage de l'adaptateur. Correction : remarque du repository checker (rôle d'état invalide \"value.percent\" sur cycleProgress, corrigé en \"value\").",
91
- "it": "Correzione: sui dispositivi che utilizzano il ramo generico della cronologia delle fasi (attualmente asciugatrici), i timestamp di fase registrati potevano mostrare valori completamente errati (spesso grandi numeri negativi), poiché il buffer interno della cronologia delle fasi non veniva mai svuotato tra i cicli e accumulava voci di ogni ciclo precedente dall'avvio dell'adattatore. Correzione: rilievo del repository checker (ruolo di stato non valido \"value.percent\" su cycleProgress, corretto in \"value\").",
92
- "es": "Corrección: en dispositivos que usan la rama genérica de historial de fases (actualmente secadoras), las marcas de tiempo de fase registradas podían mostrar valores completamente erróneos (a menudo números negativos grandes), porque el búfer interno de historial de fases nunca se vaciaba entre ciclos y acumulaba entradas de cada ciclo anterior desde el inicio del adaptador. Corrección: hallazgo del repository checker (rol de estado inválido \"value.percent\" en cycleProgress, corregido a \"value\").",
93
- "pl": "Poprawka: na urządzeniach korzystających z ogólnej gałęzi historii faz (obecnie suszarki) zarejestrowane znaczniki czasu faz mogły pokazywać zupełnie błędne wartości (często duże liczby ujemne), ponieważ wewnętrzny bufor historii faz nigdy nie był czyszczony między cyklami i gromadził wpisy z każdego poprzedniego cyklu od uruchomienia adaptera. Poprawka: uwaga repository checkera (nieprawidłowa rola stanu \"value.percent\" dla cycleProgress, poprawiona na \"value\").",
94
- "uk": "Виправлення: на пристроях, що використовують загальну гілку історії фаз (наразі сушарки), записані часові мітки фаз могли показувати абсолютно неправильні значення (часто великі від'ємні числа), оскільки внутрішній буфер історії фаз ніколи не очищався між циклами і накопичував записи з кожного попереднього циклу з моменту запуску адаптера. Виправлення: зауваження repository checker (недійсна роль стану \"value.percent\" для cycleProgress, виправлено на \"value\").",
95
- "zh-cn": "修复:在使用通用阶段历史分支的设备上(目前是烘干机),记录的阶段时间戳可能显示完全错误的值(通常是很大的负数),因为内部阶段历史缓冲区从未在各个周期之间清空,而是持续累积自适配器启动以来每个历史周期的条目。修复:仓库检查器(repository checker)发现的问题(cycleProgress 上的无效状态角色 \"value.percent\",已更正为 \"value\")。"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/main.js CHANGED
@@ -9,40 +9,46 @@ const { WashDataManager } = require("./lib/washDataManager");
9
9
  // Reuses the same admin/i18n/<lang>.json files as the admin UI, so the
10
10
  // server-side default message templates stay in sync with what the admin
11
11
  // form shows as its placeholder text - both come from one source.
12
- let _notifTranslations = null;
13
- let _notifLang = null;
12
+ //
13
+ // Cached on the adapter INSTANCE (adapter._notifTranslations/_notifLang),
14
+ // not module-level: io-package.json declares "compact": true, and under
15
+ // compact mode two instances of this same adapter (e.g. one per device)
16
+ // can share one Node.js process/require() cache. A module-level `let`
17
+ // here would leak one instance's language into another's - same bug
18
+ // class already found and fixed for ProfileStore's MIN_CONFIDENCE.
14
19
 
15
20
  /**
16
- * Loads (once, cached) the translation dictionary matching the ioBroker
17
- * system's configured language, falling back to English.
21
+ * Loads (once per instance, cached) the translation dictionary matching
22
+ * the ioBroker system's configured language, falling back to English.
18
23
  *
19
- * @param {object} adapter – the adapter instance (for getForeignObjectAsync/logging)
24
+ * @param {object} adapter – the adapter instance (for getForeignObjectAsync/logging/caching)
20
25
  * @returns {Promise<object>} – key → translated string
21
26
  */
22
27
  async function loadNotifTranslations(adapter) {
23
- if (_notifTranslations) {
24
- return _notifTranslations;
28
+ if (adapter._notifTranslations) {
29
+ return adapter._notifTranslations;
25
30
  }
31
+ let lang;
26
32
  try {
27
33
  const sysConfig = await adapter.getForeignObjectAsync("system.config");
28
- _notifLang =
29
- (sysConfig && sysConfig.common && sysConfig.common.language) || "en";
34
+ lang = (sysConfig && sysConfig.common && sysConfig.common.language) || "en";
30
35
  } catch {
31
- _notifLang = "en";
36
+ lang = "en";
32
37
  }
38
+ adapter._notifLang = lang;
33
39
  try {
34
- const file = path.join(__dirname, "admin", "i18n", `${_notifLang}.json`);
35
- _notifTranslations = JSON.parse(fs.readFileSync(file, "utf8"));
40
+ const file = path.join(__dirname, "admin", "i18n", `${lang}.json`);
41
+ adapter._notifTranslations = JSON.parse(fs.readFileSync(file, "utf8"));
36
42
  } catch {
37
43
  // Fall back to English if the detected language has no translation file
38
44
  try {
39
45
  const enFile = path.join(__dirname, "admin", "i18n", "en.json");
40
- _notifTranslations = JSON.parse(fs.readFileSync(enFile, "utf8"));
46
+ adapter._notifTranslations = JSON.parse(fs.readFileSync(enFile, "utf8"));
41
47
  } catch {
42
- _notifTranslations = {};
48
+ adapter._notifTranslations = {};
43
49
  }
44
50
  }
45
- return _notifTranslations;
51
+ return adapter._notifTranslations;
46
52
  }
47
53
 
48
54
  // Historical German default texts (this adapter was German-only before
@@ -143,17 +149,35 @@ class WashdataAdapter extends utils.Adapter {
143
149
  ? 5
144
150
  : 2,
145
151
  offDelayMin:
146
- src.offDelayMin ||
147
- (src.deviceType === "dryer"
148
- ? 8
149
- : src.deviceType === "washer_dryer"
150
- ? 10
151
- : src.deviceType === "dishwasher"
152
- ? 8
153
- : 5),
154
- durationTolerance: src.durationTolerance || 0.2,
155
- matchIntervalMin: src.matchIntervalMin || 5,
156
- matchPersist: src.matchPersist || 3,
152
+ src.offDelayMin !== undefined &&
153
+ src.offDelayMin !== null &&
154
+ src.offDelayMin !== ""
155
+ ? Number(src.offDelayMin)
156
+ : src.deviceType === "dryer"
157
+ ? 8
158
+ : src.deviceType === "washer_dryer"
159
+ ? 10
160
+ : src.deviceType === "dishwasher"
161
+ ? 8
162
+ : 5,
163
+ durationTolerance:
164
+ src.durationTolerance !== undefined &&
165
+ src.durationTolerance !== null &&
166
+ src.durationTolerance !== ""
167
+ ? Number(src.durationTolerance)
168
+ : 0.2,
169
+ matchIntervalMin:
170
+ src.matchIntervalMin !== undefined &&
171
+ src.matchIntervalMin !== null &&
172
+ src.matchIntervalMin !== ""
173
+ ? Number(src.matchIntervalMin)
174
+ : 5,
175
+ matchPersist:
176
+ src.matchPersist !== undefined &&
177
+ src.matchPersist !== null &&
178
+ src.matchPersist !== ""
179
+ ? Number(src.matchPersist)
180
+ : 3,
157
181
  autoConfirmThreshold:
158
182
  src.autoConfirmThreshold !== undefined &&
159
183
  src.autoConfirmThreshold !== null
@@ -263,6 +287,13 @@ class WashdataAdapter extends utils.Adapter {
263
287
  const deviceList = this._getDeviceConfig();
264
288
  if (deviceList.length === 0) {
265
289
  this.log.warn("No device configured.");
290
+ // info.connection here does not represent an external/cloud
291
+ // connection (there is none - connectionType is "local" and this
292
+ // adapter only subscribes to already-existing ioBroker states). It
293
+ // reflects "the adapter instance started up without errors", which
294
+ // is true even with zero devices configured (a normal, valid state
295
+ // e.g. right after a fresh install) - not an error/disconnected
296
+ // condition, so this intentionally stays true rather than false.
266
297
  this.setState("info.connection", true, true);
267
298
  return;
268
299
  }
@@ -309,6 +340,16 @@ class WashdataAdapter extends utils.Adapter {
309
340
 
310
341
  await manager.start();
311
342
  this.managers[deviceCfg.deviceId] = manager;
343
+ if (
344
+ this.sensorToDevice[deviceCfg.powerId] &&
345
+ this.sensorToDevice[deviceCfg.powerId] !== deviceCfg.deviceId
346
+ ) {
347
+ this.log.warn(
348
+ `${deviceCfg.name}: power sensor ${deviceCfg.powerId} is already used by ` +
349
+ `"${this.sensorToDevice[deviceCfg.powerId]}" - only one device can react to a given ` +
350
+ `sensor, the previous device will stop receiving updates from it`,
351
+ );
352
+ }
312
353
  this.sensorToDevice[deviceCfg.powerId] = deviceCfg.deviceId;
313
354
  await this.subscribeForeignStatesAsync(deviceCfg.powerId);
314
355
 
@@ -1369,9 +1410,6 @@ class WashdataAdapter extends utils.Adapter {
1369
1410
  async _onCycleFinished(deviceId, cycle) {
1370
1411
  // Reset notification state
1371
1412
  if (this._notifState && this._notifState[deviceId]) {
1372
- if (this._notifState[deviceId].msgBlockTimer) {
1373
- clearTimeout(this._notifState[deviceId].msgBlockTimer);
1374
- }
1375
1413
  this._notifState[deviceId] = {};
1376
1414
  }
1377
1415
  // phaseHistory im Zyklus speichern – nur wenn Post-hoc keine Phasen berechnet hat
@@ -1543,7 +1581,7 @@ class WashdataAdapter extends utils.Adapter {
1543
1581
  ),
1544
1582
  };
1545
1583
  const endTimeStr = new Date(newFinishTime).toLocaleTimeString(
1546
- _notifLang || "en",
1584
+ this._notifLang || "en",
1547
1585
  {
1548
1586
  hour: "2-digit",
1549
1587
  minute: "2-digit",
@@ -1553,7 +1591,7 @@ class WashdataAdapter extends utils.Adapter {
1553
1591
  state.lastFinishTime &&
1554
1592
  Math.abs(state.lastFinishTime - newFinishTime) > 60000
1555
1593
  ? new Date(state.lastFinishTime).toLocaleTimeString(
1556
- _notifLang || "en",
1594
+ this._notifLang || "en",
1557
1595
  {
1558
1596
  hour: "2-digit",
1559
1597
  minute: "2-digit",
@@ -1689,13 +1727,16 @@ class WashdataAdapter extends utils.Adapter {
1689
1727
  ),
1690
1728
  };
1691
1729
  const startTime = cycle.startTime
1692
- ? new Date(cycle.startTime).toLocaleTimeString(_notifLang || "en", {
1693
- hour: "2-digit",
1694
- minute: "2-digit",
1695
- })
1730
+ ? new Date(cycle.startTime).toLocaleTimeString(
1731
+ this._notifLang || "en",
1732
+ {
1733
+ hour: "2-digit",
1734
+ minute: "2-digit",
1735
+ },
1736
+ )
1696
1737
  : "";
1697
1738
  const endTime = Date.now()
1698
- ? new Date().toLocaleTimeString(_notifLang || "en", {
1739
+ ? new Date().toLocaleTimeString(this._notifLang || "en", {
1699
1740
  hour: "2-digit",
1700
1741
  minute: "2-digit",
1701
1742
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.laundrylens",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
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",