iobroker.poolcontrol 0.5.3 → 0.5.4

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/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "poolcontrol",
4
- "version": "0.5.3",
4
+ "version": "0.5.4",
5
5
  "news": {
6
+ "0.5.4": {
7
+ "en": "Fixed a rare infinite loop during weekly and monthly statistics reset that could cause Redis overload. Added timer protection and improved stability.",
8
+ "de": "Selten auftretende Endlosschleife beim Wochen- und Monatsreset der Statistik behoben, die zu Redis-Überlastung führen konnte. Timer-Schutz und Stabilität verbessert.",
9
+ "ru": "Исправлена редкая бесконечная петля при сбросе еженедельной и ежемесячной статистики, вызывавшая перегрузку Redis. Добавлена защита таймера и улучшена стабильность.",
10
+ "pt": "Corrigido um loop infinito raro durante a redefinição das estatísticas semanais e mensais que podia causar sobrecarga do Redis. Adicionada proteção de temporizador e melhorada a estabilidade.",
11
+ "nl": "Zeldzame oneindige lus opgelost tijdens de reset van wekelijkse en maandelijkse statistieken die Redis kon overbelasten. Timerbescherming en stabiliteit verbeterd.",
12
+ "fr": "Correction d'une boucle infinie rare lors de la réinitialisation des statistiques hebdomadaires et mensuelles pouvant provoquer une surcharge de Redis. Protection du minuteur et stabilité améliorées.",
13
+ "it": "Corretto un raro ciclo infinito durante il reset delle statistiche settimanali e mensili che poteva causare un sovraccarico di Redis. Migliorata la protezione del timer e la stabilità.",
14
+ "es": "Se corrigió un bucle infinito poco frecuente durante el reinicio de las estadísticas semanales y mensuales que podía causar sobrecarga de Redis. Protección de temporizador y estabilidad mejoradas.",
15
+ "pl": "Naprawiono rzadką nieskończoną pętlę podczas resetowania statystyk tygodniowych i miesięcznych, która mogła powodować przeciążenie Redis. Dodano ochronę timera i poprawiono stabilność.",
16
+ "uk": "Виправлено рідкісну нескінченну петлю під час скидання тижневої та місячної статистики, що могла спричинити перевантаження Redis. Додано захист таймера та покращено стабільність.",
17
+ "zh-cn": "修复了每周和每月统计重置期间可能导致 Redis 过载的罕见无限循环。改进了计时器保护和系统稳定性。"
18
+ },
6
19
  "0.5.3": {
7
20
  "en": "Added user selection for Telegram notifications. If no user is selected, messages are sent globally as before; if one or more usernames are specified, only those users receive the messages. Admin UI visually improved (recipient field indented under the instance). speechHelper and jsonConfig.json updated.",
8
21
  "de": "Benutzerauswahl für Telegram-Benachrichtigungen hinzugefügt. Wenn kein Benutzer ausgewählt ist, werden Nachrichten wie bisher global gesendet; bei einem oder mehreren Benutzernamen erhalten nur diese die Nachrichten. Admin-UI optisch verbessert (Empfängerfeld unter der Instanz eingerückt). speechHelper und jsonConfig.json aktualisiert.",
@@ -35,7 +35,6 @@ const statisticsHelper = {
35
35
 
36
36
  // --- Überinstallationsschutz ---
37
37
  try {
38
- // Prüft, ob alle States vorhanden sind, und legt fehlende still neu an
39
38
  await this._verifyStructure();
40
39
  } catch {
41
40
  // keine Log-Ausgabe – stiller Schutz
@@ -44,6 +43,22 @@ const statisticsHelper = {
44
43
  try {
45
44
  await this._createTemperatureStatistics();
46
45
  await this._subscribeActiveSensors();
46
+
47
+ // 🟢 NEU: Listener für Reset-Button (Einzelsensor)
48
+ adapter.subscribeStates('analytics.statistics.temperature.today.*.reset_today');
49
+ adapter.on('stateChange', async (id, state) => {
50
+ if (!state || state.ack === true) {
51
+ return;
52
+ }
53
+
54
+ if (id.includes('analytics.statistics.temperature.today.') && id.endsWith('.reset_today')) {
55
+ const sensorId = id.split('.').slice(-2, -1)[0];
56
+ adapter.log.info(`[statisticsHelper] Manueller Reset für Sensor "${sensorId}" ausgelöst.`);
57
+ await this._resetSingleSensor(sensorId);
58
+ }
59
+ });
60
+ // 🔵 ENDE NEU
61
+
47
62
  await this._scheduleMidnightReset();
48
63
  adapter.log.debug('statisticsHelper: Initialisierung abgeschlossen (Sensorüberwachung aktiv).');
49
64
  } catch (err) {
@@ -51,6 +66,55 @@ const statisticsHelper = {
51
66
  }
52
67
  },
53
68
 
69
+ /**
70
+ * Setzt alle Statistikwerte eines einzelnen Sensors (manueller Reset-Button).
71
+ *
72
+ * @param {string} sensorId - ID des Sensors, z. B. "surface" oder "flow".
73
+ */
74
+ async _resetSingleSensor(sensorId) {
75
+ const adapter = this.adapter;
76
+ const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
77
+ const basePath = `analytics.statistics.temperature.today.${sensorId}`;
78
+ adapter.log.debug(`[statisticsHelper] Starte Einzelreset für ${sensorId}.`);
79
+
80
+ const stateList = [
81
+ 'temp_min',
82
+ 'temp_max',
83
+ 'temp_min_time',
84
+ 'temp_max_time',
85
+ 'temp_avg',
86
+ 'data_points_count',
87
+ 'last_update',
88
+ ];
89
+
90
+ for (const state of stateList) {
91
+ let defValue = null;
92
+ if (state.includes('time')) {
93
+ defValue = '';
94
+ }
95
+ if (state === 'data_points_count') {
96
+ defValue = 0;
97
+ }
98
+ if (state === 'last_update') {
99
+ defValue = resetDate;
100
+ }
101
+ await adapter.setStateAsync(`${basePath}.${state}`, { val: defValue, ack: true });
102
+ }
103
+
104
+ await adapter.setStateAsync(`${basePath}.summary_json`, {
105
+ val: JSON.stringify({ date_reset: resetDate, status: 'Tageswerte zurückgesetzt' }),
106
+ ack: true,
107
+ });
108
+ await adapter.setStateAsync(`${basePath}.summary_html`, {
109
+ val: `<div style="color:gray;">Tageswerte zurückgesetzt (${resetDate})</div>`,
110
+ ack: true,
111
+ });
112
+
113
+ await this._updateOverallSummary();
114
+ adapter.log.info(`[statisticsHelper] Einzelreset für Sensor "${sensorId}" abgeschlossen.`);
115
+ },
116
+ // 🔵 ENDE NEU
117
+
54
118
  /**
55
119
  * Erstellt States, falls sie fehlen (Überinstallationsschutz)
56
120
  */
@@ -72,7 +136,6 @@ const statisticsHelper = {
72
136
  const summaryHtmlPath = `${basePath}.summary_html`;
73
137
 
74
138
  if (!isActive) {
75
- // Leere JSONs vorbereiten, damit spätere Temperaturwerte sie befüllen können
76
139
  await adapter.setStateAsync(summaryJsonPath, { val: '[]', ack: true });
77
140
  await adapter.setStateAsync(summaryHtmlPath, { val: '', ack: true });
78
141
  continue;
@@ -184,11 +247,10 @@ const statisticsHelper = {
184
247
  },
185
248
 
186
249
  /**
187
- * Verarbeitung einer Temperaturänderung für einen Sensor.
188
- * Aktualisiert Min-, Max- und Durchschnittswerte sowie die Zusammenfassungen.
250
+ * Verarbeitet Temperaturänderungen und aktualisiert Statistikdaten.
189
251
  *
190
- * @param {string} sensorId - Die ID des betroffenen Sensors (z. B. "outside" oder "flow").
191
- * @param {number} newValue - Der neue gemessene Temperaturwert in °C.
252
+ * @param {string} sensorId - ID des betroffenen Sensors.
253
+ * @param {number} newValue - Neuer gemessener Temperaturwert in °C.
192
254
  */
193
255
  async _processTemperatureChange(sensorId, newValue) {
194
256
  const adapter = this.adapter;
@@ -199,7 +261,6 @@ const statisticsHelper = {
199
261
  return;
200
262
  }
201
263
 
202
- // NEU: Rundung auf 1 Nachkommastelle
203
264
  newValue = Math.round(newValue * 10) / 10;
204
265
 
205
266
  const tempMin = (await adapter.getStateAsync(`${basePath}.temp_min`))?.val;
@@ -220,11 +281,9 @@ const statisticsHelper = {
220
281
  await adapter.setStateAsync(`${basePath}.temp_max_time`, { val: now, ack: true });
221
282
  }
222
283
 
223
- // Durchschnitt (gleitend)
224
284
  const newCount = count + 1;
225
285
  newAvg = tempAvg === null ? newValue : (tempAvg * count + newValue) / newCount;
226
286
 
227
- // NEU: Alle Temperaturwerte auf 1 Nachkommastelle runden
228
287
  newMin = Math.round(newMin * 10) / 10;
229
288
  newMax = Math.round(newMax * 10) / 10;
230
289
  newAvg = Math.round(newAvg * 10) / 10;
@@ -235,7 +294,6 @@ const statisticsHelper = {
235
294
  await adapter.setStateAsync(`${basePath}.data_points_count`, { val: newCount, ack: true });
236
295
  await adapter.setStateAsync(`${basePath}.last_update`, { val: now, ack: true });
237
296
 
238
- // Summary aktualisieren – erweitert um Datum, Zeitpunkte, Messanzahl, Name
239
297
  const summary = {
240
298
  name: 'Tagesstatistik',
241
299
  date: new Date().toISOString().slice(0, 10),
@@ -249,7 +307,6 @@ const statisticsHelper = {
249
307
  };
250
308
  await adapter.setStateAsync(`${basePath}.summary_json`, { val: JSON.stringify(summary), ack: true });
251
309
  await adapter.setStateAsync(`${basePath}.summary_html`, {
252
- // NEU: HTML-Ausgabe mit gerundeten Werten
253
310
  val: `<div><b>Min:</b> ${newMin} °C / <b>Max:</b> ${newMax} °C / <b>Ø:</b> ${newAvg} °C</div>`,
254
311
  ack: true,
255
312
  });
@@ -258,16 +315,13 @@ const statisticsHelper = {
258
315
  },
259
316
 
260
317
  /**
261
- * Gesamt-HTML/JSON-Ausgabe aktualisieren (erweiterte Version)
262
- * Liest die fertigen summary_json-Werte aller aktiven Sensoren aus
263
- * und erstellt daraus eine zusammengefasste HTML- und JSON-Ausgabe.
318
+ * Gesamt-HTML/JSON-Ausgabe aktualisieren
264
319
  */
265
320
  async _updateOverallSummary() {
266
321
  const adapter = this.adapter;
267
322
  const allData = [];
268
323
 
269
324
  try {
270
- // Alle Sensoren durchlaufen
271
325
  for (const sensor of this.sensors) {
272
326
  const summaryState = await adapter.getStateAsync(
273
327
  `analytics.statistics.temperature.today.${sensor.id}.summary_json`,
@@ -277,7 +331,6 @@ const statisticsHelper = {
277
331
  continue;
278
332
  }
279
333
 
280
- // JSON-Inhalt parsen
281
334
  let parsed;
282
335
  try {
283
336
  parsed = JSON.parse(summaryState.val);
@@ -293,17 +346,14 @@ const statisticsHelper = {
293
346
  const maxTime = parsed.temp_max_time || '';
294
347
  const count = parsed.data_points_count || 0;
295
348
 
296
- // Falls gar keine Werte vorhanden, überspringen
297
349
  if (min == null && max == null && avg == null) {
298
350
  continue;
299
351
  }
300
352
 
301
- // Werte runden
302
353
  const rMin = typeof min === 'number' ? Math.round(min * 10) / 10 : min;
303
354
  const rMax = typeof max === 'number' ? Math.round(max * 10) / 10 : max;
304
355
  const rAvg = typeof avg === 'number' ? Math.round(avg * 10) / 10 : avg;
305
356
 
306
- // Erweiterte Datensammlung
307
357
  allData.push({
308
358
  name: sensor.name,
309
359
  date,
@@ -316,7 +366,6 @@ const statisticsHelper = {
316
366
  });
317
367
  }
318
368
 
319
- // Wenn noch keine Daten vorliegen → leer lassen
320
369
  if (allData.length === 0) {
321
370
  await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
322
371
  val: '[]',
@@ -329,10 +378,8 @@ const statisticsHelper = {
329
378
  return;
330
379
  }
331
380
 
332
- // JSON-Ausgabe erstellen (komplett erweitert)
333
381
  const jsonOutput = JSON.stringify(allData, null, 2);
334
382
 
335
- // HTML-Ausgabe erstellen (mit Datum & Anzahl Messwerte)
336
383
  let html = '<table style="width:100%;border-collapse:collapse;">';
337
384
  html +=
338
385
  '<tr><th style="text-align:left;">Sensor</th><th>Datum</th><th>Min</th><th>Zeit</th><th>Max</th><th>Zeit</th><th>Ø</th><th>Anz.</th></tr>';
@@ -350,7 +397,6 @@ const statisticsHelper = {
350
397
  }
351
398
  html += '</table>';
352
399
 
353
- // States setzen (auch bei gleichen Werten → Timestamp aktualisieren)
354
400
  await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
355
401
  val: jsonOutput,
356
402
  ack: true,
@@ -389,7 +435,7 @@ const statisticsHelper = {
389
435
  },
390
436
 
391
437
  /**
392
- * Tagesstatistik zurücksetzen
438
+ * Tagesstatistik zurücksetzen (automatischer Reset)
393
439
  */
394
440
  async _resetDailyTemperatureStats() {
395
441
  const adapter = this.adapter;
@@ -465,8 +511,7 @@ const statisticsHelper = {
465
511
  },
466
512
 
467
513
  /**
468
- * Stiller Überinstallationsschutz:
469
- * Prüft und legt fehlende States erneut an, ohne bestehende Werte zu überschreiben.
514
+ * Überinstallationsschutz
470
515
  */
471
516
  async _verifyStructure() {
472
517
  try {
@@ -18,6 +18,7 @@
18
18
  const statisticsHelperMonth = {
19
19
  adapter: null,
20
20
  monthResetTimer: null,
21
+ isResetting: false,
21
22
  sensors: [
22
23
  { id: 'outside', name: 'Außentemperatur' },
23
24
  { id: 'ground', name: 'Bodentemperatur' },
@@ -348,6 +349,21 @@ const statisticsHelperMonth = {
348
349
  const nextReset = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 5, 0, 0);
349
350
  const msUntilReset = nextReset.getTime() - now.getTime();
350
351
 
352
+ // 🟢 NEU: Schutz – falls Resetzeitpunkt in der Vergangenheit liegt
353
+ if (msUntilReset < 60 * 1000) {
354
+ // unter 1 Minute Differenz
355
+ adapter.log.warn(
356
+ 'statisticsHelperMonth: Berechneter Resetzeitpunkt liegt in der Vergangenheit – Korrigiere auf nächsten Monat.',
357
+ );
358
+ const corrected = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 5, 0, 0);
359
+ const diff = corrected.getTime() - now.getTime();
360
+ this.monthResetTimer = setTimeout(async () => {
361
+ await this._resetMonthlyTemperatureStats();
362
+ await this._scheduleMonthReset();
363
+ }, diff);
364
+ return;
365
+ }
366
+
351
367
  this.monthResetTimer = setTimeout(async () => {
352
368
  await this._resetMonthlyTemperatureStats();
353
369
  await this._scheduleMonthReset();
@@ -363,75 +379,90 @@ const statisticsHelperMonth = {
363
379
  */
364
380
  async _resetMonthlyTemperatureStats() {
365
381
  const adapter = this.adapter;
366
- adapter.log.info('statisticsHelperMonth: Monatsstatistik wird zurückgesetzt.');
367
382
 
368
- const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
383
+ // 🟢 NEU: Schutz vor Endlosschleifen und Mehrfachausführung
384
+ if (this.isResetting) {
385
+ adapter.log.debug('statisticsHelperMonth: Reset bereits aktiv – übersprungen.');
386
+ return;
387
+ }
388
+ this.isResetting = true;
369
389
 
370
- for (const sensor of this.sensors) {
371
- const basePath = `analytics.statistics.temperature.month.${sensor.id}`;
372
- const activeState = `temperature.${sensor.id}.active`;
373
- const isActive = (await adapter.getStateAsync(activeState))?.val === true;
390
+ try {
391
+ adapter.log.info('statisticsHelperMonth: Monatsstatistik wird zurückgesetzt.');
374
392
 
375
- const summaryJsonPath = `${basePath}.summary_json`;
376
- const summaryHtmlPath = `${basePath}.summary_html`;
393
+ // 🟢 NEU: fehlende Zeile wieder einfügen
394
+ const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
395
+
396
+ for (const sensor of this.sensors) {
397
+ const basePath = `analytics.statistics.temperature.month.${sensor.id}`;
398
+ const activeState = `temperature.${sensor.id}.active`;
399
+ const isActive = (await adapter.getStateAsync(activeState))?.val === true;
400
+
401
+ const summaryJsonPath = `${basePath}.summary_json`;
402
+ const summaryHtmlPath = `${basePath}.summary_html`;
403
+
404
+ if (!isActive) {
405
+ await adapter.setStateAsync(summaryJsonPath, {
406
+ val: JSON.stringify({ status: 'kein Sensor aktiv' }),
407
+ ack: true,
408
+ });
409
+ await adapter.setStateAsync(summaryHtmlPath, {
410
+ val: '<div style="color:gray;">kein Sensor aktiv</div>',
411
+ ack: true,
412
+ });
413
+ continue;
414
+ }
415
+
416
+ const stateList = [
417
+ 'temp_min',
418
+ 'temp_max',
419
+ 'temp_min_time',
420
+ 'temp_max_time',
421
+ 'temp_avg',
422
+ 'data_points_count',
423
+ 'last_update',
424
+ ];
425
+
426
+ for (const state of stateList) {
427
+ const fullPath = `${basePath}.${state}`;
428
+ let defValue = null;
429
+ if (state.includes('time')) {
430
+ defValue = '';
431
+ }
432
+ if (state === 'data_points_count') {
433
+ defValue = 0;
434
+ }
435
+ if (state === 'last_update') {
436
+ defValue = resetDate;
437
+ }
438
+ await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
439
+ }
377
440
 
378
- if (!isActive) {
379
441
  await adapter.setStateAsync(summaryJsonPath, {
380
- val: JSON.stringify({ status: 'kein Sensor aktiv' }),
442
+ val: JSON.stringify({ date_reset: resetDate, status: 'Monatswerte zurückgesetzt' }),
381
443
  ack: true,
382
444
  });
383
445
  await adapter.setStateAsync(summaryHtmlPath, {
384
- val: '<div style="color:gray;">kein Sensor aktiv</div>',
446
+ val: `<div style="color:gray;">Monatswerte zurückgesetzt (${resetDate})</div>`,
385
447
  ack: true,
386
448
  });
387
- continue;
388
449
  }
389
450
 
390
- const stateList = [
391
- 'temp_min',
392
- 'temp_max',
393
- 'temp_min_time',
394
- 'temp_max_time',
395
- 'temp_avg',
396
- 'data_points_count',
397
- 'last_update',
398
- ];
399
-
400
- for (const state of stateList) {
401
- const fullPath = `${basePath}.${state}`;
402
- let defValue = null;
403
- if (state.includes('time')) {
404
- defValue = '';
405
- }
406
- if (state === 'data_points_count') {
407
- defValue = 0;
408
- }
409
- if (state === 'last_update') {
410
- defValue = resetDate;
411
- }
412
- await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
413
- }
414
-
415
- await adapter.setStateAsync(summaryJsonPath, {
416
- val: JSON.stringify({ date_reset: resetDate, status: 'Monatswerte zurückgesetzt' }),
451
+ await adapter.setStateAsync('analytics.statistics.temperature.month.outputs.summary_all_json', {
452
+ val: '{}',
417
453
  ack: true,
418
454
  });
419
- await adapter.setStateAsync(summaryHtmlPath, {
420
- val: `<div style="color:gray;">Monatswerte zurückgesetzt (${resetDate})</div>`,
455
+ await adapter.setStateAsync('analytics.statistics.temperature.month.outputs.summary_all_html', {
456
+ val: '',
421
457
  ack: true,
422
458
  });
423
- }
424
459
 
425
- await adapter.setStateAsync('analytics.statistics.temperature.month.outputs.summary_all_json', {
426
- val: '{}',
427
- ack: true,
428
- });
429
- await adapter.setStateAsync('analytics.statistics.temperature.month.outputs.summary_all_html', {
430
- val: '',
431
- ack: true,
432
- });
433
-
434
- adapter.log.debug('statisticsHelperMonth: Monatsstatistik zurückgesetzt.');
460
+ adapter.log.debug('statisticsHelperMonth: Monatsstatistik zurückgesetzt.');
461
+ } catch (err) {
462
+ adapter.log.warn(`statisticsHelperMonth: Fehler beim Monatsreset: ${err.message}`);
463
+ } finally {
464
+ this.isResetting = false; // 🟢 NEU: Flag wieder freigeben
465
+ }
435
466
  },
436
467
 
437
468
  /**
@@ -18,6 +18,7 @@
18
18
  const statisticsHelperWeek = {
19
19
  adapter: null,
20
20
  weekResetTimer: null,
21
+ isResetting: false, // 🟢 NEU
21
22
  sensors: [
22
23
  { id: 'outside', name: 'Außentemperatur' },
23
24
  { id: 'ground', name: 'Bodentemperatur' },
@@ -368,6 +369,24 @@ const statisticsHelperWeek = {
368
369
  nextReset.setHours(0, 5, 0, 0);
369
370
 
370
371
  const msUntilReset = nextReset.getTime() - now.getTime();
372
+
373
+ // 🟢 NEU: Schutz – falls Resetzeitpunkt in der Vergangenheit liegt
374
+ if (msUntilReset < 60 * 1000) {
375
+ // unter 1 Minute Differenz
376
+ adapter.log.warn(
377
+ 'statisticsHelperWeek: Berechneter Resetzeitpunkt liegt in der Vergangenheit – Korrigiere auf nächste Woche.',
378
+ );
379
+ const corrected = new Date(now);
380
+ corrected.setDate(now.getDate() + 7);
381
+ corrected.setHours(0, 5, 0, 0);
382
+ const diff = corrected.getTime() - now.getTime();
383
+ this.weekResetTimer = setTimeout(async () => {
384
+ await this._resetWeeklyTemperatureStats();
385
+ await this._scheduleWeekReset();
386
+ }, diff);
387
+ return;
388
+ }
389
+
371
390
  this.weekResetTimer = setTimeout(async () => {
372
391
  await this._resetWeeklyTemperatureStats();
373
392
  await this._scheduleWeekReset();
@@ -381,75 +400,88 @@ const statisticsHelperWeek = {
381
400
  */
382
401
  async _resetWeeklyTemperatureStats() {
383
402
  const adapter = this.adapter;
384
- adapter.log.info('statisticsHelperWeek: Wochenstatistik wird zurückgesetzt.');
385
403
 
386
- const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
404
+ // 🟢 NEU: Schutz vor Endlosschleifen und Mehrfachausführung
405
+ if (this.isResetting) {
406
+ adapter.log.debug('statisticsHelperWeek: Reset bereits aktiv – übersprungen.');
407
+ return;
408
+ }
409
+ this.isResetting = true;
387
410
 
388
- for (const sensor of this.sensors) {
389
- const basePath = `analytics.statistics.temperature.week.${sensor.id}`;
390
- const activeState = `temperature.${sensor.id}.active`;
391
- const isActive = (await adapter.getStateAsync(activeState))?.val === true;
411
+ try {
412
+ adapter.log.info('statisticsHelperWeek: Wochenstatistik wird zurückgesetzt.');
413
+ const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
392
414
 
393
- const summaryJsonPath = `${basePath}.summary_json`;
394
- const summaryHtmlPath = `${basePath}.summary_html`;
415
+ for (const sensor of this.sensors) {
416
+ const basePath = `analytics.statistics.temperature.week.${sensor.id}`;
417
+ const activeState = `temperature.${sensor.id}.active`;
418
+ const isActive = (await adapter.getStateAsync(activeState))?.val === true;
419
+
420
+ const summaryJsonPath = `${basePath}.summary_json`;
421
+ const summaryHtmlPath = `${basePath}.summary_html`;
422
+
423
+ if (!isActive) {
424
+ await adapter.setStateAsync(summaryJsonPath, {
425
+ val: JSON.stringify({ status: 'kein Sensor aktiv' }),
426
+ ack: true,
427
+ });
428
+ await adapter.setStateAsync(summaryHtmlPath, {
429
+ val: '<div style="color:gray;">kein Sensor aktiv</div>',
430
+ ack: true,
431
+ });
432
+ continue;
433
+ }
434
+
435
+ const stateList = [
436
+ 'temp_min',
437
+ 'temp_max',
438
+ 'temp_min_time',
439
+ 'temp_max_time',
440
+ 'temp_avg',
441
+ 'data_points_count',
442
+ 'last_update',
443
+ ];
444
+
445
+ for (const state of stateList) {
446
+ const fullPath = `${basePath}.${state}`;
447
+ let defValue = null;
448
+ if (state.includes('time')) {
449
+ defValue = '';
450
+ }
451
+ if (state === 'data_points_count') {
452
+ defValue = 0;
453
+ }
454
+ if (state === 'last_update') {
455
+ defValue = resetDate;
456
+ }
457
+ await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
458
+ }
395
459
 
396
- if (!isActive) {
397
460
  await adapter.setStateAsync(summaryJsonPath, {
398
- val: JSON.stringify({ status: 'kein Sensor aktiv' }),
461
+ val: JSON.stringify({ date_reset: resetDate, status: 'Wochenwerte zurückgesetzt' }),
399
462
  ack: true,
400
463
  });
401
464
  await adapter.setStateAsync(summaryHtmlPath, {
402
- val: '<div style="color:gray;">kein Sensor aktiv</div>',
465
+ val: `<div style="color:gray;">Wochenwerte zurückgesetzt (${resetDate})</div>`,
403
466
  ack: true,
404
467
  });
405
- continue;
406
468
  }
407
469
 
408
- const stateList = [
409
- 'temp_min',
410
- 'temp_max',
411
- 'temp_min_time',
412
- 'temp_max_time',
413
- 'temp_avg',
414
- 'data_points_count',
415
- 'last_update',
416
- ];
417
-
418
- for (const state of stateList) {
419
- const fullPath = `${basePath}.${state}`;
420
- let defValue = null;
421
- if (state.includes('time')) {
422
- defValue = '';
423
- }
424
- if (state === 'data_points_count') {
425
- defValue = 0;
426
- }
427
- if (state === 'last_update') {
428
- defValue = resetDate;
429
- }
430
- await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
431
- }
432
-
433
- await adapter.setStateAsync(summaryJsonPath, {
434
- val: JSON.stringify({ date_reset: resetDate, status: 'Wochenwerte zurückgesetzt' }),
470
+ await adapter.setStateAsync('analytics.statistics.temperature.week.outputs.summary_all_json', {
471
+ val: '{}',
435
472
  ack: true,
436
473
  });
437
- await adapter.setStateAsync(summaryHtmlPath, {
438
- val: `<div style="color:gray;">Wochenwerte zurückgesetzt (${resetDate})</div>`,
474
+ await adapter.setStateAsync('analytics.statistics.temperature.week.outputs.summary_all_html', {
475
+ val: '',
439
476
  ack: true,
440
477
  });
441
- }
442
478
 
443
- await adapter.setStateAsync('analytics.statistics.temperature.week.outputs.summary_all_json', {
444
- val: '{}',
445
- ack: true,
446
- });
447
- await adapter.setStateAsync('analytics.statistics.temperature.week.outputs.summary_all_html', {
448
- val: '',
449
- ack: true,
450
- });
451
-
452
- adapter.log.debug('statisticsHelperWeek: Wochenstatistik zurückgesetzt.');
479
+ adapter.log.debug('statisticsHelperWeek: Wochenstatistik zurückgesetzt.');
480
+ } catch (err) {
481
+ adapter.log.warn(`statisticsHelperWeek: Fehler beim Wochenreset: ${err.message}`);
482
+ } finally {
483
+ this.isResetting = false; // 🟢 NEU: Flag wieder freigeben
484
+ }
453
485
  },
454
486
 
455
487
  /**
@@ -76,35 +76,6 @@ const temperatureHelper = {
76
76
  : 'keine Sensoren konfiguriert'
77
77
  }`,
78
78
  );
79
- // ------------------------------------------------------
80
- // NEU: Re-Subscribe-Überwachung für hängende Sensoren
81
- // ------------------------------------------------------
82
- setInterval(
83
- async () => {
84
- for (const [key, id] of Object.entries(this.sensors)) {
85
- try {
86
- const state = await this.adapter.getStateAsync(`temperature.${key}.current`);
87
- const lastTs = state?.ts || 0;
88
- const minutes = (Date.now() - lastTs) / 60000;
89
-
90
- // Wenn länger als 10 Minuten keine Aktualisierung, Subscribe erneuern
91
- if (minutes > 10) {
92
- this.adapter.log.warn(
93
- `[temperatureHelper] Keine Aktualisierung für ${key} seit ${Math.round(
94
- minutes,
95
- )} Min – re-subscribing ${id}`,
96
- );
97
- this.adapter.subscribeForeignStates(id);
98
- }
99
- } catch (err) {
100
- this.adapter.log.debug(
101
- `[temperatureHelper] Re-Subscribe-Prüfung für ${key} fehlgeschlagen: ${err.message}`,
102
- );
103
- }
104
- }
105
- },
106
- 10 * 60 * 1000,
107
- ); // alle 10 Minuten prüfen
108
79
  },
109
80
 
110
81
  _collectActiveSensors(adapter) {
@@ -100,6 +100,7 @@ async function _createTemperatureStatsGroup(adapter, periodId, displayName) {
100
100
  { id: 'temp_avg', name: 'Durchschnittstemperatur', type: 'number', role: 'value.temperature', unit: '°C' },
101
101
  { id: 'data_points_count', name: 'Anzahl Messwerte', type: 'number', role: 'value' },
102
102
  { id: 'last_update', name: 'Letzte Aktualisierung', type: 'string', role: 'value.time' },
103
+ { id: 'reset_today', name: 'Tagesstatistik zurücksetzen', type: 'boolean', role: 'button' },
103
104
  {
104
105
  id: 'summary_json',
105
106
  name: `${displayName} (JSON)`,
@@ -122,9 +123,9 @@ async function _createTemperatureStatsGroup(adapter, periodId, displayName) {
122
123
  type: def.type,
123
124
  role: def.role,
124
125
  unit: def.unit || undefined,
125
- read: true,
126
- write: false,
127
- def: def.type === 'number' ? null : '',
126
+ read: def.type === 'boolean' && def.role === 'button' ? false : true,
127
+ write: def.type === 'boolean' && def.role === 'button' ? true : false,
128
+ def: def.type === 'number' ? null : def.type === 'boolean' ? false : '',
128
129
  persist: true,
129
130
  },
130
131
  native: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.poolcontrol",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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",