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 +25 -42
- package/io-package.json +27 -27
- package/lib/helpers/runtimeHelper.js +90 -22
- package/lib/helpers/speechTextHelper.js +83 -8
- package/lib/stateDefinitions/runtimeStates.js +108 -0
- package/lib/stateDefinitions/speechStates.js +151 -0
- package/package.json +1 -1
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.
|
|
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 =>
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
//
|
|
78
|
-
this.runtimeTotal = this.
|
|
79
|
-
this.runtimeToday = this.
|
|
80
|
-
this.runtimeSeason = this.
|
|
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
|
-
//
|
|
136
|
-
const seasonActive =
|
|
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
|
-
|
|
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(
|
|
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.
|
|
118
|
-
this.adapter.log.debug(
|
|
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.
|
|
122
|
-
this.adapter.log.debug(
|
|
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.
|
|
139
|
-
this.adapter.log.debug(
|
|
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.
|
|
147
|
-
this.adapter.log.debug(
|
|
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.
|
|
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",
|