iobroker.poolcontrol 0.4.0 → 0.5.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 +8 -0
- package/io-package.json +13 -14
- package/lib/helpers/pumpHelper.js +7 -0
- package/lib/helpers/statisticsHelper.js +94 -38
- package/lib/helpers/statisticsHelperMonth.js +417 -0
- package/lib/helpers/statisticsHelperWeek.js +422 -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,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.5.0": {
|
|
7
|
+
"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.",
|
|
8
|
+
"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.",
|
|
9
|
+
"ru": "Добавлена еженедельная и ежемесячная статистика температуры в analytics.statistics.temperature.week и analytics.statistics.temperature.month с автоматическими сводками, независимыми помощниками и постоянными точками данных.",
|
|
10
|
+
"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.",
|
|
11
|
+
"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.",
|
|
12
|
+
"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.",
|
|
13
|
+
"nl": "Wekelijkse en maandelijkse temperatuurstatistieken toegevoegd onder analytics.statistics.temperature.week en analytics.statistics.temperature.month met automatische samenvattingen, onafhankelijke helpers en persistente gegevenspunten.",
|
|
14
|
+
"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.",
|
|
15
|
+
"uk": "Додано щотижневу та щомісячну статистику температури в analytics.statistics.temperature.week та analytics.statistics.temperature.month з автоматичними зведеннями, незалежними помічниками та постійними точками даних.",
|
|
16
|
+
"zh-cn": "在 analytics.statistics.temperature.week 和 analytics.statistics.temperature.month 中添加了每周和每月温度统计,具有自动摘要、独立助手和持久数据点。"
|
|
17
|
+
},
|
|
6
18
|
"0.4.0": {
|
|
7
19
|
"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
20
|
"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.",
|
|
@@ -52,19 +64,6 @@
|
|
|
52
64
|
"es": "Recordatorio automático de contralavado añadido con notificaciones de voz y registro.",
|
|
53
65
|
"pl": "Dodano automatyczne przypomnienie o płukaniu wstecznym z powiadomieniami głosowymi i dziennikiem.",
|
|
54
66
|
"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
67
|
}
|
|
69
68
|
},
|
|
70
69
|
"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();
|
|
@@ -64,14 +64,9 @@ const statisticsHelper = {
|
|
|
64
64
|
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
65
65
|
|
|
66
66
|
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
|
-
});
|
|
67
|
+
// Leere JSONs vorbereiten, damit spätere Temperaturwerte sie befüllen können
|
|
68
|
+
await adapter.setStateAsync(summaryJsonPath, { val: '[]', ack: true });
|
|
69
|
+
await adapter.setStateAsync(summaryHtmlPath, { val: '', ack: true });
|
|
75
70
|
continue;
|
|
76
71
|
}
|
|
77
72
|
|
|
@@ -196,6 +191,9 @@ const statisticsHelper = {
|
|
|
196
191
|
return;
|
|
197
192
|
}
|
|
198
193
|
|
|
194
|
+
// NEU: Rundung auf 1 Nachkommastelle
|
|
195
|
+
newValue = Math.round(newValue * 10) / 10;
|
|
196
|
+
|
|
199
197
|
const tempMin = (await adapter.getStateAsync(`${basePath}.temp_min`))?.val;
|
|
200
198
|
const tempMax = (await adapter.getStateAsync(`${basePath}.temp_max`))?.val;
|
|
201
199
|
const tempAvg = (await adapter.getStateAsync(`${basePath}.temp_avg`))?.val;
|
|
@@ -218,6 +216,11 @@ const statisticsHelper = {
|
|
|
218
216
|
const newCount = count + 1;
|
|
219
217
|
newAvg = tempAvg === null ? newValue : (tempAvg * count + newValue) / newCount;
|
|
220
218
|
|
|
219
|
+
// NEU: Alle Temperaturwerte auf 1 Nachkommastelle runden
|
|
220
|
+
newMin = Math.round(newMin * 10) / 10;
|
|
221
|
+
newMax = Math.round(newMax * 10) / 10;
|
|
222
|
+
newAvg = Math.round(newAvg * 10) / 10;
|
|
223
|
+
|
|
221
224
|
await adapter.setStateAsync(`${basePath}.temp_min`, { val: newMin, ack: true });
|
|
222
225
|
await adapter.setStateAsync(`${basePath}.temp_max`, { val: newMax, ack: true });
|
|
223
226
|
await adapter.setStateAsync(`${basePath}.temp_avg`, { val: Math.round(newAvg * 100) / 100, ack: true });
|
|
@@ -225,15 +228,17 @@ const statisticsHelper = {
|
|
|
225
228
|
await adapter.setStateAsync(`${basePath}.last_update`, { val: now, ack: true });
|
|
226
229
|
|
|
227
230
|
// Summary aktualisieren
|
|
231
|
+
// NEU: Zusammenfassung mit gerundeten Werten (1 Nachkommastelle)
|
|
228
232
|
const summary = {
|
|
229
233
|
temp_min: newMin,
|
|
230
234
|
temp_max: newMax,
|
|
231
|
-
temp_avg:
|
|
235
|
+
temp_avg: newAvg,
|
|
232
236
|
updated: now,
|
|
233
237
|
};
|
|
234
238
|
await adapter.setStateAsync(`${basePath}.summary_json`, { val: JSON.stringify(summary), ack: true });
|
|
235
239
|
await adapter.setStateAsync(`${basePath}.summary_html`, {
|
|
236
|
-
|
|
240
|
+
// NEU: HTML-Ausgabe mit gerundeten Werten
|
|
241
|
+
val: `<div><b>Min:</b> ${newMin} °C / <b>Max:</b> ${newMax} °C / <b>Ø:</b> ${newAvg} °C</div>`,
|
|
237
242
|
ack: true,
|
|
238
243
|
});
|
|
239
244
|
|
|
@@ -241,45 +246,96 @@ const statisticsHelper = {
|
|
|
241
246
|
},
|
|
242
247
|
|
|
243
248
|
/**
|
|
244
|
-
* Gesamt-HTML/JSON-Ausgabe aktualisieren
|
|
249
|
+
* Gesamt-HTML/JSON-Ausgabe aktualisieren (Endgültige Version)
|
|
250
|
+
* Liest die fertigen summary_json-Werte aller aktiven Sensoren aus
|
|
251
|
+
* und erstellt daraus eine zusammengefasste HTML- und JSON-Ausgabe.
|
|
245
252
|
*/
|
|
246
253
|
async _updateOverallSummary() {
|
|
247
254
|
const adapter = this.adapter;
|
|
248
255
|
const allData = [];
|
|
249
256
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
continue;
|
|
257
|
+
try {
|
|
258
|
+
// Alle Sensoren durchlaufen
|
|
259
|
+
for (const sensor of this.sensors) {
|
|
260
|
+
// const isActive = (await adapter.getStateAsync(`temperature.${sensor.id}.active`))?.val === true;
|
|
261
|
+
// if (!isActive) continue;
|
|
262
|
+
|
|
263
|
+
// Fertige Einzel-Summary des Sensors abrufen
|
|
264
|
+
const summaryState = await adapter.getStateAsync(
|
|
265
|
+
`analytics.statistics.temperature.today.${sensor.id}.summary_json`,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (!summaryState || !summaryState.val) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// JSON-Inhalt parsen
|
|
273
|
+
let parsed;
|
|
274
|
+
try {
|
|
275
|
+
parsed = JSON.parse(summaryState.val);
|
|
276
|
+
} catch {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const min = parsed.temp_min;
|
|
281
|
+
const max = parsed.temp_max;
|
|
282
|
+
const avg = parsed.temp_avg;
|
|
283
|
+
|
|
284
|
+
if (min == null && max == null && avg == null) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Werte runden
|
|
289
|
+
const rMin = typeof min === 'number' ? Math.round(min * 10) / 10 : min;
|
|
290
|
+
const rMax = typeof max === 'number' ? Math.round(max * 10) / 10 : max;
|
|
291
|
+
const rAvg = typeof avg === 'number' ? Math.round(avg * 10) / 10 : avg;
|
|
292
|
+
|
|
293
|
+
allData.push({
|
|
294
|
+
name: sensor.name,
|
|
295
|
+
min: rMin,
|
|
296
|
+
max: rMax,
|
|
297
|
+
avg: rAvg,
|
|
298
|
+
});
|
|
255
299
|
}
|
|
256
300
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
301
|
+
// Wenn noch keine Daten vorliegen → leer lassen
|
|
302
|
+
if (allData.length === 0) {
|
|
303
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
|
|
304
|
+
val: '[]',
|
|
305
|
+
ack: true,
|
|
306
|
+
});
|
|
307
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_html', {
|
|
308
|
+
val: '',
|
|
309
|
+
ack: true,
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
263
313
|
|
|
264
|
-
|
|
265
|
-
|
|
314
|
+
// JSON-Ausgabe erstellen
|
|
315
|
+
const jsonOutput = JSON.stringify(allData);
|
|
266
316
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
317
|
+
// HTML-Ausgabe erstellen
|
|
318
|
+
let html = '<table style="width:100%;border-collapse:collapse;">';
|
|
319
|
+
html += '<tr><th style="text-align:left;">Sensor</th><th>Min</th><th>Max</th><th>Ø</th></tr>';
|
|
320
|
+
for (const entry of allData) {
|
|
321
|
+
html += `<tr><td>${entry.name}</td><td>${entry.min ?? '-'}</td><td>${entry.max ?? '-'}</td><td>${entry.avg ?? '-'}</td></tr>`;
|
|
322
|
+
}
|
|
323
|
+
html += '</table>';
|
|
271
324
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
325
|
+
// States setzen (auch bei gleichen Werten → Timestamp aktualisieren)
|
|
326
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
|
|
327
|
+
val: jsonOutput,
|
|
328
|
+
ack: true,
|
|
329
|
+
});
|
|
330
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.today.outputs.summary_all_html', {
|
|
331
|
+
val: html,
|
|
332
|
+
ack: true,
|
|
333
|
+
});
|
|
278
334
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
335
|
+
adapter.log.debug('statisticsHelper: Gesamtzusammenfassung (Summary All) erfolgreich aktualisiert.');
|
|
336
|
+
} catch (err) {
|
|
337
|
+
adapter.log.warn(`statisticsHelper: Fehler bei Gesamtzusammenfassung: ${err.message}`);
|
|
338
|
+
}
|
|
283
339
|
},
|
|
284
340
|
|
|
285
341
|
/**
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* statisticsHelperMonth.js
|
|
5
|
+
* ------------------------
|
|
6
|
+
* Vollständige Steuerung der Monatsstatistik (Temperatur)
|
|
7
|
+
* im Bereich analytics.statistics.temperature.month.*
|
|
8
|
+
*
|
|
9
|
+
* - Erkennt aktive Sensoren anhand temperature.<sensor>.active
|
|
10
|
+
* - Reagiert eventbasiert auf Änderungen der Temperaturwerte
|
|
11
|
+
* - Berechnet laufend Min/Max/Durchschnitt über 30 Tage
|
|
12
|
+
* - Aktualisiert JSON- und HTML-Ausgaben (pro Sensor & gesamt)
|
|
13
|
+
* - Führt automatischen Monats-Reset (1. Tag des Monats 00:05 Uhr) durch
|
|
14
|
+
*
|
|
15
|
+
* @param {ioBroker.Adapter} adapter - Aktive ioBroker-Adapterinstanz
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const statisticsHelperMonth = {
|
|
19
|
+
adapter: null,
|
|
20
|
+
monthResetTimer: null,
|
|
21
|
+
sensors: [
|
|
22
|
+
{ id: 'outside', name: 'Außentemperatur' },
|
|
23
|
+
{ id: 'ground', name: 'Bodentemperatur' },
|
|
24
|
+
{ id: 'surface', name: 'Pooloberfläche' },
|
|
25
|
+
{ id: 'flow', name: 'Vorlauf' },
|
|
26
|
+
{ id: 'return', name: 'Rücklauf' },
|
|
27
|
+
{ id: 'collector', name: 'Kollektor (Solar)' },
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
async init(adapter) {
|
|
31
|
+
this.adapter = adapter;
|
|
32
|
+
adapter.log.debug('statisticsHelperMonth: Initialisierung gestartet.');
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await this._createTemperatureStatistics();
|
|
36
|
+
await this._subscribeActiveSensors();
|
|
37
|
+
await this._scheduleMonthReset();
|
|
38
|
+
adapter.log.debug('statisticsHelperMonth: Initialisierung abgeschlossen (Sensorüberwachung aktiv).');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
adapter.log.warn(`statisticsHelperMonth: Fehler bei Initialisierung: ${err.message}`);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Erstellt States, falls sie fehlen (Überinstallationsschutz)
|
|
46
|
+
*/
|
|
47
|
+
async _createTemperatureStatistics() {
|
|
48
|
+
const adapter = this.adapter;
|
|
49
|
+
|
|
50
|
+
for (const sensor of this.sensors) {
|
|
51
|
+
const basePath = `analytics.statistics.temperature.month.${sensor.id}`;
|
|
52
|
+
await adapter.setObjectNotExistsAsync(basePath, {
|
|
53
|
+
type: 'channel',
|
|
54
|
+
common: { name: `${sensor.name} (Monatsstatistik)` },
|
|
55
|
+
native: {},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
59
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
60
|
+
|
|
61
|
+
const summaryJsonPath = `${basePath}.summary_json`;
|
|
62
|
+
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
63
|
+
|
|
64
|
+
if (!isActive) {
|
|
65
|
+
await adapter.setStateAsync(summaryJsonPath, { val: '[]', ack: true });
|
|
66
|
+
await adapter.setStateAsync(summaryHtmlPath, { val: '', ack: true });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stateDefs = [
|
|
71
|
+
{ id: 'temp_min', def: null },
|
|
72
|
+
{ id: 'temp_max', def: null },
|
|
73
|
+
{ id: 'temp_min_time', def: '' },
|
|
74
|
+
{ id: 'temp_max_time', def: '' },
|
|
75
|
+
{ id: 'temp_avg', def: null },
|
|
76
|
+
{ id: 'data_points_count', def: 0 },
|
|
77
|
+
{ id: 'last_update', def: '' },
|
|
78
|
+
{ id: 'summary_json', def: '' },
|
|
79
|
+
{ id: 'summary_html', def: '' },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const def of stateDefs) {
|
|
83
|
+
const fullPath = `${basePath}.${def.id}`;
|
|
84
|
+
const obj = await adapter.getObjectAsync(fullPath);
|
|
85
|
+
if (!obj) {
|
|
86
|
+
await adapter.setObjectNotExistsAsync(fullPath, {
|
|
87
|
+
type: 'state',
|
|
88
|
+
common: {
|
|
89
|
+
name: def.id,
|
|
90
|
+
type: typeof def.def === 'number' ? 'number' : 'string',
|
|
91
|
+
role: def.id.includes('time')
|
|
92
|
+
? 'value.time'
|
|
93
|
+
: def.id.includes('temp')
|
|
94
|
+
? 'value.temperature'
|
|
95
|
+
: 'value',
|
|
96
|
+
read: true,
|
|
97
|
+
write: false,
|
|
98
|
+
persist: true,
|
|
99
|
+
},
|
|
100
|
+
native: {},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const state = await adapter.getStateAsync(fullPath);
|
|
105
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
106
|
+
await adapter.setStateAsync(fullPath, { val: def.def, ack: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const outputBase = 'analytics.statistics.temperature.month.outputs';
|
|
112
|
+
await adapter.setObjectNotExistsAsync(outputBase, {
|
|
113
|
+
type: 'channel',
|
|
114
|
+
common: { name: 'Gesamtausgaben (alle Sensoren – Monat)' },
|
|
115
|
+
native: {},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const outputStates = [
|
|
119
|
+
{ id: 'summary_all_json', def: '' },
|
|
120
|
+
{ id: 'summary_all_html', def: '' },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
for (const out of outputStates) {
|
|
124
|
+
const fullPath = `${outputBase}.${out.id}`;
|
|
125
|
+
const obj = await adapter.getObjectAsync(fullPath);
|
|
126
|
+
if (!obj) {
|
|
127
|
+
await adapter.setObjectNotExistsAsync(fullPath, {
|
|
128
|
+
type: 'state',
|
|
129
|
+
common: {
|
|
130
|
+
name: out.id,
|
|
131
|
+
type: 'string',
|
|
132
|
+
role: out.id.endsWith('json') ? 'json' : 'html',
|
|
133
|
+
read: true,
|
|
134
|
+
write: false,
|
|
135
|
+
persist: true,
|
|
136
|
+
},
|
|
137
|
+
native: {},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const state = await adapter.getStateAsync(fullPath);
|
|
142
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
143
|
+
await adapter.setStateAsync(fullPath, { val: out.id.endsWith('json') ? '{}' : '', ack: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Abonniert alle aktiven Temperatursensoren
|
|
150
|
+
*/
|
|
151
|
+
async _subscribeActiveSensors() {
|
|
152
|
+
const adapter = this.adapter;
|
|
153
|
+
for (const sensor of this.sensors) {
|
|
154
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
155
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
156
|
+
if (isActive) {
|
|
157
|
+
const stateId = `temperature.${sensor.id}.current`;
|
|
158
|
+
adapter.subscribeStates(stateId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
adapter.on('stateChange', async (id, state) => {
|
|
163
|
+
if (!state || state.ack === false) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const sensor of this.sensors) {
|
|
167
|
+
if (id.endsWith(`temperature.${sensor.id}.current`)) {
|
|
168
|
+
await this._processTemperatureChange(sensor.id, state.val);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Verarbeitung einer Temperaturänderung für einen Sensor.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} sensorId - ID des Sensors
|
|
179
|
+
* @param {number} newValue - Neuer Messwert in °C
|
|
180
|
+
*/
|
|
181
|
+
async _processTemperatureChange(sensorId, newValue) {
|
|
182
|
+
const adapter = this.adapter;
|
|
183
|
+
const basePath = `analytics.statistics.temperature.month.${sensorId}`;
|
|
184
|
+
const now = `${new Date().toLocaleDateString('de-DE', {
|
|
185
|
+
day: '2-digit',
|
|
186
|
+
month: '2-digit',
|
|
187
|
+
})} ${new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
|
|
188
|
+
|
|
189
|
+
if (typeof newValue !== 'number') {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
newValue = Math.round(newValue * 10) / 10;
|
|
194
|
+
|
|
195
|
+
const tempMin = (await adapter.getStateAsync(`${basePath}.temp_min`))?.val;
|
|
196
|
+
const tempMax = (await adapter.getStateAsync(`${basePath}.temp_max`))?.val;
|
|
197
|
+
const tempAvg = (await adapter.getStateAsync(`${basePath}.temp_avg`))?.val;
|
|
198
|
+
const count = (await adapter.getStateAsync(`${basePath}.data_points_count`))?.val || 0;
|
|
199
|
+
|
|
200
|
+
let newMin = tempMin;
|
|
201
|
+
let newMax = tempMax;
|
|
202
|
+
let newAvg = tempAvg;
|
|
203
|
+
|
|
204
|
+
if (tempMin === null || newValue < tempMin) {
|
|
205
|
+
newMin = newValue;
|
|
206
|
+
await adapter.setStateAsync(`${basePath}.temp_min_time`, { val: now, ack: true });
|
|
207
|
+
}
|
|
208
|
+
if (tempMax === null || newValue > tempMax) {
|
|
209
|
+
newMax = newValue;
|
|
210
|
+
await adapter.setStateAsync(`${basePath}.temp_max_time`, { val: now, ack: true });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const newCount = count + 1;
|
|
214
|
+
newAvg = tempAvg === null ? newValue : (tempAvg * count + newValue) / newCount;
|
|
215
|
+
|
|
216
|
+
newMin = Math.round(newMin * 10) / 10;
|
|
217
|
+
newMax = Math.round(newMax * 10) / 10;
|
|
218
|
+
newAvg = Math.round(newAvg * 10) / 10;
|
|
219
|
+
|
|
220
|
+
await adapter.setStateAsync(`${basePath}.temp_min`, { val: newMin, ack: true });
|
|
221
|
+
await adapter.setStateAsync(`${basePath}.temp_max`, { val: newMax, ack: true });
|
|
222
|
+
await adapter.setStateAsync(`${basePath}.temp_avg`, { val: Math.round(newAvg * 100) / 100, ack: true });
|
|
223
|
+
await adapter.setStateAsync(`${basePath}.data_points_count`, { val: newCount, ack: true });
|
|
224
|
+
await adapter.setStateAsync(`${basePath}.last_update`, { val: now, ack: true });
|
|
225
|
+
|
|
226
|
+
const summary = {
|
|
227
|
+
temp_min: newMin,
|
|
228
|
+
temp_max: newMax,
|
|
229
|
+
temp_avg: newAvg,
|
|
230
|
+
updated: now,
|
|
231
|
+
};
|
|
232
|
+
await adapter.setStateAsync(`${basePath}.summary_json`, { val: JSON.stringify(summary), ack: true });
|
|
233
|
+
await adapter.setStateAsync(`${basePath}.summary_html`, {
|
|
234
|
+
val: `<div><b>Min:</b> ${newMin} °C / <b>Max:</b> ${newMax} °C / <b>Ø:</b> ${newAvg} °C</div>`,
|
|
235
|
+
ack: true,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await this._updateOverallSummary();
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Gesamtzusammenfassung (JSON + HTML)
|
|
243
|
+
*/
|
|
244
|
+
async _updateOverallSummary() {
|
|
245
|
+
const adapter = this.adapter;
|
|
246
|
+
const allData = [];
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
for (const sensor of this.sensors) {
|
|
250
|
+
const summaryState = await adapter.getStateAsync(
|
|
251
|
+
`analytics.statistics.temperature.month.${sensor.id}.summary_json`,
|
|
252
|
+
);
|
|
253
|
+
if (!summaryState || !summaryState.val) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let parsed;
|
|
258
|
+
try {
|
|
259
|
+
parsed = JSON.parse(summaryState.val);
|
|
260
|
+
} catch {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const { temp_min: min, temp_max: max, temp_avg: avg } = parsed;
|
|
265
|
+
if (min == null && max == null && avg == null) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const rMin = typeof min === 'number' ? Math.round(min * 10) / 10 : min;
|
|
270
|
+
const rMax = typeof max === 'number' ? Math.round(max * 10) / 10 : max;
|
|
271
|
+
const rAvg = typeof avg === 'number' ? Math.round(avg * 10) / 10 : avg;
|
|
272
|
+
|
|
273
|
+
allData.push({ name: sensor.name, min: rMin, max: rMax, avg: rAvg });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (allData.length === 0) {
|
|
277
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.month.outputs.summary_all_json', {
|
|
278
|
+
val: '[]',
|
|
279
|
+
ack: true,
|
|
280
|
+
});
|
|
281
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.month.outputs.summary_all_html', {
|
|
282
|
+
val: '',
|
|
283
|
+
ack: true,
|
|
284
|
+
});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const jsonOutput = JSON.stringify(allData);
|
|
289
|
+
let html = '<table style="width:100%;border-collapse:collapse;">';
|
|
290
|
+
html += '<tr><th style="text-align:left;">Sensor</th><th>Min</th><th>Max</th><th>Ø</th></tr>';
|
|
291
|
+
for (const entry of allData) {
|
|
292
|
+
html += `<tr><td>${entry.name}</td><td>${entry.min ?? '-'}</td><td>${entry.max ?? '-'}</td><td>${entry.avg ?? '-'}</td></tr>`;
|
|
293
|
+
}
|
|
294
|
+
html += '</table>';
|
|
295
|
+
|
|
296
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.month.outputs.summary_all_json', {
|
|
297
|
+
val: jsonOutput,
|
|
298
|
+
ack: true,
|
|
299
|
+
});
|
|
300
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.month.outputs.summary_all_html', {
|
|
301
|
+
val: html,
|
|
302
|
+
ack: true,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
adapter.log.debug('statisticsHelperMonth: Gesamtzusammenfassung erfolgreich aktualisiert.');
|
|
306
|
+
} catch (err) {
|
|
307
|
+
adapter.log.warn(`statisticsHelperMonth: Fehler bei Gesamtzusammenfassung: ${err.message}`);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Monats-Reset planen (1. Tag des Monats 00:05 Uhr)
|
|
313
|
+
*/
|
|
314
|
+
async _scheduleMonthReset() {
|
|
315
|
+
const adapter = this.adapter;
|
|
316
|
+
if (this.monthResetTimer) {
|
|
317
|
+
clearTimeout(this.monthResetTimer);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const now = new Date();
|
|
321
|
+
const nextReset = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 5, 0, 0);
|
|
322
|
+
const msUntilReset = nextReset.getTime() - now.getTime();
|
|
323
|
+
|
|
324
|
+
this.monthResetTimer = setTimeout(async () => {
|
|
325
|
+
await this._resetMonthlyTemperatureStats();
|
|
326
|
+
await this._scheduleMonthReset();
|
|
327
|
+
}, msUntilReset);
|
|
328
|
+
|
|
329
|
+
adapter.log.debug(
|
|
330
|
+
`statisticsHelperMonth: Monats-Reset geplant in ${Math.round(msUntilReset / 60000)} Minuten.`,
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Monatsstatistik zurücksetzen
|
|
336
|
+
*/
|
|
337
|
+
async _resetMonthlyTemperatureStats() {
|
|
338
|
+
const adapter = this.adapter;
|
|
339
|
+
adapter.log.info('statisticsHelperMonth: Monatsstatistik wird zurückgesetzt.');
|
|
340
|
+
|
|
341
|
+
const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
342
|
+
|
|
343
|
+
for (const sensor of this.sensors) {
|
|
344
|
+
const basePath = `analytics.statistics.temperature.month.${sensor.id}`;
|
|
345
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
346
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
347
|
+
|
|
348
|
+
const summaryJsonPath = `${basePath}.summary_json`;
|
|
349
|
+
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
350
|
+
|
|
351
|
+
if (!isActive) {
|
|
352
|
+
await adapter.setStateAsync(summaryJsonPath, {
|
|
353
|
+
val: JSON.stringify({ status: 'kein Sensor aktiv' }),
|
|
354
|
+
ack: true,
|
|
355
|
+
});
|
|
356
|
+
await adapter.setStateAsync(summaryHtmlPath, {
|
|
357
|
+
val: '<div style="color:gray;">kein Sensor aktiv</div>',
|
|
358
|
+
ack: true,
|
|
359
|
+
});
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const stateList = [
|
|
364
|
+
'temp_min',
|
|
365
|
+
'temp_max',
|
|
366
|
+
'temp_min_time',
|
|
367
|
+
'temp_max_time',
|
|
368
|
+
'temp_avg',
|
|
369
|
+
'data_points_count',
|
|
370
|
+
'last_update',
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
for (const state of stateList) {
|
|
374
|
+
const fullPath = `${basePath}.${state}`;
|
|
375
|
+
let defValue = null;
|
|
376
|
+
if (state.includes('time')) {
|
|
377
|
+
defValue = '';
|
|
378
|
+
}
|
|
379
|
+
if (state === 'data_points_count') {
|
|
380
|
+
defValue = 0;
|
|
381
|
+
}
|
|
382
|
+
if (state === 'last_update') {
|
|
383
|
+
defValue = resetDate;
|
|
384
|
+
}
|
|
385
|
+
await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
await adapter.setStateAsync(summaryJsonPath, {
|
|
389
|
+
val: JSON.stringify({ date_reset: resetDate, status: 'Monatswerte zurückgesetzt' }),
|
|
390
|
+
ack: true,
|
|
391
|
+
});
|
|
392
|
+
await adapter.setStateAsync(summaryHtmlPath, {
|
|
393
|
+
val: `<div style="color:gray;">Monatswerte zurückgesetzt (${resetDate})</div>`,
|
|
394
|
+
ack: true,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
await adapter.setStateAsync('analytics.statistics.temperature.month.outputs.summary_all_json', {
|
|
399
|
+
val: '{}',
|
|
400
|
+
ack: true,
|
|
401
|
+
});
|
|
402
|
+
await adapter.setStateAsync('analytics.statistics.temperature.month.outputs.summary_all_html', {
|
|
403
|
+
val: '',
|
|
404
|
+
ack: true,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
adapter.log.debug('statisticsHelperMonth: Monatsstatistik zurückgesetzt.');
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
cleanup() {
|
|
411
|
+
if (this.monthResetTimer) {
|
|
412
|
+
clearTimeout(this.monthResetTimer);
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
module.exports = statisticsHelperMonth;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* statisticsHelperWeek.js
|
|
5
|
+
* -----------------------
|
|
6
|
+
* Vollständige Steuerung der Wochenstatistik (Temperatur)
|
|
7
|
+
* im Bereich analytics.statistics.temperature.week.*
|
|
8
|
+
*
|
|
9
|
+
* - Erkennt aktive Sensoren anhand temperature.<sensor>.active
|
|
10
|
+
* - Reagiert eventbasiert auf Änderungen der Temperaturwerte
|
|
11
|
+
* - Berechnet laufend Min/Max/Durchschnitt über 7 Tage
|
|
12
|
+
* - Aktualisiert JSON- und HTML-Ausgaben (pro Sensor & gesamt)
|
|
13
|
+
* - Führt automatischen Wochen-Reset (Sonntag 00:05 Uhr) durch
|
|
14
|
+
*
|
|
15
|
+
* @param {ioBroker.Adapter} adapter - Aktive ioBroker-Adapterinstanz
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const statisticsHelperWeek = {
|
|
19
|
+
adapter: null,
|
|
20
|
+
weekResetTimer: null,
|
|
21
|
+
sensors: [
|
|
22
|
+
{ id: 'outside', name: 'Außentemperatur' },
|
|
23
|
+
{ id: 'ground', name: 'Bodentemperatur' },
|
|
24
|
+
{ id: 'surface', name: 'Pooloberfläche' },
|
|
25
|
+
{ id: 'flow', name: 'Vorlauf' },
|
|
26
|
+
{ id: 'return', name: 'Rücklauf' },
|
|
27
|
+
{ id: 'collector', name: 'Kollektor (Solar)' },
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
async init(adapter) {
|
|
31
|
+
this.adapter = adapter;
|
|
32
|
+
adapter.log.debug('statisticsHelperWeek: Initialisierung gestartet.');
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await this._createTemperatureStatistics();
|
|
36
|
+
await this._subscribeActiveSensors();
|
|
37
|
+
await this._scheduleWeekReset();
|
|
38
|
+
adapter.log.debug('statisticsHelperWeek: Initialisierung abgeschlossen (Sensorüberwachung aktiv).');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
adapter.log.warn(`statisticsHelperWeek: Fehler bei Initialisierung: ${err.message}`);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Erstellt States, falls sie fehlen (Überinstallationsschutz)
|
|
46
|
+
*/
|
|
47
|
+
async _createTemperatureStatistics() {
|
|
48
|
+
const adapter = this.adapter;
|
|
49
|
+
|
|
50
|
+
for (const sensor of this.sensors) {
|
|
51
|
+
const basePath = `analytics.statistics.temperature.week.${sensor.id}`;
|
|
52
|
+
await adapter.setObjectNotExistsAsync(basePath, {
|
|
53
|
+
type: 'channel',
|
|
54
|
+
common: { name: `${sensor.name} (Wochenstatistik)` },
|
|
55
|
+
native: {},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
59
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
60
|
+
|
|
61
|
+
const summaryJsonPath = `${basePath}.summary_json`;
|
|
62
|
+
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
63
|
+
|
|
64
|
+
if (!isActive) {
|
|
65
|
+
await adapter.setStateAsync(summaryJsonPath, { val: '[]', ack: true });
|
|
66
|
+
await adapter.setStateAsync(summaryHtmlPath, { val: '', ack: true });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stateDefs = [
|
|
71
|
+
{ id: 'temp_min', def: null },
|
|
72
|
+
{ id: 'temp_max', def: null },
|
|
73
|
+
{ id: 'temp_min_time', def: '' },
|
|
74
|
+
{ id: 'temp_max_time', def: '' },
|
|
75
|
+
{ id: 'temp_avg', def: null },
|
|
76
|
+
{ id: 'data_points_count', def: 0 },
|
|
77
|
+
{ id: 'last_update', def: '' },
|
|
78
|
+
{ id: 'summary_json', def: '' },
|
|
79
|
+
{ id: 'summary_html', def: '' },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const def of stateDefs) {
|
|
83
|
+
const fullPath = `${basePath}.${def.id}`;
|
|
84
|
+
const obj = await adapter.getObjectAsync(fullPath);
|
|
85
|
+
if (!obj) {
|
|
86
|
+
await adapter.setObjectNotExistsAsync(fullPath, {
|
|
87
|
+
type: 'state',
|
|
88
|
+
common: {
|
|
89
|
+
name: def.id,
|
|
90
|
+
type: typeof def.def === 'number' ? 'number' : 'string',
|
|
91
|
+
role: def.id.includes('time')
|
|
92
|
+
? 'value.time'
|
|
93
|
+
: def.id.includes('temp')
|
|
94
|
+
? 'value.temperature'
|
|
95
|
+
: 'value',
|
|
96
|
+
read: true,
|
|
97
|
+
write: false,
|
|
98
|
+
persist: true,
|
|
99
|
+
},
|
|
100
|
+
native: {},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const state = await adapter.getStateAsync(fullPath);
|
|
105
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
106
|
+
await adapter.setStateAsync(fullPath, { val: def.def, ack: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const outputBase = 'analytics.statistics.temperature.week.outputs';
|
|
112
|
+
await adapter.setObjectNotExistsAsync(outputBase, {
|
|
113
|
+
type: 'channel',
|
|
114
|
+
common: { name: 'Gesamtausgaben (alle Sensoren – Woche)' },
|
|
115
|
+
native: {},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const outputStates = [
|
|
119
|
+
{ id: 'summary_all_json', def: '' },
|
|
120
|
+
{ id: 'summary_all_html', def: '' },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
for (const out of outputStates) {
|
|
124
|
+
const fullPath = `${outputBase}.${out.id}`;
|
|
125
|
+
const obj = await adapter.getObjectAsync(fullPath);
|
|
126
|
+
if (!obj) {
|
|
127
|
+
await adapter.setObjectNotExistsAsync(fullPath, {
|
|
128
|
+
type: 'state',
|
|
129
|
+
common: {
|
|
130
|
+
name: out.id,
|
|
131
|
+
type: 'string',
|
|
132
|
+
role: out.id.endsWith('json') ? 'json' : 'html',
|
|
133
|
+
read: true,
|
|
134
|
+
write: false,
|
|
135
|
+
persist: true,
|
|
136
|
+
},
|
|
137
|
+
native: {},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const state = await adapter.getStateAsync(fullPath);
|
|
142
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
143
|
+
await adapter.setStateAsync(fullPath, { val: out.id.endsWith('json') ? '{}' : '', ack: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Abonniert alle aktiven Temperatursensoren
|
|
150
|
+
*/
|
|
151
|
+
async _subscribeActiveSensors() {
|
|
152
|
+
const adapter = this.adapter;
|
|
153
|
+
for (const sensor of this.sensors) {
|
|
154
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
155
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
156
|
+
if (isActive) {
|
|
157
|
+
const stateId = `temperature.${sensor.id}.current`;
|
|
158
|
+
adapter.subscribeStates(stateId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
adapter.on('stateChange', async (id, state) => {
|
|
163
|
+
if (!state || state.ack === false) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const sensor of this.sensors) {
|
|
167
|
+
if (id.endsWith(`temperature.${sensor.id}.current`)) {
|
|
168
|
+
await this._processTemperatureChange(sensor.id, state.val);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Verarbeitung einer Temperaturänderung für einen Sensor.
|
|
177
|
+
* Aktualisiert Min-, Max- und Durchschnittswerte sowie die Zusammenfassungen.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} sensorId - Die ID des Sensors (z. B. "outside", "flow", "collector" usw.)
|
|
180
|
+
* @param {number} newValue - Der neue gemessene Temperaturwert in °C
|
|
181
|
+
*/
|
|
182
|
+
async _processTemperatureChange(sensorId, newValue) {
|
|
183
|
+
const adapter = this.adapter;
|
|
184
|
+
const basePath = `analytics.statistics.temperature.week.${sensorId}`;
|
|
185
|
+
const now = new Date().toLocaleString('de-DE', {
|
|
186
|
+
weekday: 'short',
|
|
187
|
+
hour: '2-digit',
|
|
188
|
+
minute: '2-digit',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (typeof newValue !== 'number') {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
newValue = Math.round(newValue * 10) / 10;
|
|
196
|
+
|
|
197
|
+
const tempMin = (await adapter.getStateAsync(`${basePath}.temp_min`))?.val;
|
|
198
|
+
const tempMax = (await adapter.getStateAsync(`${basePath}.temp_max`))?.val;
|
|
199
|
+
const tempAvg = (await adapter.getStateAsync(`${basePath}.temp_avg`))?.val;
|
|
200
|
+
const count = (await adapter.getStateAsync(`${basePath}.data_points_count`))?.val || 0;
|
|
201
|
+
|
|
202
|
+
let newMin = tempMin;
|
|
203
|
+
let newMax = tempMax;
|
|
204
|
+
let newAvg = tempAvg;
|
|
205
|
+
|
|
206
|
+
if (tempMin === null || newValue < tempMin) {
|
|
207
|
+
newMin = newValue;
|
|
208
|
+
await adapter.setStateAsync(`${basePath}.temp_min_time`, { val: now, ack: true });
|
|
209
|
+
}
|
|
210
|
+
if (tempMax === null || newValue > tempMax) {
|
|
211
|
+
newMax = newValue;
|
|
212
|
+
await adapter.setStateAsync(`${basePath}.temp_max_time`, { val: now, ack: true });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const newCount = count + 1;
|
|
216
|
+
newAvg = tempAvg === null ? newValue : (tempAvg * count + newValue) / newCount;
|
|
217
|
+
|
|
218
|
+
newMin = Math.round(newMin * 10) / 10;
|
|
219
|
+
newMax = Math.round(newMax * 10) / 10;
|
|
220
|
+
newAvg = Math.round(newAvg * 10) / 10;
|
|
221
|
+
|
|
222
|
+
await adapter.setStateAsync(`${basePath}.temp_min`, { val: newMin, ack: true });
|
|
223
|
+
await adapter.setStateAsync(`${basePath}.temp_max`, { val: newMax, ack: true });
|
|
224
|
+
await adapter.setStateAsync(`${basePath}.temp_avg`, { val: Math.round(newAvg * 100) / 100, ack: true });
|
|
225
|
+
await adapter.setStateAsync(`${basePath}.data_points_count`, { val: newCount, ack: true });
|
|
226
|
+
await adapter.setStateAsync(`${basePath}.last_update`, { val: now, ack: true });
|
|
227
|
+
|
|
228
|
+
const summary = {
|
|
229
|
+
temp_min: newMin,
|
|
230
|
+
temp_max: newMax,
|
|
231
|
+
temp_avg: newAvg,
|
|
232
|
+
updated: now,
|
|
233
|
+
};
|
|
234
|
+
await adapter.setStateAsync(`${basePath}.summary_json`, { val: JSON.stringify(summary), ack: true });
|
|
235
|
+
await adapter.setStateAsync(`${basePath}.summary_html`, {
|
|
236
|
+
val: `<div><b>Min:</b> ${newMin} °C / <b>Max:</b> ${newMax} °C / <b>Ø:</b> ${newAvg} °C</div>`,
|
|
237
|
+
ack: true,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await this._updateOverallSummary();
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Gesamtzusammenfassung aller Sensoren aktualisieren
|
|
245
|
+
*/
|
|
246
|
+
async _updateOverallSummary() {
|
|
247
|
+
const adapter = this.adapter;
|
|
248
|
+
const allData = [];
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
for (const sensor of this.sensors) {
|
|
252
|
+
const summaryState = await adapter.getStateAsync(
|
|
253
|
+
`analytics.statistics.temperature.week.${sensor.id}.summary_json`,
|
|
254
|
+
);
|
|
255
|
+
if (!summaryState || !summaryState.val) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let parsed;
|
|
260
|
+
try {
|
|
261
|
+
parsed = JSON.parse(summaryState.val);
|
|
262
|
+
} catch {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { temp_min: min, temp_max: max, temp_avg: avg } = parsed;
|
|
267
|
+
if (min == null && max == null && avg == null) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const rMin = typeof min === 'number' ? Math.round(min * 10) / 10 : min;
|
|
272
|
+
const rMax = typeof max === 'number' ? Math.round(max * 10) / 10 : max;
|
|
273
|
+
const rAvg = typeof avg === 'number' ? Math.round(avg * 10) / 10 : avg;
|
|
274
|
+
|
|
275
|
+
allData.push({ name: sensor.name, min: rMin, max: rMax, avg: rAvg });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (allData.length === 0) {
|
|
279
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.week.outputs.summary_all_json', {
|
|
280
|
+
val: '[]',
|
|
281
|
+
ack: true,
|
|
282
|
+
});
|
|
283
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.week.outputs.summary_all_html', {
|
|
284
|
+
val: '',
|
|
285
|
+
ack: true,
|
|
286
|
+
});
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const jsonOutput = JSON.stringify(allData);
|
|
291
|
+
|
|
292
|
+
let html = '<table style="width:100%;border-collapse:collapse;">';
|
|
293
|
+
html += '<tr><th style="text-align:left;">Sensor</th><th>Min</th><th>Max</th><th>Ø</th></tr>';
|
|
294
|
+
for (const entry of allData) {
|
|
295
|
+
html += `<tr><td>${entry.name}</td><td>${entry.min ?? '-'}</td><td>${entry.max ?? '-'}</td><td>${entry.avg ?? '-'}</td></tr>`;
|
|
296
|
+
}
|
|
297
|
+
html += '</table>';
|
|
298
|
+
|
|
299
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.week.outputs.summary_all_json', {
|
|
300
|
+
val: jsonOutput,
|
|
301
|
+
ack: true,
|
|
302
|
+
});
|
|
303
|
+
await adapter.setStateChangedAsync('analytics.statistics.temperature.week.outputs.summary_all_html', {
|
|
304
|
+
val: html,
|
|
305
|
+
ack: true,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
adapter.log.debug('statisticsHelperWeek: Gesamtzusammenfassung erfolgreich aktualisiert.');
|
|
309
|
+
} catch (err) {
|
|
310
|
+
adapter.log.warn(`statisticsHelperWeek: Fehler bei Gesamtzusammenfassung: ${err.message}`);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Wochen-Reset planen (Sonntag 00:05 Uhr)
|
|
316
|
+
*/
|
|
317
|
+
async _scheduleWeekReset() {
|
|
318
|
+
const adapter = this.adapter;
|
|
319
|
+
if (this.weekResetTimer) {
|
|
320
|
+
clearTimeout(this.weekResetTimer);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const now = new Date();
|
|
324
|
+
const nextReset = new Date(now);
|
|
325
|
+
// Sonntag 00:05
|
|
326
|
+
const daysUntilSunday = (7 - now.getDay()) % 7;
|
|
327
|
+
nextReset.setDate(now.getDate() + daysUntilSunday);
|
|
328
|
+
nextReset.setHours(0, 5, 0, 0);
|
|
329
|
+
|
|
330
|
+
const msUntilReset = nextReset.getTime() - now.getTime();
|
|
331
|
+
this.weekResetTimer = setTimeout(async () => {
|
|
332
|
+
await this._resetWeeklyTemperatureStats();
|
|
333
|
+
await this._scheduleWeekReset();
|
|
334
|
+
}, msUntilReset);
|
|
335
|
+
|
|
336
|
+
adapter.log.debug(`statisticsHelperWeek: Wochen-Reset geplant in ${Math.round(msUntilReset / 60000)} Minuten.`);
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Wochenstatistik zurücksetzen
|
|
341
|
+
*/
|
|
342
|
+
async _resetWeeklyTemperatureStats() {
|
|
343
|
+
const adapter = this.adapter;
|
|
344
|
+
adapter.log.info('statisticsHelperWeek: Wochenstatistik wird zurückgesetzt.');
|
|
345
|
+
|
|
346
|
+
const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
347
|
+
|
|
348
|
+
for (const sensor of this.sensors) {
|
|
349
|
+
const basePath = `analytics.statistics.temperature.week.${sensor.id}`;
|
|
350
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
351
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
352
|
+
|
|
353
|
+
const summaryJsonPath = `${basePath}.summary_json`;
|
|
354
|
+
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
355
|
+
|
|
356
|
+
if (!isActive) {
|
|
357
|
+
await adapter.setStateAsync(summaryJsonPath, {
|
|
358
|
+
val: JSON.stringify({ status: 'kein Sensor aktiv' }),
|
|
359
|
+
ack: true,
|
|
360
|
+
});
|
|
361
|
+
await adapter.setStateAsync(summaryHtmlPath, {
|
|
362
|
+
val: '<div style="color:gray;">kein Sensor aktiv</div>',
|
|
363
|
+
ack: true,
|
|
364
|
+
});
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const stateList = [
|
|
369
|
+
'temp_min',
|
|
370
|
+
'temp_max',
|
|
371
|
+
'temp_min_time',
|
|
372
|
+
'temp_max_time',
|
|
373
|
+
'temp_avg',
|
|
374
|
+
'data_points_count',
|
|
375
|
+
'last_update',
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
for (const state of stateList) {
|
|
379
|
+
const fullPath = `${basePath}.${state}`;
|
|
380
|
+
let defValue = null;
|
|
381
|
+
if (state.includes('time')) {
|
|
382
|
+
defValue = '';
|
|
383
|
+
}
|
|
384
|
+
if (state === 'data_points_count') {
|
|
385
|
+
defValue = 0;
|
|
386
|
+
}
|
|
387
|
+
if (state === 'last_update') {
|
|
388
|
+
defValue = resetDate;
|
|
389
|
+
}
|
|
390
|
+
await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
await adapter.setStateAsync(summaryJsonPath, {
|
|
394
|
+
val: JSON.stringify({ date_reset: resetDate, status: 'Wochenwerte zurückgesetzt' }),
|
|
395
|
+
ack: true,
|
|
396
|
+
});
|
|
397
|
+
await adapter.setStateAsync(summaryHtmlPath, {
|
|
398
|
+
val: `<div style="color:gray;">Wochenwerte zurückgesetzt (${resetDate})</div>`,
|
|
399
|
+
ack: true,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
await adapter.setStateAsync('analytics.statistics.temperature.week.outputs.summary_all_json', {
|
|
404
|
+
val: '{}',
|
|
405
|
+
ack: true,
|
|
406
|
+
});
|
|
407
|
+
await adapter.setStateAsync('analytics.statistics.temperature.week.outputs.summary_all_html', {
|
|
408
|
+
val: '',
|
|
409
|
+
ack: true,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
adapter.log.debug('statisticsHelperWeek: Wochenstatistik zurückgesetzt.');
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
cleanup() {
|
|
416
|
+
if (this.weekResetTimer) {
|
|
417
|
+
clearTimeout(this.weekResetTimer);
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
module.exports = statisticsHelperWeek;
|
|
@@ -68,12 +68,19 @@ const timeHelper = {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
//
|
|
72
|
-
await this.adapter.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// --- NEU: nur schalten, wenn sich der Zustand wirklich ändert ---
|
|
72
|
+
const currentState = (await this.adapter.getForeignStateAsync(pumpSwitchId))?.val;
|
|
73
|
+
if (currentState !== shouldRun) {
|
|
74
|
+
await this.adapter.setForeignStateAsync(pumpSwitchId, {
|
|
75
|
+
val: shouldRun,
|
|
76
|
+
ack: false,
|
|
77
|
+
});
|
|
78
|
+
this.adapter.log.debug(`[timeHelper] Pumpe ${shouldRun ? 'EIN' : 'AUS'} (${hhmm})`);
|
|
79
|
+
} else {
|
|
80
|
+
this.adapter.log.debug(
|
|
81
|
+
`[timeHelper] Keine Änderung (${hhmm}) – Zustand bleibt ${shouldRun ? 'EIN' : 'AUS'}.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
77
84
|
} catch (err) {
|
|
78
85
|
this.adapter.log.warn(`[timeHelper] Fehler im Check: ${err.message}`);
|
|
79
86
|
}
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* statisticsStates.js
|
|
5
5
|
* -------------------
|
|
6
|
-
* Erstellt alle States für die
|
|
7
|
-
* Struktur:
|
|
6
|
+
* Erstellt alle States für die Temperaturstatistiken.
|
|
7
|
+
* Struktur:
|
|
8
|
+
* analytics.statistics.temperature.today.*
|
|
9
|
+
* analytics.statistics.temperature.week.*
|
|
10
|
+
* analytics.statistics.temperature.month.*
|
|
8
11
|
*
|
|
9
12
|
* - Sechs Sensorbereiche (outside, ground, surface, flow, return, collector)
|
|
10
13
|
* - Je Sensor: Min/Max/Avg + Zeitstempel + JSON/HTML-Ausgabe
|
|
@@ -17,7 +20,7 @@
|
|
|
17
20
|
* @param {ioBroker.Adapter} adapter - Instanz des ioBroker-Adapters
|
|
18
21
|
*/
|
|
19
22
|
async function createStatisticsStates(adapter) {
|
|
20
|
-
adapter.log.debug('statisticsStates: Initialisierung der
|
|
23
|
+
adapter.log.debug('statisticsStates: Initialisierung der Temperaturstatistiken gestartet.');
|
|
21
24
|
|
|
22
25
|
// Oberstruktur
|
|
23
26
|
await adapter.setObjectNotExistsAsync('analytics', {
|
|
@@ -38,9 +41,36 @@ async function createStatisticsStates(adapter) {
|
|
|
38
41
|
native: {},
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
// -------------------------------------------------------------
|
|
45
|
+
// 🔹 TAGESSTATISTIK
|
|
46
|
+
// -------------------------------------------------------------
|
|
47
|
+
await _createTemperatureStatsGroup(adapter, 'today', 'Tagesstatistik (Temperaturen)');
|
|
48
|
+
|
|
49
|
+
// -------------------------------------------------------------
|
|
50
|
+
// 🔹 WOCHENSTATISTIK
|
|
51
|
+
// -------------------------------------------------------------
|
|
52
|
+
await _createTemperatureStatsGroup(adapter, 'week', 'Wochenstatistik (Temperaturen)');
|
|
53
|
+
|
|
54
|
+
// -------------------------------------------------------------
|
|
55
|
+
// 🔹 MONATSSTATISTIK (NEU)
|
|
56
|
+
// -------------------------------------------------------------
|
|
57
|
+
await _createTemperatureStatsGroup(adapter, 'month', 'Monatsstatistik (Temperaturen)');
|
|
58
|
+
|
|
59
|
+
adapter.log.debug('statisticsStates: Tages-, Wochen- und Monatsstatistik (Temperatur) erfolgreich angelegt.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Erstellt eine Temperaturstatistik-Gruppe (z. B. "today", "week" oder "month").
|
|
64
|
+
*
|
|
65
|
+
* @param {ioBroker.Adapter} adapter - Aktive ioBroker-Adapterinstanz
|
|
66
|
+
* @param {string} periodId - z. B. "today", "week" oder "month"
|
|
67
|
+
* @param {string} displayName - Anzeigename im Objektbaum
|
|
68
|
+
*/
|
|
69
|
+
async function _createTemperatureStatsGroup(adapter, periodId, displayName) {
|
|
70
|
+
const basePathRoot = `analytics.statistics.temperature.${periodId}`;
|
|
71
|
+
await adapter.setObjectNotExistsAsync(basePathRoot, {
|
|
42
72
|
type: 'channel',
|
|
43
|
-
common: { name:
|
|
73
|
+
common: { name: displayName },
|
|
44
74
|
native: {},
|
|
45
75
|
});
|
|
46
76
|
|
|
@@ -55,11 +85,10 @@ async function createStatisticsStates(adapter) {
|
|
|
55
85
|
];
|
|
56
86
|
|
|
57
87
|
for (const sensor of sensors) {
|
|
58
|
-
const basePath =
|
|
59
|
-
|
|
88
|
+
const basePath = `${basePathRoot}.${sensor.id}`;
|
|
60
89
|
await adapter.setObjectNotExistsAsync(basePath, {
|
|
61
90
|
type: 'channel',
|
|
62
|
-
common: { name: `${sensor.name} (
|
|
91
|
+
common: { name: `${sensor.name} (${displayName})` },
|
|
63
92
|
native: {},
|
|
64
93
|
});
|
|
65
94
|
|
|
@@ -71,8 +100,18 @@ async function createStatisticsStates(adapter) {
|
|
|
71
100
|
{ id: 'temp_avg', name: 'Durchschnittstemperatur', type: 'number', role: 'value.temperature', unit: '°C' },
|
|
72
101
|
{ id: 'data_points_count', name: 'Anzahl Messwerte', type: 'number', role: 'value' },
|
|
73
102
|
{ id: 'last_update', name: 'Letzte Aktualisierung', type: 'string', role: 'value.time' },
|
|
74
|
-
{
|
|
75
|
-
|
|
103
|
+
{
|
|
104
|
+
id: 'summary_json',
|
|
105
|
+
name: `${displayName} (JSON)`,
|
|
106
|
+
type: 'string',
|
|
107
|
+
role: 'json',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'summary_html',
|
|
111
|
+
name: `${displayName} (HTML)`,
|
|
112
|
+
type: 'string',
|
|
113
|
+
role: 'html',
|
|
114
|
+
},
|
|
76
115
|
];
|
|
77
116
|
|
|
78
117
|
for (const def of stateDefs) {
|
|
@@ -94,7 +133,7 @@ async function createStatisticsStates(adapter) {
|
|
|
94
133
|
}
|
|
95
134
|
|
|
96
135
|
// Gesamt-Ausgabe (Outputs)
|
|
97
|
-
const outputBase =
|
|
136
|
+
const outputBase = `${basePathRoot}.outputs`;
|
|
98
137
|
await adapter.setObjectNotExistsAsync(outputBase, {
|
|
99
138
|
type: 'channel',
|
|
100
139
|
common: { name: 'Gesamtausgaben (alle Sensoren)' },
|
|
@@ -104,12 +143,12 @@ async function createStatisticsStates(adapter) {
|
|
|
104
143
|
const outputs = [
|
|
105
144
|
{
|
|
106
145
|
id: 'summary_all_json',
|
|
107
|
-
name:
|
|
146
|
+
name: `Gesamtzusammenfassung aller Sensoren (${displayName}, JSON)`,
|
|
108
147
|
role: 'json',
|
|
109
148
|
},
|
|
110
149
|
{
|
|
111
150
|
id: 'summary_all_html',
|
|
112
|
-
name:
|
|
151
|
+
name: `Gesamtzusammenfassung aller Sensoren (${displayName}, HTML)`,
|
|
113
152
|
role: 'html',
|
|
114
153
|
},
|
|
115
154
|
];
|
|
@@ -129,8 +168,6 @@ async function createStatisticsStates(adapter) {
|
|
|
129
168
|
native: {},
|
|
130
169
|
});
|
|
131
170
|
}
|
|
132
|
-
|
|
133
|
-
adapter.log.debug('statisticsStates: Tagesstatistik (Temperatur) erfolgreich angelegt.');
|
|
134
171
|
}
|
|
135
172
|
|
|
136
173
|
module.exports = {
|
package/main.js
CHANGED
|
@@ -9,6 +9,8 @@ const temperatureHelper = require('./lib/helpers/temperatureHelper');
|
|
|
9
9
|
const timeHelper = require('./lib/helpers/timeHelper');
|
|
10
10
|
const runtimeHelper = require('./lib/helpers/runtimeHelper');
|
|
11
11
|
const statisticsHelper = require('./lib/helpers/statisticsHelper');
|
|
12
|
+
const statisticsHelperWeek = require('./lib/helpers/statisticsHelperWeek');
|
|
13
|
+
const statisticsHelperMonth = require('./lib/helpers/statisticsHelperMonth');
|
|
12
14
|
const pumpHelper = require('./lib/helpers/pumpHelper');
|
|
13
15
|
const pumpHelper2 = require('./lib/helpers/pumpHelper2');
|
|
14
16
|
const pumpHelper3 = require('./lib/helpers/pumpHelper3');
|
|
@@ -103,6 +105,8 @@ class Poolcontrol extends utils.Adapter {
|
|
|
103
105
|
timeHelper.init(this);
|
|
104
106
|
runtimeHelper.init(this);
|
|
105
107
|
statisticsHelper.init(this);
|
|
108
|
+
statisticsHelperWeek.init(this);
|
|
109
|
+
statisticsHelperMonth.init(this);
|
|
106
110
|
pumpHelper.init(this);
|
|
107
111
|
pumpHelper2.init(this);
|
|
108
112
|
pumpHelper3.init(this);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.poolcontrol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"@iobroker/testing": "^5.1.1",
|
|
31
31
|
"eslint": "^9.36.0",
|
|
32
32
|
"prettier": "^3.6.2",
|
|
33
|
-
"proxyquire": "^2.1.3"
|
|
33
|
+
"proxyquire": "^2.1.3",
|
|
34
|
+
"release-it": "^19.0.5"
|
|
34
35
|
},
|
|
35
36
|
"main": "main.js",
|
|
36
37
|
"files": [
|
|
@@ -48,7 +49,8 @@
|
|
|
48
49
|
"test:integration": "mocha test/integration.cjs --exit",
|
|
49
50
|
"test": "npm run test:js && npm run test:package",
|
|
50
51
|
"lint": "eslint .",
|
|
51
|
-
"translate": "translate-adapter"
|
|
52
|
+
"translate": "translate-adapter",
|
|
53
|
+
"release": "release-it"
|
|
52
54
|
},
|
|
53
55
|
"bugs": {
|
|
54
56
|
"url": "https://github.com/DasBo1975/ioBroker.poolcontrol/issues"
|