iobroker.poolcontrol 0.3.0 → 0.4.0-alpha
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 +32 -18
- package/admin/jsonConfig.json +4 -0
- package/io-package.json +28 -15
- package/lib/helpers/consumptionHelper.js +27 -32
- package/lib/helpers/frostHelper.js +11 -11
- package/lib/helpers/pumpHelper.js +22 -12
- package/lib/helpers/pumpHelper2.js +18 -16
- package/lib/helpers/pumpHelper3.js +52 -17
- package/lib/helpers/runtimeHelper.js +141 -43
- package/lib/helpers/statisticsHelper.js +448 -0
- package/lib/stateDefinitions/controlStates.js +0 -37
- package/lib/stateDefinitions/pumpStates3.js +63 -11
- package/lib/stateDefinitions/runtimeStates.js +20 -5
- package/lib/stateDefinitions/statisticsStates.js +138 -0
- package/main.js +10 -1
- package/package.json +5 -3
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* statisticsHelper.js
|
|
5
|
+
* -------------------
|
|
6
|
+
* Vollständige Steuerung der Tagesstatistik (Temperatur)
|
|
7
|
+
* im Bereich analytics.statistics.temperature.today.*
|
|
8
|
+
*
|
|
9
|
+
* - Erstellt alle States (Überinstallationsschutz + Persistenz)
|
|
10
|
+
* - Erkennt aktive Sensoren anhand temperature.<sensor>.active
|
|
11
|
+
* - Reagiert eventbasiert auf Änderungen der Temperaturwerte
|
|
12
|
+
* - Berechnet laufend Min/Max/Durchschnitt
|
|
13
|
+
* - Aktualisiert JSON- und HTML-Ausgaben (pro Sensor & gesamt)
|
|
14
|
+
* - Führt automatisch täglichen Reset um Mitternacht durch
|
|
15
|
+
*
|
|
16
|
+
* @param {ioBroker.Adapter} adapter - Die aktuelle Adapterinstanz (this),
|
|
17
|
+
* über die alle ioBroker-Funktionen wie setStateAsync, getStateAsync usw. aufgerufen werden.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const statisticsHelper = {
|
|
21
|
+
adapter: null,
|
|
22
|
+
midnightTimer: null,
|
|
23
|
+
sensors: [
|
|
24
|
+
{ id: 'outside', name: 'Außentemperatur' },
|
|
25
|
+
{ id: 'ground', name: 'Bodentemperatur' },
|
|
26
|
+
{ id: 'surface', name: 'Pooloberfläche' },
|
|
27
|
+
{ id: 'flow', name: 'Vorlauf' },
|
|
28
|
+
{ id: 'return', name: 'Rücklauf' },
|
|
29
|
+
{ id: 'collector', name: 'Kollektor (Solar)' },
|
|
30
|
+
],
|
|
31
|
+
|
|
32
|
+
async init(adapter) {
|
|
33
|
+
this.adapter = adapter;
|
|
34
|
+
adapter.log.debug('statisticsHelper: Initialisierung gestartet.');
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await this._createTemperatureStatistics();
|
|
38
|
+
await this._subscribeActiveSensors();
|
|
39
|
+
await this._scheduleMidnightReset();
|
|
40
|
+
adapter.log.debug('statisticsHelper: Initialisierung abgeschlossen (Sensorüberwachung aktiv).');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
adapter.log.warn(`statisticsHelper: Fehler bei Initialisierung: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Erstellt States, falls sie fehlen (Überinstallationsschutz)
|
|
48
|
+
*/
|
|
49
|
+
async _createTemperatureStatistics() {
|
|
50
|
+
const adapter = this.adapter;
|
|
51
|
+
|
|
52
|
+
for (const sensor of this.sensors) {
|
|
53
|
+
const basePath = `analytics.statistics.temperature.today.${sensor.id}`;
|
|
54
|
+
await adapter.setObjectNotExistsAsync(basePath, {
|
|
55
|
+
type: 'channel',
|
|
56
|
+
common: { name: `${sensor.name} (Tagesstatistik)` },
|
|
57
|
+
native: {},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
61
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
62
|
+
|
|
63
|
+
const summaryJsonPath = `${basePath}.summary_json`;
|
|
64
|
+
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
65
|
+
|
|
66
|
+
if (!isActive) {
|
|
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 });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const stateDefs = [
|
|
74
|
+
{ id: 'temp_min', def: null },
|
|
75
|
+
{ id: 'temp_max', def: null },
|
|
76
|
+
{ id: 'temp_min_time', def: '' },
|
|
77
|
+
{ id: 'temp_max_time', def: '' },
|
|
78
|
+
{ id: 'temp_avg', def: null },
|
|
79
|
+
{ id: 'data_points_count', def: 0 },
|
|
80
|
+
{ id: 'last_update', def: '' },
|
|
81
|
+
{ id: 'summary_json', def: '' },
|
|
82
|
+
{ id: 'summary_html', def: '' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const def of stateDefs) {
|
|
86
|
+
const fullPath = `${basePath}.${def.id}`;
|
|
87
|
+
const obj = await adapter.getObjectAsync(fullPath);
|
|
88
|
+
if (!obj) {
|
|
89
|
+
await adapter.setObjectNotExistsAsync(fullPath, {
|
|
90
|
+
type: 'state',
|
|
91
|
+
common: {
|
|
92
|
+
name: def.id,
|
|
93
|
+
type: typeof def.def === 'number' ? 'number' : 'string',
|
|
94
|
+
role: def.id.includes('time')
|
|
95
|
+
? 'value.time'
|
|
96
|
+
: def.id.includes('temp')
|
|
97
|
+
? 'value.temperature'
|
|
98
|
+
: 'value',
|
|
99
|
+
read: true,
|
|
100
|
+
write: false,
|
|
101
|
+
persist: true,
|
|
102
|
+
},
|
|
103
|
+
native: {},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const state = await adapter.getStateAsync(fullPath);
|
|
108
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
109
|
+
await adapter.setStateAsync(fullPath, { val: def.def, ack: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const outputBase = 'analytics.statistics.temperature.today.outputs';
|
|
115
|
+
await adapter.setObjectNotExistsAsync(outputBase, {
|
|
116
|
+
type: 'channel',
|
|
117
|
+
common: { name: 'Gesamtausgaben (alle Sensoren)' },
|
|
118
|
+
native: {},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const outputStates = [
|
|
122
|
+
{ id: 'summary_all_json', def: '' },
|
|
123
|
+
{ id: 'summary_all_html', def: '' },
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const out of outputStates) {
|
|
127
|
+
const fullPath = `${outputBase}.${out.id}`;
|
|
128
|
+
const obj = await adapter.getObjectAsync(fullPath);
|
|
129
|
+
if (!obj) {
|
|
130
|
+
await adapter.setObjectNotExistsAsync(fullPath, {
|
|
131
|
+
type: 'state',
|
|
132
|
+
common: {
|
|
133
|
+
name: out.id,
|
|
134
|
+
type: 'string',
|
|
135
|
+
role: out.id.endsWith('json') ? 'json' : 'html',
|
|
136
|
+
read: true,
|
|
137
|
+
write: false,
|
|
138
|
+
persist: true,
|
|
139
|
+
},
|
|
140
|
+
native: {},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const state = await adapter.getStateAsync(fullPath);
|
|
145
|
+
if (!state || state.val === null || state.val === undefined) {
|
|
146
|
+
await adapter.setStateAsync(fullPath, { val: out.id.endsWith('json') ? '{}' : '', ack: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Abonniert alle aktiven Temperatursensoren
|
|
153
|
+
*/
|
|
154
|
+
async _subscribeActiveSensors() {
|
|
155
|
+
const adapter = this.adapter;
|
|
156
|
+
for (const sensor of this.sensors) {
|
|
157
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
158
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
159
|
+
if (isActive) {
|
|
160
|
+
const stateId = `temperature.${sensor.id}.current`;
|
|
161
|
+
adapter.subscribeStates(stateId);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
adapter.on('stateChange', async (id, state) => {
|
|
166
|
+
if (!state || state.ack === false) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const sensor of this.sensors) {
|
|
170
|
+
if (id.endsWith(`temperature.${sensor.id}.current`)) {
|
|
171
|
+
await this._processTemperatureChange(sensor.id, state.val);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Verarbeitung einer Temperaturänderung für einen Sensor.
|
|
180
|
+
* Aktualisiert Min-, Max- und Durchschnittswerte sowie die Zusammenfassungen.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} sensorId - Die ID des betroffenen Sensors (z. B. "outside" oder "flow").
|
|
183
|
+
* @param {number} newValue - Der neue gemessene Temperaturwert in °C.
|
|
184
|
+
*/
|
|
185
|
+
async _processTemperatureChange(sensorId, newValue) {
|
|
186
|
+
const adapter = this.adapter;
|
|
187
|
+
const basePath = `analytics.statistics.temperature.today.${sensorId}`;
|
|
188
|
+
const now = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
|
189
|
+
|
|
190
|
+
if (typeof newValue !== 'number') {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// NEU: Rundung auf 1 Nachkommastelle
|
|
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
|
+
// Durchschnitt (gleitend)
|
|
216
|
+
const newCount = count + 1;
|
|
217
|
+
newAvg = tempAvg === null ? newValue : (tempAvg * count + newValue) / newCount;
|
|
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
|
+
|
|
224
|
+
await adapter.setStateAsync(`${basePath}.temp_min`, { val: newMin, ack: true });
|
|
225
|
+
await adapter.setStateAsync(`${basePath}.temp_max`, { val: newMax, ack: true });
|
|
226
|
+
await adapter.setStateAsync(`${basePath}.temp_avg`, { val: Math.round(newAvg * 100) / 100, ack: true });
|
|
227
|
+
await adapter.setStateAsync(`${basePath}.data_points_count`, { val: newCount, ack: true });
|
|
228
|
+
await adapter.setStateAsync(`${basePath}.last_update`, { val: now, ack: true });
|
|
229
|
+
|
|
230
|
+
// Summary aktualisieren
|
|
231
|
+
// NEU: Zusammenfassung mit gerundeten Werten (1 Nachkommastelle)
|
|
232
|
+
const summary = {
|
|
233
|
+
temp_min: newMin,
|
|
234
|
+
temp_max: newMax,
|
|
235
|
+
temp_avg: newAvg,
|
|
236
|
+
updated: now,
|
|
237
|
+
};
|
|
238
|
+
await adapter.setStateAsync(`${basePath}.summary_json`, { val: JSON.stringify(summary), ack: true });
|
|
239
|
+
await adapter.setStateAsync(`${basePath}.summary_html`, {
|
|
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>`,
|
|
242
|
+
ack: true,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await this._updateOverallSummary();
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
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.
|
|
252
|
+
*/
|
|
253
|
+
async _updateOverallSummary() {
|
|
254
|
+
const adapter = this.adapter;
|
|
255
|
+
const allData = [];
|
|
256
|
+
|
|
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
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
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
|
+
}
|
|
313
|
+
|
|
314
|
+
// JSON-Ausgabe erstellen
|
|
315
|
+
const jsonOutput = JSON.stringify(allData);
|
|
316
|
+
|
|
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>';
|
|
324
|
+
|
|
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
|
+
});
|
|
334
|
+
|
|
335
|
+
adapter.log.debug('statisticsHelper: Gesamtzusammenfassung (Summary All) erfolgreich aktualisiert.');
|
|
336
|
+
} catch (err) {
|
|
337
|
+
adapter.log.warn(`statisticsHelper: Fehler bei Gesamtzusammenfassung: ${err.message}`);
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Mitternacht-Reset planen
|
|
343
|
+
*/
|
|
344
|
+
async _scheduleMidnightReset() {
|
|
345
|
+
const adapter = this.adapter;
|
|
346
|
+
if (this.midnightTimer) {
|
|
347
|
+
clearTimeout(this.midnightTimer);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const now = new Date();
|
|
351
|
+
const nextMidnight = new Date(now);
|
|
352
|
+
nextMidnight.setHours(24, 0, 5, 0);
|
|
353
|
+
const msUntilMidnight = nextMidnight.getTime() - now.getTime();
|
|
354
|
+
|
|
355
|
+
this.midnightTimer = setTimeout(async () => {
|
|
356
|
+
await this._resetDailyTemperatureStats();
|
|
357
|
+
await this._scheduleMidnightReset();
|
|
358
|
+
}, msUntilMidnight);
|
|
359
|
+
|
|
360
|
+
adapter.log.debug(
|
|
361
|
+
`statisticsHelper: Mitternacht-Reset geplant in ${Math.round(msUntilMidnight / 60000)} Minuten.`,
|
|
362
|
+
);
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Tagesstatistik zurücksetzen
|
|
367
|
+
*/
|
|
368
|
+
async _resetDailyTemperatureStats() {
|
|
369
|
+
const adapter = this.adapter;
|
|
370
|
+
adapter.log.info('statisticsHelper: Tagesstatistik wird zurückgesetzt.');
|
|
371
|
+
|
|
372
|
+
const resetDate = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
373
|
+
|
|
374
|
+
for (const sensor of this.sensors) {
|
|
375
|
+
const basePath = `analytics.statistics.temperature.today.${sensor.id}`;
|
|
376
|
+
const activeState = `temperature.${sensor.id}.active`;
|
|
377
|
+
const isActive = (await adapter.getStateAsync(activeState))?.val === true;
|
|
378
|
+
|
|
379
|
+
const summaryJsonPath = `${basePath}.summary_json`;
|
|
380
|
+
const summaryHtmlPath = `${basePath}.summary_html`;
|
|
381
|
+
|
|
382
|
+
if (!isActive) {
|
|
383
|
+
await adapter.setStateAsync(summaryJsonPath, {
|
|
384
|
+
val: JSON.stringify({ status: 'kein Sensor aktiv' }),
|
|
385
|
+
ack: true,
|
|
386
|
+
});
|
|
387
|
+
await adapter.setStateAsync(summaryHtmlPath, {
|
|
388
|
+
val: '<div style="color:gray;">kein Sensor aktiv</div>',
|
|
389
|
+
ack: true,
|
|
390
|
+
});
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const stateList = [
|
|
395
|
+
'temp_min',
|
|
396
|
+
'temp_max',
|
|
397
|
+
'temp_min_time',
|
|
398
|
+
'temp_max_time',
|
|
399
|
+
'temp_avg',
|
|
400
|
+
'data_points_count',
|
|
401
|
+
'last_update',
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
for (const state of stateList) {
|
|
405
|
+
const fullPath = `${basePath}.${state}`;
|
|
406
|
+
let defValue = null;
|
|
407
|
+
if (state.includes('time')) {
|
|
408
|
+
defValue = '';
|
|
409
|
+
}
|
|
410
|
+
if (state === 'data_points_count') {
|
|
411
|
+
defValue = 0;
|
|
412
|
+
}
|
|
413
|
+
if (state === 'last_update') {
|
|
414
|
+
defValue = resetDate;
|
|
415
|
+
}
|
|
416
|
+
await adapter.setStateAsync(fullPath, { val: defValue, ack: true });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
await adapter.setStateAsync(summaryJsonPath, {
|
|
420
|
+
val: JSON.stringify({ date_reset: resetDate, status: 'Tageswerte zurückgesetzt' }),
|
|
421
|
+
ack: true,
|
|
422
|
+
});
|
|
423
|
+
await adapter.setStateAsync(summaryHtmlPath, {
|
|
424
|
+
val: `<div style="color:gray;">Tageswerte zurückgesetzt (${resetDate})</div>`,
|
|
425
|
+
ack: true,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
await adapter.setStateAsync('analytics.statistics.temperature.today.outputs.summary_all_json', {
|
|
430
|
+
val: '{}',
|
|
431
|
+
ack: true,
|
|
432
|
+
});
|
|
433
|
+
await adapter.setStateAsync('analytics.statistics.temperature.today.outputs.summary_all_html', {
|
|
434
|
+
val: '',
|
|
435
|
+
ack: true,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
adapter.log.debug('statisticsHelper: Tagesstatistik zurückgesetzt.');
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
cleanup() {
|
|
442
|
+
if (this.midnightTimer) {
|
|
443
|
+
clearTimeout(this.midnightTimer);
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
module.exports = statisticsHelper;
|
|
@@ -26,43 +26,6 @@ async function createControlStates(adapter) {
|
|
|
26
26
|
native: {},
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
// Channel: control.season
|
|
30
|
-
await adapter.setObjectNotExistsAsync('control.season', {
|
|
31
|
-
type: 'channel',
|
|
32
|
-
common: {
|
|
33
|
-
name: 'Saisonsteuerung',
|
|
34
|
-
desc: 'Steuerung der aktiven Poolsaison und saisonabhängiger Funktionen',
|
|
35
|
-
},
|
|
36
|
-
native: {},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// State: control.season.active (mit Persist-Schutz)
|
|
40
|
-
await adapter.setObjectNotExistsAsync('control.season.active', {
|
|
41
|
-
type: 'state',
|
|
42
|
-
common: {
|
|
43
|
-
name: 'Poolsaison aktiv',
|
|
44
|
-
desc: 'Zeigt an, ob die Poolsaison aktiv ist (steuerbar über VIS oder Blockly)',
|
|
45
|
-
type: 'boolean',
|
|
46
|
-
role: 'switch',
|
|
47
|
-
read: true,
|
|
48
|
-
write: true,
|
|
49
|
-
def: false,
|
|
50
|
-
persist: true, // dauerhaft speichern
|
|
51
|
-
},
|
|
52
|
-
native: {},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const existingSeasonActive = await adapter.getStateAsync('control.season.active');
|
|
56
|
-
if (
|
|
57
|
-
existingSeasonActive === null ||
|
|
58
|
-
existingSeasonActive.val === null ||
|
|
59
|
-
existingSeasonActive.val === undefined
|
|
60
|
-
) {
|
|
61
|
-
const cfgValue = !!adapter.config.season_active;
|
|
62
|
-
await adapter.setStateAsync('control.season.active', { val: cfgValue, ack: true });
|
|
63
|
-
adapter.log.debug(`[controlStates] State control.season.active initialisiert mit Wert: ${cfgValue}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
29
|
// ---------------------------------------------------------------------
|
|
67
30
|
// Channel: control.pump
|
|
68
31
|
await adapter.setObjectNotExistsAsync('control.pump', {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Wird durch pumpHelper3.js verwaltet.
|
|
13
13
|
* Alle Werte sind persistent (persist: true).
|
|
14
14
|
* ----------------------------------------------------------
|
|
15
|
-
* Version: 1.0.
|
|
15
|
+
* Version: 1.0.1
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -49,7 +49,10 @@ async function createPumpStates3(adapter) {
|
|
|
49
49
|
},
|
|
50
50
|
native: {},
|
|
51
51
|
});
|
|
52
|
-
await adapter.
|
|
52
|
+
const existingPower = await adapter.getStateAsync('pump.learning.learned_avg_power_w');
|
|
53
|
+
if (existingPower === null || existingPower.val === null || existingPower.val === undefined) {
|
|
54
|
+
await adapter.setStateAsync('pump.learning.learned_avg_power_w', { val: 0, ack: true });
|
|
55
|
+
}
|
|
53
56
|
|
|
54
57
|
// ------------------------------------------------------
|
|
55
58
|
// Durchschnittlicher Durchfluss (L/h)
|
|
@@ -68,7 +71,10 @@ async function createPumpStates3(adapter) {
|
|
|
68
71
|
},
|
|
69
72
|
native: {},
|
|
70
73
|
});
|
|
71
|
-
await adapter.
|
|
74
|
+
const existingFlow = await adapter.getStateAsync('pump.learning.learned_avg_flow_lh');
|
|
75
|
+
if (existingFlow === null || existingFlow.val === null || existingFlow.val === undefined) {
|
|
76
|
+
await adapter.setStateAsync('pump.learning.learned_avg_flow_lh', { val: 0, ack: true });
|
|
77
|
+
}
|
|
72
78
|
|
|
73
79
|
// ------------------------------------------------------
|
|
74
80
|
// Normalbereich Leistung – Untere / Obere Grenze
|
|
@@ -87,7 +93,10 @@ async function createPumpStates3(adapter) {
|
|
|
87
93
|
},
|
|
88
94
|
native: {},
|
|
89
95
|
});
|
|
90
|
-
await adapter.
|
|
96
|
+
const existingPowerLow = await adapter.getStateAsync('pump.learning.normal_range_power_low');
|
|
97
|
+
if (existingPowerLow === null || existingPowerLow.val === null || existingPowerLow.val === undefined) {
|
|
98
|
+
await adapter.setStateAsync('pump.learning.normal_range_power_low', { val: 0, ack: true });
|
|
99
|
+
}
|
|
91
100
|
|
|
92
101
|
await adapter.setObjectNotExistsAsync('pump.learning.normal_range_power_high', {
|
|
93
102
|
type: 'state',
|
|
@@ -103,7 +112,10 @@ async function createPumpStates3(adapter) {
|
|
|
103
112
|
},
|
|
104
113
|
native: {},
|
|
105
114
|
});
|
|
106
|
-
await adapter.
|
|
115
|
+
const existingPowerHigh = await adapter.getStateAsync('pump.learning.normal_range_power_high');
|
|
116
|
+
if (existingPowerHigh === null || existingPowerHigh.val === null || existingPowerHigh.val === undefined) {
|
|
117
|
+
await adapter.setStateAsync('pump.learning.normal_range_power_high', { val: 0, ack: true });
|
|
118
|
+
}
|
|
107
119
|
|
|
108
120
|
// ------------------------------------------------------
|
|
109
121
|
// Normalbereich Durchfluss – Untere / Obere Grenze
|
|
@@ -122,7 +134,10 @@ async function createPumpStates3(adapter) {
|
|
|
122
134
|
},
|
|
123
135
|
native: {},
|
|
124
136
|
});
|
|
125
|
-
await adapter.
|
|
137
|
+
const existingFlowLow = await adapter.getStateAsync('pump.learning.normal_range_flow_low');
|
|
138
|
+
if (existingFlowLow === null || existingFlowLow.val === null || existingFlowLow.val === undefined) {
|
|
139
|
+
await adapter.setStateAsync('pump.learning.normal_range_flow_low', { val: 0, ack: true });
|
|
140
|
+
}
|
|
126
141
|
|
|
127
142
|
await adapter.setObjectNotExistsAsync('pump.learning.normal_range_flow_high', {
|
|
128
143
|
type: 'state',
|
|
@@ -138,7 +153,10 @@ async function createPumpStates3(adapter) {
|
|
|
138
153
|
},
|
|
139
154
|
native: {},
|
|
140
155
|
});
|
|
141
|
-
await adapter.
|
|
156
|
+
const existingFlowHigh = await adapter.getStateAsync('pump.learning.normal_range_flow_high');
|
|
157
|
+
if (existingFlowHigh === null || existingFlowHigh.val === null || existingFlowHigh.val === undefined) {
|
|
158
|
+
await adapter.setStateAsync('pump.learning.normal_range_flow_high', { val: 0, ack: true });
|
|
159
|
+
}
|
|
142
160
|
|
|
143
161
|
// ------------------------------------------------------
|
|
144
162
|
// Abweichungen (Prozent)
|
|
@@ -157,7 +175,10 @@ async function createPumpStates3(adapter) {
|
|
|
157
175
|
},
|
|
158
176
|
native: {},
|
|
159
177
|
});
|
|
160
|
-
await adapter.
|
|
178
|
+
const existingDevPower = await adapter.getStateAsync('pump.learning.deviation_power_percent');
|
|
179
|
+
if (existingDevPower === null || existingDevPower.val === null || existingDevPower.val === undefined) {
|
|
180
|
+
await adapter.setStateAsync('pump.learning.deviation_power_percent', { val: 0, ack: true });
|
|
181
|
+
}
|
|
161
182
|
|
|
162
183
|
await adapter.setObjectNotExistsAsync('pump.learning.deviation_flow_percent', {
|
|
163
184
|
type: 'state',
|
|
@@ -173,7 +194,32 @@ async function createPumpStates3(adapter) {
|
|
|
173
194
|
},
|
|
174
195
|
native: {},
|
|
175
196
|
});
|
|
176
|
-
await adapter.
|
|
197
|
+
const existingDevFlow = await adapter.getStateAsync('pump.learning.deviation_flow_percent');
|
|
198
|
+
if (existingDevFlow === null || existingDevFlow.val === null || existingDevFlow.val === undefined) {
|
|
199
|
+
await adapter.setStateAsync('pump.learning.deviation_flow_percent', { val: 0, ack: true });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ------------------------------------------------------
|
|
203
|
+
// Toleranzbereich für Normalbereichserkennung (%)
|
|
204
|
+
// ------------------------------------------------------
|
|
205
|
+
await adapter.setObjectNotExistsAsync('pump.learning.tolerance_percent', {
|
|
206
|
+
type: 'state',
|
|
207
|
+
common: {
|
|
208
|
+
name: 'Toleranzbereich für Normalbereichserkennung (%)',
|
|
209
|
+
desc: 'Abweichung, die noch als normal gilt, bevor eine Warnung ausgelöst wird',
|
|
210
|
+
type: 'number',
|
|
211
|
+
role: 'value.percent',
|
|
212
|
+
unit: '%',
|
|
213
|
+
read: true,
|
|
214
|
+
write: true,
|
|
215
|
+
persist: true,
|
|
216
|
+
},
|
|
217
|
+
native: {},
|
|
218
|
+
});
|
|
219
|
+
const existingTolerance = await adapter.getStateAsync('pump.learning.tolerance_percent');
|
|
220
|
+
if (existingTolerance === null || existingTolerance.val === null || existingTolerance.val === undefined) {
|
|
221
|
+
await adapter.setStateAsync('pump.learning.tolerance_percent', { val: 20, ack: true });
|
|
222
|
+
}
|
|
177
223
|
|
|
178
224
|
// ------------------------------------------------------
|
|
179
225
|
// Textbewertung (Status)
|
|
@@ -191,7 +237,10 @@ async function createPumpStates3(adapter) {
|
|
|
191
237
|
},
|
|
192
238
|
native: {},
|
|
193
239
|
});
|
|
194
|
-
await adapter.
|
|
240
|
+
const existingStatus = await adapter.getStateAsync('pump.learning.status_text');
|
|
241
|
+
if (existingStatus === null || existingStatus.val === null || existingStatus.val === undefined) {
|
|
242
|
+
await adapter.setStateAsync('pump.learning.status_text', { val: '', ack: true });
|
|
243
|
+
}
|
|
195
244
|
|
|
196
245
|
// ------------------------------------------------------
|
|
197
246
|
// Anzahl Lernzyklen
|
|
@@ -209,7 +258,10 @@ async function createPumpStates3(adapter) {
|
|
|
209
258
|
},
|
|
210
259
|
native: {},
|
|
211
260
|
});
|
|
212
|
-
await adapter.
|
|
261
|
+
const existingCycles = await adapter.getStateAsync('pump.learning.learning_cycles_total');
|
|
262
|
+
if (existingCycles === null || existingCycles.val === null || existingCycles.val === undefined) {
|
|
263
|
+
await adapter.setStateAsync('pump.learning.learning_cycles_total', { val: 0, ack: true });
|
|
264
|
+
}
|
|
213
265
|
|
|
214
266
|
// ------------------------------------------------------
|
|
215
267
|
// Log-Eintrag
|