iobroker.poolcontrol 0.3.1 → 0.4.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 +24 -18
- package/io-package.json +14 -14
- package/lib/helpers/consumptionHelper.js +27 -32
- package/lib/helpers/frostHelper.js +3 -8
- package/lib/helpers/pumpHelper.js +22 -12
- package/lib/helpers/pumpHelper3.js +51 -16
- package/lib/helpers/runtimeHelper.js +73 -32
- package/lib/helpers/statisticsHelper.js +392 -0
- package/lib/stateDefinitions/controlStates.js +0 -61
- package/lib/stateDefinitions/pumpStates3.js +22 -0
- package/lib/stateDefinitions/runtimeStates.js +20 -5
- package/lib/stateDefinitions/statisticsStates.js +138 -0
- package/main.js +9 -4
- package/package.json +1 -1
- package/lib/stateDefinitions/hardwareStates.js +0 -184
package/README.md
CHANGED
|
@@ -121,6 +121,30 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
|
|
|
121
121
|
## Changelog
|
|
122
122
|
### **WORK IN PROGRESS**
|
|
123
123
|
|
|
124
|
+
### **0.4.0 (26.10.2025)**
|
|
125
|
+
|
|
126
|
+
**Neue Funktionen**
|
|
127
|
+
- Einführung des neuen Statistik-Systems unter `analytics.statistics.temperature.today`
|
|
128
|
+
- Automatische Erfassung von **Min-, Max- und Durchschnittswerten** aller aktiven Temperatursensoren
|
|
129
|
+
- Pro Sensor: JSON- und HTML-Zusammenfassungen mit laufender Aktualisierung
|
|
130
|
+
- Gesamtausgabe aller Sensoren (Tabelle) unter
|
|
131
|
+
`analytics.statistics.temperature.today.outputs.summary_all_html`
|
|
132
|
+
- Vollständig **persistente Datenpunkte** mit Überinstallationsschutz
|
|
133
|
+
- **Automatischer Mitternachts-Reset** zur Tagesrücksetzung inkl. Zeitstempel
|
|
134
|
+
- Vorbereitung für zukünftige Wochen-, Monats- und Saisonstatistiken
|
|
135
|
+
|
|
136
|
+
**Verbesserungen**
|
|
137
|
+
- Einheitliche Struktur durch neuen Hauptordner `analytics`
|
|
138
|
+
- Keine dauerhaften Loops oder Timerbelastungen – reine Eventverarbeitung
|
|
139
|
+
- Verbesserte Performance und Speicherstabilität
|
|
140
|
+
- Überarbeitete Initialisierung aller Statistik-States beim Start
|
|
141
|
+
|
|
142
|
+
**Hinweis**
|
|
143
|
+
Diese Version bildet die stabile Basis für alle folgenden Statistik- und Analysefunktionen
|
|
144
|
+
(z. B. Wochen- und Monatsstatistik, Historien- und Effizienz-Auswertungen).
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
124
148
|
### 0.3.1 (2025-10-18)
|
|
125
149
|
- FrostHelper stabilisiert:
|
|
126
150
|
- Feste Hysterese von +2 °C (bisher +1 °C)
|
|
@@ -145,24 +169,6 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
|
|
|
145
169
|
> Mit dieser Version beginnt die lernfähige Phase des PoolControl-Adapters:
|
|
146
170
|
> Deine Pumpe weiß jetzt selbst, was für sie „normal“ ist.
|
|
147
171
|
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
### 0.2.2 (2025-10-08)
|
|
151
|
-
- Einführung einer **automatischen Rückspülerinnerung** (neuer Bereich im ControlHelper2)
|
|
152
|
-
- Erinnerung mit Intervall in Tagen und automatischer Rücksetzung nach erfolgter Rückspülung
|
|
153
|
-
- Log- und Sprachausgabe bei Fälligkeit oder Überfälligkeit
|
|
154
|
-
- Erweiterung des Control-Bereichs um zusätzliche States (backwash_reminder_active, interval_days, last_date, required)
|
|
155
|
-
- Anpassung der main.js und controlStates zur Integration
|
|
156
|
-
- Vorbereitung weiterer Wartungs- und Steuerungsfunktionen (Beleuchtung, Roboter, Ventile, Gegenstromanlage, Kesseldruck-Überwachung).
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
### 0.2.0 (2025-10-06)
|
|
161
|
-
- Neuer Diagnosebereich **SystemCheck** für interne Debug-Logs und Analysen.
|
|
162
|
-
- Möglichkeit, einzelne Adapterbereiche (z. B. Pumpe, Solar, Temperatur) gezielt zu überwachen.
|
|
163
|
-
- Fortlaufendes Textprotokoll mit manueller Löschfunktion.
|
|
164
|
-
- Alle bisherigen Debug-Funktionen aus `zz_debuglogs` in `SystemCheck.debug_logs` integriert.
|
|
165
|
-
- Vorbereitung für zukünftige Diagnose-Erweiterungen (Export, Systemprüfung, Plausibilitäts-Checks).
|
|
166
172
|
|
|
167
173
|
*(ältere Versionen siehe [io-package.json](./io-package.json))*
|
|
168
174
|
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.4.0": {
|
|
7
|
+
"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
|
+
"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.",
|
|
9
|
+
"ru": "Добавлена ежедневная статистика температуры в analytics.statistics.temperature.today с автоматическим отслеживанием мин./макс./средних значений, JSON и HTML сводками и логикой сброса в полночь.",
|
|
10
|
+
"pt": "Adicionadas estatísticas diárias de temperatura em analytics.statistics.temperature.today com rastreamento automático de mínimo/máximo/média, resumos em JSON e HTML e redefinição automática à meia-noite.",
|
|
11
|
+
"nl": "Dagelijkse temperatuurstatistieken toegevoegd onder analytics.statistics.temperature.today met automatische min/max/gemiddelde tracking, JSON- en HTML-samenvattingen en middernachtreset.",
|
|
12
|
+
"fr": "Ajout de statistiques quotidiennes de température sous analytics.statistics.temperature.today avec suivi automatique min/max/moyenne, résumés JSON et HTML et réinitialisation automatique à minuit.",
|
|
13
|
+
"it": "Aggiunte statistiche giornaliere della temperatura in analytics.statistics.temperature.today con monitoraggio automatico di min/max/media, riepiloghi JSON e HTML e reset automatico a mezzanotte.",
|
|
14
|
+
"es": "Se añadieron estadísticas diarias de temperatura en analytics.statistics.temperature.today con seguimiento automático de mínimos/máximos/promedios, resúmenes en JSON y HTML y reinicio automático a medianoche.",
|
|
15
|
+
"pl": "Dodano dzienne statystyki temperatury w analytics.statistics.temperature.today z automatycznym śledzeniem wartości min/max/średnich, podsumowaniami JSON i HTML oraz resetem o północy.",
|
|
16
|
+
"uk": "Додано щоденну статистику температури в analytics.statistics.temperature.today з автоматичним відстеженням мін/макс/середніх значень, JSON і HTML зведеннями та скиданням опівночі.",
|
|
17
|
+
"zh-cn": "在 analytics.statistics.temperature.today 中添加了每日温度统计,具有自动最小/最大/平均跟踪、JSON 和 HTML 摘要以及午夜重置功能。"
|
|
18
|
+
},
|
|
6
19
|
"0.3.1": {
|
|
7
20
|
"en": "Frost protection logic stabilized: fixed hysteresis of +2 °C and rounded temperature values to avoid pump switching fluctuations around 3 °C.",
|
|
8
21
|
"de": "Frostschutz-Logik stabilisiert: feste Hysterese von +2 °C und gerundete Temperaturwerte zur Vermeidung von Pumpenschaltflattern um 3 °C.",
|
|
@@ -52,19 +65,6 @@
|
|
|
52
65
|
"pl": "Naprawiono problem z niewidzialnymi stanami dla kontroli głosowej i dodano prawidłowe przetwarzanie zmiennych wewnętrznych.",
|
|
53
66
|
"uk": "Виправлено проблему з невидимими станами для керування голосом та додано правильну обробку внутрішніх змінних.",
|
|
54
67
|
"zh-cn": "修复了语音控制的隐形状态问题,并添加了正确的内部变量处理。"
|
|
55
|
-
},
|
|
56
|
-
"0.2.0": {
|
|
57
|
-
"en": "New diagnostic area 'SystemCheck' for internal debug logs and future analysis tools.",
|
|
58
|
-
"de": "Neuer Diagnosebereich 'SystemCheck' für interne Debug-Logs und künftige Analysefunktionen.",
|
|
59
|
-
"ru": "Новая диагностическая область 'SystemCheck' для внутренних журналов отладки и будущих инструментов анализа.",
|
|
60
|
-
"pt": "Nova área de diagnóstico 'SystemCheck' para logs de depuração internos e futuras ferramentas de análise.",
|
|
61
|
-
"nl": "Nieuw diagnostisch gebied 'SystemCheck' voor interne foutopsporingslogboeken en toekomstige analysetools.",
|
|
62
|
-
"fr": "Nouvelle zone de diagnostic 'SystemCheck' pour les journaux de débogage internes et les futurs outils d'analyse.",
|
|
63
|
-
"it": "Nuova area diagnostica 'SystemCheck' per i log di debug interni e futuri strumenti di analisi.",
|
|
64
|
-
"es": "Nueva área de diagnóstico 'SystemCheck' para registros internos de depuración y futuras herramientas de análisis.",
|
|
65
|
-
"pl": "Nowy obszar diagnostyczny 'SystemCheck' dla wewnętrznych dzienników debugowania i przyszłych narzędzi analitycznych.",
|
|
66
|
-
"uk": "Нова діагностична область 'SystemCheck' для внутрішніх журналів налагодження та майбутніх інструментів аналізу.",
|
|
67
|
-
"zh-cn": "新的诊断区域“SystemCheck”,用于内部调试日志和未来的分析工具。"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"titleLang": {
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* consumptionHelper
|
|
5
5
|
* - Nutzt externen kWh-Zähler (objectId aus Config)
|
|
6
6
|
* - Berechnet Periodenwerte (Tag/Woche/Monat/Jahr)
|
|
7
|
-
* - Berechnet Kosten anhand Strompreis (€/kWh)
|
|
7
|
+
* - Berechnet Kosten anhand Strompreis (€/kWh)
|
|
8
8
|
* - Offset-Mechanismus: summiert alte Werte bei Zählerwechsel/Reset auf
|
|
9
|
-
* -
|
|
9
|
+
* - Erhält Tages-/Wochen-/Monats-/Jahreswerte über Neustarts
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const consumptionHelper = {
|
|
@@ -16,7 +16,6 @@ const consumptionHelper = {
|
|
|
16
16
|
baselines: {},
|
|
17
17
|
resetTimer: null,
|
|
18
18
|
|
|
19
|
-
// interne Speicher für additive Berechnung
|
|
20
19
|
lastKnownPrice: 0,
|
|
21
20
|
baseTotalKwh: 0,
|
|
22
21
|
baseTotalEur: 0,
|
|
@@ -25,7 +24,6 @@ const consumptionHelper = {
|
|
|
25
24
|
this.adapter = adapter;
|
|
26
25
|
this.energyId = adapter.config.external_energy_total_id || null;
|
|
27
26
|
|
|
28
|
-
// Komma/Punkt tolerant interpretieren
|
|
29
27
|
this.price = parseFloat(String(adapter.config.energy_price_eur_kwh).replace(',', '.')) || 0;
|
|
30
28
|
this.lastKnownPrice = this.price;
|
|
31
29
|
|
|
@@ -39,18 +37,16 @@ const consumptionHelper = {
|
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
this._scheduleDailyReset();
|
|
42
|
-
this._loadCostBaselines();
|
|
43
|
-
this._restoreBaselinesFromStates();
|
|
40
|
+
this._loadCostBaselines();
|
|
41
|
+
this._restoreBaselinesFromStates();
|
|
44
42
|
},
|
|
45
43
|
|
|
46
44
|
async _loadCostBaselines() {
|
|
47
45
|
try {
|
|
48
46
|
const totalKwh = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
|
|
49
47
|
const totalEur = (await this.adapter.getStateAsync('costs.total_eur'))?.val || 0;
|
|
50
|
-
|
|
51
48
|
this.baseTotalKwh = totalKwh;
|
|
52
49
|
this.baseTotalEur = totalEur;
|
|
53
|
-
|
|
54
50
|
this.adapter.log.debug(
|
|
55
51
|
`[consumptionHelper] Kosten-Basis geladen → ${this.baseTotalEur.toFixed(
|
|
56
52
|
2,
|
|
@@ -65,12 +61,10 @@ const consumptionHelper = {
|
|
|
65
61
|
if (!state || id !== this.energyId) {
|
|
66
62
|
return;
|
|
67
63
|
}
|
|
68
|
-
|
|
69
64
|
const totalNowRaw = Number(state.val);
|
|
70
65
|
if (!Number.isFinite(totalNowRaw)) {
|
|
71
66
|
return;
|
|
72
67
|
}
|
|
73
|
-
|
|
74
68
|
await this._updateConsumption(totalNowRaw);
|
|
75
69
|
},
|
|
76
70
|
|
|
@@ -80,19 +74,25 @@ const consumptionHelper = {
|
|
|
80
74
|
const last = (await this.adapter.getStateAsync('consumption.last_total_kwh'))?.val || 0;
|
|
81
75
|
let totalNow = totalNowRaw;
|
|
82
76
|
|
|
83
|
-
//
|
|
77
|
+
// FIX: Schutz gegen Überinstallations-Fehler und unplausible Sprünge
|
|
84
78
|
if (totalNowRaw < last) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
if (offset === 0 && totalNowRaw < 10 && last > 10) {
|
|
80
|
+
this.adapter.log.warn(
|
|
81
|
+
'[consumptionHelper] Überinstallationsschutz aktiv – Zählerstand kleiner, Offset bleibt unverändert.',
|
|
82
|
+
);
|
|
83
|
+
totalNow = last; // kein Offset addieren
|
|
84
|
+
} else {
|
|
85
|
+
this.adapter.log.warn('[consumptionHelper] Zähler-Reset erkannt → Offset wird angepasst');
|
|
86
|
+
const newOffset = offset + last;
|
|
87
|
+
await this.adapter.setStateAsync('consumption.offset_kwh', { val: newOffset, ack: true });
|
|
88
|
+
totalNow = newOffset + totalNowRaw;
|
|
89
|
+
}
|
|
89
90
|
} else {
|
|
90
91
|
totalNow = offset + totalNowRaw;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
await this.adapter.setStateAsync('consumption.total_kwh', { val: totalNow, ack: true });
|
|
94
95
|
|
|
95
|
-
// Baselines laden (falls leer)
|
|
96
96
|
if (Object.keys(this.baselines).length === 0) {
|
|
97
97
|
await this._loadBaselines(totalNow);
|
|
98
98
|
}
|
|
@@ -112,18 +112,15 @@ const consumptionHelper = {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// === Additive Kostenberechnung über Neustarts ===
|
|
116
115
|
const deltaKwh = Math.max(0, totalNow - this.baseTotalKwh);
|
|
117
116
|
const deltaEur = deltaKwh * this.price;
|
|
118
117
|
const totalCost = this.baseTotalEur + deltaEur;
|
|
119
118
|
|
|
120
|
-
// Tages-/Wochen-/Monats-/Jahreskosten berechnen (nur Momentansicht)
|
|
121
119
|
const dayCost = values.day * this.price;
|
|
122
120
|
const weekCost = values.week * this.price;
|
|
123
121
|
const monthCost = values.month * this.price;
|
|
124
122
|
const yearCost = values.year * this.price;
|
|
125
123
|
|
|
126
|
-
// States schreiben
|
|
127
124
|
await this.adapter.setStateAsync('consumption.day_kwh', { val: Number(values.day.toFixed(3)), ack: true });
|
|
128
125
|
await this.adapter.setStateAsync('consumption.week_kwh', {
|
|
129
126
|
val: Number(values.week.toFixed(3)),
|
|
@@ -146,17 +143,13 @@ const consumptionHelper = {
|
|
|
146
143
|
await this.adapter.setStateAsync('costs.total_eur', { val: Number(totalCost.toFixed(2)), ack: true });
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
// Letzten Zählerwert speichern
|
|
150
146
|
await this.adapter.setStateAsync('consumption.last_total_kwh', { val: totalNowRaw, ack: true });
|
|
151
|
-
|
|
152
|
-
// NEU: aktuelle Baselines persistent sichern
|
|
153
147
|
await this._saveBaselines();
|
|
154
148
|
} catch (err) {
|
|
155
149
|
this.adapter.log.warn(`[consumptionHelper] Fehler bei Verbrauchsupdate: ${err.message}`);
|
|
156
150
|
}
|
|
157
151
|
},
|
|
158
152
|
|
|
159
|
-
// Lädt Baselines beim Start, wenn im Speicher leer
|
|
160
153
|
async _loadBaselines(totalNow) {
|
|
161
154
|
this.baselines = {};
|
|
162
155
|
const day = (await this.adapter.getStateAsync('consumption.day_kwh'))?.val;
|
|
@@ -172,7 +165,6 @@ const consumptionHelper = {
|
|
|
172
165
|
this.adapter.log.debug(`[consumptionHelper] Baselines geladen: ${JSON.stringify(this.baselines)}`);
|
|
173
166
|
},
|
|
174
167
|
|
|
175
|
-
// NEU: Baselines aus States rekonstruieren (beim Adapterstart)
|
|
176
168
|
async _restoreBaselinesFromStates() {
|
|
177
169
|
try {
|
|
178
170
|
const totalNow = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
|
|
@@ -194,7 +186,6 @@ const consumptionHelper = {
|
|
|
194
186
|
}
|
|
195
187
|
},
|
|
196
188
|
|
|
197
|
-
// NEU: Aktuelle Baselines speichern (persistente Sicherung)
|
|
198
189
|
async _saveBaselines() {
|
|
199
190
|
try {
|
|
200
191
|
await this.adapter.setStateAsync('consumption.day_kwh', {
|
|
@@ -218,13 +209,11 @@ const consumptionHelper = {
|
|
|
218
209
|
}
|
|
219
210
|
},
|
|
220
211
|
|
|
221
|
-
// === NEU: Manueller Gesamreset für Verbrauch & Kosten ====================
|
|
222
212
|
async resetAll(adapter) {
|
|
223
213
|
try {
|
|
224
214
|
this.adapter = adapter;
|
|
225
215
|
adapter.log.warn('[consumptionHelper] Manueller Reset aller Verbrauchs- und Kostendaten');
|
|
226
216
|
|
|
227
|
-
// Verbrauchswerte nullen
|
|
228
217
|
const consumptionKeys = [
|
|
229
218
|
'day_kwh',
|
|
230
219
|
'week_kwh',
|
|
@@ -238,13 +227,11 @@ const consumptionHelper = {
|
|
|
238
227
|
await adapter.setStateAsync(`consumption.${key}`, { val: 0, ack: true });
|
|
239
228
|
}
|
|
240
229
|
|
|
241
|
-
// Kostenwerte nullen
|
|
242
230
|
const costKeys = ['day_eur', 'week_eur', 'month_eur', 'year_eur', 'total_eur'];
|
|
243
231
|
for (const key of costKeys) {
|
|
244
232
|
await adapter.setStateAsync(`costs.${key}`, { val: 0, ack: true });
|
|
245
233
|
}
|
|
246
234
|
|
|
247
|
-
// interne Speicher ebenfalls zurücksetzen
|
|
248
235
|
this.baselines = {};
|
|
249
236
|
this.baseTotalKwh = 0;
|
|
250
237
|
this.baseTotalEur = 0;
|
|
@@ -255,15 +242,23 @@ const consumptionHelper = {
|
|
|
255
242
|
}
|
|
256
243
|
},
|
|
257
244
|
|
|
245
|
+
// FIX: täglicher Reset um Mitternacht für Tagesverbrauch
|
|
258
246
|
_scheduleDailyReset() {
|
|
259
247
|
const now = new Date();
|
|
260
248
|
const nextMidnight = new Date(now);
|
|
261
249
|
nextMidnight.setHours(24, 0, 0, 0);
|
|
262
250
|
const msUntilMidnight = nextMidnight - now;
|
|
263
251
|
|
|
264
|
-
this.resetTimer = setTimeout(() => {
|
|
265
|
-
|
|
266
|
-
|
|
252
|
+
this.resetTimer = setTimeout(async () => {
|
|
253
|
+
try {
|
|
254
|
+
this.adapter.log.info('[consumptionHelper] Tageszähler-Reset (Mitternacht)');
|
|
255
|
+
await this.adapter.setStateAsync('consumption.day_kwh', { val: 0, ack: true });
|
|
256
|
+
await this.adapter.setStateAsync('costs.day_eur', { val: 0, ack: true });
|
|
257
|
+
this.baselines.day = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
this.adapter.log.warn(`[consumptionHelper] Fehler beim Mitternachtsreset: ${err.message}`);
|
|
260
|
+
}
|
|
261
|
+
this._scheduleDailyReset(); // Timer erneut setzen
|
|
267
262
|
}, msUntilMidnight);
|
|
268
263
|
},
|
|
269
264
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* frostHelper
|
|
5
5
|
* - Prüft Außentemperatur gegen Frostschutz-Grenze
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
6
|
+
* - Frostschutz arbeitet unabhängig vom Pumpenmodus
|
|
7
|
+
* -(Sicherheitsfunktion: greift immer, wenn frost_protection_active = true)
|
|
8
|
+
* - Kleine Hysterese: +2°C zum Ausschalten
|
|
8
9
|
* - Schaltet über den zentralen Bool-State pump.pump_switch
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -39,12 +40,6 @@ const frostHelper = {
|
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
// Nur aktiv im AUTO-Modus
|
|
43
|
-
const mode = (await this.adapter.getStateAsync('pump.mode'))?.val;
|
|
44
|
-
if (mode !== 'auto') {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
43
|
// Frostschutz aktiviert?
|
|
49
44
|
const frostActive = (await this.adapter.getStateAsync('pump.frost_protection_active'))?.val;
|
|
50
45
|
if (!frostActive) {
|
|
@@ -255,31 +255,41 @@ const pumpHelper = {
|
|
|
255
255
|
let error = false;
|
|
256
256
|
let errorMsg = '';
|
|
257
257
|
|
|
258
|
+
// Pumpe EIN, aber keine Leistung
|
|
258
259
|
if (active === true && power < 5) {
|
|
259
260
|
error = true;
|
|
260
261
|
errorMsg = 'Fehler: Pumpe EIN, aber keine Leistung!';
|
|
261
262
|
}
|
|
262
263
|
|
|
264
|
+
// Pumpe AUS, aber Leistung vorhanden
|
|
263
265
|
if (active === false && power > 10) {
|
|
264
266
|
error = true;
|
|
265
267
|
errorMsg = 'Fehler: Pumpe AUS, aber Leistung vorhanden!';
|
|
266
268
|
}
|
|
267
269
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
// --- Überlastschutz (mit fester 10%-Toleranz) ---
|
|
271
|
+
if (active === true && maxWatt > 0) {
|
|
272
|
+
const overloadTolerance = 1.1; // 10 % Sicherheitsfenster
|
|
273
|
+
const overloadLimit = maxWatt * overloadTolerance;
|
|
274
|
+
|
|
275
|
+
if (power > overloadLimit) {
|
|
276
|
+
error = true;
|
|
277
|
+
errorMsg = `Überlast: ${power.toFixed(1)} W > Sicherheitsgrenze ${overloadLimit.toFixed(
|
|
278
|
+
1,
|
|
279
|
+
)} W (Maxwert ${maxWatt} W) → Pumpe wird abgeschaltet!`;
|
|
280
|
+
|
|
281
|
+
if (pumpSwitchId) {
|
|
282
|
+
await this.adapter.setForeignStateAsync(pumpSwitchId, {
|
|
283
|
+
val: false,
|
|
284
|
+
ack: false,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
271
287
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
ack: false,
|
|
288
|
+
await this.adapter.setStateAsync('pump.mode', {
|
|
289
|
+
val: 'off',
|
|
290
|
+
ack: true,
|
|
276
291
|
});
|
|
277
292
|
}
|
|
278
|
-
|
|
279
|
-
await this.adapter.setStateAsync('pump.mode', {
|
|
280
|
-
val: 'off',
|
|
281
|
-
ack: true,
|
|
282
|
-
});
|
|
283
293
|
}
|
|
284
294
|
|
|
285
295
|
if (error !== errorOld) {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - pump.live.flow_current_lh
|
|
18
18
|
* - pump.pump_switch
|
|
19
19
|
*
|
|
20
|
-
* Version: 1.0.
|
|
20
|
+
* Version: 1.0.2 (zusätzliche Throttle-Logik für Deviation-Werte)
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
const pumpHelper3 = {
|
|
@@ -29,6 +29,14 @@ const pumpHelper3 = {
|
|
|
29
29
|
flow: [],
|
|
30
30
|
},
|
|
31
31
|
|
|
32
|
+
// NEU: interne Variablen für Schreibschutz
|
|
33
|
+
_lastLearningWrite: 0,
|
|
34
|
+
_lastLearningStatus: '',
|
|
35
|
+
// NEU: Drosselung für Deviation-Werte
|
|
36
|
+
_lastDevWrite: 0,
|
|
37
|
+
_lastDevPower: 0,
|
|
38
|
+
_lastDevFlow: 0,
|
|
39
|
+
|
|
32
40
|
/**
|
|
33
41
|
* Initialisiert den Helper
|
|
34
42
|
*
|
|
@@ -167,6 +175,7 @@ const pumpHelper3 = {
|
|
|
167
175
|
|
|
168
176
|
/**
|
|
169
177
|
* Bewertet aktuelle Abweichungen und schreibt Status.
|
|
178
|
+
* (jetzt mit Throttle- und Change-Detection)
|
|
170
179
|
*/
|
|
171
180
|
async _updateDeviationAndStatus() {
|
|
172
181
|
try {
|
|
@@ -183,18 +192,40 @@ const pumpHelper3 = {
|
|
|
183
192
|
const deviationPower = ((currentPower - avgPower) / avgPower) * 100;
|
|
184
193
|
const deviationFlow = ((currentFlow - avgFlow) / avgFlow) * 100;
|
|
185
194
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
// --- NEU: Throttle + Change Detection für Deviation-Werte ---
|
|
196
|
+
const roundedPower = Math.round(deviationPower * 10) / 10;
|
|
197
|
+
const roundedFlow = Math.round(deviationFlow * 10) / 10;
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
(this._lastDevPower !== roundedPower || this._lastDevFlow !== roundedFlow) &&
|
|
201
|
+
Date.now() - this._lastDevWrite > 1000
|
|
202
|
+
) {
|
|
203
|
+
this._lastDevPower = roundedPower;
|
|
204
|
+
this._lastDevFlow = roundedFlow;
|
|
205
|
+
this._lastDevWrite = Date.now();
|
|
194
206
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
207
|
+
await this.adapter.setStateAsync('pump.learning.deviation_power_percent', {
|
|
208
|
+
val: roundedPower,
|
|
209
|
+
ack: true,
|
|
210
|
+
});
|
|
211
|
+
await this.adapter.setStateAsync('pump.learning.deviation_flow_percent', {
|
|
212
|
+
val: roundedFlow,
|
|
213
|
+
ack: true,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Bewertungstext ermitteln
|
|
218
|
+
const statusText = await this._getStatusText(deviationPower, deviationFlow);
|
|
219
|
+
|
|
220
|
+
// --- NEU: Schreibsperre + Change-Detection für Status ---
|
|
221
|
+
if (statusText !== this._lastLearningStatus && Date.now() - this._lastLearningWrite > 1000) {
|
|
222
|
+
this._lastLearningStatus = statusText;
|
|
223
|
+
this._lastLearningWrite = Date.now();
|
|
224
|
+
await this.adapter.setStateAsync('pump.learning.status_text', {
|
|
225
|
+
val: statusText,
|
|
226
|
+
ack: true,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
198
229
|
} catch (err) {
|
|
199
230
|
this.adapter.log.warn(`[pumpHelper3] Fehler bei _updateDeviationAndStatus: ${err.message}`);
|
|
200
231
|
}
|
|
@@ -206,17 +237,21 @@ const pumpHelper3 = {
|
|
|
206
237
|
* @param devPower - Aktuelle Abweichung der Leistung (W)
|
|
207
238
|
* @param devFlow - Aktuelle Abweichung des Durchflusses (L/H)
|
|
208
239
|
*/
|
|
209
|
-
_getStatusText(devPower, devFlow) {
|
|
240
|
+
async _getStatusText(devPower, devFlow) {
|
|
210
241
|
const absPower = Math.abs(devPower);
|
|
211
242
|
const absFlow = Math.abs(devFlow);
|
|
212
243
|
|
|
213
|
-
|
|
244
|
+
// NEU: Dynamischen Toleranzwert lesen (Standard 15%)
|
|
245
|
+
const toleranceState = await this.adapter.getStateAsync('pump.learning.tolerance_percent');
|
|
246
|
+
const tolerance = Number(toleranceState?.val) || 15;
|
|
247
|
+
|
|
248
|
+
if (absPower <= tolerance && absFlow <= tolerance) {
|
|
214
249
|
return 'Pumpe läuft im Normalbereich';
|
|
215
250
|
}
|
|
216
|
-
if (devPower < -
|
|
251
|
+
if (devPower < -tolerance || devFlow < -tolerance) {
|
|
217
252
|
return 'Pumpe läuft unterhalb des Normalbereichs (möglicher Filterdruck)';
|
|
218
253
|
}
|
|
219
|
-
if (devPower >
|
|
254
|
+
if (devPower > tolerance || devFlow > tolerance) {
|
|
220
255
|
return 'Pumpe läuft oberhalb des Normalbereichs (möglicher Luftstau)';
|
|
221
256
|
}
|
|
222
257
|
return 'Pumpe außerhalb des bekannten Bereichs';
|
|
@@ -94,38 +94,72 @@ const runtimeHelper = {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// FIX: Robuste Start-/Stop-Logik für pump.pump_switch
|
|
97
98
|
if (id.endsWith('pump.pump_switch')) {
|
|
98
|
-
if (state.val
|
|
99
|
-
// Pumpe
|
|
100
|
-
this.isRunning
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.
|
|
99
|
+
if (state.val) {
|
|
100
|
+
// FIX: Immer starten, wenn Pumpe an ist (egal welcher Helper)
|
|
101
|
+
if (!this.isRunning || !this.lastOn) {
|
|
102
|
+
this.isRunning = true;
|
|
103
|
+
this.lastOn = Date.now();
|
|
104
|
+
this.startCountToday += 1;
|
|
105
|
+
|
|
106
|
+
// Live-Timer starten (jede Minute)
|
|
107
|
+
this._startLiveTimer();
|
|
108
|
+
|
|
109
|
+
// Start sofort in State schreiben
|
|
110
|
+
await this.adapter.setStateAsync('runtime.start_count_today', {
|
|
111
|
+
val: this.startCountToday,
|
|
112
|
+
ack: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ------------------------------------------------------
|
|
116
|
+
// Statuswerte bei Pumpenstart setzen
|
|
117
|
+
// ------------------------------------------------------
|
|
118
|
+
const nowStr = new Date().toLocaleString();
|
|
119
|
+
await this.adapter.setStateAsync('status.pump_last_start', { val: nowStr, ack: true });
|
|
120
|
+
await this.adapter.setStateAsync('status.pump_today_count', {
|
|
121
|
+
val: this.startCountToday,
|
|
122
|
+
ack: true,
|
|
123
|
+
});
|
|
124
|
+
await this.adapter.setStateAsync('status.pump_was_on_today', { val: true, ack: true });
|
|
125
|
+
// ------------------------------------------------------
|
|
126
|
+
this.adapter.log.debug('[runtimeHelper] Pumpenlaufzeit gestartet.');
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
// FIX: Immer sauber stoppen, wenn Pumpe aus ist
|
|
130
|
+
if (this.isRunning && this.lastOn) {
|
|
131
|
+
const delta = Math.floor((Date.now() - this.lastOn) / 1000);
|
|
132
|
+
this.runtimeToday += delta;
|
|
133
|
+
this.runtimeTotal += delta;
|
|
134
|
+
|
|
135
|
+
// Saisonlaufzeit nur zählen, wenn aktiv
|
|
136
|
+
const seasonActive = !!(await this.adapter.getStateAsync('control.season.active'))?.val;
|
|
137
|
+
if (seasonActive) {
|
|
138
|
+
this.runtimeSeason += delta;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.isRunning = false;
|
|
142
|
+
this.lastOn = null;
|
|
143
|
+
|
|
144
|
+
// Live-Timer stoppen
|
|
145
|
+
this._stopLiveTimer();
|
|
146
|
+
|
|
147
|
+
// States final aktualisieren
|
|
148
|
+
await this._updateStates();
|
|
149
|
+
|
|
150
|
+
// ------------------------------------------------------
|
|
151
|
+
// Statuswert bei Pumpenstopp setzen
|
|
152
|
+
// ------------------------------------------------------
|
|
153
|
+
const nowStr = new Date().toLocaleString();
|
|
154
|
+
await this.adapter.setStateAsync('status.pump_last_stop', { val: nowStr, ack: true });
|
|
155
|
+
// ------------------------------------------------------
|
|
156
|
+
this.adapter.log.debug('[runtimeHelper] Pumpenlaufzeit gestoppt.');
|
|
157
|
+
} else {
|
|
158
|
+
// FIX: Falls Pumpe aus, aber kein aktiver Lauf (z. B. Neustart) → nur Timer sicher stoppen
|
|
159
|
+
this._stopLiveTimer();
|
|
160
|
+
this.isRunning = false;
|
|
161
|
+
this.lastOn = null;
|
|
119
162
|
}
|
|
120
|
-
|
|
121
|
-
this.isRunning = false;
|
|
122
|
-
this.lastOn = null;
|
|
123
|
-
|
|
124
|
-
// Live-Timer stoppen
|
|
125
|
-
this._stopLiveTimer();
|
|
126
|
-
|
|
127
|
-
// States final aktualisieren
|
|
128
|
-
await this._updateStates();
|
|
129
163
|
}
|
|
130
164
|
}
|
|
131
165
|
},
|
|
@@ -263,6 +297,13 @@ const runtimeHelper = {
|
|
|
263
297
|
// 👉 daily_remaining neue berechnen auf Grundlage von daily_required
|
|
264
298
|
await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRequired, ack: true });
|
|
265
299
|
|
|
300
|
+
// ------------------------------------------------------
|
|
301
|
+
// NEU: Pumpenstatuswerte um Mitternacht zurücksetzen
|
|
302
|
+
// ------------------------------------------------------
|
|
303
|
+
await this.adapter.setStateAsync('status.pump_today_count', { val: 0, ack: true });
|
|
304
|
+
await this.adapter.setStateAsync('status.pump_was_on_today', { val: false, ack: true });
|
|
305
|
+
// ------------------------------------------------------
|
|
306
|
+
|
|
266
307
|
// Nächsten Reset planen
|
|
267
308
|
this._scheduleDailyReset();
|
|
268
309
|
|
|
@@ -274,8 +315,8 @@ const runtimeHelper = {
|
|
|
274
315
|
if (this.liveTimer) {
|
|
275
316
|
clearInterval(this.liveTimer);
|
|
276
317
|
}
|
|
277
|
-
this.liveTimer = setInterval(() => this._updateStates(),
|
|
278
|
-
this.adapter.log.debug('[runtimeHelper] Live-Timer gestartet (Updates
|
|
318
|
+
this.liveTimer = setInterval(() => this._updateStates(), 10 * 1000);
|
|
319
|
+
this.adapter.log.debug('[runtimeHelper] Live-Timer gestartet (Updates alle 10 Sekunden)');
|
|
279
320
|
},
|
|
280
321
|
|
|
281
322
|
_stopLiveTimer() {
|