iobroker.poolcontrol 0.7.3 → 0.8.0

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.
@@ -8,35 +8,35 @@
8
8
  * Zentraler KI-Helper für PoolControl.
9
9
  *
10
10
  * Nutzt die States aus aiStates.js:
11
- * ai.switches.*
12
- * ai.schedule.*
13
- * ai.outputs.*
11
+ * ai.weather.switches.*
12
+ * ai.weather.schedule.*
13
+ * ai.weather.outputs.*
14
14
  *
15
15
  * Funktionen:
16
16
  * - Liest Geodaten aus system.config (Latitude/Longitude)
17
17
  * - Ruft Wetterdaten von Open-Meteo ab (bei Bedarf, max. 4x/Tag – je Modul)
18
18
  * - Erzeugt Textausgaben:
19
- * ai.outputs.weather_advice
20
- * ai.outputs.daily_summary
21
- * ai.outputs.pool_tips
22
- * ai.outputs.weekend_summary
23
- * ai.outputs.last_message
19
+ * ai.weather.outputs.weather_advice
20
+ * ai.weather.outputs.daily_summary
21
+ * ai.weather.outputs.pool_tips
22
+ * ai.weather.outputs.weekend_summary
23
+ * ai.weather.outputs.last_message
24
24
  * - Optional: legt Texte in speech.queue für Sprachausgabe
25
25
  *
26
26
  * Wichtige Schalter:
27
- * - ai.switches.enabled → globaler KI-Schalter
28
- * - ai.switches.allow_speech → Sprachausgabe erlaubt
29
- * - ai.switches.weather_advice_enabled
30
- * - ai.switches.daily_summary_enabled
31
- * - ai.switches.daily_pool_tips_enabled
32
- * - ai.switches.weekend_summary_enabled
33
- * - ai.switches.debug_mode
27
+ * - ai.enabled → globaler KI-Schalter
28
+ * - ai.weather.switches.allow_speech → Sprachausgabe erlaubt
29
+ * - ai.weather.switches.weather_advice_enabled
30
+ * - ai.weather.switches.daily_summary_enabled
31
+ * - ai.weather.switches.daily_pool_tips_enabled
32
+ * - ai.weather.switches.weekend_summary_enabled
33
+ * - ai.weather.switches.debug_mode
34
34
  *
35
35
  * Zeitsteuerung (HH:MM, lokal):
36
- * - ai.schedule.weather_advice_time
37
- * - ai.schedule.daily_summary_time
38
- * - ai.schedule.daily_pool_tips_time
39
- * - ai.schedule.weekend_summary_time
36
+ * - ai.weather.schedule.weather_advice_time
37
+ * - ai.weather.schedule.daily_summary_time
38
+ * - ai.weather.schedule.daily_pool_tips_time
39
+ * - ai.weather.schedule.weekend_summary_time
40
40
  */
41
41
 
42
42
  const https = require('https');
