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.
- package/README.md +54 -3
- package/admin/jsonConfig.json +96 -2
- package/io-package.json +27 -27
- package/lib/helpers/aiForecastHelper.js +448 -0
- package/lib/helpers/aiHelper.js +255 -55
- package/lib/stateDefinitions/aiStates.js +91 -27
- package/lib/stateDefinitions/controlStates.js +10 -1
- package/main.js +13 -3
- package/package.json +1 -1
package/lib/helpers/aiHelper.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
374
|
-
|
|
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(
|
|
384
|
-
const lon = Number(
|
|
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 –
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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: {
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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: {
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|