iobroker.poolcontrol 1.3.13 → 1.3.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
@@ -188,6 +188,31 @@ New features are added regularly – please refer to the changelog.
188
188
  ---
189
189
 
190
190
  ## Changelog
191
+ ### 1.3.15 (2026-05-08)
192
+
193
+ - Added speech source cooldown handling for solar and time announcements
194
+ - Added configurable `speech.sources.*` states for source-based speech control
195
+ - Reduced excessive solar speech notifications during unstable weather conditions
196
+ - Added persistent numeric runtime second states:
197
+ - `runtime.total_seconds`
198
+ - `runtime.today_seconds`
199
+ - `runtime.current_session_seconds`
200
+ - `runtime.season_total_seconds`
201
+ - Improved runtime restore handling using numeric fallback logic
202
+ - Fixed live runtime display consistency for total, today and season runtimes
203
+ - Improved season runtime handling using `status.season_active`
204
+ - Converted runtime helper timers to ioBroker adapter timer methods
205
+ - Improved runtime timer cleanup and reset protection
206
+ - Improved speech logging for skipped and cooldown-limited announcements
207
+
208
+ ### 1.3.14 (2026-05-08)
209
+
210
+ - Improved runtime handling and persistence
211
+ - Fixed season runtime calculation using the correct season state
212
+ - Unified live runtime calculations for total, today and season values
213
+ - Added numeric runtime second states for robust persistence and recovery
214
+ - Converted runtime timers to adapter-managed timers for improved ioBroker compatibility
215
+
191
216
  ### 1.3.13 (2026-05-08)
192
217
  - (copilot) Adapter requires node.js >= 22 now
193
218
  - Fixed invalid `common.installedFrom` entry in `io-package.json`
@@ -262,48 +287,6 @@ New features are added regularly – please refer to the changelog.
262
287
  - improves separation of solar control modes
263
288
  - stabilizes speech output behavior
264
289
 
265
- ### 1.3.10 (2026-05-01)
266
-
267
- New: Photovoltaic Insights
268
- - Introduced a new analytics module `analytics.insights.photovoltaic`
269
- - Tracks PV-based pump runtime, energy usage and estimated savings
270
- - New helper: `photovoltaicInsightsHelper`
271
- - New states: inputs, calculation, results, debug
272
- - Includes summary outputs (text, JSON, HTML)
273
- - Fully integrated with i18n translations
274
-
275
- Improvement: Active Helper Handling
276
- - Added consistent `pump.active_helper` ownership handling for:
277
- - photovoltaicHelper
278
- - solarHelper
279
- - solarExtendedHelper
280
- - Each helper now:
281
- - sets its own identifier when controlling the pump
282
- - releases it when stopping
283
- - does not override other active helpers
284
- - Ensures correct priority handling and prevents conflicts
285
-
286
- Improvement: PV Runtime Evaluation
287
- - Photovoltaic runtime is now only counted when:
288
- - PV surplus is active
289
- - AND photovoltaicHelper actually owns the pump
290
- - Enables accurate runtime, energy and savings calculation
291
-
292
- Fix: PV Circulation Logic
293
- - Fixed issue where pump stopped despite `photovoltaic.ignore_on_circulation = false`
294
- - Circulation check is now only applied when explicitly enabled
295
-
296
- ### 1.3.9 (2026-04-24)
297
-
298
- - Fix: solarLogbookHelper no longer creates duplicate or unnecessary log entries (improved filtering & throttling logic)
299
- - Fix: Removed obsolete "no runtime today" entries once solar has actually run
300
- - Fix: Improved handling of weather summary text to avoid broken or cut-off sentences
301
- - Fix: solarLogbookHelper now updates `last_entry_time` only when a real log entry is written
302
- - Fix: solarInsightsHelper runtime calculation improved (no incorrect time accumulation on state changes)
303
- - Fix: solarInsightsHelper now correctly tracks previous solar state for more accurate active time calculation
304
- - Fix: Added missing `debug.last_update` update on successful calculation
305
- - Improvement: General stability and plausibility improvements in solar insights and logbook processing
306
-
307
290
  ## Support
