iobroker.poolcontrol 0.4.0 → 0.5.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 +8 -0
- package/io-package.json +26 -26
- package/lib/helpers/pumpHelper.js +7 -0
- package/lib/helpers/statisticsHelper.js +134 -40
- package/lib/helpers/statisticsHelperMonth.js +466 -0
- package/lib/helpers/statisticsHelperWeek.js +501 -0
- package/lib/helpers/timeHelper.js +13 -6
- package/lib/stateDefinitions/statisticsStates.js +52 -15
- package/main.js +4 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -121,6 +121,14 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
|
|
|
121
121
|
## Changelog
|
|
122
122
|
### **WORK IN PROGRESS**
|
|
123
123
|
|
|
124
|
+
## v0.5.0 (2025-10-28)
|
|
125
|
+
- Erweiterung der Temperaturstatistik um Wochen- und Monatsauswertung
|
|
126
|
+
(`analytics.statistics.temperature.week` / `.month`)
|
|
127
|
+
- Eigenständige, eventbasierte Helper für Woche und Monat
|
|
128
|
+
- Persistente Datenpunkte mit automatischen JSON- und HTML-Zusammenfassungen
|
|
129
|
+
- Vorbereitung für zukünftige Erweiterungen (Saison- und Jahresstatistik)
|
|
130
|
+
|
|
131
|
+
|
|
124
132
|
### **0.4.0 (26.10.2025)**
|
|
125
133
|
|
|
126
134
|
**Neue Funktionen**
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.5.1": {
|
|
7
|
+
"en": "Extended week and month statistics with persistent data, unified JSON format, and installation protection.",
|
|
8
|
+
"de": "Erweiterte Wochen- und Monatsstatistik mit persistenten Daten, einheitlichem JSON-Format und Überinstallationsschutz.",
|
|
9
|
+
"ru": "Расширенная недельная и месячная статистика с постоянными данными, унифицированным форматом JSON и защитой при переустановке.",
|
|
10
|
+
"pt": "Estatísticas semanais e mensais expandidas com dados persistentes, formato JSON unificado e proteção de reinstalação.",
|
|
11
|
+
"nl": "Uitgebreide week- en maandstatistieken met persistente gegevens, uniforme JSON-indeling en installatiebescherming.",
|
|
12
|
+
"fr": "Statistiques hebdomadaires et mensuelles étendues avec données persistantes, format JSON unifié et protection à la réinstallation.",
|
|
13
|
+
"it": "Statistiche settimanali e mensili estese con dati persistenti, formato JSON unificato e protezione dall'installazione.",
|
|
14
|
+
"es": "Estadísticas semanales y mensuales ampliadas con datos persistentes, formato JSON unificado y protección de reinstalación.",
|
|
15
|
+
"pl": "Rozszerzone statystyki tygodniowe i miesięczne z trwałymi danymi, ujednoliconym formatem JSON i ochroną przed ponowną instalacją.",
|
|
16
|
+
"uk": "Розширена тижнева та місячна статистика з постійними даними, уніфікованим форматом JSON і захистом під час перевстановлення.",
|
|
17
|
+
"zh-cn": "扩展的周和月统计,具有持久数据、统一的 JSON 格式和重新安装保护。"
|
|
18
|
+
},
|
|
19
|
+
"0.5.0": {
|
|
20
|
+
"en": "Added weekly and monthly temperature statistics under analytics.statistics.temperature.week and analytics.statistics.temperature.month with automatic summaries, independent helpers and persistent data points.",
|
|
21
|
+
"de": "Wöchentliche und monatliche Temperaturstatistiken unter analytics.statistics.temperature.week und analytics.statistics.temperature.month hinzugefügt – mit automatischen Zusammenfassungen, unabhängigen Helpern und persistenten Datenpunkten.",
|
|
22
|
+
"ru": "Добавлена еженедельная и ежемесячная статистика температуры в analytics.statistics.temperature.week и analytics.statistics.temperature.month с автоматическими сводками, независимыми помощниками и постоянными точками данных.",
|
|
23
|
+
"fr": "Ajout des statistiques de température hebdomadaires et mensuelles sous analytics.statistics.temperature.week et analytics.statistics.temperature.month avec résumés automatiques, helpers indépendants et points de données persistants.",
|
|
24
|
+
"it": "Aggiunte statistiche settimanali e mensili della temperatura in analytics.statistics.temperature.week e analytics.statistics.temperature.month con riepiloghi automatici, helper indipendenti e punti dati persistenti.",
|
|
25
|
+
"es": "Se añadieron estadísticas semanales y mensuales de temperatura en analytics.statistics.temperature.week y analytics.statistics.temperature.month con resúmenes automáticos, ayudantes independientes y puntos de datos persistentes.",
|
|
26
|
+
"nl": "Wekelijkse en maandelijkse temperatuurstatistieken toegevoegd onder analytics.statistics.temperature.week en analytics.statistics.temperature.month met automatische samenvattingen, onafhankelijke helpers en persistente gegevenspunten.",
|
|
27
|
+
"pl": "Dodano tygodniowe i miesięczne statystyki temperatury w analytics.statistics.temperature.week i analytics.statistics.temperature.month z automatycznymi podsumowaniami, niezależnymi pomocnikami i trwałymi punktami danych.",
|
|
28
|
+
"uk": "Додано щотижневу та щомісячну статистику температури в analytics.statistics.temperature.week та analytics.statistics.temperature.month з автоматичними зведеннями, незалежними помічниками та постійними точками даних.",
|
|
29
|
+
"zh-cn": "在 analytics.statistics.temperature.week 和 analytics.statistics.temperature.month 中添加了每周和每月温度统计,具有自动摘要、独立助手和持久数据点。"
|
|
30
|
+
},
|
|
6
31
|
"0.4.0": {
|
|
7
32
|
"en": "Added daily temperature statistics under analytics.statistics.temperature.today with automatic min/max/average tracking, JSON and HTML summaries, and midnight reset logic.",
|
|
8
33
|
"de": "Tägliche Temperaturstatistik unter analytics.statistics.temperature.today hinzugefügt mit automatischer Erfassung von Min-/Max-/Durchschnittswerten, JSON- und HTML-Zusammenfassungen sowie Mitternachts-Reset.",
|
|
@@ -40,31 +65,6 @@
|
|
|
40
65
|
"es": "Se añadió el cálculo del caudal real de la bomba, la monitorización en vivo y un sistema de rango normal autoaprendente para el análisis de la bomba.",
|
|
41
66
|
"pl": "Dodano obliczanie rzeczywistego przepływu pompy, monitorowanie na żywo i samouczący się system normalnego zakresu do analizy pompy.",
|
|
42
67
|
"zh-cn": "添加了实际泵流量计算、实时监控以及用于泵分析的自学习正常范围系统。"
|
|
43
|
-
},
|
|
44
|
-
"0.2.2": {
|
|
45
|
-
"en": "Added automatic backwash reminder with speech and log notifications.",
|
|
46
|
-
"de": "Automatische Rückspülerinnerung mit Sprach- und Log-Benachrichtigung hinzugefügt.",
|
|
47
|
-
"ru": "Добавлено автоматическое напоминание о обратной промывке с голосовыми и журнальными уведомлениями.",
|
|
48
|
-
"pt": "Adicionado lembrete automático de retrolavagem com notificações de voz e registro.",
|
|
49
|
-
"nl": "Automatische terugspoelherinnering toegevoegd met spraak- en logmeldingen.",
|
|
50
|
-
"fr": "Ajout d’un rappel automatique de contre-lavage avec notifications vocales et journaux.",
|
|
51
|
-
"it": "Aggiunto promemoria automatico di controlavaggio con notifiche vocali e log.",
|
|
52
|
-
"es": "Recordatorio automático de contralavado añadido con notificaciones de voz y registro.",
|
|
53
|
-
"pl": "Dodano automatyczne przypomnienie o płukaniu wstecznym z powiadomieniami głosowymi i dziennikiem.",
|
|
54
|
-
"zh-cn": "添加了带语音和日志通知的自动反冲洗提醒。"
|
|
55
|
-
},
|
|
56
|
-
"0.2.1": {
|
|
57
|
-
"en": "Fixed the issue with invisible states for speech control and added proper internal variable handling.",
|
|
58
|
-
"de": "Behebung des Problems mit unsichtbaren States für die Sprachsteuerung und hinzugefügte korrekte Handhabung interner Variablen.",
|
|
59
|
-
"ru": "Исправлена проблема с невидимыми состояниями для управления речью и добавлена правильная обработка внутренних переменных.",
|
|
60
|
-
"pt": "Corrigido o problema com estados invisíveis para controle de fala e adicionada a manipulação adequada de variáveis internas.",
|
|
61
|
-
"nl": "Het probleem met onzichtbare toestanden voor spraakbesturing is opgelost en de juiste verwerking van interne variabelen toegevoegd.",
|
|
62
|
-
"fr": "Correction du problème des états invisibles pour le contrôle vocal et ajout d'un traitement correct des variables internes.",
|
|
63
|
-
"it": "Risolto il problema con gli stati invisibili per il controllo vocale e aggiunto un corretto trattamento delle variabili interne.",
|
|
64
|
-
"es": "Se solucionó el problema con los estados invisibles para el control de voz y se añadió el manejo adecuado de las variables internas.",
|
|
65
|
-
"pl": "Naprawiono problem z niewidzialnymi stanami dla kontroli głosowej i dodano prawidłowe przetwarzanie zmiennych wewnętrznych.",
|
|
66
|
-
"uk": "Виправлено проблему з невидимими станами для керування голосом та додано правильну обробку внутрішніх змінних.",
|
|
67
|
-
"zh-cn": "修复了语音控制的隐形状态问题,并添加了正确的内部变量处理。"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"titleLang": {
|
|
@@ -118,6 +118,13 @@ const pumpHelper = {
|
|
|
118
118
|
if (this.deviceId && id === this.deviceId) {
|
|
119
119
|
const val = !!state.val;
|
|
120
120
|
const current = (await this.adapter.getStateAsync('pump.pump_switch'))?.val;
|
|
121
|
+
|
|
122
|
+
// NEU: Filter gegen zyklische Fremdmeldungen ohne echten Zustandswechsel
|
|
123
|
+
if (current === val) {
|
|
124
|
+
this.adapter.log.debug('[pumpHelper] Fremd-State meldet identischen Wert – ignoriert.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
if (current !== val) {
|
|
122
129
|
await this.adapter.setStateAsync('pump.pump_switch', { val, ack: true });
|
|
123
130
|
await this._updateStatus();
|
|
@@ -33,6 +33,14 @@ const statisticsHelper = {
|
|
|
33
33
|
this.adapter = adapter;
|
|
34
34
|
adapter.log.debug('statisticsHelper: Initialisierung gestartet.');
|
|
35
35
|
|
|
36
|
+
// --- Überinstallationsschutz ---
|
|
37
|
+
try {
|
|
38
|
+
// Prüft, ob alle States vorhanden sind, und legt fehlende still neu an
|
|
39
|
+
await this._verifyStructure();
|
|
40
|
+
} catch {
|
|
41
|
+
// keine Log-Ausgabe – stiller Schutz
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
try {
|
|
37
45
|
await this._createTemperatureStatistics();
|
|
38
46
|
await this._subscribeActiveSensors();
|
|
@@ -64,14 +72,9 @@ const statisticsHelper = {
|
|
|
64
72
|
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
65
73
|
|
|
66
74
|
if (!isActive) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
71
|
-
await adapter.setStateAsync(summaryHtmlPath, {
|
|
72
|
-
val: '<div style="color:gray;">kein Sensor aktiv</div>',
|
|
73
|
-
ack: true,
|
|
74
|
-
});
|
|
75
|
+
// Leere JSONs vorbereiten, damit spätere Temperaturwerte sie befüllen können
|
|
76
|
+
await adapter.setStateAsync(summaryJsonPath, { val: '[]', ack: true });
|
|
77
|
+
await adapter.setStateAsync(summaryHtmlPath, { val: '', ack: true });
|
|
75
78
|
continue;
|
|
76
79
|
}
|
|
77
80
|
|
|
@@ -196,6 +199,9 @@ const statisticsHelper = {
|
|
|
196
199
|
return;
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
// NEU: Rundung auf 1 Nachkommastelle
|
|
203
|
+
newValue = Math.round(newValue * 10) / 10;
|
|
204
|
+
|
|
199
205
|
const tempMin = (await adapter.getStateAsync(`${basePath}.temp_min`))?.val;
|
|
200
206
|
const tempMax = (await adapter.getStateAsync(`${basePath}.temp_max`))?.val;
|
|
201
207
|
const tempAvg = (await adapter.getStateAsync(`${basePath}.temp_avg`))?.val;
|
|
@@ -218,22 +224,33 @@ const statisticsHelper = {
|
|
|
218
224
|
const newCount = count + 1;
|
|
219
225
|
newAvg = tempAvg === null ? newValue : (tempAvg * count + newValue) / newCount;
|
|
220
226
|
|
|
227
|
+
// NEU: Alle Temperaturwerte auf 1 Nachkommastelle runden
|
|
228
|
+
newMin = Math.round(newMin * 10) / 10;
|
|
229
|
+
newMax = Math.round(newMax * 10) / 10;
|
|
230
|
+
newAvg = Math.round(newAvg * 10) / 10;
|
|
231
|
+
|
|
221
232
|
await adapter.setStateAsync(`${basePath}.temp_min`, { val: newMin, ack: true });
|
|
222
233
|
await adapter.setStateAsync(`${basePath}.temp_max`, { val: newMax, ack: true });
|
|
223
234
|
await adapter.setStateAsync(`${basePath}.temp_avg`, { val: Math.round(newAvg * 100) / 100, ack: true });
|
|
224
235
|
await adapter.setStateAsync(`${basePath}.data_points_count`, { val: newCount, ack: true });
|
|
225
236
|
await adapter.setStateAsync(`${basePath}.last_update`, { val: now, ack: true });
|
|
226
237
|
|
|
227
|
-
// Summary aktualisieren
|
|
238
|
+
// Summary aktualisieren – erweitert um Datum, Zeitpunkte, Messanzahl, Name
|
|
228
239
|
const summary = {
|
|
240
|
+
name: 'Tagesstatistik',
|
|
241
|
+
date: new Date().toISOString().slice(0, 10),
|
|
229
242
|
temp_min: newMin,
|
|
243
|
+
temp_min_time: (await adapter.getStateAsync(`${basePath}.temp_min_time`))?.val || '',
|
|
230
244
|
temp_max: newMax,
|
|
231
|
-
|
|
245
|
+
temp_max_time: (await adapter.getStateAsync(`${basePath}.temp_max_time`))?.val || '',
|
|
246
|
+
temp_avg: newAvg,
|
|
247
|
+
data_points_count: newCount,
|
|
232
248
|
updated: now,
|
|
233
249
|
};
|
|
234
250
|
await adapter.setStateAsync(`${basePath}.summary_json`, { val: JSON.stringify(summary), ack: true });
|
|
235
251
|
await adapter.setStateAsync(`${basePath}.summary_html`, {
|
|
236
|
-
|
|
252
|
+
// NEU: HTML-Ausgabe mit gerundeten Werten
|
|
253
|
+
val: `<div><b>Min:</b> ${newMin} °C / <b>Max:</b> ${newMax} °C / <b>Ø:</b> ${newAvg} °C</div>`,
|
|
237
254
|
ack: true,
|
|
238
255
|
});
|
|
239
256
|
|
|
@@ -241,45 +258,110 @@ const statisticsHelper = {
|
|
|
241
258
|
},
|
|
242
259
|
|
|
243
260
|
/**
|
|
244
|
-
* Gesamt-HTML/JSON-Ausgabe aktualisieren
|
|
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.
|
|
245
264
|
*/
|
|
246
265
|
async _updateOverallSummary() {
|
|
247
266
|
const adapter = this.adapter;
|
|
248
267
|
const allData = [];
|
|
249
268
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
269
|
+
try {
|
|
270
|
+
// Alle Sensoren durchlaufen
|
|
271
|
+
for (const sensor of this.sensors) {
|
|
272
|
+
const summaryState = await adapter.getStateAsync(
|
|
273
|
+
`analytics.statistics.temperature.today.${sensor.id}.summary_json`,
|
|
274
|
+
);
|
|
256
275
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
?.val;
|
|
261
|
-
const avg = (await adapter.getStateAsync(`analytics.statistics.temperature.today.${sensor.id}.temp_avg`))
|
|
262
|
-
?.val;
|
|
276
|
+
if (!summaryState || !summaryState.val) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
263
279
|
|
|
264
|
-
|
|
265
|
-
|
|
280
|
+
// JSON-Inhalt parsen
|
|
281
|
+
let parsed;
|
|
282
|
+
try {
|
|
283
|
+
parsed = JSON.parse(summaryState.val);
|
|
284
|
+
} catch {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
266
287
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
288
|
+
const min = parsed.temp_min;
|
|
289
|
+
const max = parsed.temp_max;
|
|
290
|
+
const avg = parsed.temp_avg;
|
|
291
|
+
const date = parsed.date || '';
|
|
292
|
+
const minTime = parsed.temp_min_time || '';
|
|
293
|
+
const maxTime = parsed.temp_max_time || '';
|
|
294
|
+
const count = parsed.data_points_count || 0;
|
|
295
|
+
|
|
296
|
+
// Falls gar keine Werte vorhanden, überspringen
|
|
297
|
+
if (min == null && max == null && avg == null) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
271
300
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
301
|
+
// Werte runden
|
|
302
|
+
const rMin = typeof min === 'number' ? Math.round(min * 10) / 10 : min;
|
|
303
|
+
const rMax = typeof max === 'number' ? Math.round(max * 10) / 10 : max;
|
|
304
|
+
const rAvg = typeof avg === 'number' ? Math.round(avg * 10) / 10 : avg;
|
|
305
|
+
|
|
306
|
+
// Erweiterte Datensammlung
|
|
307
|
+
allData.push({
|
|
308
|
+
name: sensor.name,
|
|
309
|
+
date,
|
|
310
|
+
min: rMin,
|
|
311
|
+
min_time: minTime,
|
|
312
|
+
max: rMax,
|
|
313
|
+
max_time: maxTime,
|
|
314
|
+
avg: rAvg,
|
|
315
|
+
data_points_count: count,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
278
318
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
319
|
+
// Wenn noch keine Daten vorliegen → leer lassen
|
|
320
|
+
if (allData.length === 0) {
|
|
321
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
|
|
322
|
+
val: '[]',
|
|
323
|
+
ack: true,
|
|
324
|
+
});
|
|
325
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_html', {
|
|
326
|
+
val: '',
|
|
327
|
+
ack: true,
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// JSON-Ausgabe erstellen (komplett erweitert)
|
|
333
|
+
const jsonOutput = JSON.stringify(allData, null, 2);
|
|
334
|
+
|
|
335
|
+
// HTML-Ausgabe erstellen (mit Datum & Anzahl Messwerte)
|
|
336
|
+
let html = '<table style="width:100%;border-collapse:collapse;">';
|
|
337
|
+
html +=
|
|
338
|
+
'<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>';
|
|
339
|
+
for (const entry of allData) {
|
|
340
|
+
html += `<tr>
|
|
341
|
+
<td>${entry.name}</td>
|
|
342
|
+
<td>${entry.date || '-'}</td>
|
|
343
|
+
<td>${entry.min ?? '-'}</td>
|
|
344
|
+
<td>${entry.min_time || '-'}</td>
|
|
345
|
+
<td>${entry.max ?? '-'}</td>
|
|
346
|
+
<td>${entry.max_time || '-'}</td>
|
|
347
|
+
<td>${entry.avg ?? '-'}</td>
|
|
348
|
+
<td>${entry.data_points_count ?? '-'}</td>
|
|
349
|
+
</tr>`;
|
|
350
|
+
}
|
|
351
|
+
html += '</table>';
|
|
352
|
+
|
|
353
|
+
// States setzen (auch bei gleichen Werten → Timestamp aktualisieren)
|
|
354
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
|
|
355
|
+
val: jsonOutput,
|
|
356
|
+
ack: true,
|
|
357
|
+
});
|
|
358
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_html', {
|
|
359
|
+
val: html,
|
|
360
|
+
ack: true,
|
|
361
|
+
});
|
|
362
|
+
} catch {
|
|
363
|
+
// bewusst kein Log hier – stiller Schutz
|
|
364
|
+
}
|
|
283
365
|
},
|
|
284
366
|
|
|
285
367
|
/**
|
|
@@ -382,6 +464,18 @@ const statisticsHelper = {
|
|
|
382
464
|
adapter.log.debug('statisticsHelper: Tagesstatistik zurückgesetzt.');
|
|
383
465
|
},
|
|
384
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Stiller Überinstallationsschutz:
|
|
469
|
+
* Prüft und legt fehlende States erneut an, ohne bestehende Werte zu überschreiben.
|
|
470
|
+
*/
|
|
471
|
+
async _verifyStructure() {
|
|
472
|
+
try {
|
|
473
|
+
await this._createTemperatureStatistics();
|
|
474
|
+
} catch {
|
|
475
|
+
// bewusst keine Logs – stiller Selbstschutz
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
|
|
385
479
|
cleanup() {
|
|
386
480
|
if (this.midnightTimer) {
|
|
387
481
|
clearTimeout(this.midnightTimer);
|