iobroker.poolcontrol 0.6.1 → 0.6.3

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
@@ -135,6 +135,13 @@ Neue Funktionen werden regelmäßig ergänzt – bitte den Changelog beachten.
135
135
  ## Changelog
136
136
  ### **WORK IN PROGRESS**
137
137
 
138
+ ## v0.6.2 (2025-11-07)
139
+ - Überarbeitung der Instanzübersicht mit neuen Header-Strukturen für klarere Bedienung
140
+ - Neues Startseitenbild „Egon im Blaumann“ in der Admin-Oberfläche integriert
141
+ - Erweiterung des Sprachsystems um konfigurierbare Alexa-Ausgabezeiten
142
+ - Anpassungen und Aufräumarbeiten in jsonConfig, speechHelper und speechStates
143
+
144
+
138
145
  ## v0.6.0 (2025-11-03)
139
146
  - Einführung der vollständigen Photovoltaik-Steuerung mit automatischer Pumpenlogik
140
147
  (neuer Pumpenmodus `Automatik (PV)` unter `pump.mode`)
@@ -185,31 +192,6 @@ Neue Funktionen werden regelmäßig ergänzt – bitte den Changelog beachten.
185
192
  Diese Version bildet die stabile Basis für alle folgenden Statistik- und Analysefunktionen
186
193
  (z. B. Wochen- und Monatsstatistik, Historien- und Effizienz-Auswertungen).
187
194
 
188
- ---
189
-
190
- ### 0.3.1 (2025-10-18)
191
- - FrostHelper stabilisiert:
192
- - Feste Hysterese von +2 °C (bisher +1 °C)
193
- - Ganzzahl-Rundung eingeführt zur Vermeidung von Schaltflattern um 3 °C
194
- - Keine Änderungen an States oder Konfiguration erforderlich
195
-
196
- ---
197
-
198
- ### 0.3.0 (12.10.2025)
199
- **Neu:** Intelligentes Pumpen-Monitoring & Lernsystem
200
-
201
- - Hinzugefügt: **Reelle Durchflussberechnung** auf Basis der tatsächlichen Leistungsaufnahme.
202
- - Neuer Bereich **`pump.live`** zur Live-Überwachung von Leistung, Durchfluss und prozentualer Auslastung.
203
- - Die **tägliche Umwälzberechnung** verwendet nun den realen Durchflusswert anstelle eines Fixwerts.
204
- - Neuer Lernbereich **`pump.learning`**:
205
- - Lernt automatisch die durchschnittlichen Leistungs- und Durchflusswerte.
206
- - Bestimmt daraus einen dynamischen **Normalbereich (± 15 %)**.
207
- - Berechnet prozentuale Abweichungen und erstellt **textbasierte Statusmeldungen**.
208
- - Alle Lernwerte werden **persistent** gespeichert und bleiben auch nach Neustart erhalten.
209
- - Vollständig **ereignisgesteuerte Logik** ohne zusätzliche Timer oder Polling-Zyklen.
210
-
211
- > Mit dieser Version beginnt die lernfähige Phase des PoolControl-Adapters:
212
- > Deine Pumpe weiß jetzt selbst, was für sie „normal“ ist.
213
195
 
214
196
 
215
197
  *(ältere Versionen siehe [io-package.json](./io-package.json))*
Binary file
@@ -6,9 +6,16 @@
6
6
  "type": "panel",
7
7
  "label": "Allgemeine Einstellungen",