308
291
  - [ioBroker Forum](https://forum.iobroker.net/)
309
292
  - [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.13",
4
+ "version": "1.3.15",
5
5
  "news": {
6
+ "1.3.15": {
7
+ "en": "Added speech source cooldown handling, improved solar/time speech control, added persistent runtime second states, improved runtime restore logic and converted runtime timers to ioBroker adapter timers.",
8
+ "de": "Sprachquellen mit Cooldown-Steuerung ergänzt, Solar-/Zeit-Sprachsteuerung verbessert, persistente Runtime-Sekundenstates hinzugefügt, Runtime-Wiederherstellung verbessert und Runtime-Timer auf ioBroker-Adapter-Timer umgestellt.",
9
+ "ru": "Добавлена ​​обработка перезарядки источника речи, улучшено управление речью по солнечной энергии и времени, добавлены постоянные секунды времени выполнения, улучшена логика восстановления времени выполнения и преобразованы таймеры времени выполнения в таймеры адаптера ioBroker.",
10
+ "pt": "Adicionado tratamento de resfriamento da fonte de fala, controle de fala solar/tempo aprimorado, segundos estados de tempo de execução persistentes adicionados, lógica de restauração de tempo de execução aprimorada e temporizadores de tempo de execução convertidos em temporizadores de adaptador ioBroker.",
11
+ "nl": "Afkoeling van spraakbronnen toegevoegd, verbeterde spraakcontrole op zonne-energie/tijd, persistente tweede runtime-statussen toegevoegd, verbeterde runtime-herstellogica en geconverteerde runtime-timers naar ioBroker-adaptertimers.",
12
+ "fr": "Ajout de la gestion du temps de recharge de la source vocale, du contrôle vocal solaire/temporel amélioré, ajout des seconds états d'exécution persistants, amélioration de la logique de restauration de l'exécution et conversion des minuteries d'exécution en minuteries d'adaptateur ioBroker.",
13
+ "it": "Aggiunta la gestione del raffreddamento della sorgente vocale, controllo vocale solare/ora migliorato, aggiunti secondi stati di runtime persistenti, logica di ripristino del runtime migliorata e timer di runtime convertiti in timer dell'adattatore ioBroker.",
14
+ "es": "Se agregó manejo de enfriamiento de la fuente de voz, control de voz solar/tiempo mejorado, se agregaron segundos estados de tiempo de ejecución persistentes, lógica de restauración de tiempo de ejecución mejorada y temporizadores de tiempo de ejecución convertidos en temporizadores de adaptador ioBroker.",
15
+ "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.",
16
+ "uk": "Додано обробку перезарядки джерела мовлення, покращено керування мовленням із сонячними променями/часом, додано постійні другі стани під час виконання, покращено логіку відновлення під час виконання та перетворено таймери часу виконання на таймери адаптера ioBroker.",
17
+ "zh-cn": "添加了语音源冷却处理、改进了太阳/时间语音控制、添加了持久运行时第二状态、改进了运行时恢复逻辑并将运行时计时器转换为 ioBroker 适配器计时器。"
18
+ },
19
+ "1.3.14": {
20
+ "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.",
21
+ "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.",
22
+ "ru": "Улучшена обработка и сохранение во время выполнения. Исправлен расчет времени выполнения сезона для использования правильного состояния сезона, улучшены обновления времени выполнения в режиме реального времени, добавлены надежные числовые значения второго состояния времени выполнения для более безопасного сохранения и восстановления, а также преобразованы таймеры времени выполнения в таймеры, управляемые адаптером, для лучшей совместимости с ioBroker.",
23
+ "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.",
24
+ "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.",
25
+ "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.",
26
+ "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.",
27
+ "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.",
28
+ "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.",
29
+ "uk": "Покращена обробка та збереження часу виконання. Виправлено розрахунок часу виконання сезону для використання правильного стану сезону, покращено поточні оновлення середовища виконання, додано надійні численні секунди часу виконання для безпечнішого збереження та відновлення, а також перетворено таймери часу виконання на таймери, керовані адаптером, для кращої сумісності з ioBroker.",
30
+ "zh-cn": "改进了运行时处理和持久性。修复了季节运行时计算以使用正确的季节状态,改进了实时运行时更新,添加了强大的数字运行时第二状态以实现更安全的持久性和恢复,并将运行时计时器转换为适配器管理的计时器以实现更好的 ioBroker 兼容性。"
31
+ },
6
32
  "1.3.13": {
7
33
  "en": "Added German and English function overview documentation. Fixed invalid common.installedFrom entry in io-package.json.",
8
34
  "de": "Deutsche und englische Funktionsübersicht ergänzt. Ungültigen common.installedFrom-Eintrag in der io-package.json korrigiert.",
@@ -41,32 +67,6 @@
41
67
  "pl": "Nowość: Dodano moduł oceny pH (chemistry.ph) z ręcznym i zewnętrznym wprowadzaniem danych, walidacją, obsługą miejsca pomiaru i zaleceniami. Nowość: Dodano moduł oceny TDS (chemistry.tds) z analizą trendów (24h, 7d, 30d), śledzeniem referencji i wynikami podsumowującymi (tekst/JSON/HTML). Poprawka: rozwiązano problem z przełączaniem mowy słonecznej między trybem standardowym i rozszerzonym. solarExtendedHelper nie zastępuje już mowy.solar_active w trybie standardowym.",
42
68
  "uk": "Нове: додано модуль оцінки pH (chemistry.ph) із ручним і зовнішнім введенням, перевіркою, обробкою місця вимірювання та рекомендаціями. Нове: додано модуль оцінки TDS (chemistry.tds) з ​​аналізом тенденцій (24 години, 7 днів, 30 днів), відстеженням посилань і підсумковими виходами (текст/JSON/HTML). Виправлення: вирішено проблему перемикання сонячної мови між стандартним і розширеним режимами. solarExtendedHelper більше не перезаписує speak.solar_active у стандартному режимі.",
43
69
  "zh-cn": "新增内容:添加了 pH 评估模块 (chemistry.ph),具有手动和外部输入、验证、测量位置处理和建议。新增内容:添加了 TDS 评估模块 (chemistry.tds),具有趋势分析(24 小时、7 天、30 天)、参考跟踪和摘要输出(文本/JSON/HTML)。修复:解决了标准模式和扩展模式之间的太阳能语音切换问题。在标准模式下,solarExtendedHelper 不再覆盖pepe.solar_active。"
44
- },
45
- "1.3.10": {
46
- "en": "Added photovoltaic insights (runtime, energy, savings) with new analytics states and helper. Improved active_helper ownership handling across photovoltaic, solar and extended solar helpers. Fixed PV circulation logic when ignore_on_circulation is disabled.",
47
- "de": "Photovoltaik-Einblicke (Laufzeit, Energie, Einsparungen) mit neuen Analysezuständen und Hilfsfunktionen hinzugefügt. Verbesserte Handhabung des Active_Helper-Besitzes für Photovoltaik-, Solar- und erweiterte Solar-Helfer. Die PV-Zirkulationslogik wurde korrigiert, wenn „ignore_on_circulation“ deaktiviert ist.",
48
- "ru": "Добавлены данные по фотоэлектрической энергии (время работы, энергия, экономия) с новыми аналитическими состояниями и помощником. Улучшено управление владением active_helper для фотоэлектрических, солнечных и расширенных солнечных помощников. Исправлена ​​логика циркуляции PV, когда ignore_on_circulation отключен.",
49
- "pt": "Adicionados insights fotovoltaicos (tempo de execução, energia, economia) com novos estados analíticos e auxiliares. Melhor gerenciamento de propriedade de active_helper em ajudantes fotovoltaicos, solares e solares estendidos. Lógica de circulação fotovoltaica corrigida quando ignore_on_circulation está desativado.",
50
- "nl": "Fotovoltaïsche inzichten toegevoegd (runtime, energie, besparingen) met nieuwe analysestatussen en helper. Verbeterde afhandeling van active_helper-eigendom voor fotovoltaïsche, zonne-energie en uitgebreide zonne-helpers. Vaste PV-circulatielogica wanneer negeer_on_circulatie is uitgeschakeld.",
51
- "fr": "Ajout d'informations photovoltaïques (durée de fonctionnement, énergie, économies) avec de nouveaux états d'analyse et une nouvelle aide. Amélioration de la gestion de la propriété active_helper pour les assistants photovoltaïques, solaires et solaires étendus. Correction de la logique de circulation PV lorsque ignore_on_circulation est désactivé.",
52
- "it": "Aggiunti approfondimenti sul fotovoltaico (autonomia, energia, risparmio) con nuovi stati di analisi e supporto. Migliorata la gestione della proprietà di active_helper tra gli helper fotovoltaici, solari e solari estesi. Risolta la logica di circolazione FV quando ignore_on_circulation è disabilitato.",
53
- "es": "Se agregaron conocimientos fotovoltaicos (tiempo de ejecución, energía, ahorros) con nuevos estados analíticos y ayuda. Se mejoró el manejo de la propiedad de active_helper en los asistentes fotovoltaicos, solares y solares extendidos. Se corrigió la lógica de circulación de PV cuando ignore_on_circulation estaba deshabilitado.",
54
- "pl": "Dodano statystyki fotowoltaiczne (czas pracy, energia, oszczędności) z nowymi stanami analitycznymi i pomocą. Ulepszona obsługa własności active_helper w przypadku fotowoltaiki, energii słonecznej i rozszerzonych pomocników energii słonecznej. Naprawiono logikę cyrkulacji PV, gdy ignorowanie_cyrkulacji jest wyłączone.",
55
- "uk": "Додано статистику фотоелектричної системи (час роботи, енергія, економія) з новими аналітичними станами та помічником. Покращено керування правами власності active_helper на фотоелектричні, сонячні та розширені сонячні помічники. Виправлена ​​логіка циркуляції PV, коли ignore_on_circulation вимкнено.",
56
- "zh-cn": "通过新的分析状态和帮助器添加了光伏见解(运行时间、能源、节省)。改进了跨光伏、太阳能和扩展太阳能助手的 active_helper 所有权处理。修复了禁用ignore_on_circulation时的PV循环逻辑。"
57
- },
58
- "1.3.9": {
59
- "en": "Fix solar logbook duplication and invalid entries, improve log stability and weather text handling; fix solar insights runtime calculation and debug timestamp update.",
60
- "de": "Behebung von doppelten und ungültigen Solar-Logbuch-Einträgen, Verbesserung der Log-Stabilität und Wettertext-Verarbeitung; Korrektur der Laufzeitberechnung im Solar-Insights-Bereich sowie Aktualisierung des Debug-Zeitstempels.",
61
- "ru": "Исправьте дублирование журнала солнечной энергии и неверные записи, улучшите стабильность журнала и обработку текста о погоде; исправить расчет времени выполнения Solar Insights и обновить временную метку отладки.",
62
- "pt": "Corrija a duplicação do diário de bordo solar e entradas inválidas, melhore a estabilidade do registro e o manuseio de textos meteorológicos; corrigir o cálculo do tempo de execução do Solar Insights e depurar a atualização do carimbo de data/hora.",
63
- "nl": "Herstel het dupliceren van zonnelogboeken en ongeldige vermeldingen, verbeter de logstabiliteit en de verwerking van weerteksten; runtimeberekening van zonne-inzichten repareren en tijdstempelupdate debuggen.",
64
- "fr": "Corrigez la duplication du journal solaire et les entrées invalides, améliorez la stabilité du journal et la gestion des textes météorologiques ; correction du calcul d'exécution des informations solaires et de la mise à jour de l'horodatage de débogage.",
65
- "it": "Correggere la duplicazione del registro solare e le voci non valide, migliorare la stabilità del registro e la gestione del testo meteorologico; correggere il calcolo del runtime di Solar Insights e l'aggiornamento del timestamp di debug.",
66
- "es": "Corrija la duplicación del libro de registro solar y las entradas no válidas, mejore la estabilidad del registro y el manejo de textos meteorológicos; corrige el cálculo del tiempo de ejecución de Solar Insights y la actualización de la marca de tiempo de depuración.",
67
- "pl": "Napraw duplikację dziennika słonecznego i nieprawidłowe wpisy, popraw stabilność dziennika i obsługę tekstu pogodowego; napraw obliczenia czasu działania Solar Insights i aktualizację znacznika czasu debugowania.",
68
- "uk": "Виправте дублювання сонячного журналу та недійсні записи, покращте стабільність журналу та обробку тексту погоди; виправити розрахунок часу виконання solar insights і налагодити оновлення часових позначок.",
69
- "zh-cn": "修复太阳能日志重复和无效条目,提高日志稳定性和天气文本处理;修复 Solar Insights 运行时计算和调试时间戳更新。"
70
70
  }
71
71
  },
72
72
  "titleLang": {
@@ -18,6 +18,7 @@ const runtimeHelper = {
18
18
  runtimeToday: 0, // Tageszeit (s)
19
19
  runtimeSeason: 0, // Laufzeit der aktuellen Saison (s)
20
20
  startCountToday: 0, // Anzahl Starts heute
21
+ restoreTimer: null, // FIX: ioBroker timer for delayed restore
21
22
  resetTimer: null,
22
23
  liveTimer: null, // Timer für Live-Updates
23
24
 
@@ -37,7 +38,12 @@ const runtimeHelper = {
37
38
  // vollständig aus der Datenbank laden kann (Überinstallationsschutz)
38
39
  // ------------------------------------------------------
39
40
  this.adapter.log.debug('[runtimeHelper] Waiting 3 seconds to load persistent states ...');
40
- await new Promise(resolve => setTimeout(resolve, 3000));
41
+ await new Promise(resolve => {
42
+ this.restoreTimer = this.adapter.setTimeout(() => {
43
+ this.restoreTimer = null;
44
+ resolve();
45
+ }, 3000);
46
+ });
41
47
 
42
48
  // Pumpenschalter überwachen
43
49
  this.adapter.subscribeStates('pump.pump_switch');
@@ -65,21 +71,24 @@ const runtimeHelper = {
65
71
  const totalRaw = (await this.adapter.getStateAsync('runtime.total'))?.val;
66
72
  const todayRaw = (await this.adapter.getStateAsync('runtime.today'))?.val;
67
73
  const seasonRaw = (await this.adapter.getStateAsync('runtime.season_total'))?.val;
68
- const countRaw = (await this.adapter.getStateAsync('runtime.start_count_today'))?.val;
69
74
 
70
- // FIX: Falls States leer oder neu angelegt sind, Warnhinweis ausgeben und Werte nicht überschreiben
71
- if (!totalRaw && !seasonRaw) {
72
- this.adapter.log.info(
73
- '[runtimeHelper] No saved runtimes found – possibly a new or reinstalled instance. Runtimes start at 0.',
74
- );
75
- }
75
+ const totalSecondsRaw = (await this.adapter.getStateAsync('runtime.total_seconds'))?.val;
76
+ const todaySecondsRaw = (await this.adapter.getStateAsync('runtime.today_seconds'))?.val;
77
+ const seasonSecondsRaw = (await this.adapter.getStateAsync('runtime.season_total_seconds'))?.val;
78
+
79
+ const countRaw = (await this.adapter.getStateAsync('runtime.start_count_today'))?.val;
76
80
 
77
- // >>> NEU: Formatierten Text (z. B. "3h 12m 5s") in Sekunden umwandeln
78
- this.runtimeTotal = this._parseFormattedTimeToSeconds(totalRaw);
79
- this.runtimeToday = this._parseFormattedTimeToSeconds(todayRaw);
80
- this.runtimeSeason = this._parseFormattedTimeToSeconds(seasonRaw);
81
+ // FIX: Restore prefers robust numeric seconds states and falls back to formatted legacy text states.
82
+ this.runtimeTotal = this._restoreRuntimeValue(totalSecondsRaw, totalRaw, 'runtime.total');
83
+ this.runtimeToday = this._restoreRuntimeValue(todaySecondsRaw, todayRaw, 'runtime.today');
84
+ this.runtimeSeason = this._restoreRuntimeValue(seasonSecondsRaw, seasonRaw, 'runtime.season_total');
81
85
  this.startCountToday = Number(countRaw) || 0;
82
86
 
87
+ await this.adapter.setStateAsync('runtime.total_seconds', { val: this.runtimeTotal, ack: true });
88
+ await this.adapter.setStateAsync('runtime.today_seconds', { val: this.runtimeToday, ack: true });
89
+ await this.adapter.setStateAsync('runtime.season_total_seconds', { val: this.runtimeSeason, ack: true });
90
+ await this.adapter.setStateAsync('runtime.current_session_seconds', { val: 0, ack: true });
91
+
83
92
  // Falls Pumpe gerade läuft → Status wiederherstellen
84
93
  const active = !!(await this.adapter.getStateAsync('pump.pump_switch'))?.val;
85
94
  if (active) {
@@ -132,8 +141,8 @@ const runtimeHelper = {
132
141
  this.runtimeToday += delta;
133
142
  this.runtimeTotal += delta;
134
143
 
135
- // Saisonlaufzeit nur zählen, wenn aktiv
136
- const seasonActive = !!(await this.adapter.getStateAsync('control.season.active'))?.val;
144
+ // FIX: Use the actual season state used by the adapter; keep legacy fallback if it exists.
145
+ const seasonActive = await this._isSeasonActive();
137
146
  if (seasonActive) {
138
147
  this.runtimeSeason += delta;
139
148
  }
@@ -167,20 +176,27 @@ const runtimeHelper = {
167
176
  async _updateStates() {
168
177
  try {
169
178
  // Falls Pumpe läuft → temporäre Laufzeit seit lastOn einrechnen
179
+ const seasonActive = await this._isSeasonActive();
180
+ let effectiveTotal = this.runtimeTotal;
170
181
  let effectiveToday = this.runtimeToday;
171
182
  let effectiveSeason = this.runtimeSeason;
172
183
  let currentSessionSeconds = 0;
173
184
 
174
185
  if (this.isRunning && this.lastOn) {
175
186
  const delta = Math.floor((Date.now() - this.lastOn) / 1000);
187
+ // FIX: Show the currently running session consistently in total and today.
188
+ effectiveTotal += delta;
176
189
  effectiveToday += delta;
177
- effectiveSeason += delta;
190
+ // FIX: Only show the running session in season_total while the same season status is active.
191
+ if (seasonActive) {
192
+ effectiveSeason += delta;
193
+ }
178
194
  currentSessionSeconds = delta;
179
195
  }
180
196
 
181
197
  // Formatiert schreiben
182
198
  const formattedToday = this._formatTime(effectiveToday);
183
- const formattedTotal = this._formatTime(this.runtimeTotal);
199
+ const formattedTotal = this._formatTime(effectiveTotal);
184
200
  const formattedSeason = this._formatTime(effectiveSeason);
185
201
  const formattedCurrent = this._formatTime(currentSessionSeconds);
186
202
 
@@ -188,6 +204,16 @@ const runtimeHelper = {
188
204
  await this.adapter.setStateAsync('runtime.today', { val: formattedToday, ack: true });
189
205
  await this.adapter.setStateAsync('runtime.current_session', { val: formattedCurrent, ack: true });
190
206
  await this.adapter.setStateAsync('runtime.season_total', { val: formattedSeason, ack: true });
207
+
208
+ // FIX: Write robust numeric seconds states in parallel to existing formatted text states.
209
+ await this.adapter.setStateAsync('runtime.total_seconds', { val: effectiveTotal, ack: true });
210
+ await this.adapter.setStateAsync('runtime.today_seconds', { val: effectiveToday, ack: true });
211
+ await this.adapter.setStateAsync('runtime.current_session_seconds', {
212
+ val: currentSessionSeconds,
213
+ ack: true,
214
+ });
215
+ await this.adapter.setStateAsync('runtime.season_total_seconds', { val: effectiveSeason, ack: true });
216
+
191
217
  await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
192
218
 
193
219
  // Poolparameter laden (vor Durchflussprüfung!)
@@ -242,6 +268,38 @@ const runtimeHelper = {
242
268
  return `${h}h ${m}m ${s}s`;
243
269
  },
244
270
 
271
+ _restoreRuntimeValue(secondsRaw, formattedRaw, stateName) {
272
+ const seconds = Number(secondsRaw);
273
+ if (Number.isFinite(seconds) && seconds > 0) {
274
+ return Math.floor(seconds);
275
+ }
276
+
277
+ const parsed = this._parseFormattedTimeToSeconds(formattedRaw);
278
+ if (parsed > 0) {
279
+ return parsed;
280
+ }
281
+
282
+ if (formattedRaw && String(formattedRaw).trim() !== '0h 0m 0s') {
283
+ this.adapter.log.warn(
284
+ `[runtimeHelper] Could not restore ${stateName} from value "${formattedRaw}". Keeping runtime at 0.`,
285
+ );
286
+ }
287
+
288
+ return 0;
289
+ },
290
+
291
+ async _isSeasonActive() {
292
+ // FIX: status.season_active is the canonical season state used by the adapter.
293
+ const statusSeason = await this.adapter.getStateAsync('status.season_active');
294
+ if (statusSeason && statusSeason.val !== null && statusSeason.val !== undefined) {
295
+ return !!statusSeason.val;
296
+ }
297
+
298
+ // FIX: Keep a backward-compatible fallback for installations that still provide this legacy state.
299
+ const controlSeason = await this.adapter.getStateAsync('control.season.active');
300
+ return !!controlSeason?.val;
301
+ },
302
+
245
303
  // >>> NEU: formatierten Text (z. B. "3h 12m 5s") in Sekunden zurückrechnen
246
304
  _parseFormattedTimeToSeconds(value) {
247
305
  if (typeof value === 'number' && Number.isFinite(value)) {
@@ -272,18 +330,22 @@ const runtimeHelper = {
272
330
  },
273
331
 
274
332
  _scheduleDailyReset() {
333
+ if (this.resetTimer) {
334
+ this.adapter.clearTimeout(this.resetTimer);
335
+ this.resetTimer = null;
336
+ }
275
337
  const now = new Date();
276
338
  const nextMidnight = new Date(now);
277
339
  nextMidnight.setHours(24, 0, 0, 0);
278
340
  const msUntilMidnight = nextMidnight - now;
279
341
 
280
- this.resetTimer = setTimeout(async () => {
342
+ this.resetTimer = this.adapter.setTimeout(async () => {
281
343
  this.runtimeToday = 0;
282
344
  this.startCountToday = 0;
283
345
  this.lastOn = this.isRunning ? Date.now() : null;
284
346
 
285
347
  // Laufzeiten zurücksetzen
286
- this._updateStates();
348
+ await this._updateStates();
287
349
 
288
350
  // --- NEU: Circulation-Werte um Mitternacht zurücksetzen ---
289
351
  await this.adapter.setStateAsync('circulation.daily_total', { val: 0, ack: true });
@@ -313,25 +375,31 @@ const runtimeHelper = {
313
375
 
314
376
  _startLiveTimer() {
315
377
  if (this.liveTimer) {
316
- clearInterval(this.liveTimer);
378
+ this.adapter.clearInterval(this.liveTimer);
317
379
  }
318
- this.liveTimer = setInterval(() => this._updateStates(), 10 * 1000);
380
+ this.liveTimer = this.adapter.setInterval(() => this._updateStates(), 10 * 1000);
319
381
  this.adapter.log.debug('[runtimeHelper] Live timer started (updates every 10 seconds)');
320
382
  },
321
383
 
322
384
  _stopLiveTimer() {
323
385
  if (this.liveTimer) {
324
- clearInterval(this.liveTimer);
386
+ this.adapter.clearInterval(this.liveTimer);
325
387
  this.liveTimer = null;
326
388
  this.adapter.log.debug('[runtimeHelper] Live timer stopped');
327
389
  }
328
390
  },
329
391
 
330
392
  cleanup() {
393
+ if (this.restoreTimer) {
394
+ this.adapter.clearTimeout(this.restoreTimer);
395
+ this.restoreTimer = null;
396
+ }
397
+
331
398
  if (this.resetTimer) {
332
- clearTimeout(this.resetTimer);
399
+ this.adapter.clearTimeout(this.resetTimer);
333
400
  this.resetTimer = null;
334
401
  }
402
+
335
403
  this._stopLiveTimer();
336
404
  },
337
405
  };
@@ -77,6 +77,14 @@ const speechTextHelper = {
77
77
  if (id.endsWith('solar.collector_warning')) {
78
78
  const val = !!state.val;
79
79
 
80
+ const warnSpeech = !!(await this.adapter.getStateAsync('solar.warn_speech'))?.val;
81
+ if (!warnSpeech) {
82
+ this.adapter.log.debug(
83
+ '[speechTextHelper] Solar warning speech skipped (solar.warn_speech=false).',
84
+ );
85
+ return;
86
+ }
87
+
80
88
  if (val) {
81
89
  // Neue Warnung aktiv
82
90
  const collectorTemp = Number(
@@ -100,6 +108,8 @@ const speechTextHelper = {
100
108
  // --- NEU: Reaktion auf Solarsteuerung ---
101
109
  if (id.endsWith('speech.solar_active')) {
102
110
  const val = !!state.val;
111
+ const canSpeak = await this._canSendFromSource('solar');
112
+
103
113
  // Pumpenstatus aktualisieren, damit auch im VIS korrekt sichtbar
104
114
  if (val) {
105
115
  await this.adapter.setStateAsync('pump.status', {
@@ -114,12 +124,20 @@ const speechTextHelper = {
114
124
  }
115
125
  if (val) {
116
126
  const text = 'Die Poolpumpe wurde durch die Solarsteuerung eingeschaltet.';
117
- await this._sendSpeech(text);
118
- this.adapter.log.debug('[speechTextHelper] Solar control activated → announcement sent.');
127
+ const sent = await this._sendSpeechFromSource('solar', text, canSpeak);
128
+ this.adapter.log.debug(
129
+ sent
130
+ ? '[speechTextHelper] Solar control activated → announcement sent.'
131
+ : '[speechTextHelper] Solar control activated → announcement skipped.',
132
+ );
119
133
  } else {
120
134
  const text = 'Solarsteuerung beendet – Poolpumpe ausgeschaltet.';
121
- await this._sendSpeech(text);
122
- this.adapter.log.debug('[speechTextHelper] Solar control deactivated → announcement sent.');
135
+ const sent = await this._sendSpeechFromSource('solar', text, canSpeak);
136
+ this.adapter.log.debug(
137
+ sent
138
+ ? '[speechTextHelper] Solar control deactivated → announcement sent.'
139
+ : '[speechTextHelper] Solar control deactivated → announcement skipped.',
140
+ );
123
141
  }
124
142
  return;
125
143
  }
@@ -127,6 +145,7 @@ const speechTextHelper = {
127
145
  // --- NEU: Reaktion auf Zeitsteuerung ---
128
146
  if (id.endsWith('speech.time_active')) {
129
147
  const val = !!state.val;
148
+ const canSpeak = await this._canSendFromSource('time');
130
149
 
131
150
  // Pumpenstatus mitpflegen
132
151
  if (val) {
@@ -135,16 +154,24 @@ const speechTextHelper = {
135
154
  ack: true,
136
155
  });
137
156
  const text = 'Die Poolpumpe wurde durch die Zeitsteuerung eingeschaltet.';
138
- await this._sendSpeech(text);
139
- this.adapter.log.debug('[speechTextHelper] Time control activated → announcement sent.');
157
+ const sent = await this._sendSpeechFromSource('time', text, canSpeak);
158
+ this.adapter.log.debug(
159
+ sent
160
+ ? '[speechTextHelper] Time control activated → announcement sent.'
161
+ : '[speechTextHelper] Time control activated → announcement skipped.',
162
+ );
140
163
  } else {
141
164
  await this.adapter.setStateAsync('pump.status', {
142
165
  val: 'AUS (Zeitsteuerung beendet)',
143
166
  ack: true,
144
167
  });
145
168
  const text = 'Zeitsteuerung beendet – Poolpumpe ausgeschaltet.';
146
- await this._sendSpeech(text);
147
- this.adapter.log.debug('[speechTextHelper] Time control deactivated → announcement sent.');
169
+ const sent = await this._sendSpeechFromSource('time', text, canSpeak);
170
+ this.adapter.log.debug(
171
+ sent
172
+ ? '[speechTextHelper] Time control deactivated → announcement sent.'
173
+ : '[speechTextHelper] Time control deactivated → announcement skipped.',
174
+ );
148
175
  }
149
176
  return;
150
177
  }
@@ -155,6 +182,54 @@ const speechTextHelper = {
155
182
  }
156
183
  },
157
184
 
185
+ async _canSendFromSource(source) {
186
+ const enabled = !!(await this.adapter.getStateAsync(`speech.sources.${source}.enabled`))?.val;
187
+ if (!enabled) {
188
+ this.adapter.log.debug(`[speechTextHelper] Speech source "${source}" is disabled.`);
189
+ return false;
190
+ }
191
+
192
+ const cooldownMinutes = Number(
193
+ (await this.adapter.getStateAsync(`speech.sources.${source}.cooldown_minutes`))?.val || 0,
194
+ );
195
+ if (cooldownMinutes <= 0) {
196
+ return true;
197
+ }
198
+
199
+ const lastSentRaw = (await this.adapter.getStateAsync(`speech.sources.${source}.last_sent`))?.val;
200
+ const lastSentTs = Date.parse(String(lastSentRaw || ''));
201
+
202
+ if (!Number.isFinite(lastSentTs)) {
203
+ return true;
204
+ }
205
+
206
+ const elapsedMs = Date.now() - lastSentTs;
207
+ const cooldownMs = cooldownMinutes * 60 * 1000;
208
+
209
+ if (elapsedMs < cooldownMs) {
210
+ this.adapter.log.debug(
211
+ `[speechTextHelper] Speech source "${source}" skipped by cooldown (${cooldownMinutes} min).`,
212
+ );
213
+ return false;
214
+ }
215
+
216
+ return true;
217
+ },
218
+
219
+ async _sendSpeechFromSource(source, text, canSpeak) {
220
+ if (!canSpeak) {
221
+ return false;
222
+ }
223
+
224
+ await this._sendSpeech(text);
225
+ await this.adapter.setStateAsync(`speech.sources.${source}.last_sent`, {
226
+ val: new Date().toISOString(),
227
+ ack: true,
228
+ });
229
+
230
+ return true;
231
+ },
232
+
158
233
  /**
159
234
  * Sendet Text an speech.queue.
160
235
  *
@@ -3,10 +3,14 @@
3
3
  /**
4
4
  * Legt alle States für Laufzeit- und Umwälzwerte an:
5
5
  * - runtime.total
6
+ * - runtime.total_seconds
6
7
  * - runtime.today
8
+ * - runtime.today_seconds
7
9
  * - runtime.start_count_today
8
10
  * - runtime.current_session
11
+ * - runtime.current_session_seconds
9
12
  * - runtime.season_total
13
+ * - runtime.season_total_seconds
10
14
  * - circulation.daily_total
11
15
  * - circulation.daily_required
12
16
  * - circulation.daily_remaining
@@ -53,6 +57,32 @@ async function createRuntimeStates(adapter) {
53
57
  await adapter.setStateAsync('runtime.total', { val: '0h 0m 0s', ack: true }); // FIX: Nur setzen, wenn leer
54
58
  }
55
59
 
60
+ // FIX: Gesamtlaufzeit als robuster Rohwert in Sekunden
61
+ await adapter.setObjectNotExistsAsync('runtime.total_seconds', {
62
+ type: 'state',
63
+ common: {
64
+ name: {
65
+ en: 'Total runtime (seconds)',
66
+ de: 'Gesamtlaufzeit (Sekunden)',
67
+ },
68
+ desc: {
69
+ en: 'Total pump runtime as raw value in seconds',
70
+ de: 'Gesamtlaufzeit der Pumpe als Rohwert in Sekunden',
71
+ },
72
+ type: 'number',
73
+ role: 'value',
74
+ unit: 's',
75
+ read: true,
76
+ write: false,
77
+ persist: true,
78
+ },
79
+ native: {},
80
+ });
81
+ const existingTotalSeconds = await adapter.getStateAsync('runtime.total_seconds');
82
+ if (!existingTotalSeconds || existingTotalSeconds.val === null || existingTotalSeconds.val === undefined) {
83
+ await adapter.setStateAsync('runtime.total_seconds', { val: 0, ack: true });
84
+ }
85
+
56
86
  // Tageslaufzeit (formatiert)
57
87
  await adapter.setObjectNotExistsAsync('runtime.today', {
58
88
  type: 'state',
@@ -78,6 +108,32 @@ async function createRuntimeStates(adapter) {
78
108
  await adapter.setStateAsync('runtime.today', { val: '0h 0m 0s', ack: true }); // FIX
79
109
  }
80
110
 
111
+ // FIX: Tageslaufzeit als robuster Rohwert in Sekunden
112
+ await adapter.setObjectNotExistsAsync('runtime.today_seconds', {
113
+ type: 'state',
114
+ common: {
115
+ name: {
116
+ en: 'Today runtime (seconds)',
117
+ de: 'Heutige Laufzeit (Sekunden)',
118
+ },
119
+ desc: {
120
+ en: 'Pump runtime for today as raw value in seconds',
121
+ de: 'Heutige Laufzeit der Pumpe als Rohwert in Sekunden',
122
+ },
123
+ type: 'number',
124
+ role: 'value',
125
+ unit: 's',
126
+ read: true,
127
+ write: false,
128
+ persist: true,
129
+ },
130
+ native: {},
131
+ });
132
+ const existingTodaySeconds = await adapter.getStateAsync('runtime.today_seconds');
133
+ if (!existingTodaySeconds || existingTodaySeconds.val === null || existingTodaySeconds.val === undefined) {
134
+ await adapter.setStateAsync('runtime.today_seconds', { val: 0, ack: true });
135
+ }
136
+
81
137
  // -------------------------------------------------------------------------
82
138
  // NEU: Pumpenstarts heute
83
139
  await adapter.setObjectNotExistsAsync('runtime.start_count_today', {
@@ -129,6 +185,32 @@ async function createRuntimeStates(adapter) {
129
185
  await adapter.setStateAsync('runtime.current_session', { val: '0h 0m 0s', ack: true }); // FIX
130
186
  }
131
187
 
188
+ // FIX: Aktuelle Session als robuster Rohwert in Sekunden
189
+ await adapter.setObjectNotExistsAsync('runtime.current_session_seconds', {
190
+ type: 'state',
191
+ common: {
192
+ name: {
193
+ en: 'Current runtime (seconds)',
194
+ de: 'Aktuelle Laufzeit (Sekunden)',
195
+ },
196
+ desc: {
197
+ en: 'Current pump runtime since switch-on as raw value in seconds',
198
+ de: 'Aktuelle Laufzeit seit dem Einschalten der Pumpe als Rohwert in Sekunden',
199
+ },
200
+ type: 'number',
201
+ role: 'value',
202
+ unit: 's',
203
+ read: true,
204
+ write: false,
205
+ persist: true,
206
+ },
207
+ native: {},
208
+ });
209
+ const existingCurrentSeconds = await adapter.getStateAsync('runtime.current_session_seconds');
210
+ if (!existingCurrentSeconds || existingCurrentSeconds.val === null || existingCurrentSeconds.val === undefined) {
211
+ await adapter.setStateAsync('runtime.current_session_seconds', { val: 0, ack: true });
212
+ }
213
+
132
214
  // NEU: Gesamtlaufzeit der aktuellen Saison (formatiert)
133
215
  await adapter.setObjectNotExistsAsync('runtime.season_total', {
134
216
  type: 'state',
@@ -154,6 +236,32 @@ async function createRuntimeStates(adapter) {
154
236
  await adapter.setStateAsync('runtime.season_total', { val: '0h 0m 0s', ack: true }); // FIX
155
237
  }
156
238
 
239
+ // FIX: Saisonlaufzeit als robuster Rohwert in Sekunden
240
+ await adapter.setObjectNotExistsAsync('runtime.season_total_seconds', {
241
+ type: 'state',
242
+ common: {
243
+ name: {
244
+ en: 'Season runtime (seconds)',
245
+ de: 'Saisonlaufzeit (Sekunden)',
246
+ },
247
+ desc: {
248
+ en: 'Pump runtime for the current season as raw value in seconds',
249
+ de: 'Laufzeit der Pumpe fuer die aktuelle Saison als Rohwert in Sekunden',
250
+ },
251
+ type: 'number',
252
+ role: 'value',
253
+ unit: 's',
254
+ read: true,
255
+ write: false,
256
+ persist: true,
257
+ },
258
+ native: {},
259
+ });
260
+ const existingSeasonSeconds = await adapter.getStateAsync('runtime.season_total_seconds');
261
+ if (!existingSeasonSeconds || existingSeasonSeconds.val === null || existingSeasonSeconds.val === undefined) {
262
+ await adapter.setStateAsync('runtime.season_total_seconds', { val: 0, ack: true });
263
+ }
264
+
157
265
  // -------------------------------------------------------------------------
158
266
  // --- Kanal circulation ---
159
267
  await adapter.setObjectNotExistsAsync('circulation', {
@@ -285,6 +285,157 @@ async function createSpeechStates(adapter) {
285
285
 
286
286
  adapter.log.debug('[speechStates] Amazon Alexa quiet-time states checked and created.');
287
287
 
288
+ // ------------------------------------------------------------------
289
+ // FIX: Speech sources - user configurable message sources
290
+ // ------------------------------------------------------------------
291
+
292
+ await adapter.setObjectNotExistsAsync('speech.sources', {
293
+ type: 'channel',
294
+ common: {
295
+ name: {
296
+ en: 'Speech sources',
297
+ de: 'Sprachquellen',
298
+ },
299
+ },
300
+ native: {},
301
+ });
302
+
303
+ const speechSources = [
304
+ {
305
+ id: 'solar',
306
+ name: {
307
+ en: 'Solar control messages',
308
+ de: 'Solarsteuerung-Meldungen',
309
+ },
310
+ enabledName: {
311
+ en: 'Enable solar control messages',
312
+ de: 'Solarsteuerung-Meldungen aktivieren',
313
+ },
314
+ enabledDesc: {
315
+ en: 'Allows speech messages when the solar control switches the pump on or off',
316
+ de: 'Erlaubt Sprachmeldungen, wenn die Solarsteuerung die Pumpe ein- oder ausschaltet',
317
+ },
318
+ cooldownName: {
319
+ en: 'Solar message cooldown',
320
+ de: 'Solar-Meldungssperre',
321
+ },
322
+ cooldownDesc: {
323
+ en: 'Minimum time in minutes between solar control speech messages',
324
+ de: 'Mindestzeit in Minuten zwischen Solarsteuerung-Sprachmeldungen',
325
+ },
326
+ cooldownDefault: 30,
327
+ },
328
+ {
329
+ id: 'time',
330
+ name: {
331
+ en: 'Time control messages',
332
+ de: 'Zeitsteuerung-Meldungen',
333
+ },
334
+ enabledName: {
335
+ en: 'Enable time control messages',
336
+ de: 'Zeitsteuerung-Meldungen aktivieren',
337
+ },
338
+ enabledDesc: {
339
+ en: 'Allows speech messages when the time control switches the pump on or off',
340
+ de: 'Erlaubt Sprachmeldungen, wenn die Zeitsteuerung die Pumpe ein- oder ausschaltet',
341
+ },
342
+ cooldownName: {
343
+ en: 'Time control message cooldown',
344
+ de: 'Zeitsteuerung-Meldungssperre',
345
+ },
346
+ cooldownDesc: {
347
+ en: 'Minimum time in minutes between time control speech messages',
348
+ de: 'Mindestzeit in Minuten zwischen Zeitsteuerung-Sprachmeldungen',
349
+ },
350
+ cooldownDefault: 0,
351
+ },
352
+ ];
353
+
354
+ for (const source of speechSources) {
355
+ const baseId = `speech.sources.${source.id}`;
356
+
357
+ await adapter.setObjectNotExistsAsync(baseId, {
358
+ type: 'channel',
359
+ common: {
360
+ name: source.name,
361
+ },
362
+ native: {},
363
+ });
364
+
365
+ await adapter.setObjectNotExistsAsync(`${baseId}.enabled`, {
366
+ type: 'state',
367
+ common: {
368
+ name: source.enabledName,
369
+ desc: source.enabledDesc,
370
+ type: 'boolean',
371
+ role: 'switch',
372
+ read: true,
373
+ write: true,
374
+ def: true,
375
+ persist: true,
376
+ },
377
+ native: {},
378
+ });
379
+
380
+ const enabledState = await adapter.getStateAsync(`${baseId}.enabled`);
381
+ if (!enabledState || enabledState.val === null || enabledState.val === undefined) {
382
+ await adapter.setStateAsync(`${baseId}.enabled`, { val: true, ack: true });
383
+ }
384
+
385
+ await adapter.setObjectNotExistsAsync(`${baseId}.cooldown_minutes`, {
386
+ type: 'state',
387
+ common: {
388
+ name: source.cooldownName,
389
+ desc: source.cooldownDesc,
390
+ type: 'number',
391
+ role: 'level',
392
+ unit: 'min',
393
+ read: true,
394
+ write: true,
395
+ min: 0,
396
+ def: source.cooldownDefault,
397
+ persist: true,
398
+ },
399
+ native: {},
400
+ });
401
+
402
+ const cooldownState = await adapter.getStateAsync(`${baseId}.cooldown_minutes`);
403
+ if (!cooldownState || cooldownState.val === null || cooldownState.val === undefined) {
404
+ await adapter.setStateAsync(`${baseId}.cooldown_minutes`, {
405
+ val: source.cooldownDefault,
406
+ ack: true,
407
+ });
408
+ }
409
+
410
+ await adapter.setObjectNotExistsAsync(`${baseId}.last_sent`, {
411
+ type: 'state',
412
+ common: {
413
+ name: {
414
+ en: 'Last message sent',
415
+ de: 'Letzte gesendete Meldung',
416
+ },
417
+ desc: {
418
+ en: 'Timestamp of the last speech message for this source',
419
+ de: 'Zeitstempel der letzten Sprachmeldung dieser Quelle',
420
+ },
421
+ type: 'string',
422
+ role: 'value.time',
423
+ read: true,
424
+ write: false,
425
+ def: '',
426
+ persist: true,
427
+ },
428
+ native: {},
429
+ });
430
+
431
+ const lastSentState = await adapter.getStateAsync(`${baseId}.last_sent`);
432
+ if (!lastSentState || lastSentState.val === null || lastSentState.val === undefined) {
433
+ await adapter.setStateAsync(`${baseId}.last_sent`, { val: '', ack: true });
434
+ }
435
+ }
436
+
437
+ adapter.log.debug('[speechStates] Speech source states checked and created');
438
+
288
439
  // versteckte Dateien
289
440
 
290
441
  await adapter.setObjectNotExistsAsync('speech.solar_active', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.poolcontrol",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
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",