iobroker.poolcontrol 1.3.17 → 1.3.19

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
@@ -188,6 +188,21 @@ New features are added regularly – please refer to the changelog.
188
188
  ---
189
189
 
190
190
  ## Changelog
191
+ ### 1.3.19 (2026-05-13)
192
+
193
+ - Added runtime self-healing for missed pump start events
194
+ - Stabilized runtime and circulation calculations for delayed or filtered pump switch updates
195
+ - Fixed a rare synchronization issue where circulation counting could stop although pump live values were still available
196
+ - Improved internal runtime synchronization
197
+
198
+ ### 1.3.18 (2026-05-11)
199
+
200
+ - Fixed incorrect date display in the pH, ORP and TDS areas.
201
+ - Time states with `value.time` are now stored as numeric timestamps instead of localized date strings.
202
+ - Improved compatibility with ioBroker/Admin date handling.
203
+ - Added backward-compatible handling for previously stored German date strings.
204
+ - Kept history JSON output unchanged with readable date strings for users and VIS displays.
205
+
191
206
  ### 1.3.17 (2026-05-11)
192
207
 
193
208
  - Fixed release/upload issue from v1.3.16.
@@ -264,19 +279,6 @@ New features are added regularly – please refer to the changelog.
264
279
  - Improved runtime timer cleanup and reset protection
265
280
  - Improved speech logging for skipped and cooldown-limited announcements
266
281
 
267
- ### 1.3.14 (2026-05-08)
268
-
269
- - Improved runtime handling and persistence
270
- - Fixed season runtime calculation using the correct season state
271
- - Unified live runtime calculations for total, today and season values
272
- - Added numeric runtime second states for robust persistence and recovery
273
- - Converted runtime timers to adapter-managed timers for improved ioBroker compatibility
274
-
275
- ### 1.3.13 (2026-05-08)
276
- - (copilot) Adapter requires node.js >= 22 now
277
- - Fixed invalid `common.installedFrom` entry in `io-package.json`
278
- - Added German and English function overview documentation
279
-
280
282
  ## Support