@@ -47,6 +47,11 @@ const aiHelper = {
47
47
  _lastScheduleValues: {}, // NEU: merkt sich letzte Zeitwerte
48
48
  _debugMode: false,
49
49
 
50
+ // Anti-Spam-Level (merkt sich letzte Warnungen)
51
+ _lastPoolTipCode: null,
52
+ _lastPoolTipWindLevel: null,
53
+ _lastPoolTipTimestamp: 0,
54
+
50
55
  /**
51
56
  * Initialisiert den AI-Helper (Timer + Grundkonfiguration).
52
57
  *
@@ -101,7 +106,7 @@ const aiHelper = {
101
106
  // ---------------------------------------------------------
102
107
  // FIX 2: Uhrzeitänderungen IMMER erkennen und loggen
103
108
  // ---------------------------------------------------------
104
- if (id.includes('.ai.schedule.')) {
109
+ if (id.includes('.ai.weather.schedule.')) {
105
110
  const oldVal = this._lastScheduleValues[id];
106
111
  const newVal = state.val;
107
112
 
@@ -114,13 +119,49 @@ const aiHelper = {
114
119
 
115
120
  // Timer neu aufbauen
116
121
  await this._refreshTimers();
122
+
123
+ // NEU: Wenn Uhrzeit heute noch in der Zukunft liegt → sofort ausführen
124
+ try {
125
+ const now = new Date();
126
+ const [hourStr, minuteStr] = String(newVal).split(':');
127
+ const hour = Number(hourStr);
128
+ const minute = Number(minuteStr);
129
+
130
+ if (!Number.isNaN(hour) && !Number.isNaN(minute)) {
131
+ const target = new Date();
132
+ target.setHours(hour, minute, 0, 0);
133
+
134
+ // Nur wenn Zielzeit HEUTE noch bevorsteht
135
+ if (target > now) {
136
+ this.adapter.log.info(
137
+ `[aiHelper] Neue Uhrzeit liegt heute noch in der Zukunft → führe Modul sofort aus (${id})`,
138
+ );
139
+
140
+ if (id.endsWith('weather_advice_time')) {
141
+ await this._runWeatherAdvice();
142
+ }
143
+ if (id.endsWith('daily_summary_time')) {
144
+ await this._runDailySummary();
145
+ }
146
+ if (id.endsWith('daily_pool_tips_time')) {
147
+ await this._runDailyPoolTips();
148
+ }
149
+ if (id.endsWith('weekend_summary_time')) {
150
+ await this._runWeekendSummary();
151
+ }
152
+ }
153
+ }
154
+ } catch (e) {
155
+ this.adapter.log.warn(`[aiHelper] Fehler bei Sofortausführung nach Zeitänderung: ${e.message}`);
156
+ }
157
+
117
158
  return;
118
159
  }
119
160
 
120
161
  // ---------------------------------------------------------
121
162
  // FIX 3: Schalteränderungen immer melden
122
163
  // ---------------------------------------------------------
123
- if (id.includes('.ai.switches.')) {
164
+ if (id.includes('.ai.weather.switches.')) {
124
165
  this.adapter.log.info(`[aiHelper] Schalter geändert: ${id} = ${state.val} – Timer werden neu aufgebaut`);
125
166
 
126
167
  await this._refreshTimers();
@@ -148,63 +189,106 @@ const aiHelper = {
148
189
  async _refreshTimers() {
149
190
  this._clearTimers();
150
191
 
151
- const aiEnabled = await this._getBool('ai.switches.enabled', false);
152
- this._debugMode = await this._getBool('ai.switches.debug_mode', false);
192
+ const aiEnabled = await this._getBool('ai.enabled', false);
193
+ this._debugMode = await this._getBool('ai.weather.switches.debug_mode', false);
153
194
 
154
195
  if (!aiEnabled) {
155
- this.adapter.log.info('[aiHelper] KI ist deaktiviert (ai.switches.enabled = false) – keine Timer aktiv');
196
+ this.adapter.log.info('[aiHelper] KI ist deaktiviert (ai.enabled = false) – keine Timer aktiv');
156
197
  return;
157
198
  }
158
199
 
159
200
  this.adapter.log.info('[aiHelper] KI ist aktiv – Timer werden gesetzt');
160
201
 
161
202
  // --- Wetterhinweise ---
162
- const weatherEnabled = await this._getBool('ai.switches.weather_advice_enabled', false);
203
+ const weatherEnabled = await this._getBool('ai.weather.switches.weather_advice_enabled', false);
163
204
  if (weatherEnabled) {
164
- const time = await this._getTimeOrDefault('ai.schedule.weather_advice_time', '08:00');
205
+ const time = await this._getTimeOrDefault('ai.weather.schedule.weather_advice_time', '08:00');
165
206
  this._createDailyTimer(time, async () => {
166
207
  await this._runWeatherAdvice();
167
208
  });
168
- this.adapter.log.info(
209
+ this.adapter.log.debug(
169
210
  `[aiHelper] Wetterhinweis-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
170
211
  );
171
212
  }
172
213
 
173
214
  // --- Tägliche Zusammenfassung ---
174
- const summaryEnabled = await this._getBool('ai.switches.daily_summary_enabled', false);
215
+ const summaryEnabled = await this._getBool('ai.weather.switches.daily_summary_enabled', false);
175
216
  if (summaryEnabled) {
176
- const time = await this._getTimeOrDefault('ai.schedule.daily_summary_time', '09:00');
217
+ const time = await this._getTimeOrDefault('ai.weather.schedule.daily_summary_time', '09:00');
177
218
  this._createDailyTimer(time, async () => {
178
219
  await this._runDailySummary();
179
220
  });
180
- this.adapter.log.info(
221
+ this.adapter.log.debug(
181
222
  `[aiHelper] Daily-Summary-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
182
223
  );
183
224
  }
184
225
 
185
226
  // --- Tägliche Pool-Tipps ---
186
- const tipsEnabled = await this._getBool('ai.switches.daily_pool_tips_enabled', false);
227
+ const tipsEnabled = await this._getBool('ai.weather.switches.daily_pool_tips_enabled', false);
187
228
  if (tipsEnabled) {
188
- const time = await this._getTimeOrDefault('ai.schedule.daily_pool_tips_time', '10:00');
229
+ const time = await this._getTimeOrDefault('ai.weather.schedule.daily_pool_tips_time', '10:00');
189
230
  this._createDailyTimer(time, async () => {
190
231
  await this._runDailyPoolTips();
191
232
  });
192
- this.adapter.log.info(
233
+ this.adapter.log.debug(
193
234
  `[aiHelper] Pool-Tipps-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
194
235
  );
195
236
  }
196
237
 
197
238
  // --- Wochenend-Zusammenfassung ---
198
- const weekendEnabled = await this._getBool('ai.switches.weekend_summary_enabled', false);
239
+ const weekendEnabled = await this._getBool('ai.weather.switches.weekend_summary_enabled', false);
199
240
  if (weekendEnabled) {
200
- const time = await this._getTimeOrDefault('ai.schedule.weekend_summary_time', '18:00');
241
+ const time = await this._getTimeOrDefault('ai.weather.schedule.weekend_summary_time', '18:00');
201
242
  this._createDailyTimer(time, async () => {
202
243
  await this._runWeekendSummary();
203
244
  });
204
- this.adapter.log.info(
245
+ this.adapter.log.debug(
205
246
  `[aiHelper] Wochenend-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
206
247
  );
207
248
  }
249
+
250
+ //--------------------------------------------------------
251
+ // NEU: Stündlicher Wetter-Update-Timer
252
+ //--------------------------------------------------------
253
+ this.adapter.log.debug('[aiHelper] Stündlicher Wetter-Update-Timer wird gesetzt');
254
+
255
+ const hourlyTimer = setInterval(
256
+ async () => {
257
+ try {
258
+ const geo = await this._loadGeoLocation();
259
+ if (!geo) {
260
+ this.adapter.log.info('[aiHelper] Wetter-Update abgebrochen – keine Geodaten verfügbar');
261
+ return;
262
+ }
263
+
264
+ const weather = await this._fetchWeather(geo.lat, geo.lon);
265
+ if (!weather) {
266
+ this.adapter.log.info('[aiHelper] Wetter-Update abgebrochen – keine Wetterdaten verfügbar');
267
+ return;
268
+ }
269
+
270
+ // WeatherAdvice aktualisieren (heutiges Wetter)
271
+ const weatherText = this._buildWeatherAdviceText(weather);
272
+ await this._writeOutput('weather_advice', weatherText);
273
+
274
+ // NEU: Pool-Tipps automatisch mit aktualisieren
275
+ const seasonActive = await this._getBool('status.season_active', false);
276
+ const poolTipsText = this._buildPoolTipsText(weather, seasonActive);
277
+ await this._writeOutput('pool_tips', poolTipsText);
278
+
279
+ if (this._debugMode) {
280
+ this.adapter.log.debug(
281
+ '[aiHelper] Stündliches Wetter-Update durchgeführt (Weather + Pool-Tipps)',
282
+ );
283
+ }
284
+ } catch (err) {
285
+ this.adapter.log.warn(`[aiHelper] Fehler beim stündlichen Wetter-Update: ${err.message}`);
286
+ }
287
+ },
288
+ 60 * 60 * 1000,
289
+ ); // 1 Stunde
290
+
291
+ this.timers.push(hourlyTimer);
208
292
  },
209
293
 
210
294
  /**
@@ -225,14 +309,14 @@ const aiHelper = {
225
309
  if (diffMinutes > 0 && diffMinutes <= 2) {
226
310
  try {
227
311
  await callback();
228
- this.adapter.log.info('[aiHelper] Zeit knapp verpasst – nachträgliche Ausführung durchgeführt');
312
+ this.adapter.log.debug('[aiHelper] Zeit knapp verpasst – nachträgliche Ausführung durchgeführt');
229
313
  } catch (err) {
230
314
  this.adapter.log.warn(`[aiHelper] Fehler bei nachträglicher Ausführung: ${err.message}`);
231
315
  }
232
316
  }
233
317
 
234
318
  if (now.getHours() === hour && now.getMinutes() === minute) {
235
- this.adapter.log.info(
319
+ this.adapter.log.debug(
236
320
  `[aiHelper] Timer ausgelöst: ${hour}:${String(minute).padStart(2, '0')} → Callback wird ausgeführt`,
237
321
  ); // NEU
238
322
 
@@ -359,32 +443,57 @@ const aiHelper = {
359
443
  }
360
444
  },
361
445
 
446
+ //--------------------------------------------------------
447
+ // NEU: Stündliches Wetter-Update als eigene Funktion
448
+ //--------------------------------------------------------
449
+ async _runWeatherAutoUpdate() {
450
+ try {
451
+ const geo = await this._loadGeoLocation();
452
+ if (!geo) {
453
+ this.adapter.log.info('[aiHelper] Auto-Wetterupdate abgebrochen – keine Geodaten verfügbar');
454
+ return;
455
+ }
456
+
457
+ const weather = await this._fetchWeather(geo.lat, geo.lon);
458
+ if (!weather) {
459
+ this.adapter.log.info('[aiHelper] Auto-Wetterupdate abgebrochen – keine Wetterdaten verfügbar');
460
+ return;
461
+ }
462
+
463
+ // WeatherAdvice aktualisieren
464
+ const text = this._buildWeatherAdviceText(weather);
465
+ await this._writeOutput('weather_advice', text);
466
+
467
+ if (this._debugMode) {
468
+ this.adapter.log.debug('[aiHelper] Auto-Wetterupdate erfolgreich durchgeführt');
469
+ }
470
+ } catch (err) {
471
+ this.adapter.log.warn(`[aiHelper] Fehler bei Auto-Wetterupdate: ${err.message}`);
472
+ }
473
+ },
474
+
362
475
  // ---------------------------------------------------------------------
363
476
  // Geodaten + Wetter
364
477
  // ---------------------------------------------------------------------
365
478
 
366
479
  /**
367
- * Lädt Geokoordinaten aus system.config.
480
+ * Lädt Geokoordinaten korrekt aus system.config.
368
481
  *
369
482
  * @returns {{lat:number,lon:number}|null}
370
483
  */
371
484
  async _loadGeoLocation() {
372
485
  try {
373
- const latState = await this.adapter.getForeignStateAsync('system.config.latitude');
374
- const lonState = await this.adapter.getForeignStateAsync('system.config.longitude');
375
-
376
- if (!latState || !lonState || latState.val == null || lonState.val == null) {
377
- this.adapter.log.warn(
378
- '[aiHelper] Geodaten in system.config nicht gesetzt – bitte im Admin konfigurieren',
379
- );
486
+ const obj = await this.adapter.getForeignObjectAsync('system.config');
487
+ if (!obj || !obj.common) {
488
+ this.adapter.log.warn('[aiHelper] Konnte system.config nicht laden');
380
489
  return null;
381
490
  }
382
491
 
383
- const lat = Number(latState.val);
384
- const lon = Number(lonState.val);
492
+ const lat = Number(obj.common.latitude);
493
+ const lon = Number(obj.common.longitude);
385
494
 
386
495
  if (Number.isNaN(lat) || Number.isNaN(lon)) {
387
- this.adapter.log.warn('[aiHelper] Geodaten ungültig – latitude/longitude sind keine Zahlen');
496
+ this.adapter.log.warn('[aiHelper] Geodaten ungültig – bitte in Admin unter System/Standort eintragen');
388
497
  return null;
389
498
  }
390
499
 
@@ -567,6 +676,54 @@ const aiHelper = {
567
676
 
568
677
  let text = 'Pool-Tipp für heute: ';
569
678
 
679
+ //--------------------------------------------------------
680
+ // NEU: Erweiterte Analyse für Pool-Tipps
681
+ //--------------------------------------------------------
682
+
683
+ // 1) Wind / Sturm
684
+ const wind = weather?.current?.wind_speed_10m ?? null;
685
+ if (wind != null) {
686
+ if (wind >= 60) {
687
+ text +=
688
+ '⚠️ Extrem starker Sturm erwartet! Bitte unbedingt die Abdeckung sichern und alle Gegenstände im Poolbereich fest verankern. ';
689
+ } else if (wind >= 45) {
690
+ text += 'Achtung: Starke Windböen treten auf. Bitte Abdeckung fixieren und lose Gegenstände sichern. ';
691
+ } else if (wind >= 30) {
692
+ text += 'Es wird windig – Abdeckung gut verschließen und empfindliches Zubehör schützen. ';
693
+ }
694
+ }
695
+
696
+ // 2) Regen / Starkregen
697
+ if (code != null) {
698
+ if ([65, 82].includes(code)) {
699
+ text += 'Kräftige Regenschauer erwartet – Abdeckung geschlossen halten. ';
700
+ } else if ([61, 63, 80, 81].includes(code)) {
701
+ text += 'Es wird regnerisch – Abdeckung eher geschlossen lassen. ';
702
+ }
703
+ }
704
+
705
+ // 3) Gewitter / Hagel
706
+ if (code === 95) {
707
+ text += '⚡ Gewitterwarnung! Bitte Solarfolie sichern und Technik vor Feuchtigkeit schützen. ';
708
+ }
709
+ if (code === 96 || code === 99) {
710
+ text += '⚠️ Hagelgefahr! Bitte empfindliche Geräte schützen und Poolbereich räumen. ';
711
+ }
712
+
713
+ // 4) Temperatur / Hitze
714
+ if (tmax >= 28) {
715
+ text += 'Sehr warmes Badewetter – Abdeckung tagsüber offen lassen. Chlorverbrauch steigt. ';
716
+ } else if (tmax >= 22) {
717
+ text += 'Angenehme Temperaturen – normaler Poolbetrieb empfohlen. ';
718
+ } else if (tmax <= 16) {
719
+ text += 'Kühle Temperaturen – Abdeckung geschlossen halten, um Wärmeverluste zu reduzieren. ';
720
+ }
721
+
722
+ // Fallback falls noch nichts geschrieben wurde
723
+ if (text.trim() === 'Pool-Tipp für heute:') {
724
+ text += desc ? `${desc}. ` : 'Keine besonderen Hinweise für den heutigen Tag. ';
725
+ }
726
+
570
727
  if (tmax >= 26) {
571
728
  text += 'Es wird warm bis sehr warm – gutes Badewetter. ';
572
729
  text +=
@@ -587,6 +744,47 @@ const aiHelper = {
587
744
  text += 'Bei sonniger Witterung steigt der Chlorverbrauch – Wasserwerte im Auge behalten.';
588
745
  }
589
746
 
747
+ //--------------------------------------------------------
748
+ // NEU: Anti-Spam-Logik
749
+ //--------------------------------------------------------
750
+
751
+ // // Wettercode vergleichen
752
+ // if (code != null) {
753
+ // if (this._lastPoolTipCode === code) {
754
+ // // gleiches Wetter wie vorher → eventuell abbrechen
755
+ // const nowTs = Date.now();
756
+ // // nur jede 3 Stunden dieselbe Warnung erneut ausgeben
757
+ // if (nowTs - this._lastPoolTipTimestamp < 3 * 60 * 60 * 1000) {
758
+ // return 'Pool-Tipp: Keine neuen Hinweise – Bedingungen unverändert.';
759
+ // }
760
+ // }
761
+ // }
762
+
763
+ // Windlevel kategorisieren: 0 = ruhig, 1 = windig, 2 = stark, 3 = Sturm
764
+ let windLevel = 0;
765
+ if (wind != null) {
766
+ if (wind >= 60) {
767
+ windLevel = 3;
768
+ } else if (wind >= 45) {
769
+ windLevel = 2;
770
+ } else if (wind >= 30) {
771
+ windLevel = 1;
772
+ }
773
+ }
774
+
775
+ // // prüfen, ob derselbe Windlevel schon gemeldet wurde
776
+ // if (windLevel === this._lastPoolTipWindLevel) {
777
+ // const nowTs = Date.now();
778
+ // if (nowTs - this._lastPoolTipTimestamp < 3 * 60 * 60 * 1000) {
779
+ // return 'Pool-Tipp: Keine neuen Informationen – Wetter gleich geblieben.';
780
+ // }
781
+ // }
782
+
783
+ // Wenn wir hier sind → neuer Hinweis → Werte speichern
784
+ this._lastPoolTipCode = code;
785
+ this._lastPoolTipWindLevel = windLevel;
786
+ this._lastPoolTipTimestamp = Date.now();
787
+
590
788
  return text;
591
789
  },
592
790
 
@@ -761,11 +959,11 @@ const aiHelper = {
761
959
  }
762
960
 
763
961
  try {
764
- await this.adapter.setStateAsync(`ai.outputs.${id}`, { val: text, ack: true });
765
- await this.adapter.setStateAsync('ai.outputs.last_message', { val: text, ack: true });
962
+ await this.adapter.setStateAsync(`ai.weather.outputs.${id}`, { val: text, ack: true });
963
+ await this.adapter.setStateAsync('ai.weather.outputs.last_message', { val: text, ack: true });
766
964
 
767
965
  if (this._debugMode) {
768
- this.adapter.log.info(`[aiHelper] Output geschrieben → ai.outputs.${id}: ${text}`);
966
+ this.adapter.log.debug(`[aiHelper] Output geschrieben → ai.weather.outputs.${id}: ${text}`);
769
967
  }
770
968
  } catch (err) {
771
969
  this.adapter.log.error(`[aiHelper] Fehler beim Schreiben eines Outputs (${id}): ${err.message}`);
@@ -785,10 +983,12 @@ const aiHelper = {
785
983
  return;
786
984
  }
787
985
 
788
- const allowSpeech = await this._getBool('ai.switches.allow_speech', false);
986
+ const allowSpeech = await this._getBool('ai.weather.switches.allow_speech', false);
789
987
  if (!allowSpeech) {
790
988
  if (this._debugMode) {
791
- this.adapter.log.debug('[aiHelper] Sprachausgabe deaktiviert (ai.switches.allow_speech = false)');
989
+ this.adapter.log.debug(
990
+ '[aiHelper] Sprachausgabe deaktiviert (ai.weather.switches.allow_speech = false)',
991
+ );
792
992
  }
793
993
  return;
794
994
  }
@@ -870,7 +1070,7 @@ const aiHelper = {
870
1070
  const str = await this._getString(id, def);
871
1071
 
872
1072
  // NEU: Log, welche Uhrzeit der Helper tatsächlich verwendet
873
- this.adapter.log.info(`[aiHelper] Uhrzeit geladen: ${id} = "${str}" (Default: ${def})`);
1073
+ this.adapter.log.debug(`[aiHelper] Uhrzeit geladen: ${id} = "${str}" (Default: ${def})`);
874
1074
 
875
1075
  const match = /^(\d{1,2}):(\d{2})$/.exec(str || '');
876
1076
  let hour = 0;
@@ -4,10 +4,13 @@
4
4
  * aiStates.js
5
5
  * ----------------------------------------------------------
6
6
  * Definiert alle States für den KI-Bereich des PoolControl-Adapters.
7
- * Struktur:
8
- * ai.switches.*
9
- * ai.schedule.*
10
- * ai.outputs.*
7
+ *
8
+ * Neue Struktur:
9
+ * ai.enabled → globaler KI-Hauptschalter
10
+ *
11
+ * ai.weather.switches.* → Schalter für Wetter-KI
12
+ * ai.weather.schedule.* → Zeitpläne für Wetter-KI
13
+ * ai.weather.outputs.* → Textausgaben der Wetter-KI
11
14
  * ----------------------------------------------------------
12
15
  *
13
16
  * @param {import('iobroker').Adapter} adapter - ioBroker Adapterinstanz
@@ -21,7 +24,9 @@
21
24
  async function createAiStates(adapter) {
22
25
  adapter.log.debug('[aiStates] Initialisierung gestartet');
23
26
 
24
- // Hauptordner
27
+ // ------------------------------------------------------
28
+ // Hauptordner: ai
29
+ // ------------------------------------------------------
25
30
  await adapter.setObjectNotExistsAsync('ai', {
26
31
  type: 'channel',
27
32
  common: { name: 'KI / AI-Funktionen' },
@@ -29,26 +34,67 @@ async function createAiStates(adapter) {
29
34
  });
30
35
 
31
36
  // ------------------------------------------------------
32
- // Unterordner: switches
37
+ // NEU / GEÄNDERT:
38
+ // Globaler KI-Hauptschalter (anstatt ai.switches.enabled)
39
+ // ------------------------------------------------------
40
+ await adapter.setObjectNotExistsAsync('ai.enabled', {
41
+ type: 'state',
42
+ common: {
43
+ name: 'KI aktivieren',
44
+ desc: 'Globaler Hauptschalter für alle KI-Funktionen',
45
+ type: 'boolean',
46
+ role: 'switch',
47
+ read: true,
48
+ write: true,
49
+ def: false,
50
+ persist: true,
51
+ },
52
+ native: {},
53
+ });
54
+
55
+ // ------------------------------------------------------
56
+ // NEU: Wetter-Hauptordner
57
+ // ------------------------------------------------------
58
+ await adapter.setObjectNotExistsAsync('ai.weather', {
59
+ type: 'channel',
60
+ common: {
61
+ name: 'Wetterbezogene KI-Funktionen',
62
+ desc: 'Alle KI-Funktionen rund um Wetter, Vorhersagen und Pooltipps',
63
+ },
64
+ native: {},
65
+ });
66
+
67
+ // ------------------------------------------------------
68
+ // NEU: Unterordner Wetter-Switches
33
69
  // ------------------------------------------------------
34
- await adapter.setObjectNotExistsAsync('ai.switches', {
70
+ await adapter.setObjectNotExistsAsync('ai.weather.switches', {
35
71
  type: 'channel',
36
- common: { name: 'Schalter (KI-Steuerung)' },
72
+ common: {
73
+ name: 'Schalter (Wetter-KI)',
74
+ desc: 'Einzelne Schalter für wetterbezogene KI-Funktionen',
75
+ },
37
76
  native: {},
38
77
  });
39
78
 
40
- const switches = [
41
- { id: 'enabled', name: 'KI aktivieren', def: false },
42
- { id: 'allow_speech', name: 'Sprachausgabe durch KI erlauben', def: false },
79
+ // NEU / GEÄNDERT:
80
+ // Alle bisherigen wetterbezogenen Switches liegen jetzt unter ai.weather.switches.*
81
+ const weatherSwitches = [
82
+ // Steuerung & Debug für Wetter-KI
83
+ { id: 'allow_speech', name: 'Sprachausgabe für Wetter-KI erlauben', def: false }, // GEÄNDERT: von ai.switches → ai.weather.switches
84
+ { id: 'debug_mode', name: 'Debugmodus für Wetter-KI', def: false }, // GEÄNDERT: von ai.switches → ai.weather.switches
85
+
86
+ // Modulspezifische Schalter
43
87
  { id: 'daily_summary_enabled', name: 'Tägliche Zusammenfassung aktiv', def: false },
44
88
  { id: 'daily_pool_tips_enabled', name: 'Tägliche Pool-Tipps aktiv', def: false },
45
89
  { id: 'weather_advice_enabled', name: 'Wetterhinweise aktiv', def: false },
46
90
  { id: 'weekend_summary_enabled', name: 'Wochenende-Zusammenfassung aktiv', def: false },
47
- { id: 'debug_mode', name: 'KI-Debugmodus', def: false },
91
+
92
+ // NEU: Schalter für "Vorhersage für morgen"
93
+ { id: 'tomorrow_forecast_enabled', name: 'Vorhersage für morgen aktiv', def: false }, // NEU
48
94
  ];
49
95
 
50
- for (const s of switches) {
51
- await adapter.setObjectNotExistsAsync(`ai.switches.${s.id}`, {
96
+ for (const s of weatherSwitches) {
97
+ await adapter.setObjectNotExistsAsync(`ai.weather.switches.${s.id}`, {
52
98
  type: 'state',
53
99
  common: {
54
100
  name: s.name,
@@ -64,23 +110,31 @@ async function createAiStates(adapter) {
64
110
  }
65
111
 
66
112
  // ------------------------------------------------------
67
- // Unterordner: schedule
113
+ // NEU: Unterordner Wetter-Schedule
68
114
  // ------------------------------------------------------
69
- await adapter.setObjectNotExistsAsync('ai.schedule', {
115
+ await adapter.setObjectNotExistsAsync('ai.weather.schedule', {
70
116
  type: 'channel',
71
- common: { name: 'Zeitpläne (KI-Ausgaben)' },
117
+ common: {
118
+ name: 'Zeitpläne (Wetter-KI)',
119
+ desc: 'Zeitsteuerung für wetterbezogene KI-Ausgaben',
120
+ },
72
121
  native: {},
73
122
  });
74
123
 
75
- const schedule = [
124
+ // NEU / GEÄNDERT:
125
+ // Alle Zeitpläne liegen jetzt unter ai.weather.schedule.*
126
+ const weatherSchedule = [
76
127
  { id: 'daily_summary_time', name: 'Zeit für tägliche Zusammenfassung', def: '09:00' },
77
128
  { id: 'daily_pool_tips_time', name: 'Zeit für tägliche Pool-Tipps', def: '10:00' },
78
129
  { id: 'weather_advice_time', name: 'Zeit für Wetterhinweise', def: '08:00' },
79
130
  { id: 'weekend_summary_time', name: 'Zeit für Wochenend-Zusammenfassung', def: '18:00' },
131
+
132
+ // NEU: Zeitplan für "Vorhersage für morgen"
133
+ { id: 'tomorrow_forecast_time', name: 'Zeit für morgige Vorhersage', def: '19:00' }, // NEU
80
134
  ];
81
135
 
82
- for (const t of schedule) {
83
- await adapter.setObjectNotExistsAsync(`ai.schedule.${t.id}`, {
136
+ for (const t of weatherSchedule) {
137
+ await adapter.setObjectNotExistsAsync(`ai.weather.schedule.${t.id}`, {
84
138
  type: 'state',
85
139
  common: {
86
140
  name: t.name,
@@ -96,24 +150,34 @@ async function createAiStates(adapter) {
96
150
  }
97
151
 
98
152
  // ------------------------------------------------------
99
- // Unterordner: outputs
153
+ // NEU: Unterordner Wetter-Outputs
100
154
  // ------------------------------------------------------
101
- await adapter.setObjectNotExistsAsync('ai.outputs', {
155
+ await adapter.setObjectNotExistsAsync('ai.weather.outputs', {
102
156
  type: 'channel',
103
- common: { name: 'KI-Ausgaben (Texte)' },
157
+ common: {
158
+ name: 'KI-Ausgaben (Wetter-Texte)',
159
+ desc: 'Textausgaben der Wetter-KI (Hinweise, Tipps, Zusammenfassungen)',
160
+ },
104
161
  native: {},
105
162
  });
106
163
 
107
- const outputs = [
164
+ // NEU / GEÄNDERT:
165
+ // Alle bisherigen Ausgaben + neue Vorhersage + last_message unter ai.weather.outputs.*
166
+ const weatherOutputs = [
108
167
  { id: 'daily_summary', name: 'Tägliche Zusammenfassung' },
109
168
  { id: 'pool_tips', name: 'Pool-Tipps' },
110
169
  { id: 'weather_advice', name: 'Wetterhinweise' },
111
170
  { id: 'weekend_summary', name: 'Wochenende-Zusammenfassung' },
112
- { id: 'last_message', name: 'Letzte KI-Meldung' },
171
+
172
+ // NEU: Ausgabefeld für die Vorhersage für morgen
173
+ { id: 'tomorrow_forecast', name: 'Vorhersage für morgen' }, // NEU
174
+
175
+ // GEÄNDERT: last_message gehört aktuell zur Wetter-KI
176
+ { id: 'last_message', name: 'Letzte Wetter-KI-Meldung' }, // GEÄNDERT: von ai.outputs → ai.weather.outputs
113
177
  ];
114
178
 
115
- for (const o of outputs) {
116
- await adapter.setObjectNotExistsAsync(`ai.outputs.${o.id}`, {
179
+ for (const o of weatherOutputs) {
180
+ await adapter.setObjectNotExistsAsync(`ai.weather.outputs.${o.id}`, {
117
181
  type: 'state',
118
182
  common: {
119
183
  name: o.name,