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
package/README.md
CHANGED
|
@@ -121,6 +121,38 @@ 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
|
+
|
|
148
|
+
### 0.3.1 (2025-10-18)
|
|
149
|
+
- FrostHelper stabilisiert:
|
|
150
|
+
- Feste Hysterese von +2 °C (bisher +1 °C)
|
|
151
|
+
- Ganzzahl-Rundung eingeführt zur Vermeidung von Schaltflattern um 3 °C
|
|
152
|
+
- Keine Änderungen an States oder Konfiguration erforderlich
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
124
156
|
### 0.3.0 (12.10.2025)
|
|
125
157
|
**Neu:** Intelligentes Pumpen-Monitoring & Lernsystem
|
|
126
158
|
|
|
@@ -137,24 +169,6 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
|
|
|
137
169
|
> Mit dieser Version beginnt die lernfähige Phase des PoolControl-Adapters:
|
|
138
170
|
> Deine Pumpe weiß jetzt selbst, was für sie „normal“ ist.
|
|
139
171
|
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
### 0.2.2 (2025-10-08)
|
|
143
|
-
- Einführung einer **automatischen Rückspülerinnerung** (neuer Bereich im ControlHelper2)
|
|
144
|
-
- Erinnerung mit Intervall in Tagen und automatischer Rücksetzung nach erfolgter Rückspülung
|
|
145
|
-
- Log- und Sprachausgabe bei Fälligkeit oder Überfälligkeit
|
|
146
|
-
- Erweiterung des Control-Bereichs um zusätzliche States (backwash_reminder_active, interval_days, last_date, required)
|
|
147
|
-
- Anpassung der main.js und controlStates zur Integration
|
|
148
|
-
- Vorbereitung weiterer Wartungs- und Steuerungsfunktionen (Beleuchtung, Roboter, Ventile, Gegenstromanlage, Kesseldruck-Überwachung).
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
### 0.2.0 (2025-10-06)
|
|
153
|
-
- Neuer Diagnosebereich **SystemCheck** für interne Debug-Logs und Analysen.
|
|
154
|
-
- Möglichkeit, einzelne Adapterbereiche (z. B. Pumpe, Solar, Temperatur) gezielt zu überwachen.
|
|
155
|
-
- Fortlaufendes Textprotokoll mit manueller Löschfunktion.
|
|
156
|
-
- Alle bisherigen Debug-Funktionen aus `zz_debuglogs` in `SystemCheck.debug_logs` integriert.
|
|
157
|
-
- Vorbereitung für zukünftige Diagnose-Erweiterungen (Export, Systemprüfung, Plausibilitäts-Checks).
|
|
158
172
|
|
|
159
173
|
*(ältere Versionen siehe [io-package.json](./io-package.json))*
|
|
160
174
|
|
package/admin/jsonConfig.json
CHANGED
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
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
|
+
},
|
|
19
|
+
"0.3.1": {
|
|
20
|
+
"en": "Frost protection logic stabilized: fixed hysteresis of +2 °C and rounded temperature values to avoid pump switching fluctuations around 3 °C.",
|
|
21
|
+
"de": "Frostschutz-Logik stabilisiert: feste Hysterese von +2 °C und gerundete Temperaturwerte zur Vermeidung von Pumpenschaltflattern um 3 °C.",
|
|
22
|
+
"ru": "Логика защиты от замерзания стабилизирована: фиксированная гистерезис +2 °C и округленные значения температуры для предотвращения колебаний включения насоса около 3 °C.",
|
|
23
|
+
"pt": "Lógica de proteção contra congelamento estabilizada: histerese fixa de +2 °C e valores de temperatura arredondados para evitar flutuações de comutação da bomba em torno de 3 °C.",
|
|
24
|
+
"nl": "Vorstbeschermingslogica gestabiliseerd: vaste hysterese van +2 °C en afgeronde temperatuurwaarden om pompfluctuaties rond 3 °C te voorkomen.",
|
|
25
|
+
"fr": "Logique de protection antigel stabilisée : hystérésis fixe de +2 °C et valeurs de température arrondies pour éviter les fluctuations de commutation de la pompe autour de 3 °C.",
|
|
26
|
+
"it": "Logica di protezione antigelo stabilizzata: isteresi fissa di +2 °C e valori di temperatura arrotondati per evitare fluttuazioni di commutazione della pompa intorno a 3 °C.",
|
|
27
|
+
"es": "Lógica de protección contra heladas estabilizada: histéresis fija de +2 °C y valores de temperatura redondeados para evitar fluctuaciones de conmutación de la bomba alrededor de 3 °C.",
|
|
28
|
+
"pl": "Ustabilizowano logikę ochrony przed zamarzaniem: stała histereza +2 °C i zaokrąglone wartości temperatury, aby uniknąć wahań przełączania pompy w okolicach 3 °C.",
|
|
29
|
+
"uk": "Стабілізовано логіку захисту від замерзання: фіксована гістерезис +2 °C і округлені значення температури, щоб уникнути коливань увімкнення насоса біля 3 °C.",
|
|
30
|
+
"zh-cn": "防冻逻辑稳定:固定 +2 °C 滞后并四舍五入温度值,以避免泵在 3 °C 附近频繁切换。"
|
|
31
|
+
},
|
|
6
32
|
"0.3.0": {
|
|
7
33
|
"en": "Added real pump flow calculation, live monitoring, and self-learning normal range system for pump analysis.",
|
|
8
34
|
"de": "Reelle Durchflussberechnung, Liveüberwachung und selbstlernendes Normalbereich-System zur Pumpenanalyse hinzugefügt.",
|
|
@@ -14,7 +40,7 @@
|
|
|
14
40
|
"es": "Se añadió el cálculo del caudal real de la bomba, la monitorización en vivo y un sistema de rango normal autoaprendente para el análisis de la bomba.",
|
|
15
41
|
"pl": "Dodano obliczanie rzeczywistego przepływu pompy, monitorowanie na żywo i samouczący się system normalnego zakresu do analizy pompy.",
|
|
16
42
|
"zh-cn": "添加了实际泵流量计算、实时监控以及用于泵分析的自学习正常范围系统。"
|
|
17
|
-
|
|
43
|
+
},
|
|
18
44
|
"0.2.2": {
|
|
19
45
|
"en": "Added automatic backwash reminder with speech and log notifications.",
|
|
20
46
|
"de": "Automatische Rückspülerinnerung mit Sprach- und Log-Benachrichtigung hinzugefügt.",
|
|
@@ -39,19 +65,6 @@
|
|
|
39
65
|
"pl": "Naprawiono problem z niewidzialnymi stanami dla kontroli głosowej i dodano prawidłowe przetwarzanie zmiennych wewnętrznych.",
|
|
40
66
|
"uk": "Виправлено проблему з невидимими станами для керування голосом та додано правильну обробку внутрішніх змінних.",
|
|
41
67
|
"zh-cn": "修复了语音控制的隐形状态问题,并添加了正确的内部变量处理。"
|
|
42
|
-
},
|
|
43
|
-
"0.2.0": {
|
|
44
|
-
"en": "New diagnostic area 'SystemCheck' for internal debug logs and future analysis tools.",
|
|
45
|
-
"de": "Neuer Diagnosebereich 'SystemCheck' für interne Debug-Logs und künftige Analysefunktionen.",
|
|
46
|
-
"ru": "Новая диагностическая область 'SystemCheck' для внутренних журналов отладки и будущих инструментов анализа.",
|
|
47
|
-
"pt": "Nova área de diagnóstico 'SystemCheck' para logs de depuração internos e futuras ferramentas de análise.",
|
|
48
|
-
"nl": "Nieuw diagnostisch gebied 'SystemCheck' voor interne foutopsporingslogboeken en toekomstige analysetools.",
|
|
49
|
-
"fr": "Nouvelle zone de diagnostic 'SystemCheck' pour les journaux de débogage internes et les futurs outils d'analyse.",
|
|
50
|
-
"it": "Nuova area diagnostica 'SystemCheck' per i log di debug interni e futuri strumenti di analisi.",
|
|
51
|
-
"es": "Nueva área de diagnóstico 'SystemCheck' para registros internos de depuración y futuras herramientas de análisis.",
|
|
52
|
-
"pl": "Nowy obszar diagnostyczny 'SystemCheck' dla wewnętrznych dzienników debugowania i przyszłych narzędzi analitycznych.",
|
|
53
|
-
"uk": "Нова діагностична область 'SystemCheck' для внутрішніх журналів налагодження та майбутніх інструментів аналізу.",
|
|
54
|
-
"zh-cn": "新的诊断区域“SystemCheck”,用于内部调试日志和未来的分析工具。"
|
|
55
68
|
}
|
|
56
69
|
},
|
|
57
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) {
|
|
@@ -68,10 +63,15 @@ const frostHelper = {
|
|
|
68
63
|
const pumpActive = (await this.adapter.getStateAsync('pump.pump_switch'))?.val;
|
|
69
64
|
let shouldRun = pumpActive;
|
|
70
65
|
|
|
71
|
-
//
|
|
72
|
-
|
|
66
|
+
// FIX: Stabilere Logik mit fester Hysterese von +2 °C und Ganzzahl-Rundung
|
|
67
|
+
const outsideRounded = Math.round(outside);
|
|
68
|
+
const frostTempRounded = Math.round(frostTemp);
|
|
69
|
+
|
|
70
|
+
// Einschalten bei <= frostTempRounded
|
|
71
|
+
// Ausschalten erst bei >= frostTempRounded + 2 (2 K Hysterese)
|
|
72
|
+
if (outsideRounded <= frostTempRounded) {
|
|
73
73
|
shouldRun = true;
|
|
74
|
-
} else if (
|
|
74
|
+
} else if (outsideRounded >= frostTempRounded + 2) {
|
|
75
75
|
shouldRun = false;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -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) {
|
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Alle Zielstates sind persistent (siehe pumpStates2.js).
|
|
22
22
|
* ----------------------------------------------------------
|
|
23
|
-
* Version: 1.0.
|
|
23
|
+
* Version: 1.0.3
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
const pumpHelper2 = {
|
|
27
27
|
adapter: null,
|
|
28
|
+
lastKnownFlow: 0, // merkt sich den letzten gültigen Durchflusswert
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Initialisiert den PumpHelper2
|
|
@@ -61,11 +62,20 @@ const pumpHelper2 = {
|
|
|
61
62
|
await this._updateLiveValues();
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
// Pumpenstatus-Änderung →
|
|
65
|
+
// Pumpenstatus-Änderung → letzten Durchflusswert sichern
|
|
65
66
|
if (id.endsWith('pump.pump_switch')) {
|
|
66
67
|
const pumpOn = state.val === true;
|
|
67
68
|
if (!pumpOn) {
|
|
68
|
-
|
|
69
|
+
// FIX: Verwende den zuletzt gemerkten Wert, statt live zu lesen (verhindert 0-Durchfluss)
|
|
70
|
+
const flowBeforeStop = this.lastKnownFlow;
|
|
71
|
+
if (flowBeforeStop > 0) {
|
|
72
|
+
await this._setIfChanged('pump.live.last_flow_lh', flowBeforeStop);
|
|
73
|
+
this.adapter.log.debug(
|
|
74
|
+
`[pumpHelper2] FIX: Letzter Durchflusswert gesichert: ${flowBeforeStop} l/h`,
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
this.adapter.log.debug('[pumpHelper2] Kein gespeicherter Durchflusswert vorhanden.');
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
},
|
|
@@ -94,6 +104,11 @@ const pumpHelper2 = {
|
|
|
94
104
|
// Reeller Durchfluss
|
|
95
105
|
const flowCurrentLh = Math.round(nominalFlow * (currentPower / maxPower) * 10) / 10; // 1 Nachkommastelle
|
|
96
106
|
|
|
107
|
+
// Letzten gültigen Wert merken
|
|
108
|
+
if (flowCurrentLh > 0) {
|
|
109
|
+
this.lastKnownFlow = flowCurrentLh;
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
// In States schreiben
|
|
98
113
|
await this._setIfChanged('pump.live.current_power_w', currentPower);
|
|
99
114
|
await this._setIfChanged('pump.live.flow_current_lh', flowCurrentLh);
|
|
@@ -107,19 +122,6 @@ const pumpHelper2 = {
|
|
|
107
122
|
}
|
|
108
123
|
},
|
|
109
124
|
|
|
110
|
-
/**
|
|
111
|
-
* Speichert den letzten bekannten Durchflusswert beim Pumpen-Stopp.
|
|
112
|
-
*/
|
|
113
|
-
async _storeLastFlowValue() {
|
|
114
|
-
try {
|
|
115
|
-
const currentFlow = await this._getNumber('pump.live.flow_current_lh');
|
|
116
|
-
await this._setIfChanged('pump.live.last_flow_lh', currentFlow);
|
|
117
|
-
this.adapter.log.debug(`[pumpHelper2] Letzter Durchflusswert gespeichert: ${currentFlow} l/h`);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
this.adapter.log.warn(`[pumpHelper2] Fehler bei _storeLastFlowValue: ${err.message}`);
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
|
|
123
125
|
/**
|
|
124
126
|
* Liest einen numerischen Statewert (oder 0 bei Fehler).
|
|
125
127
|
*
|
|
@@ -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
|
*
|
|
@@ -53,7 +61,7 @@ const pumpHelper3 = {
|
|
|
53
61
|
* @param {ioBroker.State} state - neuer Statewert
|
|
54
62
|
*/
|
|
55
63
|
async handleStateChange(id, state) {
|
|
56
|
-
if (!state
|
|
64
|
+
if (!state) {
|
|
57
65
|
return;
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -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';
|