8
8
  "items": {
9
- "divider1": {
10
- "type": "divider",
11
- "newLine": true
9
+ "general_header": {
10
+ "type": "header",
11
+ "text": "Allgemeine Einstellungen zum Pool",
12
+ "size": 4,
13
+ "newLine": true,
14
+ "xs": 12,
15
+ "sm": 12,
16
+ "md": 12,
17
+ "lg": 12,
18
+ "xl": 12
12
19
  },
13
20
  "pool_name": {
14
21
  "type": "text",
@@ -18,7 +25,8 @@
18
25
  "sm": 3,
19
26
  "md": 3,
20
27
  "lg": 3,
21
- "xl": 3
28
+ "xl": 3,
29
+ "newLine": true
22
30
  },
23
31
  "pool_size": {
24
32
  "type": "number",
@@ -47,12 +55,18 @@
47
55
  "sm": 3,
48
56
  "md": 3,
49
57
  "lg": 3,
50
- "xl": 3,
51
- "newLine": true
58
+ "xl": 3
52
59
  },
53
- "divider_season1": {
54
- "type": "divider",
55
- "newLine": true
60
+ "general_season_header": {
61
+ "type": "header",
62
+ "text": "Saisoneinstellungen",
63
+ "size": 4,
64
+ "newLine": true,
65
+ "xs": 12,
66
+ "sm": 12,
67
+ "md": 12,
68
+ "lg": 12,
69
+ "xl": 12
56
70
  },
57
71
  "season_active": {
58
72
  "type": "checkbox",
@@ -65,19 +79,32 @@
65
79
  "xl": 3,
66
80
  "newLine": true
67
81
  },
68
- "divider_hardware1": {
69
- "type": "divider",
70
- "newLine": true
71
- }
82
+ "egon_image": {
83
+ "type": "staticImage",
84
+ "src": "adapter/poolcontrol/admin/egon_blaumann.jpg",
85
+ "newLine": true,
86
+ "xs": 3,
87
+ "sm": 3,
88
+ "md": 3,
89
+ "lg": 3,
90
+ "xl": 3
91
+ },
72
92
  }
73
93
  },
74
94
  "pump": {
75
95
  "type": "panel",
76
96
  "label": "Pumpe",
77
97
  "items": {
78
- "divider2": {
79
- "type": "divider",
80
- "newLine": true
98
+ "Leistung_header": {
99
+ "type": "header",
100
+ "text": "Pumpeneinstellungen (Nennleistung)",
101
+ "size": 4,
102
+ "newLine": true,
103
+ "xs": 12,
104
+ "sm": 12,
105
+ "md": 12,
106
+ "lg": 12,
107
+ "xl": 12
81
108
  },
82
109
  "pump_max_watt": {
83
110
  "type": "number",
@@ -87,7 +114,8 @@
87
114
  "sm": 3,
88
115
  "md": 3,
89
116
  "lg": 3,
90
- "xl": 3
117
+ "xl": 3,
118
+ "newLine": true
91
119
  },
92
120
  "pump_power_lph": {
93
121
  "type": "number",
@@ -97,12 +125,18 @@
97
125
  "sm": 3,
98
126
  "md": 3,
99
127
  "lg": 3,
100
- "xl": 3,
101
- "newLine": true
128
+ "xl": 3
102
129
  },
103
- "divider3": {
104
- "type": "divider",
105
- "newLine": true
130
+ "Frostschutz_header": {
131
+ "type": "header",
132
+ "text": "Frostschutz",
133
+ "size": 4,
134
+ "newLine": true,
135
+ "xs": 12,
136
+ "sm": 12,
137
+ "md": 12,
138
+ "lg": 12,
139
+ "xl": 12
106
140
  },
107
141
  "frost_protection_active": {
108
142
  "type": "checkbox",
@@ -125,9 +159,16 @@
125
159
  "lg": 3,
126
160
  "xl": 3
127
161
  },
128
- "divider4": {
129
- "type": "divider",
130
- "newLine": true
162
+ "pumpswitch_header": {
163
+ "type": "header",
164
+ "text": "Pumpendatenpunkte (Messsteckdose & Aktueller Verbrauch Watt)",
165
+ "size": 4,
166
+ "newLine": true,
167
+ "xs": 12,
168
+ "sm": 12,
169
+ "md": 12,
170
+ "lg": 12,
171
+ "xl": 12
131
172
  },
132
173
  "pump_switch": {
133
174
  "type": "objectId",
@@ -149,10 +190,6 @@
149
190
  "lg": 3,
150
191
  "xl": 3
151
192
  },
152
- "divider9": {
153
- "type": "divider",
154
- "newLine": true
155
- },
156
193
  "pump_mode_hint": {
157
194
  "type": "staticText",
158
195
  "text": "Pumpenmodus (Automatik/Manuell/Aus/Zeit) kann über den Datenpunkt 'pump.mode' gesteuert werden.",
@@ -163,9 +200,16 @@
163
200
  "lg": 12,
164
201
  "xl": 12
165
202
  },
166
- "divider10": {
167
- "type": "divider",
168
- "newLine": true
203
+ "pump_safety_header": {
204
+ "type": "header",
205
+ "text": "Sicherheitsfunktionen",
206
+ "size": 4,
207
+ "newLine": true,
208
+ "xs": 12,
209
+ "sm": 12,
210
+ "md": 12,
211
+ "lg": 12,
212
+ "xl": 12
169
213
  },
170
214
  "manual_safety_enabled": {
171
215
  "type": "checkbox",
@@ -194,9 +238,16 @@
194
238
  "type": "panel",
195
239
  "label": "Temperaturverwaltung",
196
240
  "items": {
197
- "divider5": {
198
- "type": "divider",
199
- "newLine": true
241
+ "tempsensor_header": {
242
+ "type": "header",
243
+ "text": "Temperatursensoren",
244
+ "size": 4,
245
+ "newLine": true,
246
+ "xs": 12,
247
+ "sm": 12,
248
+ "md": 12,
249
+ "lg": 12,
250
+ "xl": 12
200
251
  },
201
252
  "surface_temp_active": {
202
253
  "type": "checkbox",
@@ -330,9 +381,16 @@
330
381
  "type": "panel",
331
382
  "label": "Solarverwaltung",
332
383
  "items": {
333
- "divider6": {
334
- "type": "divider",
335
- "newLine": true
384
+ "solar_control_active_header": {
385
+ "type": "header",
386
+ "text": "Solarsteuerung aktivieren",
387
+ "size": 4,
388
+ "newLine": true,
389
+ "xs": 12,
390
+ "sm": 12,
391
+ "md": 12,
392
+ "lg": 12,
393
+ "xl": 12
336
394
  },
337
395
  "solar_control_active": {
338
396
  "type": "checkbox",
@@ -345,9 +403,16 @@
345
403
  "xl": 3,
346
404
  "newLine": true
347
405
  },
348
- "divider_solar1": {
349
- "type": "divider",
350
- "newLine": true
406
+ "solar_hysteresis_active_header": {
407
+ "type": "header",
408
+ "text": "Hysterese",
409
+ "size": 4,
410
+ "newLine": true,
411
+ "xs": 12,
412
+ "sm": 12,
413
+ "md": 12,
414
+ "lg": 12,
415
+ "xl": 12
351
416
  },
352
417
  "solar_hysteresis_active": {
353
418
  "type": "checkbox",
@@ -380,10 +445,17 @@
380
445
  "lg": 3,
381
446
  "xl": 3
382
447
  },
383
- "divider_solar2": {
384
- "type": "divider",
385
- "newLine": true
386
- },
448
+ "solar_collector_warn_active_header": {
449
+ "type": "header",
450
+ "text": "Kollektortemperatur-Warnung",
451
+ "size": 4,
452
+ "newLine": true,
453
+ "xs": 12,
454
+ "sm": 12,
455
+ "md": 12,
456
+ "lg": 12,
457
+ "xl": 12
458
+ },
387
459
  "solar_collector_warn_active": {
388
460
  "type": "checkbox",
389
461
  "label": "Kollektortemperatur-Warnung aktivieren",
@@ -432,9 +504,16 @@
432
504
  "type": "panel",
433
505
  "label": "Zeitsteuerung",
434
506
  "items": {
435
- "divider_time1": {
436
- "type": "divider",
437
- "newLine": true
507
+ "time1_active_header": {
508
+ "type": "header",
509
+ "text": "Zeitfenster 1",
510
+ "size": 4,
511
+ "newLine": true,
512
+ "xs": 12,
513
+ "sm": 12,
514
+ "md": 12,
515
+ "lg": 12,
516
+ "xl": 12
438
517
  },
439
518
  "time1_active": {
440
519
  "type": "checkbox",
@@ -538,9 +617,16 @@
538
617
  "lg": 1,
539
618
  "xl": 1
540
619
  },
541
- "divider_time2": {
542
- "type": "divider",
543
- "newLine": true
620
+ "time2_active_header": {
621
+ "type": "header",
622
+ "text": "Zeitfenster 2",
623
+ "size": 4,
624
+ "newLine": true,
625
+ "xs": 12,
626
+ "sm": 12,
627
+ "md": 12,
628
+ "lg": 12,
629
+ "xl": 12
544
630
  },
545
631
  "time2_active": {
546
632
  "type": "checkbox",
@@ -644,9 +730,16 @@
644
730
  "lg": 1,
645
731
  "xl": 1
646
732
  },
647
- "divider_time3": {
648
- "type": "divider",
649
- "newLine": true
733
+ "time3_active_header": {
734
+ "type": "header",
735
+ "text": "Zeitfenster 3",
736
+ "size": 4,
737
+ "newLine": true,
738
+ "xs": 12,
739
+ "sm": 12,
740
+ "md": 12,
741
+ "lg": 12,
742
+ "xl": 12
650
743
  },
651
744
  "time3_active": {
652
745
  "type": "checkbox",
@@ -756,10 +849,17 @@
756
849
  "type": "panel",
757
850
  "label": "Sprachausgaben",
758
851
  "items": {
759
- "divider_speech1": {
760
- "type": "divider",
761
- "newLine": true
762
- },
852
+ "speech_general_header": {
853
+ "type": "header",
854
+ "text": "Generell",
855
+ "size": 4,
856
+ "newLine": true,
857
+ "xs": 12,
858
+ "sm": 12,
859
+ "md": 12,
860
+ "lg": 12,
861
+ "xl": 12
862
+ },
763
863
  "speech_active": {
764
864
  "type": "checkbox",
765
865
  "label": "Sprachausgaben aktivieren",
@@ -772,10 +872,17 @@
772
872
  "newLine": true
773
873
  },
774
874
 
775
- "divider_speech2": {
776
- "type": "divider",
777
- "newLine": true
778
- },
875
+ "speech_alexa_header": {
876
+ "type": "header",
877
+ "text": "Amazon Alexa",
878
+ "size": 4,
879
+ "newLine": true,
880
+ "xs": 12,
881
+ "sm": 12,
882
+ "md": 12,
883
+ "lg": 12,
884
+ "xl": 12
885
+ },
779
886
  "speech_alexa_enabled": {
780
887
  "type": "checkbox",
781
888
  "label": "Ausgabe über Alexa aktivieren",
@@ -823,10 +930,17 @@
823
930
  "xl": 3
824
931
  },
825
932
 
826
- "divider_speech3": {
827
- "type": "divider",
828
- "newLine": true
829
- },
933
+ "speech_telegram_header": {
934
+ "type": "header",
935
+ "text": "Telegram",
936
+ "size": 4,
937
+ "newLine": true,
938
+ "xs": 12,
939
+ "sm": 12,
940
+ "md": 12,
941
+ "lg": 12,
942
+ "xl": 12
943
+ },
830
944
  "speech_telegram_enabled": {
831
945
  "type": "checkbox",
832
946
  "label": "Ausgabe über Telegram aktivieren",
@@ -870,9 +984,16 @@
870
984
  "lg": 6,
871
985
  "xl": 6
872
986
  },
873
- "divider_speech_email": {
874
- "type": "divider",
875
- "newLine": true
987
+ "speech_mail_header": {
988
+ "type": "header",
989
+ "text": "Mail",
990
+ "size": 4,
991
+ "newLine": true,
992
+ "xs": 12,
993
+ "sm": 12,
994
+ "md": 12,
995
+ "lg": 12,
996
+ "xl": 12
876
997
  },
877
998
  "speech_email_enabled": {
878
999
  "type": "checkbox",
@@ -948,10 +1069,17 @@
948
1069
  "type": "panel",
949
1070
  "label": "PV, Verbrauch & Kosten",
950
1071
  "items": {
951
- "divider_cons1": {
952
- "type": "divider",
953
- "newLine": true
954
- },
1072
+ "consumption_header": {
1073
+ "type": "header",
1074
+ "text": "Verbrauch- und Kostenberechnung",
1075
+ "size": 4,
1076
+ "newLine": true,
1077
+ "xs": 12,
1078
+ "sm": 12,
1079
+ "md": 12,
1080
+ "lg": 12,
1081
+ "xl": 12
1082
+ },
955
1083
  "consumption_enabled": {
956
1084
  "type": "checkbox",
957
1085
  "label": "Verbrauchs- und Kostenberechnung aktivieren",
@@ -974,11 +1102,6 @@
974
1102
  "lg": 6,
975
1103
  "xl": 6
976
1104
  },
977
-
978
- "divider_cons2": {
979
- "type": "divider",
980
- "newLine": true
981
- },
982
1105
  "energy_price_eur_kwh": {
983
1106
  "type": "number",
984
1107
  "label": "Strompreis (€ / kWh)",
@@ -1003,11 +1126,6 @@
1003
1126
  "lg": 12,
1004
1127
  "xl": 12
1005
1128
  },
1006
-
1007
- "divider_pv1": {
1008
- "type": "divider",
1009
- "newLine": true
1010
- },
1011
1129
  "pv_header": {
1012
1130
  "type": "header",
1013
1131
  "text": "Photovoltaik (Überschusserkennung)",
@@ -1061,9 +1179,16 @@
1061
1179
  "type": "panel",
1062
1180
  "label": "Hilfe",
1063
1181
  "items": {
1064
- "divider_help": {
1065
- "type": "divider",
1066
- "newLine": true
1182
+ "help_header": {
1183
+ "type": "header",
1184
+ "text": "Hilfe und Hinweise",
1185
+ "size": 4,
1186
+ "newLine": true,
1187
+ "xs": 12,
1188
+ "sm": 12,
1189
+ "md": 12,
1190
+ "lg": 12,
1191
+ "xl": 12
1067
1192
  },
1068
1193
  "helpLink": {
1069
1194
  "type": "staticLink",
package/io-package.json CHANGED
@@ -1,8 +1,33 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "poolcontrol",
4
- "version": "0.6.1",
4
+ "version": "0.6.3",
5
5
  "news": {
6
+ "0.6.3": {
7
+ "en": "Fix for weekly, monthly and yearly reset in consumptionHelper",
8
+ "de": "Fix für Wochen-, Monats- und Jahres-Reset im ConsumptionHelper",
9
+ "ru": "Исправление еженедельного, ежемесячного и ежегодного сброса в consumptionHelper",
10
+ "pt": "Correção para redefinição semanal, mensal e anual no consumptionHelper",
11
+ "nl": "Fix voor wekelijkse, maandelijkse en jaarlijkse reset in consumptionHelper",
12
+ "fr": "Correctif pour la réinitialisation hebdomadaire, mensuelle et annuelle dans consumptionHelper",
13
+ "it": "Correzione per il reset settimanale, mensile e annuale in consumptionHelper",
14
+ "es": "Corrección para el reinicio semanal, mensual y anual en consumptionHelper",
15
+ "pl": "Poprawka cotygodniowego, comiesięcznego i corocznego resetu w consumptionHelper",
16
+ "uk": "Виправлення щотижневого, щомісячного та щорічного скидання в consumptionHelper",
17
+ "zh-cn": "修复 consumptionHelper 中每周、每月和每年重置的问题"
18
+ },
19
+ "0.6.2": {
20
+ "en": "UI refinement and admin interface improvements. Added image integration ('Egon in blue overalls') for visual identification. Speech system extended with Alexa output time configuration. Cleaned and optimized jsonConfig with section headers for improved clarity.",
21
+ "de": "Oberflächenüberarbeitung und Verbesserungen der Admin-Ansicht. Bildintegration ('Egon im Blaumann') zur visuellen Wiedererkennung hinzugefügt. Sprachsystem um konfigurierbare Alexa-Ausgabezeiten erweitert. jsonConfig mit Abschnittsüberschriften bereinigt und übersichtlicher gestaltet.",
22
+ "ru": "Улучшение интерфейса и доработка административной панели. Добавлено изображение ('Эгон в синем комбинезоне') для визуальной идентификации. Расширена система речи с настройкой времени вывода Alexa. Очищена и оптимизирована jsonConfig с заголовками разделов для лучшей читаемости.",
23
+ "pt": "Aprimoramento da interface e melhorias na administração. Adicionada imagem ('Egon de macacão azul') para identificação visual. Sistema de fala ampliado com configuração de horários de saída Alexa. jsonConfig limpa e otimizada com cabeçalhos de seção para melhor clareza.",
24
+ "nl": "UI verfijnd en verbeteringen in het beheerpaneel. Afbeelding ('Egon in blauwe overall') toegevoegd voor visuele herkenning. Spraaksysteem uitgebreid met Alexa-uitvoertijdaanpassing. jsonConfig opgeschoond en overzichtelijker gemaakt met sectiekoppen.",
25
+ "fr": "Amélioration de l'interface utilisateur et du panneau d'administration. Image ajoutée ('Egon en bleu de travail') pour identification visuelle. Système vocal étendu avec configuration des heures de sortie Alexa. jsonConfig nettoyé et optimisé avec en-têtes de section pour plus de clarté.",
26
+ "it": "Raffinamento dell'interfaccia e miglioramenti al pannello di amministrazione. Aggiunta l'immagine ('Egon in tuta blu') per l'identificazione visiva. Sistema vocale ampliato con configurazione degli orari di uscita Alexa. jsonConfig ripulito e ottimizzato con intestazioni di sezione per maggiore chiarezza.",
27
+ "es": "Refinamiento de la interfaz y mejoras en el panel de administración. Se agregó imagen ('Egon con mono azul') para identificación visual. Sistema de voz ampliado con configuración de horarios de salida de Alexa. jsonConfig limpiado y optimizado con encabezados de sección para mayor claridad.",
28
+ "pl": "Udoskonalenie interfejsu i panelu administracyjnego. Dodano obraz ('Egon w niebieskim kombinezonie') dla wizualnej identyfikacji. Rozszerzono system mowy o konfigurację czasu wyjścia Alexa. Wyczyczono i zoptymalizowano jsonConfig z nagłówkami sekcji dla lepszej przejrzystości.",
29
+ "zh-cn": "改进了界面和管理界面。添加了图像('蓝色工作服的Egon')以实现视觉识别。语音系统扩展了Alexa输出时间配置。清理并优化了带有章节标题的jsonConfig,使其更清晰。"
30
+ },
6
31
  "0.6.1": {
7
32
  "en": "Fixed false monthly reset when no last_reset date existed. Corrected real last_update timestamp in monthly statistics.",
8
33
  "de": "Fehlerhaften Monatsreset ohne gültiges last_reset-Datum behoben. Korrigierter echter Zeitstempel last_update in der Monatsstatistik.",
@@ -66,31 +91,6 @@
66
91
  "pl": "Dodano wybór użytkowników dla powiadomień Telegram. Gdy nie wybrano żadnego użytkownika, wiadomości są wysyłane globalnie jak wcześniej; po podaniu jednego lub kilku nazw użytkowników wiadomości otrzymują tylko oni. Ulepszono UI administracyjne (pole odbiorców pod instancją). Zaktualizowano speechHelper i jsonConfig.json.",
67
92
  "uk": "Додано вибір користувачів для сповіщень Telegram. Якщо користувача не вибрано, повідомлення надсилаються глобально, як і раніше; якщо вказано одне чи кілька імен, їх отримують лише ці користувачі. Візуально покращено інтерфейс адміністрування (поле одержувача під екземпляром). Оновлено speechHelper та jsonConfig.json.",
68
93
  "zh-cn": "新增 Telegram 通知的用户选择功能。未选择用户时消息将像以前一样全局发送;指定一个或多个用户名时,仅这些用户会收到消息。优化了管理界面显示(收件人字段缩进至实例下方)。已更新 speechHelper 和 jsonConfig.json。"
69
- },
70
- "0.5.2": {
71
- "en": "Extended helper priority system: fixed time/solar conflicts, frost pauses during time windows. Stable pump behavior and improved coordination between helpers.",
72
- "de": "Erweitertes Helper-Vorrangsystem: Konflikte zwischen Zeit- und Solarsteuerung behoben, Frostschutz pausiert während Zeitfenstern. Stabiles Pumpenverhalten und verbesserte Koordination zwischen den Helpern.",
73
- "ru": "Расширена система приоритетов помощников: устранены конфликты между таймером и солнечным управлением, мороз приостанавливается во время временных окон. Стабильная работа насоса и улучшенная координация между помощниками.",
74
- "pt": "Sistema de prioridade dos helpers expandido: corrigidos conflitos de tempo/solar, proteção contra geada pausa durante janelas de tempo. Comportamento estável da bomba e melhor coordenação entre helpers.",
75
- "nl": "Uitgebreid helperprioriteitssysteem: conflicten tussen tijd- en zonregeling opgelost, vorstbeveiliging pauzeert tijdens tijdvensters. Stabiel pompgedrag en betere coördinatie tussen helpers.",
76
- "fr": "Système de priorité des helpers étendu : conflits temps/solaire corrigés, protection antigel en pause pendant les plages horaires. Comportement de pompe stable et meilleure coordination entre les helpers.",
77
- "it": "Sistema di priorità degli helper esteso: risolti i conflitti tempo/solare, la protezione antigelo si mette in pausa durante le finestre temporali. Comportamento stabile della pompa e migliore coordinamento tra gli helper.",
78
- "es": "Sistema de prioridad de helpers ampliado: solucionados los conflictos de tiempo/solar, la protección contra heladas se pausa durante las ventanas de tiempo. Comportamiento estable de la bomba y mejor coordinación entre helpers.",
79
- "pl": "Rozszerzony system priorytetów helperów: naprawiono konflikty między czasem a sterowaniem solarnym, ochrona przed mrozem wstrzymuje się podczas okien czasowych. Stabilne działanie pompy i lepsza koordynacja między helperami.",
80
- "zh-cn": "扩展的助手优先级系统:修复时间/太阳能冲突,防冻在时间窗口内暂停。泵运行稳定,助手之间协调更好。"
81
- },
82
- "0.5.1": {
83
- "en": "Extended week and month statistics with persistent data, unified JSON format, and installation protection.",
84
- "de": "Erweiterte Wochen- und Monatsstatistik mit persistenten Daten, einheitlichem JSON-Format und Überinstallationsschutz.",
85
- "ru": "Расширенная недельная и месячная статистика с постоянными данными, унифицированным форматом JSON и защитой при переустановке.",
86
- "pt": "Estatísticas semanais e mensais expandidas com dados persistentes, formato JSON unificado e proteção de reinstalação.",
87
- "nl": "Uitgebreide week- en maandstatistieken met persistente gegevens, uniforme JSON-indeling en installatiebescherming.",
88
- "fr": "Statistiques hebdomadaires et mensuelles étendues avec données persistantes, format JSON unifié et protection à la réinstallation.",
89
- "it": "Statistiche settimanali e mensili estese con dati persistenti, formato JSON unificato e protezione dall'installazione.",
90
- "es": "Estadísticas semanales y mensuales ampliadas con datos persistentes, formato JSON unificado y protección de reinstalación.",
91
- "pl": "Rozszerzone statystyki tygodniowe i miesięczne z trwałymi danymi, ujednoliconym formatem JSON i ochroną przed ponowną instalacją.",
92
- "uk": "Розширена тижнева та місячна статистика з постійними даними, уніфікованим форматом JSON і захистом під час перевстановлення.",
93
- "zh-cn": "扩展的周和月统计,具有持久数据、统一的 JSON 格式和重新安装保护。"
94
94
  }
95
95
  },
96
96
  "titleLang": {
@@ -39,6 +39,11 @@ const consumptionHelper = {
39
39
  this._scheduleDailyReset();
40
40
  this._loadCostBaselines();
41
41
  this._restoreBaselinesFromStates();
42
+
43
+ // NEU: regelmäßige Perioden-Resets
44
+ this._scheduleWeeklyReset();
45
+ this._scheduleMonthlyReset();
46
+ this._scheduleYearlyReset();
42
47
  },
43
48
 
44
49
  async _loadCostBaselines() {
@@ -262,6 +267,77 @@ const consumptionHelper = {
262
267
  }, msUntilMidnight);
263
268
  },
264
269
 
270
+ // ---------------------------------------------------------
271
+ // 🔵 WÖCHENTLICHER RESET (Montag 00:05 Uhr)
272
+ // ---------------------------------------------------------
273
+ _scheduleWeeklyReset() {
274
+ const now = new Date();
275
+ const next = new Date(now);
276
+
277
+ // Montag = 1 (Sonntag = 0)
278
+ const day = now.getDay();
279
+ const daysUntilMonday = (1 - day + 7) % 7;
280
+
281
+ next.setDate(now.getDate() + daysUntilMonday);
282
+ next.setHours(0, 5, 0, 0);
283
+
284
+ const delay = next - now;
285
+
286
+ setTimeout(async () => {
287
+ try {
288
+ this.adapter.log.info('[consumptionHelper] Wochen-Reset (Montag 00:05)');
289
+ await this.adapter.setStateAsync('consumption.week_kwh', { val: 0, ack: true });
290
+ await this.adapter.setStateAsync('costs.week_eur', { val: 0, ack: true });
291
+ this.baselines.week = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
292
+ } catch (err) {
293
+ this.adapter.log.warn(`[consumptionHelper] Fehler beim Wochenreset: ${err.message}`);
294
+ }
295
+ this._scheduleWeeklyReset(); // erneut planen
296
+ }, delay);
297
+ },
298
+
299
+ // ---------------------------------------------------------
300
+ // 🔵 MONATLICHER RESET (1. des Monats 00:05 Uhr)
301
+ // ---------------------------------------------------------
302
+ _scheduleMonthlyReset() {
303
+ const now = new Date();
304
+ const next = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 5, 0, 0);
305
+ const delay = next - now;
306
+
307
+ setTimeout(async () => {
308
+ try {
309
+ this.adapter.log.info('[consumptionHelper] Monats-Reset (1. 00:05)');
310
+ await this.adapter.setStateAsync('consumption.month_kwh', { val: 0, ack: true });
311
+ await this.adapter.setStateAsync('costs.month_eur', { val: 0, ack: true });
312
+ this.baselines.month = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
313
+ } catch (err) {
314
+ this.adapter.log.warn(`[consumptionHelper] Fehler beim Monatsreset: ${err.message}`);
315
+ }
316
+ this._scheduleMonthlyReset();
317
+ }, delay);
318
+ },
319
+
320
+ // ---------------------------------------------------------
321
+ // 🔵 JÄHRLICHER RESET (1. Januar 00:10 Uhr)
322
+ // ---------------------------------------------------------
323
+ _scheduleYearlyReset() {
324
+ const now = new Date();
325
+ const next = new Date(now.getFullYear() + 1, 0, 1, 0, 10, 0, 0);
326
+ const delay = next - now;
327
+
328
+ setTimeout(async () => {
329
+ try {
330
+ this.adapter.log.info('[consumptionHelper] Jahres-Reset (1. Januar 00:10)');
331
+ await this.adapter.setStateAsync('consumption.year_kwh', { val: 0, ack: true });
332
+ await this.adapter.setStateAsync('costs.year_eur', { val: 0, ack: true });
333
+ this.baselines.year = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
334
+ } catch (err) {
335
+ this.adapter.log.warn(`[consumptionHelper] Fehler beim Jahresreset: ${err.message}`);
336
+ }
337
+ this._scheduleYearlyReset();
338
+ }, delay);
339
+ },
340
+
265
341
  cleanup() {
266
342
  if (this.resetTimer) {
267
343
  clearTimeout(this.resetTimer);
@@ -13,6 +13,9 @@ const frostHelper = {
13
13
  adapter: null,
14
14
  checkTimer: null,
15
15
 
16
+ // NEU: interner Zwischenspeicher für den vorherigen Modus, um nach Frost sauber zurückzuspringen
17
+ _prevModeBeforeFrost: null, // ENDE NEU
18
+
16
19
  init(adapter) {
17
20
  this.adapter = adapter;
18
21
 
@@ -92,6 +95,30 @@ const frostHelper = {
92
95
 
93
96
  // Schalten nur, wenn sich etwas ändert
94
97
  if (shouldRun !== pumpActive) {
98
+ // NEU: Beim Einschalten Modus/Helper setzen und vorherigen Modus merken
99
+ if (shouldRun) {
100
+ // Nur merken, wenn wir nicht bereits im Frostmodus sind
101
+ const currentMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'auto';
102
+ if (this._prevModeBeforeFrost == null) {
103
+ this._prevModeBeforeFrost = currentMode;
104
+ }
105
+
106
+ // Frost als aktiven Helper/Modus setzen (damit der pumpHelper "EIN (Frostschutz)" anzeigt)
107
+ await this.adapter.setStateAsync('pump.active_helper', { val: 'frostHelper', ack: true });
108
+ await this.adapter.setStateAsync('pump.mode', { val: 'frostHelper', ack: true });
109
+ } else {
110
+ // Ausschalten: Nur zurücksetzen, wenn der Frosthelfer wirklich aktiv war
111
+ const activeHelperNow = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
112
+ if (activeHelperNow === 'frostHelper') {
113
+ const modeToRestore = this._prevModeBeforeFrost || 'auto';
114
+ await this.adapter.setStateAsync('pump.active_helper', { val: '', ack: true });
115
+ await this.adapter.setStateAsync('pump.mode', { val: modeToRestore, ack: true });
116
+ }
117
+ // internen Zwischenspeicher nach Ende löschen
118
+ this._prevModeBeforeFrost = null;
119
+ }
120
+ // ENDE NEU
121
+
95
122
  await this.adapter.setStateAsync('pump.pump_switch', {
96
123
  val: shouldRun,
97
124
  ack: false,
@@ -10,6 +10,7 @@ const speechHelper = {
10
10
  adapter: null,
11
11
  lastTempNotify: {}, // Cooldown-Speicher pro Sensor
12
12
  lastPumpState: null, // interner Speicher für letzten Pumpenzustand
13
+ quietTime: {}, // 🆕 Cache für Alexa-Ruhezeiten
13
14
 
14
15
  init(adapter) {
15
16
  this.adapter = adapter;
@@ -22,7 +23,10 @@ const speechHelper = {
22
23
  this.adapter.subscribeStates('temperature.*.current'); // Temp-Trigger
23
24
  this.adapter.subscribeStates('pump.pump_switch'); // wichtig für Flankenerkennung
24
25
  this.adapter.subscribeStates('speech.last_text');
25
- this.adapter.subscribeStates('speech.queue'); // <<< NEU: zentrale Nachrichtenwarteschlange
26
+ this.adapter.subscribeStates('speech.queue'); // zentrale Nachrichtenwarteschlange
27
+
28
+ // 🆕 Alexa-Ruhezeit-States abonnieren
29
+ this.adapter.subscribeStates('speech.amazon_alexa.*');
26
30
 
27
31
  this.adapter.log.debug('[speechHelper] initialisiert');
28
32
  },
@@ -32,6 +36,13 @@ const speechHelper = {
32
36
  return;
33
37
  }
34
38
 
39
+ // 🆕 Wenn sich ein Alexa-Ruhezeit-State geändert hat, Werte zwischenspeichern
40
+ if (id.startsWith('poolcontrol.0.speech.amazon_alexa.')) {
41
+ const key = id.split('.').pop();
42
+ this.quietTime[key] = state.val;
43
+ return;
44
+ }
45
+
35
46
  // Globale Aktivierung prüfen
36
47
  const active = (await this.adapter.getStateAsync('speech.active'))?.val;
37
48
  if (!active) {
@@ -66,35 +77,6 @@ const speechHelper = {
66
77
  return;
67
78
  }
68
79
 
69
- /*
70
- *
71
- * Deaktiviert, ersetzt durch speechTextHelper
72
- *
73
- * // === Pumpenstart / -stop nur bei Zustandswechsel ===
74
- * if (id.endsWith('pump.pump_switch')) {
75
- * const newVal = !!state.val;
76
- *
77
- * // Nur wenn sich der Zustand wirklich geändert hat
78
- * if (this.lastPumpState !== newVal) {
79
- * this.lastPumpState = newVal;
80
- *
81
- * if (newVal) {
82
- * const txt =
83
- * (await this.adapter.getStateAsync('speech.start_text'))?.val ||
84
- * 'Die Poolpumpe wurde gestartet.';
85
- * await this._speak(txt);
86
- * } else {
87
- * const txt =
88
- * (await this.adapter.getStateAsync('speech.end_text'))?.val || 'Die Poolpumpe wurde gestoppt.';
89
- * await this._speak(txt);
90
- * }
91
- * } else {
92
- * this.adapter.log.debug('[speechHelper] Ignoriere Pumpenmeldung – kein Zustandswechsel.');
93
- * }
94
- * return;
95
- * }
96
- */
97
-
98
80
  // Nur Pool-Oberflächentemperatur berücksichtigen
99
81
  if (!id.includes('temperature.surface')) {
100
82
  return;
@@ -129,6 +111,64 @@ const speechHelper = {
129
111
  return;
130
112
  },
131
113
 
114
+ // 🆕 Prüft, ob Alexa aktuell sprechen darf
115
+ async _isAlexaAllowed() {
116
+ try {
117
+ const now = new Date();
118
+ const currentDay = now.getDay(); // 0=So, 6=Sa
119
+ const isWeekend = currentDay === 0 || currentDay === 6;
120
+ const hhmm = now.toTimeString().slice(0, 5);
121
+
122
+ // Werte aus Cache oder States holen
123
+ const prefix = 'speech.amazon_alexa.';
124
+ const enabledState = isWeekend ? 'quiet_time_weekend_enabled' : 'quiet_time_week_enabled';
125
+ const startState = isWeekend ? 'quiet_time_weekend_start' : 'quiet_time_week_start';
126
+ const endState = isWeekend ? 'quiet_time_weekend_end' : 'quiet_time_week_end';
127
+
128
+ const enabled =
129
+ this.quietTime[enabledState] ?? (await this.adapter.getStateAsync(prefix + enabledState))?.val ?? false;
130
+ const start =
131
+ this.quietTime[startState] ?? (await this.adapter.getStateAsync(prefix + startState))?.val ?? '22:00';
132
+ const end =
133
+ this.quietTime[endState] ?? (await this.adapter.getStateAsync(prefix + endState))?.val ?? '07:00';
134
+
135
+ if (!enabled) {
136
+ await this.adapter.setStateAsync(`${prefix}quiet_time_active_now`, { val: false, ack: true });
137
+ return true;
138
+ }
139
+
140
+ // Zeitvergleich
141
+ const inRange = this._isTimeInRange(hhmm, start, end);
142
+ await this.adapter.setStateAsync(`${prefix}quiet_time_active_now`, { val: inRange, ack: true });
143
+
144
+ if (inRange) {
145
+ this.adapter.log.debug('[speechHelper] Alexa-Ruhezeit aktiv – Sprachausgabe blockiert.');
146
+ return false;
147
+ }
148
+ return true;
149
+ } catch (err) {
150
+ this.adapter.log.warn(`[speechHelper] Fehler bei Alexa-Ruhezeitprüfung: ${err.message}`);
151
+ return true; // im Zweifel sprechen lassen
152
+ }
153
+ },
154
+
155
+ // 🆕 Hilfsfunktion zum Zeitvergleich
156
+ _isTimeInRange(now, start, end) {
157
+ // Umwandeln in Minuten
158
+ const toMinutes = t => {
159
+ const [h, m] = t.split(':').map(Number);
160
+ return h * 60 + m;
161
+ };
162
+ const n = toMinutes(now);
163
+ const s = toMinutes(start);
164
+ const e = toMinutes(end);
165
+
166
+ if (s < e) {
167
+ return n >= s && n < e;
168
+ }
169
+ return n >= s || n < e; // über Mitternacht
170
+ },
171
+
132
172
  async _speak(text) {
133
173
  try {
134
174
  if (!text) {
@@ -138,29 +178,31 @@ const speechHelper = {
138
178
  // Letzten Text speichern
139
179
  await this.adapter.setStateAsync('speech.last_text', { val: text, ack: true });
140
180
 
141
- // Alexa-Ausgabe
181
+ // 🆕 Alexa-Ausgabe mit Ruhezeitprüfung
142
182
  if (this.adapter.config.speech_alexa_enabled && this.adapter.config.speech_alexa_device) {
143
- await this.adapter.setForeignStateAsync(this.adapter.config.speech_alexa_device, text);
144
- this.adapter.log.info(`[speechHelper] Alexa sagt: ${text}`);
183
+ const allowed = await this._isAlexaAllowed();
184
+ if (allowed) {
185
+ await this.adapter.setForeignStateAsync(this.adapter.config.speech_alexa_device, text);
186
+ this.adapter.log.info(`[speechHelper] Alexa sagt: ${text}`);
187
+ } else {
188
+ this.adapter.log.debug('[speechHelper] Alexa stumm (Ruhezeit aktiv).');
189
+ }
145
190
  }
146
191
 
147
192
  // Telegram-Ausgabe (modern über sendTo)
148
193
  if (this.adapter.config.speech_telegram_enabled && this.adapter.config.speech_telegram_instance) {
149
194
  const instance = this.adapter.config.speech_telegram_instance;
150
195
  try {
151
- // NEU: Benutzerliste aus Admin lesen (Komma-getrennte Namen)
152
196
  const rawUsers = this.adapter.config.speech_telegram_users || '';
153
197
  const users = rawUsers
154
198
  .split(',')
155
199
  .map(u => u.trim())
156
200
  .filter(Boolean);
157
201
 
158
- // Wenn keine Benutzer angegeben → Standard: global an alle senden
159
202
  if (users.length === 0) {
160
203
  await this.adapter.sendToAsync(instance, { text, parse_mode: 'Markdown' });
161
204
  this.adapter.log.info(`[speechHelper] Telegram (global): ${text}`);
162
205
  } else {
163
- // Nur an ausgewählte Benutzer senden
164
206
  for (const user of users) {
165
207
  await this.adapter.sendToAsync(instance, { user, text, parse_mode: 'Markdown' });
166
208
  this.adapter.log.info(`[speechHelper] Telegram an ${user}: ${text}`);
@@ -175,7 +217,7 @@ const speechHelper = {
175
217
 
176
218
  // E-Mail-Ausgabe (modern über sendTo)
177
219
  if (this.adapter.config.speech_email_enabled && this.adapter.config.speech_email_instance) {
178
- const instance = this.adapter.config.speech_email_instance; // z. B. "email.0"
220
+ const instance = this.adapter.config.speech_email_instance;
179
221
  try {
180
222
  this.adapter.sendTo(instance, {
181
223
  to: this.adapter.config.speech_email_recipient,
@@ -195,7 +237,9 @@ const speechHelper = {
195
237
  },
196
238
 
197
239
  cleanup() {
198
- // nichts nötig bisher
240
+ if (this.adapter) {
241
+ this.adapter.log.debug('[speechHelper] Cleanup abgeschlossen.');
242
+ }
199
243
  },
200
244
  };
201
245
 
@@ -104,6 +104,93 @@ async function createSpeechStates(adapter) {
104
104
  ack: true,
105
105
  });
106
106
 
107
+ // ------------------------------------------------------------------
108
+ // 🆕 Amazon Alexa – Ruhezeiten
109
+ // ------------------------------------------------------------------
110
+
111
+ await adapter.setObjectNotExistsAsync('speech.amazon_alexa', {
112
+ type: 'channel',
113
+ common: { name: 'Amazon Alexa (Sprachausgabe)' },
114
+ native: {},
115
+ });
116
+
117
+ const alexaStates = [
118
+ {
119
+ id: 'quiet_time_week_enabled',
120
+ name: 'Ruhezeit (Mo–Fr) aktiv',
121
+ type: 'boolean',
122
+ role: 'switch',
123
+ def: false,
124
+ },
125
+ {
126
+ id: 'quiet_time_week_start',
127
+ name: 'Ruhezeit (Mo–Fr) Startzeit (HH:MM)',
128
+ type: 'string',
129
+ role: 'value.time',
130
+ def: '22:00',
131
+ },
132
+ {
133
+ id: 'quiet_time_week_end',
134
+ name: 'Ruhezeit (Mo–Fr) Endzeit (HH:MM)',
135
+ type: 'string',
136
+ role: 'value.time',
137
+ def: '07:00',
138
+ },
139
+ {
140
+ id: 'quiet_time_weekend_enabled',
141
+ name: 'Ruhezeit (Sa–So) aktiv',
142
+ type: 'boolean',
143
+ role: 'switch',
144
+ def: false,
145
+ },
146
+ {
147
+ id: 'quiet_time_weekend_start',
148
+ name: 'Ruhezeit (Sa–So) Startzeit (HH:MM)',
149
+ type: 'string',
150
+ role: 'value.time',
151
+ def: '22:00',
152
+ },
153
+ {
154
+ id: 'quiet_time_weekend_end',
155
+ name: 'Ruhezeit (Sa–So) Endzeit (HH:MM)',
156
+ type: 'string',
157
+ role: 'value.time',
158
+ def: '08:00',
159
+ },
160
+ {
161
+ id: 'quiet_time_active_now',
162
+ name: 'Alexa derzeit stumm (Ruhezeit aktiv)',
163
+ type: 'boolean',
164
+ role: 'indicator',
165
+ def: false,
166
+ write: false,
167
+ },
168
+ ];
169
+
170
+ for (const s of alexaStates) {
171
+ const fullId = `speech.amazon_alexa.${s.id}`;
172
+ await adapter.setObjectNotExistsAsync(fullId, {
173
+ type: 'state',
174
+ common: {
175
+ name: s.name,
176
+ type: s.type,
177
+ role: s.role,
178
+ read: true,
179
+ write: Boolean(s.write !== false), // Standard: true
180
+ def: s.def,
181
+ persist: true,
182
+ },
183
+ native: {},
184
+ });
185
+
186
+ const current = await adapter.getStateAsync(fullId);
187
+ if (!current || current.val === null || current.val === undefined) {
188
+ await adapter.setStateAsync(fullId, { val: s.def, ack: true });
189
+ }
190
+ }
191
+
192
+ adapter.log.debug('[speechStates] Amazon Alexa – Ruhezeit-States geprüft und angelegt.');
193
+
107
194
  // versteckte Dateien
108
195
 
109
196
  await adapter.setObjectNotExistsAsync('speech.solar_active', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.poolcontrol",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
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",