281
283
  - [ioBroker Forum](https://forum.iobroker.net/)
282
284
  - [GitHub Issues](https://github.com/DasBo1975/ioBroker.poolcontrol/issues)
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "poolcontrol",
4
- "version": "1.3.17",
4
+ "version": "1.3.19",
5
5
  "news": {
6
+ "1.3.19": {
7
+ "en": "Added runtime self-healing for missed pump start events. Stabilized runtime and circulation calculations for delayed or filtered pump switch updates. Fixed a rare synchronization issue where circulation counting could stop although pump live values were still available. Improved internal runtime synchronization.",
8
+ "de": "Runtime-Selbstheilung für verpasste Pumpenstart-Ereignisse ergänzt. Laufzeit- und Umwälzberechnung bei verzögerten oder gefilterten Pumpenschalter-Updates stabilisiert. Seltenen Synchronisationsfehler behoben, bei dem die Umwälzberechnung trotz aktiver Pumpen-Livewerte stehen bleiben konnte. Interne Runtime-Synchronisierung verbessert.",
9
+ "ru": "Добавлено самовосстановление во время выполнения для пропущенных событий запуска насоса. Стабилизированные расчеты времени работы и циркуляции для отложенных или отфильтрованных обновлений переключателей насосов. Исправлена ​​редкая проблема синхронизации, из-за которой подсчет циркуляции мог остановиться, хотя текущие значения насоса все еще были доступны. Улучшена внутренняя синхронизация времени выполнения.",
10
+ "pt": "Adicionada autocorreção em tempo de execução para eventos de inicialização de bomba perdidos. Cálculos de tempo de execução e circulação estabilizados para atualizações de interruptores de bomba filtrados ou atrasados. Foi corrigido um raro problema de sincronização em que a contagem de circulação poderia parar embora os valores ativos da bomba ainda estivessem disponíveis. Sincronização de tempo de execução interna aprimorada.",
11
+ "nl": "Zelfherstel tijdens runtime toegevoegd voor gemiste pompstartgebeurtenissen. Gestabiliseerde looptijd- en circulatieberekeningen voor vertraagde of gefilterde updates van pompschakelaars. Er is een zeldzaam synchronisatieprobleem opgelost waarbij het tellen van de circulatie kon stoppen, hoewel de live-waarden van de pomp nog steeds beschikbaar waren. Verbeterde interne runtime-synchronisatie.",
12
+ "fr": "Ajout de l'auto-réparation du temps d'exécution pour les événements de démarrage de pompe manqués. Calculs de temps de fonctionnement et de circulation stabilisés pour les mises à jour retardées ou filtrées des interrupteurs de pompe. Correction d'un problème de synchronisation rare où le comptage de circulation pouvait s'arrêter même si les valeurs en direct de la pompe étaient toujours disponibles. Synchronisation d'exécution interne améliorée.",
13
+ "it": "Aggiunta l'autoriparazione del tempo di esecuzione per gli eventi di avvio della pompa mancati. Calcoli di autonomia e circolazione stabilizzati per aggiornamenti ritardati o filtrati dell'interruttore della pompa. Risolto un raro problema di sincronizzazione per cui il conteggio della circolazione poteva interrompersi anche se i valori in tempo reale della pompa erano ancora disponibili. Sincronizzazione runtime interna migliorata.",
14
+ "es": "Se agregó autorreparación en tiempo de ejecución para eventos de inicio de bomba perdidos. Cálculos de circulación y tiempo de funcionamiento estabilizados para actualizaciones retrasadas o filtradas del interruptor de la bomba. Se solucionó un problema de sincronización poco común por el cual el conteo de circulación podía detenerse aunque los valores activos de la bomba todavía estaban disponibles. Sincronización interna del tiempo de ejecución mejorada.",
15
+ "pl": "Dodano funkcję samonaprawy w czasie wykonywania w przypadku zdarzeń związanych z nieudanym uruchomieniem pompy. Stabilizowane obliczenia czasu pracy i cyrkulacji dla opóźnionych lub filtrowanych aktualizacji przełączników pomp. Naprawiono rzadki problem z synchronizacją, w wyniku którego zliczanie cyrkulacji mogło zostać zatrzymane, mimo że wartości bieżące pompy były nadal dostępne. Ulepszona wewnętrzna synchronizacja środowiska wykonawczego.",
16
+ "uk": "Додано самовідновлення під час виконання для пропущених подій запуску насоса. Стабілізований час роботи та обчислення циркуляції для відкладених або фільтрованих оновлень перемикача насоса. Виправлено рідкісну проблему синхронізації, через яку підрахунок циркуляції міг припинитися, хоча поточні значення насоса все ще були доступні. Покращена внутрішня синхронізація часу виконання.",
17
+ "zh-cn": "为错过的泵启动事件添加了运行时自我修复功能。延迟或过滤泵开关更新的稳定运行时间和循环计算。修复了一个罕见的同步问题,即尽管泵的实时值仍然可用,但循环计数可能会停止。改进了内部运行时同步。"
18
+ },
19
+ "1.3.18": {
20
+ "en": "Fixed incorrect date display for pH, ORP and TDS time states by storing value.time states as numeric timestamps.",
21
+ "de": "Falsche Datumsanzeige für pH-, ORP- und TDS-Zeitzustände behoben, indem value.time-Zustände als numerische Zeitstempel gespeichert wurden.",
22
+ "ru": "Исправлено неправильное отображение даты для состояний времени pH, ОВП и TDS за счет сохранения состояний value.time в виде числовых меток времени.",
23
+ "pt": "Corrigida a exibição incorreta de data para estados de tempo de pH, ORP e TDS, armazenando estados de valor.tempo como carimbos de data/hora numéricos.",
24
+ "nl": "Foutieve datumweergave voor pH-, ORP- en TDS-tijdstatussen opgelost door value.time-statussen op te slaan als numerieke tijdstempels.",
25
+ "fr": "Correction de l'affichage incorrect de la date pour les états temporels pH, ORP et TDS en stockant les états value.time sous forme d'horodatages numériques.",
26
+ "it": "Risolto il problema con la visualizzazione errata della data per gli stati temporali pH, ORP e TDS memorizzando gli stati value.time come timestamp numerici.",
27
+ "es": "Se corrigió la visualización de fecha incorrecta para los estados de tiempo de pH, ORP y TDS al almacenar los estados de valor.hora como marcas de tiempo numéricas.",
28
+ "pl": "Naprawiono nieprawidłowe wyświetlanie daty dla stanów czasowych pH, ​​ORP i TDS poprzez przechowywanie stanów wartość.czas jako numeryczne znaczniki czasu.",
29
+ "uk": "Виправлено неправильне відображення дати для часових станів pH, ORP і TDS шляхом збереження станів value.time як числових позначок часу.",
30
+ "zh-cn": "通过将 value.time 状态存储为数字时间戳,修复了 pH、ORP 和 TDS 时间状态的不正确日期显示。"
31
+ },
6
32
  "1.3.17": {
7
33
  "en": "Fixed release packaging issue from v1.3.16. Improved speech system stability, stabilized runtime persistence, reduced repeated solar notifications, fixed circulation calculation in time mode and prepared new ORP/Redox chemistry analysis area.",
8
34
  "de": "Release-/Uploadproblem aus v1.3.16 behoben. Sprachsystem verbessert, Runtime-Persistenz stabilisiert, wiederholte Solarbenachrichtigungen reduziert, Umwälzberechnung im Zeitmodus korrigiert und neuer ORP-/Redox-Chemiebereich vorbereitet.",
@@ -41,32 +67,6 @@
41
67
  "pl": "Dodano obsługę schładzania źródła mowy, ulepszoną kontrolę mowy w czasie słonecznym/czasem, dodano trwałe drugie stany czasu wykonania, ulepszoną logikę przywracania w czasie wykonywania i przekonwertowano liczniki czasu działania na liczniki czasu adaptera ioBroker.",
42
68
  "uk": "Додано обробку перезарядки джерела мовлення, покращено керування мовленням із сонячними променями/часом, додано постійні другі стани під час виконання, покращено логіку відновлення під час виконання та перетворено таймери часу виконання на таймери адаптера ioBroker.",
43
69
  "zh-cn": "添加了语音源冷却处理、改进了太阳/时间语音控制、添加了持久运行时第二状态、改进了运行时恢复逻辑并将运行时计时器转换为 ioBroker 适配器计时器。"
44
- },
45
- "1.3.14": {
46
- "en": "Improved runtime handling and persistence. Fixed season runtime calculation to use the correct season state, improved live runtime updates, added robust numeric runtime second states for safer persistence and recovery, and converted runtime timers to adapter-managed timers for better ioBroker compatibility.",
47
- "de": "Laufzeit-Handling und Persistenz verbessert. Saisonlaufzeit nutzt jetzt den korrekten Saison-Status, Live-Laufzeiten wurden vereinheitlicht, robuste Sekunden-States fuer Laufzeiten zur sicheren Wiederherstellung hinzugefuegt und Runtime-Timer auf adapterverwaltete Timer fuer bessere ioBroker-Kompatibilitaet umgestellt.",
48
- "ru": "Улучшена обработка и сохранение во время выполнения. Исправлен расчет времени выполнения сезона для использования правильного состояния сезона, улучшены обновления времени выполнения в режиме реального времени, добавлены надежные числовые значения второго состояния времени выполнения для более безопасного сохранения и восстановления, а также преобразованы таймеры времени выполнения в таймеры, управляемые адаптером, для лучшей совместимости с ioBroker.",
49
- "pt": "Melhor manipulação e persistência do tempo de execução. Cálculo de tempo de execução de temporada corrigido para usar o estado de temporada correto, atualizações de tempo de execução ao vivo aprimoradas, segundos estados de tempo de execução numéricos robustos adicionados para persistência e recuperação mais seguras e temporizadores de tempo de execução convertidos em temporizadores gerenciados por adaptador para melhor compatibilidade com ioBroker.",
50
- "nl": "Verbeterde runtime-afhandeling en persistentie. Vaste seizoensruntimeberekening om de juiste seizoensstatus te gebruiken, verbeterde live runtime-updates, robuuste numerieke runtime-secondestatussen toegevoegd voor veiliger persistentie en herstel, en geconverteerde runtime-timers naar door adapter beheerde timers voor betere ioBroker-compatibiliteit.",
51
- "fr": "Gestion de l'exécution et persistance améliorées. Correction du calcul de la durée d'exécution de la saison pour utiliser l'état de saison correct, mises à jour d'exécution en direct améliorées, ajout de seconds états d'exécution numériques robustes pour une persistance et une récupération plus sûres, et conversion des minuteries d'exécution en minuteries gérées par l'adaptateur pour une meilleure compatibilité ioBroker.",
52
- "it": "Gestione e persistenza del runtime migliorate. Risolto il problema con il calcolo del runtime della stagione per utilizzare lo stato della stagione corretto, aggiornamenti migliorati del runtime in tempo reale, aggiunti robusti secondi stati di runtime numerici per persistenza e ripristino più sicuri e timer di runtime convertiti in timer gestiti dall'adattatore per una migliore compatibilità con ioBroker.",
53
- "es": "Manejo y persistencia del tiempo de ejecución mejorados. Se corrigió el cálculo del tiempo de ejecución de la temporada para usar el estado correcto de la temporada, se mejoraron las actualizaciones del tiempo de ejecución en vivo, se agregaron segundos estados de tiempo de ejecución numéricos sólidos para una persistencia y recuperación más seguras y se convirtieron los temporizadores de tiempo de ejecución en temporizadores administrados por adaptador para una mejor compatibilidad con ioBroker.",
54
- "pl": "Poprawiona obsługa i trwałość środowiska wykonawczego. Naprawiono obliczanie czasu wykonania sezonu w celu wykorzystania prawidłowego stanu sezonu, ulepszono aktualizacje czasu działania na żywo, dodano solidne numeryczne drugie stany czasu działania dla bezpieczniejszej trwałości i odzyskiwania oraz przekonwertowano liczniki czasu działania na zegary zarządzane przez adapter dla lepszej kompatybilności z ioBroker.",
55
- "uk": "Покращена обробка та збереження часу виконання. Виправлено розрахунок часу виконання сезону для використання правильного стану сезону, покращено поточні оновлення середовища виконання, додано надійні численні секунди часу виконання для безпечнішого збереження та відновлення, а також перетворено таймери часу виконання на таймери, керовані адаптером, для кращої сумісності з ioBroker.",
56
- "zh-cn": "改进了运行时处理和持久性。修复了季节运行时计算以使用正确的季节状态,改进了实时运行时更新,添加了强大的数字运行时第二状态以实现更安全的持久性和恢复,并将运行时计时器转换为适配器管理的计时器以实现更好的 ioBroker 兼容性。"
57
- },
58
- "1.3.13": {
59
- "en": "Added German and English function overview documentation. Fixed invalid common.installedFrom entry in io-package.json.",
60
- "de": "Deutsche und englische Funktionsübersicht ergänzt. Ungültigen common.installedFrom-Eintrag in der io-package.json korrigiert.",
61
- "ru": "Добавлена ​​обзорная документация по функциям на немецком и английском языках. Исправлена ​​неверная запись common.installedFrom в io-package.json.",
62
- "pt": "Adicionada documentação de visão geral das funções em alemão e inglês. Corrigida entrada common.installedFrom inválida em io-package.json.",
63
- "nl": "Duitse en Engelse functieoverzichtdocumentatie toegevoegd. Ongeldige common.installedFrom-vermelding in io-package.json opgelost.",
64
- "fr": "Ajout d'une documentation de présentation des fonctions en allemand et en anglais. Correction de l'entrée common.installedFrom invalide dans io-package.json.",
65
- "it": "Aggiunta la documentazione panoramica delle funzioni in tedesco e inglese. Risolto il problema con la voce common.installedFrom non valida in io-package.json.",
66
- "es": "Se agregó documentación de descripción general de funciones en alemán e inglés. Se corrigió la entrada common.installedFrom no válida en io-package.json.",
67
- "pl": "Dodano dokumentację przeglądu funkcji w języku niemieckim i angielskim. Naprawiono nieprawidłowy wpis common.installedFrom w io-package.json.",
68
- "uk": "Додано документацію щодо огляду функцій німецькою та англійською мовами. Виправлено недійсний запис common.installedFrom у io-package.json.",
69
- "zh-cn": "添加了德语和英语功能概述文档。修复了 io-package.json 中无效的 common.installedFrom 条目。"
70
70
  }
71
71
  },
72
72
  "titleLang": {
@@ -238,7 +238,7 @@ const chemistryOrpHelper = {
238
238
  const now = new Date();
239
239
  const value = Number(rawValue);
240
240
 
241
- await this._setString('chemistry.orp.input.last_value_at', this._formatDateTime(now));
241
+ await this._setNumber('chemistry.orp.input.last_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
242
242
 
243
243
  if (source === 'manual') {
244
244
  await this._setBool('chemistry.orp.input.source_valid', true);
@@ -277,7 +277,7 @@ const chemistryOrpHelper = {
277
277
  recommendation: measurementAllowed.recommendation,
278
278
  });
279
279
  await this._setString('chemistry.orp.debug.last_reason', reason || measurementAllowed.reason);
280
- await this._setString('chemistry.orp.debug.last_update', this._formatDateTime(now));
280
+ await this._setNumber('chemistry.orp.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
281
281
  return;
282
282
  }
283
283
 
@@ -296,7 +296,7 @@ const chemistryOrpHelper = {
296
296
  await this._writeOutputs(value, phReference, trend, evaluation);
297
297
 
298
298
  await this._setString('chemistry.orp.debug.last_reason', reason || 'value_processed');
299
- await this._setString('chemistry.orp.debug.last_update', this._formatDateTime(now));
299
+ await this._setNumber('chemistry.orp.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
300
300
  },
301
301
 
302
302
  async _checkMeasurementAllowed(now) {
@@ -385,22 +385,18 @@ const chemistryOrpHelper = {
385
385
 
386
386
  async _updateLastValues(value, now) {
387
387
  const lastValid = await this._readNumberOrNull('chemistry.orp.input.last_valid_value');
388
- const lastValidAt = await this._readString('chemistry.orp.input.last_valid_value_at');
388
+ const lastValidAt = await this._readTimestampOrNull('chemistry.orp.input.last_valid_value_at'); // FIX: accept numeric ms timestamps and legacy German strings.
389
389
 
390
390
  if (lastValid !== null && lastValidAt) {
391
391
  await this._setNumber('chemistry.orp.input.previous_value', lastValid);
392
- await this._setString('chemistry.orp.input.previous_value_at', lastValidAt);
392
+ await this._setNumber('chemistry.orp.input.previous_value_at', lastValidAt); // FIX: pass stored timestamp through unchanged.
393
393
 
394
- const previousDate = this._parseGermanDateTime(lastValidAt);
395
-
396
- if (previousDate) {
397
- const minutes = Math.max(0, Math.round((now.getTime() - previousDate.getTime()) / 60000));
398
- await this._setNumber('chemistry.orp.input.minutes_since_previous_value', minutes);
399
- }
394
+ const minutes = Math.max(0, Math.round((now.getTime() - lastValidAt) / 60000)); // FIX: calculate from numeric ms timestamp.
395
+ await this._setNumber('chemistry.orp.input.minutes_since_previous_value', minutes);
400
396
  }
401
397
 
402
398
  await this._setNumber('chemistry.orp.input.last_valid_value', value);
403
- await this._setString('chemistry.orp.input.last_valid_value_at', this._formatDateTime(now));
399
+ await this._setNumber('chemistry.orp.input.last_valid_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
404
400
  },
405
401
 
406
402
  async _updateHistory(value, now, forceSample) {
@@ -430,13 +426,15 @@ const chemistryOrpHelper = {
430
426
  await this._setNumber('chemistry.orp.history.samples_count', samples.length);
431
427
 
432
428
  if (samples.length) {
433
- await this._setString(
429
+ await this._setNumber(
430
+ // FIX: history value.time states store sample timestamps, not readable strings.
434
431
  'chemistry.orp.history.oldest_sample_at',
435
- samples[0].time || this._formatDateTime(new Date(samples[0].ts)),
432
+ Number(samples[0].ts),
436
433
  );
437
- await this._setString(
434
+ await this._setNumber(
435
+ // FIX: history value.time states store sample timestamps, not readable strings.
438
436
  'chemistry.orp.history.newest_sample_at',
439
- samples[samples.length - 1].time || this._formatDateTime(new Date(samples[samples.length - 1].ts)),
437
+ Number(samples[samples.length - 1].ts),
440
438
  );
441
439
  }
442
440
 
@@ -544,15 +542,15 @@ const chemistryOrpHelper = {
544
542
 
545
543
  async _writeTrend(trend) {
546
544
  await this._setNumber('chemistry.orp.trend.reference_24h_value', trend.ref24h ? trend.ref24h.value : 0);
547
- await this._setString('chemistry.orp.trend.reference_24h_at', trend.ref24h ? trend.ref24h.time : '');
545
+ await this._setNumber('chemistry.orp.trend.reference_24h_at', trend.ref24h ? trend.ref24h.ts : 0); // FIX
548
546
  await this._setNumber('chemistry.orp.trend.delta_24h', trend.ref24h ? trend.delta24h : 0);
549
547
 
550
548
  await this._setNumber('chemistry.orp.trend.reference_7d_value', trend.ref7d ? trend.ref7d.value : 0);
551
- await this._setString('chemistry.orp.trend.reference_7d_at', trend.ref7d ? trend.ref7d.time : '');
549
+ await this._setNumber('chemistry.orp.trend.reference_7d_at', trend.ref7d ? trend.ref7d.ts : 0); // FIX
552
550
  await this._setNumber('chemistry.orp.trend.delta_7d', trend.ref7d ? trend.delta7d : 0);
553
551
 
554
552
  await this._setNumber('chemistry.orp.trend.reference_30d_value', trend.ref30d ? trend.ref30d.value : 0);
555
- await this._setString('chemistry.orp.trend.reference_30d_at', trend.ref30d ? trend.ref30d.time : '');
553
+ await this._setNumber('chemistry.orp.trend.reference_30d_at', trend.ref30d ? trend.ref30d.ts : 0); // FIX
556
554
  await this._setNumber('chemistry.orp.trend.delta_30d', trend.ref30d ? trend.delta30d : 0);
557
555
 
558
556
  await this._setString('chemistry.orp.trend.direction', trend.direction);
@@ -692,7 +690,8 @@ const chemistryOrpHelper = {
692
690
  await this._setString('chemistry.orp.evaluation.recommendation', I18n.translate('ORP evaluation is disabled.'));
693
691
  await this._setBool('chemistry.orp.evaluation.action_required', false);
694
692
  await this._setString('chemistry.orp.debug.last_reason', reason);
695
- await this._setString('chemistry.orp.debug.last_update', this._formatDateTime(new Date()));
693
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
694
+ await this._setNumber('chemistry.orp.debug.last_update', now.getTime()); // FIX
696
695
  },
697
696
 
698
697
  async _writeInvalid(recommendation, reason) {
@@ -705,7 +704,8 @@ const chemistryOrpHelper = {
705
704
  await this._setString('chemistry.orp.evaluation.recommendation', recommendation);
706
705
  await this._setBool('chemistry.orp.evaluation.action_required', false);
707
706
  await this._setString('chemistry.orp.debug.last_reason', reason);
708
- await this._setString('chemistry.orp.debug.last_update', this._formatDateTime(new Date()));
707
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
708
+ await this._setNumber('chemistry.orp.debug.last_update', now.getTime()); // FIX
709
709
  },
710
710
 
711
711
  async _readString(id) {
@@ -725,6 +725,19 @@ const chemistryOrpHelper = {
725
725
  return Number.isFinite(value) ? value : null;
726
726
  },
727
727
 
728
+ async _readTimestampOrNull(id) {
729
+ const state = await this.adapter.getStateAsync(id);
730
+ const value = state?.val;
731
+ const numberValue = Number(value);
732
+
733
+ if (Number.isFinite(numberValue) && numberValue > 0) {
734
+ return numberValue; // FIX: numeric value.time timestamps are used directly.
735
+ }
736
+
737
+ const legacyDate = this._parseGermanDateTime(value); // FIX: keep backward compatibility for old string values.
738
+ return legacyDate ? legacyDate.getTime() : null;
739
+ },
740
+
728
741
  async _readBoolean(id) {
729
742
  const state = await this.adapter.getStateAsync(id);
730
743
  return !!state?.val;
@@ -215,7 +215,8 @@ const chemistryPhHelper = {
215
215
  await this._setBool('chemistry.ph.measurement.allowed', false);
216
216
  await this._setString('chemistry.ph.measurement.ignored_reason', 'disabled');
217
217
  await this._setString('chemistry.ph.debug.last_reason', reason || 'disabled');
218
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(new Date()));
218
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
219
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX
219
220
  return;
220
221
  }
221
222
 
@@ -234,7 +235,8 @@ const chemistryPhHelper = {
234
235
  await this._setBool('chemistry.ph.measurement.allowed', false);
235
236
  await this._setString('chemistry.ph.measurement.ignored_reason', 'source_disabled');
236
237
  await this._setString('chemistry.ph.debug.last_reason', reason || 'source_disabled');
237
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(new Date()));
238
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
239
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX
238
240
  return;
239
241
  }
240
242
 
@@ -263,7 +265,8 @@ const chemistryPhHelper = {
263
265
  await this._setBool('chemistry.ph.measurement.allowed', false);
264
266
  await this._setString('chemistry.ph.measurement.ignored_reason', 'missing_source_state');
265
267
  await this._setString('chemistry.ph.debug.last_reason', reason || 'missing_source_state');
266
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(new Date()));
268
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
269
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX
267
270
  return;
268
271
  }
269
272
 
@@ -287,7 +290,8 @@ const chemistryPhHelper = {
287
290
  await this._setBool('chemistry.ph.measurement.allowed', false);
288
291
  await this._setString('chemistry.ph.measurement.ignored_reason', 'source_read_error');
289
292
  await this._setString('chemistry.ph.debug.last_reason', `source_read_error: ${err.message}`);
290
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(new Date()));
293
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
294
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX
291
295
  return;
292
296
  }
293
297
 
@@ -307,7 +311,8 @@ const chemistryPhHelper = {
307
311
  await this._setBool('chemistry.ph.measurement.allowed', false);
308
312
  await this._setString('chemistry.ph.measurement.ignored_reason', 'source_not_found');
309
313
  await this._setString('chemistry.ph.debug.last_reason', reason || 'source_not_found');
310
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(new Date()));
314
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
315
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX
311
316
  return;
312
317
  }
313
318
 
@@ -320,7 +325,8 @@ const chemistryPhHelper = {
320
325
  await this._setBool('chemistry.ph.evaluation.action_required', false);
321
326
  await this._setString('chemistry.ph.measurement.ignored_reason', 'unknown_source_mode');
322
327
  await this._setString('chemistry.ph.debug.last_reason', reason || 'unknown_source_mode');
323
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(new Date()));
328
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
329
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX
324
330
  } catch (err) {
325
331
  this.adapter.log.warn(`[chemistryPhHelper] Evaluation failed: ${err.message}`);
326
332
  }
@@ -330,7 +336,7 @@ const chemistryPhHelper = {
330
336
  const now = new Date();
331
337
  const value = Number(rawValue);
332
338
 
333
- await this._setString('chemistry.ph.input.last_value_at', this._formatDateTime(now));
339
+ await this._setNumber('chemistry.ph.input.last_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
334
340
 
335
341
  if (source === 'manual') {
336
342
  await this._setBool('chemistry.ph.input.source_valid', true);
@@ -355,7 +361,7 @@ const chemistryPhHelper = {
355
361
  await this._setBool('chemistry.ph.measurement.allowed', false);
356
362
  await this._setString('chemistry.ph.measurement.ignored_reason', 'invalid_value');
357
363
  await this._setString('chemistry.ph.debug.last_reason', reason || 'invalid_value');
358
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(now));
364
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
359
365
  return;
360
366
  }
361
367
 
@@ -368,7 +374,7 @@ const chemistryPhHelper = {
368
374
  await this._setBool('chemistry.ph.measurement.allowed', false);
369
375
  await this._setString('chemistry.ph.measurement.ignored_reason', measurementAllowed.reason);
370
376
  await this._setString('chemistry.ph.debug.last_reason', reason || measurementAllowed.reason);
371
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(now));
377
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
372
378
  return;
373
379
  }
374
380
 
@@ -385,7 +391,7 @@ const chemistryPhHelper = {
385
391
  await this._writeOutputs(value, trend, evaluation);
386
392
 
387
393
  await this._setString('chemistry.ph.debug.last_reason', reason || 'value_processed');
388
- await this._setString('chemistry.ph.debug.last_update', this._formatDateTime(now));
394
+ await this._setNumber('chemistry.ph.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
389
395
  },
390
396
 
391
397
  async _checkMeasurementAllowed(now) {
@@ -442,22 +448,18 @@ const chemistryPhHelper = {
442
448
 
443
449
  async _updateLastValues(value, now) {
444
450
  const lastValid = await this._readNumberOrNull('chemistry.ph.input.last_valid_value');
445
- const lastValidAt = await this._readString('chemistry.ph.input.last_valid_value_at');
451
+ const lastValidAt = await this._readTimestampOrNull('chemistry.ph.input.last_valid_value_at'); // FIX: accept numeric ms timestamps and legacy German strings.
446
452
 
447
453
  if (lastValid !== null && lastValidAt) {
448
454
  await this._setNumber('chemistry.ph.input.previous_value', lastValid);
449
- await this._setString('chemistry.ph.input.previous_value_at', lastValidAt);
455
+ await this._setNumber('chemistry.ph.input.previous_value_at', lastValidAt); // FIX: pass stored timestamp through unchanged.
450
456
 
451
- const previousDate = this._parseGermanDateTime(lastValidAt);
452
-
453
- if (previousDate) {
454
- const minutes = Math.max(0, Math.round((now.getTime() - previousDate.getTime()) / 60000));
455
- await this._setNumber('chemistry.ph.input.minutes_since_previous_value', minutes);
456
- }
457
+ const minutes = Math.max(0, Math.round((now.getTime() - lastValidAt) / 60000)); // FIX: calculate from numeric ms timestamp.
458
+ await this._setNumber('chemistry.ph.input.minutes_since_previous_value', minutes);
457
459
  }
458
460
 
459
461
  await this._setNumber('chemistry.ph.input.last_valid_value', value);
460
- await this._setString('chemistry.ph.input.last_valid_value_at', this._formatDateTime(now));
462
+ await this._setNumber('chemistry.ph.input.last_valid_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
461
463
  },
462
464
 
463
465
  async _updateHistory(value, now, forceSample) {
@@ -487,13 +489,15 @@ const chemistryPhHelper = {
487
489
  await this._setNumber('chemistry.ph.history.samples_count', samples.length);
488
490
 
489
491
  if (samples.length) {
490
- await this._setString(
492
+ await this._setNumber(
493
+ // FIX: history value.time states store sample timestamps, not readable strings.
491
494
  'chemistry.ph.history.oldest_sample_at',
492
- samples[0].time || this._formatDateTime(new Date(samples[0].ts)),
495
+ Number(samples[0].ts),
493
496
  );
494
- await this._setString(
497
+ await this._setNumber(
498
+ // FIX: history value.time states store sample timestamps, not readable strings.
495
499
  'chemistry.ph.history.newest_sample_at',
496
- samples[samples.length - 1].time || this._formatDateTime(new Date(samples[samples.length - 1].ts)),
500
+ Number(samples[samples.length - 1].ts),
497
501
  );
498
502
  }
499
503
 
@@ -601,15 +605,15 @@ const chemistryPhHelper = {
601
605
 
602
606
  async _writeTrend(trend) {
603
607
  await this._setNumber('chemistry.ph.trend.reference_24h_value', trend.ref24h ? trend.ref24h.value : 0);
604
- await this._setString('chemistry.ph.trend.reference_24h_at', trend.ref24h ? trend.ref24h.time : '');
608
+ await this._setNumber('chemistry.ph.trend.reference_24h_at', trend.ref24h ? trend.ref24h.ts : 0); // FIX
605
609
  await this._setNumber('chemistry.ph.trend.delta_24h', trend.ref24h ? trend.delta24h : 0);
606
610
 
607
611
  await this._setNumber('chemistry.ph.trend.reference_7d_value', trend.ref7d ? trend.ref7d.value : 0);
608
- await this._setString('chemistry.ph.trend.reference_7d_at', trend.ref7d ? trend.ref7d.time : '');
612
+ await this._setNumber('chemistry.ph.trend.reference_7d_at', trend.ref7d ? trend.ref7d.ts : 0); // FIX
609
613
  await this._setNumber('chemistry.ph.trend.delta_7d', trend.ref7d ? trend.delta7d : 0);
610
614
 
611
615
  await this._setNumber('chemistry.ph.trend.reference_30d_value', trend.ref30d ? trend.ref30d.value : 0);
612
- await this._setString('chemistry.ph.trend.reference_30d_at', trend.ref30d ? trend.ref30d.time : '');
616
+ await this._setNumber('chemistry.ph.trend.reference_30d_at', trend.ref30d ? trend.ref30d.ts : 0); // FIX
613
617
  await this._setNumber('chemistry.ph.trend.delta_30d', trend.ref30d ? trend.delta30d : 0);
614
618
 
615
619
  await this._setString('chemistry.ph.trend.direction', trend.direction);
@@ -869,6 +873,19 @@ const chemistryPhHelper = {
869
873
  return Number.isFinite(value) ? value : null;
870
874
  },
871
875
 
876
+ async _readTimestampOrNull(id) {
877
+ const state = await this.adapter.getStateAsync(id);
878
+ const value = state?.val;
879
+ const numberValue = Number(value);
880
+
881
+ if (Number.isFinite(numberValue) && numberValue > 0) {
882
+ return numberValue; // FIX: numeric value.time timestamps are used directly.
883
+ }
884
+
885
+ const legacyDate = this._parseGermanDateTime(value); // FIX: keep backward compatibility for old string values.
886
+ return legacyDate ? legacyDate.getTime() : null;
887
+ },
888
+
872
889
  async _readJsonArray(id) {
873
890
  const state = await this.adapter.getStateAsync(id);
874
891
 
@@ -246,7 +246,7 @@ const chemistryTdsHelper = {
246
246
  const now = new Date();
247
247
  const value = Number(rawValue);
248
248
 
249
- await this._setString('chemistry.tds.input.last_value_at', this._formatDateTime(now));
249
+ await this._setNumber('chemistry.tds.input.last_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
250
250
 
251
251
  if (source === 'manual') {
252
252
  await this._setBool('chemistry.tds.input.source_valid', true);
@@ -278,7 +278,7 @@ const chemistryTdsHelper = {
278
278
  await this._setString('chemistry.tds.evaluation.recommendation', measurementAllowed.recommendation);
279
279
  await this._setBool('chemistry.tds.evaluation.action_required', false);
280
280
  await this._setString('chemistry.tds.debug.last_reason', reason || measurementAllowed.reason);
281
- await this._setString('chemistry.tds.debug.last_update', this._formatDateTime(now));
281
+ await this._setNumber('chemistry.tds.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
282
282
  return;
283
283
  }
284
284
 
@@ -297,7 +297,7 @@ const chemistryTdsHelper = {
297
297
  await this._writeOutputs(value, reference, trend, evaluation);
298
298
 
299
299
  await this._setString('chemistry.tds.debug.last_reason', reason || 'value_processed');
300
- await this._setString('chemistry.tds.debug.last_update', this._formatDateTime(now));
300
+ await this._setNumber('chemistry.tds.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
301
301
  },
302
302
 
303
303
  async _checkMeasurementAllowed(now) {
@@ -357,22 +357,18 @@ const chemistryTdsHelper = {
357
357
 
358
358
  async _updateLastValues(value, now) {
359
359
  const lastValid = await this._readNumberOrNull('chemistry.tds.input.last_valid_value');
360
- const lastValidAt = await this._readString('chemistry.tds.input.last_valid_value_at');
360
+ const lastValidAt = await this._readTimestampOrNull('chemistry.tds.input.last_valid_value_at'); // FIX: accept numeric ms timestamps and legacy German strings.
361
361
 
362
362
  if (lastValid !== null && lastValidAt) {
363
363
  await this._setNumber('chemistry.tds.input.previous_value', lastValid);
364
- await this._setString('chemistry.tds.input.previous_value_at', lastValidAt);
364
+ await this._setNumber('chemistry.tds.input.previous_value_at', lastValidAt); // FIX: pass stored timestamp through unchanged.
365
365
 
366
- const previousDate = this._parseGermanDateTime(lastValidAt);
367
-
368
- if (previousDate) {
369
- const minutes = Math.max(0, Math.round((now.getTime() - previousDate.getTime()) / 60000));
370
- await this._setNumber('chemistry.tds.input.minutes_since_previous_value', minutes);
371
- }
366
+ const minutes = Math.max(0, Math.round((now.getTime() - lastValidAt) / 60000)); // FIX: calculate from numeric ms timestamp.
367
+ await this._setNumber('chemistry.tds.input.minutes_since_previous_value', minutes);
372
368
  }
373
369
 
374
370
  await this._setNumber('chemistry.tds.input.last_valid_value', value);
375
- await this._setString('chemistry.tds.input.last_valid_value_at', this._formatDateTime(now));
371
+ await this._setNumber('chemistry.tds.input.last_valid_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
376
372
  },
377
373
 
378
374
  async _updateHistory(value, now, forceSample) {
@@ -402,13 +398,15 @@ const chemistryTdsHelper = {
402
398
  await this._setNumber('chemistry.tds.history.samples_count', samples.length);
403
399
 
404
400
  if (samples.length) {
405
- await this._setString(
401
+ await this._setNumber(
402
+ // FIX: history value.time states store sample timestamps, not readable strings.
406
403
  'chemistry.tds.history.oldest_sample_at',
407
- samples[0].time || this._formatDateTime(new Date(samples[0].ts)),
404
+ Number(samples[0].ts),
408
405
  );
409
- await this._setString(
406
+ await this._setNumber(
407
+ // FIX: history value.time states store sample timestamps, not readable strings.
410
408
  'chemistry.tds.history.newest_sample_at',
411
- samples[samples.length - 1].time || this._formatDateTime(new Date(samples[samples.length - 1].ts)),
409
+ Number(samples[samples.length - 1].ts),
412
410
  );
413
411
  }
414
412
 
@@ -424,7 +422,7 @@ const chemistryTdsHelper = {
424
422
  initialSet = true;
425
423
 
426
424
  await this._setNumber('chemistry.tds.reference.initial_value', initialValue);
427
- await this._setString('chemistry.tds.reference.initial_value_at', this._formatDateTime(now));
425
+ await this._setNumber('chemistry.tds.reference.initial_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
428
426
  await this._setBool('chemistry.tds.reference.initial_set', true);
429
427
  }
430
428
 
@@ -450,11 +448,11 @@ const chemistryTdsHelper = {
450
448
  const now = new Date();
451
449
 
452
450
  await this._setNumber('chemistry.tds.reference.initial_value', currentValue);
453
- await this._setString('chemistry.tds.reference.initial_value_at', this._formatDateTime(now));
451
+ await this._setNumber('chemistry.tds.reference.initial_value_at', now.getTime()); // FIX: value.time uses numeric ms timestamp.
454
452
  await this._setBool('chemistry.tds.reference.initial_set', true);
455
453
  await this._setNumber('chemistry.tds.reference.delta_since_initial', 0);
456
454
  await this._setString('chemistry.tds.debug.last_reason', 'reference_reset');
457
- await this._setString('chemistry.tds.debug.last_update', this._formatDateTime(now));
455
+ await this._setNumber('chemistry.tds.debug.last_update', now.getTime()); // FIX: value.time uses numeric ms timestamp.
458
456
 
459
457
  this._scheduleEvaluation('reference_reset', 500);
460
458
  },
@@ -560,15 +558,15 @@ const chemistryTdsHelper = {
560
558
 
561
559
  async _writeTrend(trend) {
562
560
  await this._setNumber('chemistry.tds.trend.reference_24h_value', trend.ref24h ? trend.ref24h.value : 0);
563
- await this._setString('chemistry.tds.trend.reference_24h_at', trend.ref24h ? trend.ref24h.time : '');
561
+ await this._setNumber('chemistry.tds.trend.reference_24h_at', trend.ref24h ? trend.ref24h.ts : 0); // FIX
564
562
  await this._setNumber('chemistry.tds.trend.delta_24h', trend.ref24h ? trend.delta24h : 0);
565
563
 
566
564
  await this._setNumber('chemistry.tds.trend.reference_7d_value', trend.ref7d ? trend.ref7d.value : 0);
567
- await this._setString('chemistry.tds.trend.reference_7d_at', trend.ref7d ? trend.ref7d.time : '');
565
+ await this._setNumber('chemistry.tds.trend.reference_7d_at', trend.ref7d ? trend.ref7d.ts : 0); // FIX
568
566
  await this._setNumber('chemistry.tds.trend.delta_7d', trend.ref7d ? trend.delta7d : 0);
569
567
 
570
568
  await this._setNumber('chemistry.tds.trend.reference_30d_value', trend.ref30d ? trend.ref30d.value : 0);
571
- await this._setString('chemistry.tds.trend.reference_30d_at', trend.ref30d ? trend.ref30d.time : '');
569
+ await this._setNumber('chemistry.tds.trend.reference_30d_at', trend.ref30d ? trend.ref30d.ts : 0); // FIX
572
570
  await this._setNumber('chemistry.tds.trend.delta_30d', trend.ref30d ? trend.delta30d : 0);
573
571
 
574
572
  await this._setString('chemistry.tds.trend.direction', trend.direction);
@@ -718,7 +716,8 @@ const chemistryTdsHelper = {
718
716
  await this._setString('chemistry.tds.evaluation.recommendation', I18n.translate('TDS evaluation is disabled.'));
719
717
  await this._setBool('chemistry.tds.evaluation.action_required', false);
720
718
  await this._setString('chemistry.tds.debug.last_reason', reason);
721
- await this._setString('chemistry.tds.debug.last_update', this._formatDateTime(new Date()));
719
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
720
+ await this._setNumber('chemistry.tds.debug.last_update', now.getTime()); // FIX
722
721
  },
723
722
 
724
723
  async _writeInvalid(recommendation, reason) {
@@ -730,7 +729,8 @@ const chemistryTdsHelper = {
730
729
  await this._setString('chemistry.tds.evaluation.recommendation', recommendation);
731
730
  await this._setBool('chemistry.tds.evaluation.action_required', false);
732
731
  await this._setString('chemistry.tds.debug.last_reason', reason);
733
- await this._setString('chemistry.tds.debug.last_update', this._formatDateTime(new Date()));
732
+ const now = new Date(); // FIX: write value.time as numeric Unix timestamp in milliseconds.
733
+ await this._setNumber('chemistry.tds.debug.last_update', now.getTime()); // FIX
734
734
  },
735
735
 
736
736
  async _readString(id) {
@@ -750,6 +750,19 @@ const chemistryTdsHelper = {
750
750
  return Number.isFinite(value) ? value : null;
751
751
  },
752
752
 
753
+ async _readTimestampOrNull(id) {
754
+ const state = await this.adapter.getStateAsync(id);
755
+ const value = state?.val;
756
+ const numberValue = Number(value);
757
+
758
+ if (Number.isFinite(numberValue) && numberValue > 0) {
759
+ return numberValue; // FIX: numeric value.time timestamps are used directly.
760
+ }
761
+
762
+ const legacyDate = this._parseGermanDateTime(value); // FIX: keep backward compatibility for old string values.
763
+ return legacyDate ? legacyDate.getTime() : null;
764
+ },
765
+
753
766
  async _readBoolean(id) {
754
767
  const state = await this.adapter.getStateAsync(id);
755
768
  return !!state?.val;
@@ -21,6 +21,7 @@ const runtimeHelper = {
21
21
  restoreTimer: null, // FIX: ioBroker timer for delayed restore
22
22
  resetTimer: null,
23
23
  liveTimer: null, // Timer für Live-Updates
24
+ syncTimer: null, // FIX: Timer zur Selbstheilung bei verpasstem Pumpenstart/-stopp
24
25
 
25
26
  /**
26
27
  * Initialisiert den Runtime-Helper.
@@ -54,6 +55,9 @@ const runtimeHelper = {
54
55
  // Tagesreset einplanen
55
56
  this._scheduleDailyReset();
56
57
 
58
+ // FIX: Start sync timer to recover from missed pump start/stop events
59
+ this._startSyncTimer();
60
+
57
61
  // Erst nach Restore einmal berechnen
58
62
  this._updateStates();
59
63
 
@@ -62,6 +66,10 @@ const runtimeHelper = {
62
66
  .catch(err => {
63
67
  this.adapter.log.warn(`[runtimeHelper] Restore failed: ${err.message}`);
64
68
  this._scheduleDailyReset();
69
+
70
+ // FIX: Start sync timer to recover from missed pump start/stop events
71
+ this._startSyncTimer();
72
+
65
73
  this._updateStates();
66
74
  this.adapter.log.debug('[runtimeHelper] Initialized (without restore)');
67
75
  });
@@ -112,7 +120,7 @@ const runtimeHelper = {
112
120
  this.lastOn = Date.now();
113
121
  this.startCountToday += 1;
114
122
 
115
- // Live-Timer starten (jede Minute)
123
+ // Live-Timer starten (alle 10 Sekunden)
116
124
  this._startLiveTimer();
117
125
 
118
126
  // Start sofort in State schreiben
@@ -389,6 +397,43 @@ const runtimeHelper = {
389
397
  }
390
398
  },
391
399
 
400
+ _startSyncTimer() {
401
+ if (this.syncTimer) {
402
+ this.adapter.clearInterval(this.syncTimer);
403
+ }
404
+
405
+ this.syncTimer = this.adapter.setInterval(() => this._syncPumpRuntimeState(), 30 * 1000);
406
+ this.adapter.log.debug('[runtimeHelper] FIX: Runtime sync timer started (checks every 30 seconds)');
407
+ },
408
+
409
+ async _syncPumpRuntimeState() {
410
+ try {
411
+ const pumpState = await this.adapter.getStateAsync('pump.pump_switch');
412
+ const pumpOn = pumpState?.val === true;
413
+
414
+ if (pumpOn && (!this.isRunning || !this.lastOn)) {
415
+ this.adapter.log.debug(
416
+ '[runtimeHelper] FIX: Pump switch is ON but runtime was not running. Synchronizing runtime start.',
417
+ );
418
+ await this.handleStateChange('pump.pump_switch', { val: true, ack: true });
419
+ return;
420
+ }
421
+
422
+ // FIX: Stop synchronization is intentionally not handled here.
423
+ // A missed stop event could otherwise cause duplicate runtime accounting
424
+ // during parallel async stop handling. Normal stop events are still handled
425
+ // through handleStateChange().
426
+ // if (!pumpOn && (this.isRunning || this.lastOn)) {
427
+ // this.adapter.log.debug(
428
+ // '[runtimeHelper] FIX: Pump switch is OFF but runtime was still running. Synchronizing runtime stop.',
429
+ // );
430
+ // await this.handleStateChange('pump.pump_switch', { val: false, ack: true });
431
+ // }
432
+ } catch (err) {
433
+ this.adapter.log.warn(`[runtimeHelper] Error while synchronizing pump runtime state: ${err.message}`);
434
+ }
435
+ },
436
+
392
437
  cleanup() {
393
438
  if (this.restoreTimer) {
394
439
  this.adapter.clearTimeout(this.restoreTimer);
@@ -400,6 +445,11 @@ const runtimeHelper = {
400
445
  this.resetTimer = null;
401
446
  }
402
447
 
448
+ if (this.syncTimer) {
449
+ this.adapter.clearInterval(this.syncTimer);
450
+ this.syncTimer = null;
451
+ }
452
+
403
453
  this._stopLiveTimer();
404
454
  },
405
455
  };
@@ -214,11 +214,11 @@ async function createChemistryOrpStates(adapter) {
214
214
  en: 'Readable date and time when the last ORP value was received.',
215
215
  de: 'Lesbares Datum und Uhrzeit, wann der letzte ORP-Wert empfangen wurde.',
216
216
  },
217
- type: 'string',
217
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
218
218
  role: 'value.time',
219
219
  read: true,
220
220
  write: false,
221
- def: '',
221
+ def: 0, // FIX: numeric timestamp default for value.time.
222
222
  persist: true,
223
223
  });
224
224
 
@@ -251,11 +251,11 @@ async function createChemistryOrpStates(adapter) {
251
251
  en: 'Readable date and time of the last valid ORP value.',
252
252
  de: 'Lesbares Datum und Uhrzeit des letzten gültigen ORP-Werts.',
253
253
  },
254
- type: 'string',
254
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
255
255
  role: 'value.time',
256
256
  read: true,
257
257
  write: false,
258
- def: '',
258
+ def: 0, // FIX: numeric timestamp default for value.time.
259
259
  persist: true,
260
260
  });
261
261
 
@@ -288,11 +288,11 @@ async function createChemistryOrpStates(adapter) {
288
288
  en: 'Readable date and time of the previous valid ORP value.',
289
289
  de: 'Lesbares Datum und Uhrzeit des vorherigen gültigen ORP-Werts.',
290
290
  },
291
- type: 'string',
291
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
292
292
  role: 'value.time',
293
293
  read: true,
294
294
  write: false,
295
- def: '',
295
+ def: 0, // FIX: numeric timestamp default for value.time.
296
296
  persist: true,
297
297
  });
298
298
 
@@ -685,11 +685,11 @@ async function createChemistryOrpStates(adapter) {
685
685
  en: 'Readable timestamp of the 24h ORP reference value.',
686
686
  de: 'Lesbarer Zeitstempel des 24h-ORP-Referenzwerts.',
687
687
  },
688
- type: 'string',
688
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
689
689
  role: 'value.time',
690
690
  read: true,
691
691
  write: false,
692
- def: '',
692
+ def: 0, // FIX: numeric timestamp default for value.time.
693
693
  persist: true,
694
694
  });
695
695
 
@@ -738,11 +738,11 @@ async function createChemistryOrpStates(adapter) {
738
738
  en: 'Readable timestamp of the 7 day ORP reference value.',
739
739
  de: 'Lesbarer Zeitstempel des 7-Tage-ORP-Referenzwerts.',
740
740
  },
741
- type: 'string',
741
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
742
742
  role: 'value.time',
743
743
  read: true,
744
744
  write: false,
745
- def: '',
745
+ def: 0, // FIX: numeric timestamp default for value.time.
746
746
  persist: true,
747
747
  });
748
748
 
@@ -791,11 +791,11 @@ async function createChemistryOrpStates(adapter) {
791
791
  en: 'Readable timestamp of the 30 day ORP reference value.',
792
792
  de: 'Lesbarer Zeitstempel des 30-Tage-ORP-Referenzwerts.',
793
793
  },
794
- type: 'string',
794
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
795
795
  role: 'value.time',
796
796
  read: true,
797
797
  write: false,
798
- def: '',
798
+ def: 0, // FIX: numeric timestamp default for value.time.
799
799
  persist: true,
800
800
  });
801
801
 
@@ -934,11 +934,11 @@ async function createChemistryOrpStates(adapter) {
934
934
  en: 'Readable timestamp of the oldest stored ORP sample.',
935
935
  de: 'Lesbarer Zeitstempel des ältesten gespeicherten ORP-Messwerts.',
936
936
  },
937
- type: 'string',
937
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
938
938
  role: 'value.time',
939
939
  read: true,
940
940
  write: false,
941
- def: '',
941
+ def: 0, // FIX: numeric timestamp default for value.time.
942
942
  persist: true,
943
943
  });
944
944
 
@@ -951,11 +951,11 @@ async function createChemistryOrpStates(adapter) {
951
951
  en: 'Readable timestamp of the newest stored ORP sample.',
952
952
  de: 'Lesbarer Zeitstempel des neuesten gespeicherten ORP-Messwerts.',
953
953
  },
954
- type: 'string',
954
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
955
955
  role: 'value.time',
956
956
  read: true,
957
957
  write: false,
958
- def: '',
958
+ def: 0, // FIX: numeric timestamp default for value.time.
959
959
  persist: true,
960
960
  });
961
961
 
@@ -1035,11 +1035,11 @@ async function createChemistryOrpStates(adapter) {
1035
1035
  en: 'Readable timestamp of the last ORP evaluation update.',
1036
1036
  de: 'Lesbarer Zeitstempel der letzten ORP-Auswertung.',
1037
1037
  },
1038
- type: 'string',
1038
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
1039
1039
  role: 'value.time',
1040
1040
  read: true,
1041
1041
  write: false,
1042
- def: '',
1042
+ def: 0, // FIX: numeric timestamp default for value.time.
1043
1043
  persist: true,
1044
1044
  });
1045
1045
 
@@ -209,11 +209,11 @@ async function createChemistryPhStates(adapter) {
209
209
  en: 'Readable date and time when the last pH value was received.',
210
210
  de: 'Lesbares Datum und Uhrzeit, wann der letzte pH-Wert empfangen wurde.',
211
211
  },
212
- type: 'string',
212
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
213
213
  role: 'value.time',
214
214
  read: true,
215
215
  write: false,
216
- def: '',
216
+ def: 0, // FIX: numeric timestamp default for value.time.
217
217
  persist: true,
218
218
  });
219
219
 
@@ -245,11 +245,11 @@ async function createChemistryPhStates(adapter) {
245
245
  en: 'Readable date and time of the last valid pH value.',
246
246
  de: 'Lesbares Datum und Uhrzeit des letzten gültigen pH-Werts.',
247
247
  },
248
- type: 'string',
248
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
249
249
  role: 'value.time',
250
250
  read: true,
251
251
  write: false,
252
- def: '',
252
+ def: 0, // FIX: numeric timestamp default for value.time.
253
253
  persist: true,
254
254
  });
255
255
 
@@ -281,11 +281,11 @@ async function createChemistryPhStates(adapter) {
281
281
  en: 'Readable date and time of the previous valid pH value.',
282
282
  de: 'Lesbares Datum und Uhrzeit des vorherigen gültigen pH-Werts.',
283
283
  },
284
- type: 'string',
284
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
285
285
  role: 'value.time',
286
286
  read: true,
287
287
  write: false,
288
- def: '',
288
+ def: 0, // FIX: numeric timestamp default for value.time.
289
289
  persist: true,
290
290
  });
291
291
 
@@ -698,11 +698,11 @@ async function createChemistryPhStates(adapter) {
698
698
  en: 'Oldest sample time',
699
699
  de: 'Ältester Messwert',
700
700
  },
701
- type: 'string',
701
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
702
702
  role: 'value.time',
703
703
  read: true,
704
704
  write: false,
705
- def: '',
705
+ def: 0, // FIX: numeric timestamp default for value.time.
706
706
  persist: true,
707
707
  });
708
708
 
@@ -711,11 +711,11 @@ async function createChemistryPhStates(adapter) {
711
711
  en: 'Newest sample time',
712
712
  de: 'Neuester Messwert',
713
713
  },
714
- type: 'string',
714
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
715
715
  role: 'value.time',
716
716
  read: true,
717
717
  write: false,
718
- def: '',
718
+ def: 0, // FIX: numeric timestamp default for value.time.
719
719
  persist: true,
720
720
  });
721
721
 
@@ -763,11 +763,11 @@ async function createChemistryPhStates(adapter) {
763
763
  en: enName,
764
764
  de: deName,
765
765
  },
766
- type: 'string',
766
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
767
767
  role: 'value.time',
768
768
  read: true,
769
769
  write: false,
770
- def: '',
770
+ def: 0, // FIX: numeric timestamp default for value.time.
771
771
  persist: true,
772
772
  });
773
773
  }
@@ -882,11 +882,11 @@ async function createChemistryPhStates(adapter) {
882
882
  en: 'Readable timestamp of the last pH evaluation update.',
883
883
  de: 'Lesbarer Zeitstempel der letzten pH-Auswertung.',
884
884
  },
885
- type: 'string',
885
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
886
886
  role: 'value.time',
887
887
  read: true,
888
888
  write: false,
889
- def: '',
889
+ def: 0, // FIX: numeric timestamp default for value.time.
890
890
  persist: true,
891
891
  });
892
892
 
@@ -211,11 +211,11 @@ async function createChemistryTdsStates(adapter) {
211
211
  en: 'Readable date and time when the last TDS value was received.',
212
212
  de: 'Lesbares Datum und Uhrzeit, wann der letzte TDS-Wert empfangen wurde.',
213
213
  },
214
- type: 'string',
214
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
215
215
  role: 'value.time',
216
216
  read: true,
217
217
  write: false,
218
- def: '',
218
+ def: 0, // FIX: numeric timestamp default for value.time.
219
219
  persist: true,
220
220
  });
221
221
 
@@ -247,11 +247,11 @@ async function createChemistryTdsStates(adapter) {
247
247
  en: 'Readable date and time of the last valid TDS value.',
248
248
  de: 'Lesbares Datum und Uhrzeit des letzten gültigen TDS-Werts.',
249
249
  },
250
- type: 'string',
250
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
251
251
  role: 'value.time',
252
252
  read: true,
253
253
  write: false,
254
- def: '',
254
+ def: 0, // FIX: numeric timestamp default for value.time.
255
255
  persist: true,
256
256
  });
257
257
 
@@ -283,11 +283,11 @@ async function createChemistryTdsStates(adapter) {
283
283
  en: 'Readable date and time of the previous valid TDS value.',
284
284
  de: 'Lesbares Datum und Uhrzeit des vorherigen gültigen TDS-Werts.',
285
285
  },
286
- type: 'string',
286
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
287
287
  role: 'value.time',
288
288
  read: true,
289
289
  write: false,
290
- def: '',
290
+ def: 0, // FIX: numeric timestamp default for value.time.
291
291
  persist: true,
292
292
  });
293
293
 
@@ -480,11 +480,11 @@ async function createChemistryTdsStates(adapter) {
480
480
  en: 'Readable date and time when the initial reference value was set.',
481
481
  de: 'Lesbares Datum und Uhrzeit, wann der Referenzwert gesetzt wurde.',
482
482
  },
483
- type: 'string',
483
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
484
484
  role: 'value.time',
485
485
  read: true,
486
486
  write: false,
487
- def: '',
487
+ def: 0, // FIX: numeric timestamp default for value.time.
488
488
  persist: true,
489
489
  });
490
490
 
@@ -584,11 +584,11 @@ async function createChemistryTdsStates(adapter) {
584
584
  en: enName,
585
585
  de: deName,
586
586
  },
587
- type: 'string',
587
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
588
588
  role: 'value.time',
589
589
  read: true,
590
590
  write: false,
591
- def: '',
591
+ def: 0, // FIX: numeric timestamp default for value.time.
592
592
  persist: true,
593
593
  });
594
594
  }
@@ -767,11 +767,11 @@ async function createChemistryTdsStates(adapter) {
767
767
  en: 'Oldest sample time',
768
768
  de: 'Ältester Messwert',
769
769
  },
770
- type: 'string',
770
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
771
771
  role: 'value.time',
772
772
  read: true,
773
773
  write: false,
774
- def: '',
774
+ def: 0, // FIX: numeric timestamp default for value.time.
775
775
  persist: true,
776
776
  });
777
777
 
@@ -780,11 +780,11 @@ async function createChemistryTdsStates(adapter) {
780
780
  en: 'Newest sample time',
781
781
  de: 'Neuester Messwert',
782
782
  },
783
- type: 'string',
783
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
784
784
  role: 'value.time',
785
785
  read: true,
786
786
  write: false,
787
- def: '',
787
+ def: 0, // FIX: numeric timestamp default for value.time.
788
788
  persist: true,
789
789
  });
790
790
 
@@ -860,11 +860,11 @@ async function createChemistryTdsStates(adapter) {
860
860
  en: 'Last update',
861
861
  de: 'Letzte Aktualisierung',
862
862
  },
863
- type: 'string',
863
+ type: 'number', // FIX: ioBroker value.time states must store Unix timestamps in milliseconds.
864
864
  role: 'value.time',
865
865
  read: true,
866
866
  write: false,
867
- def: '',
867
+ def: 0, // FIX: numeric timestamp default for value.time.
868
868
  persist: true,
869
869
  });
870
870
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.poolcontrol",
3
- "version": "1.3.17",
3
+ "version": "1.3.19",
4
4
  "description": "Steuerung & Automatisierung für den Pool (Pumpe, Heizung, Ventile, Sensoren).",
5
5
  "author": "DasBo1975 <dasbo1975@outlook.de>",
6
6
  "homepage": "https://github.com/DasBo1975/ioBroker.poolcontrol",