iobroker.poolcontrol 0.7.3 → 0.8.1
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 +40 -40
- package/lib/helpers/aiForecastHelper.js +453 -0
- package/lib/helpers/aiHelper.js +270 -58
- package/lib/stateDefinitions/aiStates.js +91 -27
- package/lib/stateDefinitions/controlStates.js +10 -1
- package/main.js +13 -3
- package/package.json +2 -2
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,13 @@ const aiHelper = {
|
|
|
47
47
|
_lastScheduleValues: {}, // NEU: merkt sich letzte Zeitwerte
|
|
48
48
|
_debugMode: false,
|
|
49
49
|
|
|
50
|
+
_adapterStartedAt: Date.now(), // FIX: Zeitpunkt des Adapterstarts
|
|
51
|
+
|
|
52
|
+
// Anti-Spam-Level (merkt sich letzte Warnungen)
|
|
53
|
+
_lastPoolTipCode: null,
|
|
54
|
+
_lastPoolTipWindLevel: null,
|
|
55
|
+
_lastPoolTipTimestamp: 0,
|
|
56
|
+
|
|
50
57
|
/**
|
|
51
58
|
* Initialisiert den AI-Helper (Timer + Grundkonfiguration).
|
|
52
59
|
*
|
|
@@ -101,7 +108,7 @@ const aiHelper = {
|
|
|
101
108
|
// ---------------------------------------------------------
|
|
102
109
|
// FIX 2: Uhrzeitänderungen IMMER erkennen und loggen
|
|
103
110
|
// ---------------------------------------------------------
|
|
104
|
-
if (id.includes('.ai.schedule.')) {
|
|
111
|
+
if (id.includes('.ai.weather.schedule.')) {
|
|
105
112
|
const oldVal = this._lastScheduleValues[id];
|
|
106
113
|
const newVal = state.val;
|
|
107
114
|
|
|
@@ -114,13 +121,54 @@ const aiHelper = {
|
|
|
114
121
|
|
|
115
122
|
// Timer neu aufbauen
|
|
116
123
|
await this._refreshTimers();
|
|
124
|
+
|
|
125
|
+
// ----------------------------------------------------------
|
|
126
|
+
// NEU: Delay, damit ioBroker alle States laden kann
|
|
127
|
+
// ----------------------------------------------------------
|
|
128
|
+
await new Promise(res => setTimeout(res, 1500));
|
|
129
|
+
|
|
130
|
+
// NEU: Wenn Uhrzeit heute noch in der Zukunft liegt → sofort ausführen
|
|
131
|
+
try {
|
|
132
|
+
const now = new Date();
|
|
133
|
+
const [hourStr, minuteStr] = String(newVal).split(':');
|
|
134
|
+
const hour = Number(hourStr);
|
|
135
|
+
const minute = Number(minuteStr);
|
|
136
|
+
|
|
137
|
+
if (!Number.isNaN(hour) && !Number.isNaN(minute)) {
|
|
138
|
+
const target = new Date();
|
|
139
|
+
target.setHours(hour, minute, 0, 0);
|
|
140
|
+
|
|
141
|
+
// Nur wenn Zielzeit HEUTE noch bevorsteht
|
|
142
|
+
if (target > now) {
|
|
143
|
+
this.adapter.log.info(
|
|
144
|
+
`[aiHelper] Neue Uhrzeit liegt heute noch in der Zukunft → führe Modul sofort aus (${id})`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (id.endsWith('weather_advice_time')) {
|
|
148
|
+
await this._runWeatherAdvice();
|
|
149
|
+
}
|
|
150
|
+
if (id.endsWith('daily_summary_time')) {
|
|
151
|
+
await this._runDailySummary();
|
|
152
|
+
}
|
|
153
|
+
if (id.endsWith('daily_pool_tips_time')) {
|
|
154
|
+
await this._runDailyPoolTips();
|
|
155
|
+
}
|
|
156
|
+
if (id.endsWith('weekend_summary_time')) {
|
|
157
|
+
await this._runWeekendSummary();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
this.adapter.log.warn(`[aiHelper] Fehler bei Sofortausführung nach Zeitänderung: ${e.message}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
117
165
|
return;
|
|
118
166
|
}
|
|
119
167
|
|
|
120
168
|
// ---------------------------------------------------------
|
|
121
169
|
// FIX 3: Schalteränderungen immer melden
|
|
122
170
|
// ---------------------------------------------------------
|
|
123
|
-
if (id.includes('.ai.switches.')) {
|
|
171
|
+
if (id.includes('.ai.weather.switches.')) {
|
|
124
172
|
this.adapter.log.info(`[aiHelper] Schalter geändert: ${id} = ${state.val} – Timer werden neu aufgebaut`);
|
|
125
173
|
|
|
126
174
|
await this._refreshTimers();
|
|
@@ -148,63 +196,106 @@ const aiHelper = {
|
|
|
148
196
|
async _refreshTimers() {
|
|
149
197
|
this._clearTimers();
|
|
150
198
|
|
|
151
|
-
const aiEnabled = await this._getBool('ai.
|
|
152
|
-
this._debugMode = await this._getBool('ai.switches.debug_mode', false);
|
|
199
|
+
const aiEnabled = await this._getBool('ai.enabled', false);
|
|
200
|
+
this._debugMode = await this._getBool('ai.weather.switches.debug_mode', false);
|
|
153
201
|
|
|
154
202
|
if (!aiEnabled) {
|
|
155
|
-
this.adapter.log.info('[aiHelper] KI ist deaktiviert (ai.
|
|
203
|
+
this.adapter.log.info('[aiHelper] KI ist deaktiviert (ai.enabled = false) – keine Timer aktiv');
|
|
156
204
|
return;
|
|
157
205
|
}
|
|
158
206
|
|
|
159
207
|
this.adapter.log.info('[aiHelper] KI ist aktiv – Timer werden gesetzt');
|
|
160
208
|
|
|
161
209
|
// --- Wetterhinweise ---
|
|
162
|
-
const weatherEnabled = await this._getBool('ai.switches.weather_advice_enabled', false);
|
|
210
|
+
const weatherEnabled = await this._getBool('ai.weather.switches.weather_advice_enabled', false);
|
|
163
211
|
if (weatherEnabled) {
|
|
164
|
-
const time = await this._getTimeOrDefault('ai.schedule.weather_advice_time', '08:00');
|
|
212
|
+
const time = await this._getTimeOrDefault('ai.weather.schedule.weather_advice_time', '08:00');
|
|
165
213
|
this._createDailyTimer(time, async () => {
|
|
166
214
|
await this._runWeatherAdvice();
|
|
167
215
|
});
|
|
168
|
-
this.adapter.log.
|
|
216
|
+
this.adapter.log.debug(
|
|
169
217
|
`[aiHelper] Wetterhinweis-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
|
|
170
218
|
);
|
|
171
219
|
}
|
|
172
220
|
|
|
173
221
|
// --- Tägliche Zusammenfassung ---
|
|
174
|
-
const summaryEnabled = await this._getBool('ai.switches.daily_summary_enabled', false);
|
|
222
|
+
const summaryEnabled = await this._getBool('ai.weather.switches.daily_summary_enabled', false);
|
|
175
223
|
if (summaryEnabled) {
|
|
176
|
-
const time = await this._getTimeOrDefault('ai.schedule.daily_summary_time', '09:00');
|
|
224
|
+
const time = await this._getTimeOrDefault('ai.weather.schedule.daily_summary_time', '09:00');
|
|
177
225
|
this._createDailyTimer(time, async () => {
|
|
178
226
|
await this._runDailySummary();
|
|
179
227
|
});
|
|
180
|
-
this.adapter.log.
|
|
228
|
+
this.adapter.log.debug(
|
|
181
229
|
`[aiHelper] Daily-Summary-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
|
|
182
230
|
);
|
|
183
231
|
}
|
|
184
232
|
|
|
185
233
|
// --- Tägliche Pool-Tipps ---
|
|
186
|
-
const tipsEnabled = await this._getBool('ai.switches.daily_pool_tips_enabled', false);
|
|
234
|
+
const tipsEnabled = await this._getBool('ai.weather.switches.daily_pool_tips_enabled', false);
|
|
187
235
|
if (tipsEnabled) {
|
|
188
|
-
const time = await this._getTimeOrDefault('ai.schedule.daily_pool_tips_time', '10:00');
|
|
236
|
+
const time = await this._getTimeOrDefault('ai.weather.schedule.daily_pool_tips_time', '10:00');
|
|
189
237
|
this._createDailyTimer(time, async () => {
|
|
190
238
|
await this._runDailyPoolTips();
|
|
191
239
|
});
|
|
192
|
-
this.adapter.log.
|
|
240
|
+
this.adapter.log.debug(
|
|
193
241
|
`[aiHelper] Pool-Tipps-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
|
|
194
242
|
);
|
|
195
243
|
}
|
|
196
244
|
|
|
197
245
|
// --- Wochenend-Zusammenfassung ---
|
|
198
|
-
const weekendEnabled = await this._getBool('ai.switches.weekend_summary_enabled', false);
|
|
246
|
+
const weekendEnabled = await this._getBool('ai.weather.switches.weekend_summary_enabled', false);
|
|
199
247
|
if (weekendEnabled) {
|
|
200
|
-
const time = await this._getTimeOrDefault('ai.schedule.weekend_summary_time', '18:00');
|
|
248
|
+
const time = await this._getTimeOrDefault('ai.weather.schedule.weekend_summary_time', '18:00');
|
|
201
249
|
this._createDailyTimer(time, async () => {
|
|
202
250
|
await this._runWeekendSummary();
|
|
203
251
|
});
|
|
204
|
-
this.adapter.log.
|
|
252
|
+
this.adapter.log.debug(
|
|
205
253
|
`[aiHelper] Wochenend-Timer gesetzt für ${time.hour}:${String(time.minute).padStart(2, '0')}`,
|
|
206
254
|
);
|
|
207
255
|
}
|
|
256
|
+
|
|
257
|
+
//--------------------------------------------------------
|
|
258
|
+
// NEU: Stündlicher Wetter-Update-Timer
|
|
259
|
+
//--------------------------------------------------------
|
|
260
|
+
this.adapter.log.debug('[aiHelper] Stündlicher Wetter-Update-Timer wird gesetzt');
|
|
261
|
+
|
|
262
|
+
const hourlyTimer = setInterval(
|
|
263
|
+
async () => {
|
|
264
|
+
try {
|
|
265
|
+
const geo = await this._loadGeoLocation();
|
|
266
|
+
if (!geo) {
|
|
267
|
+
this.adapter.log.info('[aiHelper] Wetter-Update abgebrochen – keine Geodaten verfügbar');
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const weather = await this._fetchWeather(geo.lat, geo.lon);
|
|
272
|
+
if (!weather) {
|
|
273
|
+
this.adapter.log.info('[aiHelper] Wetter-Update abgebrochen – keine Wetterdaten verfügbar');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// WeatherAdvice aktualisieren (heutiges Wetter)
|
|
278
|
+
const weatherText = this._buildWeatherAdviceText(weather);
|
|
279
|
+
await this._writeOutput('weather_advice', weatherText);
|
|
280
|
+
|
|
281
|
+
// NEU: Pool-Tipps automatisch mit aktualisieren
|
|
282
|
+
const seasonActive = await this._getBool('status.season_active', false);
|
|
283
|
+
const poolTipsText = this._buildPoolTipsText(weather, seasonActive);
|
|
284
|
+
await this._writeOutput('pool_tips', poolTipsText);
|
|
285
|
+
|
|
286
|
+
if (this._debugMode) {
|
|
287
|
+
this.adapter.log.debug(
|
|
288
|
+
'[aiHelper] Stündliches Wetter-Update durchgeführt (Weather + Pool-Tipps)',
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
this.adapter.log.warn(`[aiHelper] Fehler beim stündlichen Wetter-Update: ${err.message}`);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
60 * 60 * 1000,
|
|
296
|
+
); // 1 Stunde
|
|
297
|
+
|
|
298
|
+
this.timers.push(hourlyTimer);
|
|
208
299
|
},
|
|
209
300
|
|
|
210
301
|
/**
|
|
@@ -219,20 +310,25 @@ const aiHelper = {
|
|
|
219
310
|
const timer = setInterval(async () => {
|
|
220
311
|
const now = new Date();
|
|
221
312
|
|
|
222
|
-
// ---
|
|
313
|
+
// --- FIX: Nachholen nur in den ersten 3 Minuten nach Adapterstart ---
|
|
223
314
|
const diffMinutes = now.getHours() * 60 + now.getMinutes() - (hour * 60 + minute);
|
|
224
315
|
|
|
225
|
-
|
|
316
|
+
const adapterUptimeMs = Date.now() - this._adapterStartedAt;
|
|
317
|
+
const withinStartupWindow = adapterUptimeMs <= 3 * 60 * 1000; // 3 Minuten
|
|
318
|
+
|
|
319
|
+
if (withinStartupWindow && diffMinutes > 0 && diffMinutes <= 2) {
|
|
226
320
|
try {
|
|
227
321
|
await callback();
|
|
228
|
-
this.adapter.log.info(
|
|
322
|
+
this.adapter.log.info(
|
|
323
|
+
'[aiHelper] Nachholung ausgeführt (innerhalb der ersten 3 Minuten nach Start)',
|
|
324
|
+
);
|
|
229
325
|
} catch (err) {
|
|
230
|
-
this.adapter.log.warn(`[aiHelper] Fehler bei
|
|
326
|
+
this.adapter.log.warn(`[aiHelper] Fehler bei Nachholung: ${err.message}`);
|
|
231
327
|
}
|
|
232
328
|
}
|
|
233
329
|
|
|
234
330
|
if (now.getHours() === hour && now.getMinutes() === minute) {
|
|
235
|
-
this.adapter.log.
|
|
331
|
+
this.adapter.log.debug(
|
|
236
332
|
`[aiHelper] Timer ausgelöst: ${hour}:${String(minute).padStart(2, '0')} → Callback wird ausgeführt`,
|
|
237
333
|
); // NEU
|
|
238
334
|
|
|
@@ -359,32 +455,57 @@ const aiHelper = {
|
|
|
359
455
|
}
|
|
360
456
|
},
|
|
361
457
|
|
|
458
|
+
//--------------------------------------------------------
|
|
459
|
+
// NEU: Stündliches Wetter-Update als eigene Funktion
|
|
460
|
+
//--------------------------------------------------------
|
|
461
|
+
async _runWeatherAutoUpdate() {
|
|
462
|
+
try {
|
|
463
|
+
const geo = await this._loadGeoLocation();
|
|
464
|
+
if (!geo) {
|
|
465
|
+
this.adapter.log.info('[aiHelper] Auto-Wetterupdate abgebrochen – keine Geodaten verfügbar');
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const weather = await this._fetchWeather(geo.lat, geo.lon);
|
|
470
|
+
if (!weather) {
|
|
471
|
+
this.adapter.log.info('[aiHelper] Auto-Wetterupdate abgebrochen – keine Wetterdaten verfügbar');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// WeatherAdvice aktualisieren
|
|
476
|
+
const text = this._buildWeatherAdviceText(weather);
|
|
477
|
+
await this._writeOutput('weather_advice', text);
|
|
478
|
+
|
|
479
|
+
if (this._debugMode) {
|
|
480
|
+
this.adapter.log.debug('[aiHelper] Auto-Wetterupdate erfolgreich durchgeführt');
|
|
481
|
+
}
|
|
482
|
+
} catch (err) {
|
|
483
|
+
this.adapter.log.warn(`[aiHelper] Fehler bei Auto-Wetterupdate: ${err.message}`);
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
|
|
362
487
|
// ---------------------------------------------------------------------
|
|
363
488
|
// Geodaten + Wetter
|
|
364
489
|
// ---------------------------------------------------------------------
|
|
365
490
|
|
|
366
491
|
/**
|
|
367
|
-
* Lädt Geokoordinaten aus system.config.
|
|
492
|
+
* Lädt Geokoordinaten korrekt aus system.config.
|
|
368
493
|
*
|
|
369
494
|
* @returns {{lat:number,lon:number}|null}
|
|
370
495
|
*/
|
|
371
496
|
async _loadGeoLocation() {
|
|
372
497
|
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
|
-
);
|
|
498
|
+
const obj = await this.adapter.getForeignObjectAsync('system.config');
|
|
499
|
+
if (!obj || !obj.common) {
|
|
500
|
+
this.adapter.log.warn('[aiHelper] Konnte system.config nicht laden');
|
|
380
501
|
return null;
|
|
381
502
|
}
|
|
382
503
|
|
|
383
|
-
const lat = Number(
|
|
384
|
-
const lon = Number(
|
|
504
|
+
const lat = Number(obj.common.latitude);
|
|
505
|
+
const lon = Number(obj.common.longitude);
|
|
385
506
|
|
|
386
507
|
if (Number.isNaN(lat) || Number.isNaN(lon)) {
|
|
387
|
-
this.adapter.log.warn('[aiHelper] Geodaten ungültig –
|
|
508
|
+
this.adapter.log.warn('[aiHelper] Geodaten ungültig – bitte in Admin unter System/Standort eintragen');
|
|
388
509
|
return null;
|
|
389
510
|
}
|
|
390
511
|
|
|
@@ -567,6 +688,54 @@ const aiHelper = {
|
|
|
567
688
|
|
|
568
689
|
let text = 'Pool-Tipp für heute: ';
|
|
569
690
|
|
|
691
|
+
//--------------------------------------------------------
|
|
692
|
+
// NEU: Erweiterte Analyse für Pool-Tipps
|
|
693
|
+
//--------------------------------------------------------
|
|
694
|
+
|
|
695
|
+
// 1) Wind / Sturm
|
|
696
|
+
const wind = weather?.current?.wind_speed_10m ?? null;
|
|
697
|
+
if (wind != null) {
|
|
698
|
+
if (wind >= 60) {
|
|
699
|
+
text +=
|
|
700
|
+
'⚠️ Extrem starker Sturm erwartet! Bitte unbedingt die Abdeckung sichern und alle Gegenstände im Poolbereich fest verankern. ';
|
|
701
|
+
} else if (wind >= 45) {
|
|
702
|
+
text += 'Achtung: Starke Windböen treten auf. Bitte Abdeckung fixieren und lose Gegenstände sichern. ';
|
|
703
|
+
} else if (wind >= 30) {
|
|
704
|
+
text += 'Es wird windig – Abdeckung gut verschließen und empfindliches Zubehör schützen. ';
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// 2) Regen / Starkregen
|
|
709
|
+
if (code != null) {
|
|
710
|
+
if ([65, 82].includes(code)) {
|
|
711
|
+
text += 'Kräftige Regenschauer erwartet – Abdeckung geschlossen halten. ';
|
|
712
|
+
} else if ([61, 63, 80, 81].includes(code)) {
|
|
713
|
+
text += 'Es wird regnerisch – Abdeckung eher geschlossen lassen. ';
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// 3) Gewitter / Hagel
|
|
718
|
+
if (code === 95) {
|
|
719
|
+
text += '⚡ Gewitterwarnung! Bitte Solarfolie sichern und Technik vor Feuchtigkeit schützen. ';
|
|
720
|
+
}
|
|
721
|
+
if (code === 96 || code === 99) {
|
|
722
|
+
text += '⚠️ Hagelgefahr! Bitte empfindliche Geräte schützen und Poolbereich räumen. ';
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// 4) Temperatur / Hitze
|
|
726
|
+
if (tmax >= 28) {
|
|
727
|
+
text += 'Sehr warmes Badewetter – Abdeckung tagsüber offen lassen. Chlorverbrauch steigt. ';
|
|
728
|
+
} else if (tmax >= 22) {
|
|
729
|
+
text += 'Angenehme Temperaturen – normaler Poolbetrieb empfohlen. ';
|
|
730
|
+
} else if (tmax <= 16) {
|
|
731
|
+
text += 'Kühle Temperaturen – Abdeckung geschlossen halten, um Wärmeverluste zu reduzieren. ';
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Fallback falls noch nichts geschrieben wurde
|
|
735
|
+
if (text.trim() === 'Pool-Tipp für heute:') {
|
|
736
|
+
text += desc ? `${desc}. ` : 'Keine besonderen Hinweise für den heutigen Tag. ';
|
|
737
|
+
}
|
|
738
|
+
|
|
570
739
|
if (tmax >= 26) {
|
|
571
740
|
text += 'Es wird warm bis sehr warm – gutes Badewetter. ';
|
|
572
741
|
text +=
|
|
@@ -587,6 +756,47 @@ const aiHelper = {
|
|
|
587
756
|
text += 'Bei sonniger Witterung steigt der Chlorverbrauch – Wasserwerte im Auge behalten.';
|
|
588
757
|
}
|
|
589
758
|
|
|
759
|
+
//--------------------------------------------------------
|
|
760
|
+
// NEU: Anti-Spam-Logik
|
|
761
|
+
//--------------------------------------------------------
|
|
762
|
+
|
|
763
|
+
// // Wettercode vergleichen
|
|
764
|
+
// if (code != null) {
|
|
765
|
+
// if (this._lastPoolTipCode === code) {
|
|
766
|
+
// // gleiches Wetter wie vorher → eventuell abbrechen
|
|
767
|
+
// const nowTs = Date.now();
|
|
768
|
+
// // nur jede 3 Stunden dieselbe Warnung erneut ausgeben
|
|
769
|
+
// if (nowTs - this._lastPoolTipTimestamp < 3 * 60 * 60 * 1000) {
|
|
770
|
+
// return 'Pool-Tipp: Keine neuen Hinweise – Bedingungen unverändert.';
|
|
771
|
+
// }
|
|
772
|
+
// }
|
|
773
|
+
// }
|
|
774
|
+
|
|
775
|
+
// Windlevel kategorisieren: 0 = ruhig, 1 = windig, 2 = stark, 3 = Sturm
|
|
776
|
+
let windLevel = 0;
|
|
777
|
+
if (wind != null) {
|
|
778
|
+
if (wind >= 60) {
|
|
779
|
+
windLevel = 3;
|
|
780
|
+
} else if (wind >= 45) {
|
|
781
|
+
windLevel = 2;
|
|
782
|
+
} else if (wind >= 30) {
|
|
783
|
+
windLevel = 1;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// // prüfen, ob derselbe Windlevel schon gemeldet wurde
|
|
788
|
+
// if (windLevel === this._lastPoolTipWindLevel) {
|
|
789
|
+
// const nowTs = Date.now();
|
|
790
|
+
// if (nowTs - this._lastPoolTipTimestamp < 3 * 60 * 60 * 1000) {
|
|
791
|
+
// return 'Pool-Tipp: Keine neuen Informationen – Wetter gleich geblieben.';
|
|
792
|
+
// }
|
|
793
|
+
// }
|
|
794
|
+
|
|
795
|
+
// Wenn wir hier sind → neuer Hinweis → Werte speichern
|
|
796
|
+
this._lastPoolTipCode = code;
|
|
797
|
+
this._lastPoolTipWindLevel = windLevel;
|
|
798
|
+
this._lastPoolTipTimestamp = Date.now();
|
|
799
|
+
|
|
590
800
|
return text;
|
|
591
801
|
},
|
|
592
802
|
|
|
@@ -761,11 +971,11 @@ const aiHelper = {
|
|
|
761
971
|
}
|
|
762
972
|
|
|
763
973
|
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 });
|
|
974
|
+
await this.adapter.setStateAsync(`ai.weather.outputs.${id}`, { val: text, ack: true });
|
|
975
|
+
await this.adapter.setStateAsync('ai.weather.outputs.last_message', { val: text, ack: true });
|
|
766
976
|
|
|
767
977
|
if (this._debugMode) {
|
|
768
|
-
this.adapter.log.
|
|
978
|
+
this.adapter.log.debug(`[aiHelper] Output geschrieben → ai.weather.outputs.${id}: ${text}`);
|
|
769
979
|
}
|
|
770
980
|
} catch (err) {
|
|
771
981
|
this.adapter.log.error(`[aiHelper] Fehler beim Schreiben eines Outputs (${id}): ${err.message}`);
|
|
@@ -785,10 +995,12 @@ const aiHelper = {
|
|
|
785
995
|
return;
|
|
786
996
|
}
|
|
787
997
|
|
|
788
|
-
const allowSpeech = await this._getBool('ai.switches.allow_speech', false);
|
|
998
|
+
const allowSpeech = await this._getBool('ai.weather.switches.allow_speech', false);
|
|
789
999
|
if (!allowSpeech) {
|
|
790
1000
|
if (this._debugMode) {
|
|
791
|
-
this.adapter.log.debug(
|
|
1001
|
+
this.adapter.log.debug(
|
|
1002
|
+
'[aiHelper] Sprachausgabe deaktiviert (ai.weather.switches.allow_speech = false)',
|
|
1003
|
+
);
|
|
792
1004
|
}
|
|
793
1005
|
return;
|
|
794
1006
|
}
|
|
@@ -870,7 +1082,7 @@ const aiHelper = {
|
|
|
870
1082
|
const str = await this._getString(id, def);
|
|
871
1083
|
|
|
872
1084
|
// NEU: Log, welche Uhrzeit der Helper tatsächlich verwendet
|
|
873
|
-
this.adapter.log.
|
|
1085
|
+
this.adapter.log.debug(`[aiHelper] Uhrzeit geladen: ${id} = "${str}" (Default: ${def})`);
|
|
874
1086
|
|
|
875
1087
|
const match = /^(\d{1,2}):(\d{2})$/.exec(str || '');
|
|
876
1088
|
let hour = 0;
|