iobroker.poolcontrol 0.1.1 → 0.2.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 +25 -0
- package/admin/jsonConfig.json +3 -1
- package/io-package.json +27 -1
- package/lib/helpers/consumptionHelper.js +148 -55
- package/lib/helpers/controlHelper.js +187 -0
- package/lib/helpers/debugLogHelper.js +193 -0
- package/lib/helpers/pumpHelper.js +25 -0
- package/lib/helpers/runtimeHelper.js +46 -34
- package/lib/helpers/speechHelper.js +65 -33
- package/lib/helpers/temperatureHelper.js +29 -0
- package/lib/stateDefinitions/controlStates.js +184 -0
- package/lib/stateDefinitions/debugLogStates.js +124 -0
- package/lib/stateDefinitions/runtimeStates.js +48 -10
- package/lib/stateDefinitions/speechStates.js +1 -18
- package/main.js +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,18 @@ Er ermöglicht die Automatisierung von Pumpen, Temperatur- und Solarsteuerung so
|
|
|
48
48
|
- Ausgabe über Alexa oder Telegram
|
|
49
49
|
- Ansagen bei Pumpenstart/-stopp, Fehlern oder Temperaturschwellen
|
|
50
50
|
|
|
51
|
+
- **Systemcheck (Diagnosebereich)**
|
|
52
|
+
Ab Version **0.2.0** enthält der Adapter einen neuen Diagnosebereich **SystemCheck**.
|
|
53
|
+
Er bietet interne Debug-Logs, mit denen bestimmte Teilbereiche (z. B. Pumpen-, Solar- oder Temperatursteuerung) gezielt überwacht werden können.
|
|
54
|
+
|
|
55
|
+
*Funktionen:*
|
|
56
|
+
- Auswahl des zu überwachenden Bereichs
|
|
57
|
+
- Fortlaufendes Log der letzten Änderungen
|
|
58
|
+
- Manuelles Löschen des Logs möglich
|
|
59
|
+
|
|
60
|
+
Dieser Bereich dient ausschließlich der Analyse und Fehlerdiagnose.
|
|
61
|
+
Im normalen Betrieb sollte die Überwachung deaktiviert bleiben.
|
|
62
|
+
|
|
51
63
|
---
|
|
52
64
|
|
|
53
65
|
## Installation
|
|
@@ -94,6 +106,19 @@ Funktionen können sich ändern, bitte regelmäßig den Changelog beachten.
|
|
|
94
106
|
## Changelog
|
|
95
107
|
Auszug, vollständige Liste siehe `io-package.json`:
|
|
96
108
|
|
|
109
|
+
### 0.2.0 (2025-10-06)
|
|
110
|
+
- Neuer Diagnosebereich **SystemCheck** für interne Debug-Logs und Analysen.
|
|
111
|
+
- Möglichkeit, einzelne Adapterbereiche (z. B. Pumpe, Solar, Temperatur) gezielt zu überwachen.
|
|
112
|
+
- Fortlaufendes Textprotokoll mit manueller Löschfunktion.
|
|
113
|
+
- Alle bisherigen Debug-Funktionen aus `zz_debuglogs` in `SystemCheck.debug_logs` integriert.
|
|
114
|
+
- Vorbereitung für zukünftige Diagnose-Erweiterungen (Export, Systemprüfung, Plausibilitäts-Checks).
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
### 0.1.2
|
|
118
|
+
- Verbesserung: Beim Adapterstart werden nun die letzten bekannten Temperaturwerte aller aktiven Sensoren (z. B. Oberfläche, Kollektor, Außentemperatur usw.) automatisch übernommen.
|
|
119
|
+
- Dadurch werden auch Sensoren korrekt angezeigt, die ihren Messwert nur selten aktualisieren (z. B. Homematic oder stromsparende Funk-Sensoren).
|
|
120
|
+
- Keine Änderung am Verhalten der restlichen Logik, reine Komfort- und Stabilitätsverbesserung.
|
|
121
|
+
|
|
97
122
|
### 0.1.1
|
|
98
123
|
- Fehlerbehebung: Endlosschleife zwischen `pump_switch` und externer Steckdose (`deviceId`) behoben, die bei bestimmten Smart-Steckdosen (z. B. Shelly, Tasmota, FritzDECT) auftreten konnte.
|
|
99
124
|
- Verbesserte Stabilität im `pumpHelper` durch interne Rückkopplungsprüfung.
|
package/admin/jsonConfig.json
CHANGED
|
@@ -557,7 +557,7 @@
|
|
|
557
557
|
"sm": 3,
|
|
558
558
|
"md": 3,
|
|
559
559
|
"lg": 3,
|
|
560
|
-
"xl": 3
|
|
560
|
+
"xl": 3
|
|
561
561
|
},
|
|
562
562
|
"time2_end": {
|
|
563
563
|
"type": "text",
|
|
@@ -958,6 +958,8 @@
|
|
|
958
958
|
"type": "number",
|
|
959
959
|
"label": "Strompreis (€ / kWh)",
|
|
960
960
|
"default": 0.35,
|
|
961
|
+
"min": 0,
|
|
962
|
+
"step": 0.01,
|
|
961
963
|
"xs": 12,
|
|
962
964
|
"sm": 3,
|
|
963
965
|
"md": 3,
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.2.0": {
|
|
7
|
+
"en": "New diagnostic area 'SystemCheck' for internal debug logs and future analysis tools.",
|
|
8
|
+
"de": "Neuer Diagnosebereich 'SystemCheck' für interne Debug-Logs und künftige Analysefunktionen.",
|
|
9
|
+
"ru": "Новая диагностическая область 'SystemCheck' для внутренних журналов отладки и будущих инструментов анализа.",
|
|
10
|
+
"pt": "Nova área de diagnóstico 'SystemCheck' para logs de depuração internos e futuras ferramentas de análise.",
|
|
11
|
+
"nl": "Nieuw diagnostisch gebied 'SystemCheck' voor interne foutopsporingslogboeken en toekomstige analysetools.",
|
|
12
|
+
"fr": "Nouvelle zone de diagnostic 'SystemCheck' pour les journaux de débogage internes et les futurs outils d'analyse.",
|
|
13
|
+
"it": "Nuova area diagnostica 'SystemCheck' per i log di debug interni e futuri strumenti di analisi.",
|
|
14
|
+
"es": "Nueva área de diagnóstico 'SystemCheck' para registros internos de depuración y futuras herramientas de análisis.",
|
|
15
|
+
"pl": "Nowy obszar diagnostyczny 'SystemCheck' dla wewnętrznych dzienników debugowania i przyszłych narzędzi analitycznych.",
|
|
16
|
+
"uk": "Нова діагностична область 'SystemCheck' для внутрішніх журналів налагодження та майбутніх інструментів аналізу.",
|
|
17
|
+
"zh-cn": "新的诊断区域“SystemCheck”,用于内部调试日志和未来的分析工具。"
|
|
18
|
+
},
|
|
19
|
+
"0.1.2": {
|
|
20
|
+
"en": "Improved: The last known temperature values of all active sensors are now read at adapter start. Sensors that report rarely are now displayed correctly.",
|
|
21
|
+
"de": "Verbesserung: Beim Adapterstart werden nun die letzten bekannten Temperaturwerte aller aktiven Sensoren übernommen. Sensoren, die nur selten Werte senden, werden jetzt korrekt angezeigt.",
|
|
22
|
+
"ru": "Улучшение: При запуске адаптера теперь считываются последние известные значения температуры всех активных датчиков.",
|
|
23
|
+
"pt": "Melhoria: Os últimos valores de temperatura conhecidos de todos os sensores ativos agora são lidos ao iniciar o adaptador.",
|
|
24
|
+
"nl": "Verbetering: Bij het starten van de adapter worden nu de laatst bekende temperatuurwaarden van alle actieve sensoren opgehaald.",
|
|
25
|
+
"fr": "Amélioration : les dernières valeurs de température connues de tous les capteurs actifs sont désormais lues au démarrage de l’adaptateur.",
|
|
26
|
+
"it": "Miglioramento: all'avvio dell'adattatore vengono ora letti gli ultimi valori di temperatura noti di tutti i sensori attivi.",
|
|
27
|
+
"es": "Mejora: ahora se leen al iniciar el adaptador los últimos valores de temperatura conocidos de todos los sensores activos.",
|
|
28
|
+
"pl": "Ulepszenie: podczas uruchamiania adaptera odczytywane są ostatnie znane wartości temperatury wszystkich aktywnych czujników.",
|
|
29
|
+
"uk": "Покращення: під час запуску адаптера тепер зчитуються останні відомі значення температури всіх активних датчиків.",
|
|
30
|
+
"zh-cn": "改进:适配器启动时现在会读取所有活动传感器的最后已知温度值。"
|
|
31
|
+
},
|
|
6
32
|
"0.1.1": {
|
|
7
33
|
"en": "Fixed endless loop between pump_switch and deviceId for some smart sockets",
|
|
8
34
|
"de": "Endlosschleife zwischen pump_switch und Steckdose (deviceId) behoben",
|
|
@@ -4,21 +4,32 @@
|
|
|
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) mit additiver Logik über Neustarts
|
|
8
8
|
* - Offset-Mechanismus: summiert alte Werte bei Zählerwechsel/Reset auf
|
|
9
|
+
* - NEU: Erhält Tages-/Wochen-/Monats-/Jahreswerte über Neustarts (keine Datenpunkte neu!)
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
const consumptionHelper = {
|
|
12
13
|
adapter: null,
|
|
13
|
-
energyId: null,
|
|
14
|
-
price: 0,
|
|
15
|
-
baselines: {},
|
|
14
|
+
energyId: null,
|
|
15
|
+
price: 0,
|
|
16
|
+
baselines: {},
|
|
16
17
|
resetTimer: null,
|
|
17
18
|
|
|
19
|
+
// interne Speicher für additive Berechnung
|
|
20
|
+
lastKnownPrice: 0,
|
|
21
|
+
baseTotalKwh: 0,
|
|
22
|
+
baseTotalEur: 0,
|
|
23
|
+
|
|
18
24
|
init(adapter) {
|
|
19
25
|
this.adapter = adapter;
|
|
20
26
|
this.energyId = adapter.config.external_energy_total_id || null;
|
|
21
|
-
|
|
27
|
+
|
|
28
|
+
// Komma/Punkt tolerant interpretieren
|
|
29
|
+
this.price = parseFloat(String(adapter.config.energy_price_eur_kwh).replace(',', '.')) || 0;
|
|
30
|
+
this.lastKnownPrice = this.price;
|
|
31
|
+
|
|
32
|
+
this.adapter.log.info(`[consumptionHelper] Strompreis: ${this.price} €/kWh`);
|
|
22
33
|
|
|
23
34
|
if (this.energyId) {
|
|
24
35
|
adapter.subscribeForeignStates(this.energyId);
|
|
@@ -27,15 +38,31 @@ const consumptionHelper = {
|
|
|
27
38
|
adapter.log.info('[consumptionHelper] Kein externer kWh-Zähler konfiguriert → Verbrauchslogik inaktiv.');
|
|
28
39
|
}
|
|
29
40
|
|
|
30
|
-
// Reset-Timer für Mitternacht
|
|
31
41
|
this._scheduleDailyReset();
|
|
42
|
+
this._loadCostBaselines(); // Basiswerte laden
|
|
43
|
+
this._restoreBaselinesFromStates(); // <-- NEU: Baselines aus bestehenden States wiederherstellen
|
|
32
44
|
},
|
|
33
45
|
|
|
34
|
-
async
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
async _loadCostBaselines() {
|
|
47
|
+
try {
|
|
48
|
+
const totalKwh = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
|
|
49
|
+
const totalEur = (await this.adapter.getStateAsync('costs.total_eur'))?.val || 0;
|
|
50
|
+
|
|
51
|
+
this.baseTotalKwh = totalKwh;
|
|
52
|
+
this.baseTotalEur = totalEur;
|
|
53
|
+
|
|
54
|
+
this.adapter.log.info(
|
|
55
|
+
`[consumptionHelper] Kosten-Basis geladen → ${this.baseTotalEur.toFixed(
|
|
56
|
+
2,
|
|
57
|
+
)} € bei ${this.baseTotalKwh.toFixed(3)} kWh`,
|
|
58
|
+
);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
this.adapter.log.warn(`[consumptionHelper] Fehler beim Laden der Kosten-Basis: ${err.message}`);
|
|
37
61
|
}
|
|
38
|
-
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
async handleStateChange(id, state) {
|
|
65
|
+
if (!state || id !== this.energyId) {
|
|
39
66
|
return;
|
|
40
67
|
}
|
|
41
68
|
|
|
@@ -49,37 +76,27 @@ const consumptionHelper = {
|
|
|
49
76
|
|
|
50
77
|
async _updateConsumption(totalNowRaw) {
|
|
51
78
|
try {
|
|
52
|
-
// Offset laden
|
|
53
79
|
const offset = (await this.adapter.getStateAsync('consumption.offset_kwh'))?.val || 0;
|
|
54
80
|
const last = (await this.adapter.getStateAsync('consumption.last_total_kwh'))?.val || 0;
|
|
55
|
-
|
|
56
81
|
let totalNow = totalNowRaw;
|
|
57
82
|
|
|
58
|
-
//
|
|
83
|
+
// Reset-Erkennung
|
|
59
84
|
if (totalNowRaw < last) {
|
|
60
85
|
this.adapter.log.warn('[consumptionHelper] Zähler-Reset erkannt → Offset wird angepasst');
|
|
61
86
|
const newOffset = offset + last;
|
|
62
|
-
await this.adapter.setStateAsync('consumption.offset_kwh', {
|
|
63
|
-
val: newOffset,
|
|
64
|
-
ack: true,
|
|
65
|
-
});
|
|
87
|
+
await this.adapter.setStateAsync('consumption.offset_kwh', { val: newOffset, ack: true });
|
|
66
88
|
totalNow = newOffset + totalNowRaw;
|
|
67
89
|
} else {
|
|
68
90
|
totalNow = offset + totalNowRaw;
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
await this.adapter.setStateAsync('consumption.total_kwh', {
|
|
73
|
-
val: totalNow,
|
|
74
|
-
ack: true,
|
|
75
|
-
});
|
|
93
|
+
await this.adapter.setStateAsync('consumption.total_kwh', { val: totalNow, ack: true });
|
|
76
94
|
|
|
77
95
|
// Baselines laden (falls leer)
|
|
78
96
|
if (Object.keys(this.baselines).length === 0) {
|
|
79
97
|
await this._loadBaselines(totalNow);
|
|
80
98
|
}
|
|
81
99
|
|
|
82
|
-
// Differenzen berechnen
|
|
83
100
|
const values = {
|
|
84
101
|
day: totalNow - (this.baselines.day ?? totalNow),
|
|
85
102
|
week: totalNow - (this.baselines.week ?? totalNow),
|
|
@@ -87,7 +104,7 @@ const consumptionHelper = {
|
|
|
87
104
|
year: totalNow - (this.baselines.year ?? totalNow),
|
|
88
105
|
};
|
|
89
106
|
|
|
90
|
-
// Negative Werte
|
|
107
|
+
// Negative Werte vermeiden
|
|
91
108
|
for (const key of Object.keys(values)) {
|
|
92
109
|
if (values[key] < 0) {
|
|
93
110
|
this.baselines[key] = totalNow;
|
|
@@ -95,11 +112,19 @@ const consumptionHelper = {
|
|
|
95
112
|
}
|
|
96
113
|
}
|
|
97
114
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
// === Additive Kostenberechnung über Neustarts ===
|
|
116
|
+
const deltaKwh = Math.max(0, totalNow - this.baseTotalKwh);
|
|
117
|
+
const deltaEur = deltaKwh * this.price;
|
|
118
|
+
const totalCost = this.baseTotalEur + deltaEur;
|
|
119
|
+
|
|
120
|
+
// Tages-/Wochen-/Monats-/Jahreskosten berechnen (nur Momentansicht)
|
|
121
|
+
const dayCost = values.day * this.price;
|
|
122
|
+
const weekCost = values.week * this.price;
|
|
123
|
+
const monthCost = values.month * this.price;
|
|
124
|
+
const yearCost = values.year * this.price;
|
|
125
|
+
|
|
126
|
+
// States schreiben
|
|
127
|
+
await this.adapter.setStateAsync('consumption.day_kwh', { val: Number(values.day.toFixed(3)), ack: true });
|
|
103
128
|
await this.adapter.setStateAsync('consumption.week_kwh', {
|
|
104
129
|
val: Number(values.week.toFixed(3)),
|
|
105
130
|
ack: true,
|
|
@@ -113,40 +138,25 @@ const consumptionHelper = {
|
|
|
113
138
|
ack: true,
|
|
114
139
|
});
|
|
115
140
|
|
|
116
|
-
// Kosten berechnen
|
|
117
141
|
if (this.price > 0) {
|
|
118
|
-
await this.adapter.setStateAsync('costs.day_eur', {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
await this.adapter.setStateAsync('costs.
|
|
123
|
-
val: Number((values.week * this.price).toFixed(2)),
|
|
124
|
-
ack: true,
|
|
125
|
-
});
|
|
126
|
-
await this.adapter.setStateAsync('costs.month_eur', {
|
|
127
|
-
val: Number((values.month * this.price).toFixed(2)),
|
|
128
|
-
ack: true,
|
|
129
|
-
});
|
|
130
|
-
await this.adapter.setStateAsync('costs.year_eur', {
|
|
131
|
-
val: Number((values.year * this.price).toFixed(2)),
|
|
132
|
-
ack: true,
|
|
133
|
-
});
|
|
134
|
-
await this.adapter.setStateAsync('costs.total_eur', {
|
|
135
|
-
val: Number((totalNow * this.price).toFixed(2)),
|
|
136
|
-
ack: true,
|
|
137
|
-
});
|
|
142
|
+
await this.adapter.setStateAsync('costs.day_eur', { val: Number(dayCost.toFixed(2)), ack: true });
|
|
143
|
+
await this.adapter.setStateAsync('costs.week_eur', { val: Number(weekCost.toFixed(2)), ack: true });
|
|
144
|
+
await this.adapter.setStateAsync('costs.month_eur', { val: Number(monthCost.toFixed(2)), ack: true });
|
|
145
|
+
await this.adapter.setStateAsync('costs.year_eur', { val: Number(yearCost.toFixed(2)), ack: true });
|
|
146
|
+
await this.adapter.setStateAsync('costs.total_eur', { val: Number(totalCost.toFixed(2)), ack: true });
|
|
138
147
|
}
|
|
139
148
|
|
|
140
|
-
// Letzten
|
|
141
|
-
await this.adapter.setStateAsync('consumption.last_total_kwh', {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
// Letzten Zählerwert speichern
|
|
150
|
+
await this.adapter.setStateAsync('consumption.last_total_kwh', { val: totalNowRaw, ack: true });
|
|
151
|
+
|
|
152
|
+
// NEU: aktuelle Baselines persistent sichern
|
|
153
|
+
await this._saveBaselines();
|
|
145
154
|
} catch (err) {
|
|
146
155
|
this.adapter.log.warn(`[consumptionHelper] Fehler bei Verbrauchsupdate: ${err.message}`);
|
|
147
156
|
}
|
|
148
157
|
},
|
|
149
158
|
|
|
159
|
+
// Lädt Baselines beim Start, wenn im Speicher leer
|
|
150
160
|
async _loadBaselines(totalNow) {
|
|
151
161
|
this.baselines = {};
|
|
152
162
|
const day = (await this.adapter.getStateAsync('consumption.day_kwh'))?.val;
|
|
@@ -162,6 +172,89 @@ const consumptionHelper = {
|
|
|
162
172
|
this.adapter.log.info(`[consumptionHelper] Baselines geladen: ${JSON.stringify(this.baselines)}`);
|
|
163
173
|
},
|
|
164
174
|
|
|
175
|
+
// NEU: Baselines aus States rekonstruieren (beim Adapterstart)
|
|
176
|
+
async _restoreBaselinesFromStates() {
|
|
177
|
+
try {
|
|
178
|
+
const totalNow = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
|
|
179
|
+
const day = (await this.adapter.getStateAsync('consumption.day_kwh'))?.val || 0;
|
|
180
|
+
const week = (await this.adapter.getStateAsync('consumption.week_kwh'))?.val || 0;
|
|
181
|
+
const month = (await this.adapter.getStateAsync('consumption.month_kwh'))?.val || 0;
|
|
182
|
+
const year = (await this.adapter.getStateAsync('consumption.year_kwh'))?.val || 0;
|
|
183
|
+
|
|
184
|
+
this.baselines.day = totalNow - day;
|
|
185
|
+
this.baselines.week = totalNow - week;
|
|
186
|
+
this.baselines.month = totalNow - month;
|
|
187
|
+
this.baselines.year = totalNow - year;
|
|
188
|
+
|
|
189
|
+
this.adapter.log.info(`[consumptionHelper] Bestehende Verbrauchswerte wiederhergestellt.`);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
this.adapter.log.warn(
|
|
192
|
+
`[consumptionHelper] Fehler beim Wiederherstellen der Verbrauchsstände: ${err.message}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// NEU: Aktuelle Baselines speichern (persistente Sicherung)
|
|
198
|
+
async _saveBaselines() {
|
|
199
|
+
try {
|
|
200
|
+
await this.adapter.setStateAsync('consumption.day_kwh', {
|
|
201
|
+
val: (await this.adapter.getStateAsync('consumption.day_kwh'))?.val,
|
|
202
|
+
ack: true,
|
|
203
|
+
});
|
|
204
|
+
await this.adapter.setStateAsync('consumption.week_kwh', {
|
|
205
|
+
val: (await this.adapter.getStateAsync('consumption.week_kwh'))?.val,
|
|
206
|
+
ack: true,
|
|
207
|
+
});
|
|
208
|
+
await this.adapter.setStateAsync('consumption.month_kwh', {
|
|
209
|
+
val: (await this.adapter.getStateAsync('consumption.month_kwh'))?.val,
|
|
210
|
+
ack: true,
|
|
211
|
+
});
|
|
212
|
+
await this.adapter.setStateAsync('consumption.year_kwh', {
|
|
213
|
+
val: (await this.adapter.getStateAsync('consumption.year_kwh'))?.val,
|
|
214
|
+
ack: true,
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
this.adapter.log.warn(`[consumptionHelper] Fehler beim Speichern der Baselines: ${err.message}`);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// === NEU: Manueller Gesamreset für Verbrauch & Kosten ====================
|
|
222
|
+
async resetAll(adapter) {
|
|
223
|
+
try {
|
|
224
|
+
this.adapter = adapter;
|
|
225
|
+
adapter.log.warn('[consumptionHelper] Manueller Reset aller Verbrauchs- und Kostendaten');
|
|
226
|
+
|
|
227
|
+
// Verbrauchswerte nullen
|
|
228
|
+
const consumptionKeys = [
|
|
229
|
+
'day_kwh',
|
|
230
|
+
'week_kwh',
|
|
231
|
+
'month_kwh',
|
|
232
|
+
'year_kwh',
|
|
233
|
+
'total_kwh',
|
|
234
|
+
'offset_kwh',
|
|
235
|
+
'last_total_kwh',
|
|
236
|
+
];
|
|
237
|
+
for (const key of consumptionKeys) {
|
|
238
|
+
await adapter.setStateAsync(`consumption.${key}`, { val: 0, ack: true });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Kostenwerte nullen
|
|
242
|
+
const costKeys = ['day_eur', 'week_eur', 'month_eur', 'year_eur', 'total_eur'];
|
|
243
|
+
for (const key of costKeys) {
|
|
244
|
+
await adapter.setStateAsync(`costs.${key}`, { val: 0, ack: true });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// interne Speicher ebenfalls zurücksetzen
|
|
248
|
+
this.baselines = {};
|
|
249
|
+
this.baseTotalKwh = 0;
|
|
250
|
+
this.baseTotalEur = 0;
|
|
251
|
+
|
|
252
|
+
adapter.log.info('[consumptionHelper] Verbrauch und Kosten erfolgreich auf 0 gesetzt');
|
|
253
|
+
} catch (err) {
|
|
254
|
+
this.adapter.log.error(`[consumptionHelper] Fehler beim manuellen Reset: ${err.message}`);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
165
258
|
_scheduleDailyReset() {
|
|
166
259
|
const now = new Date();
|
|
167
260
|
const nextMidnight = new Date(now);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* controlHelper
|
|
5
|
+
* - Überwacht States im Bereich "control"
|
|
6
|
+
* - Steuert manuelle Aktionen: Rückspülung, Wartungsmodus
|
|
7
|
+
* - Synchronisiert mit Pumpenmodus und Sprach-/Benachrichtigungssystem
|
|
8
|
+
* - Erweiterung: automatische Sprach- und Benachrichtigungs-Ausgabe bei Start/Ende
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
let adapter;
|
|
12
|
+
let backwashTimer = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialisiert den Control-Helper.
|
|
16
|
+
*
|
|
17
|
+
* @param {import('iobroker').Adapter} a - ioBroker Adapterinstanz
|
|
18
|
+
*/
|
|
19
|
+
function init(a) {
|
|
20
|
+
adapter = a;
|
|
21
|
+
adapter.log.info('[controlHelper] initialisiert');
|
|
22
|
+
|
|
23
|
+
// States abonnieren
|
|
24
|
+
adapter.subscribeStates('control.season.active');
|
|
25
|
+
adapter.subscribeStates('control.pump.backwash_start');
|
|
26
|
+
adapter.subscribeStates('control.pump.maintenance_active');
|
|
27
|
+
adapter.subscribeStates('control.energy.reset'); // NEU: Energie-Reset-Button
|
|
28
|
+
|
|
29
|
+
adapter.log.debug('[controlHelper] Überwachung der Control-States aktiviert');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reagiert auf Änderungen der States im Bereich control.*
|
|
34
|
+
*
|
|
35
|
+
* @param {string} id - ID des geänderten States
|
|
36
|
+
* @param {ioBroker.State} state - Neuer State-Wert
|
|
37
|
+
*/
|
|
38
|
+
async function handleStateChange(id, state) {
|
|
39
|
+
try {
|
|
40
|
+
if (!state || state.ack) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// === SAISONSTATUS =====================================================
|
|
45
|
+
if (id.endsWith('control.season.active')) {
|
|
46
|
+
const newVal = !!state.val;
|
|
47
|
+
adapter.log.info(`[controlHelper] Poolsaison wurde ${newVal ? 'aktiviert' : 'deaktiviert'}.`);
|
|
48
|
+
await adapter.setStateAsync('status.season_active', { val: newVal, ack: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// === WARTUNGSMODUS ====================================================
|
|
52
|
+
if (id.endsWith('control.pump.maintenance_active')) {
|
|
53
|
+
const active = !!state.val;
|
|
54
|
+
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
55
|
+
|
|
56
|
+
if (active) {
|
|
57
|
+
await adapter.setStateAsync('pump.mode', { val: 'override', ack: true });
|
|
58
|
+
await adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
59
|
+
adapter.log.info('[controlHelper] Wartungsmodus aktiviert. Automatik pausiert.');
|
|
60
|
+
|
|
61
|
+
if (notify) {
|
|
62
|
+
await sendNotification(
|
|
63
|
+
'Wartungsmodus aktiviert. Automatikfunktionen sind vorübergehend deaktiviert.',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
await adapter.setStateAsync('pump.mode', { val: 'auto', ack: true });
|
|
68
|
+
adapter.log.info('[controlHelper] Wartungsmodus beendet. Automatik wieder aktiv.');
|
|
69
|
+
|
|
70
|
+
if (notify) {
|
|
71
|
+
await sendNotification('Wartungsmodus beendet. Automatikbetrieb ist wieder aktiv.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// === RÜCKSPÜLUNG ======================================================
|
|
77
|
+
if (id.endsWith('control.pump.backwash_start') && state.val === true) {
|
|
78
|
+
const duration = (await adapter.getStateAsync('control.pump.backwash_duration'))?.val || 1;
|
|
79
|
+
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
80
|
+
const prevMode = (await adapter.getStateAsync('pump.mode'))?.val || 'auto';
|
|
81
|
+
const active = (await adapter.getStateAsync('control.pump.backwash_active'))?.val;
|
|
82
|
+
|
|
83
|
+
if (active) {
|
|
84
|
+
adapter.log.warn('[controlHelper] Rückspülung bereits aktiv – neuer Start abgelehnt.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await adapter.setStateAsync('control.pump.backwash_active', { val: true, ack: true });
|
|
89
|
+
await adapter.setStateAsync('pump.mode', { val: 'override', ack: true });
|
|
90
|
+
await adapter.setStateAsync('pump.pump_switch', { val: true, ack: false });
|
|
91
|
+
|
|
92
|
+
const durationText = duration === 1 ? 'eine Minute' : `${duration} Minuten`;
|
|
93
|
+
adapter.log.info(`[controlHelper] Rückspülung gestartet (Dauer: ${duration} Minuten).`);
|
|
94
|
+
|
|
95
|
+
if (notify) {
|
|
96
|
+
await sendNotification(`Rückspülung gestartet. Dauer ${durationText}.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (backwashTimer) {
|
|
100
|
+
clearTimeout(backwashTimer);
|
|
101
|
+
}
|
|
102
|
+
backwashTimer = setTimeout(
|
|
103
|
+
async () => {
|
|
104
|
+
try {
|
|
105
|
+
await adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
106
|
+
await adapter.setStateAsync('pump.mode', { val: prevMode, ack: true });
|
|
107
|
+
await adapter.setStateAsync('control.pump.backwash_active', { val: false, ack: true });
|
|
108
|
+
|
|
109
|
+
adapter.log.info('[controlHelper] Rückspülung beendet. Automatik wieder aktiv.');
|
|
110
|
+
|
|
111
|
+
if (notify) {
|
|
112
|
+
await sendNotification('Rückspülung abgeschlossen. Automatikmodus wieder aktiv.');
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
adapter.log.warn(`[controlHelper] Fehler beim Beenden der Rückspülung: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
duration * 60 * 1000,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// === ENERGIEZÄHLER RESET =============================================
|
|
123
|
+
if (id.endsWith('control.energy.reset') && state.val === true) {
|
|
124
|
+
const now = new Date();
|
|
125
|
+
const timestamp = now.toLocaleString('de-DE');
|
|
126
|
+
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
127
|
+
|
|
128
|
+
adapter.log.info(`[controlHelper] Energiezähler-Reset ausgelöst (${timestamp})`);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Den eigentlichen Reset im consumptionHelper ausführen
|
|
132
|
+
const consumptionHelper = require('../helpers/consumptionHelper');
|
|
133
|
+
if (consumptionHelper && typeof consumptionHelper.resetAll === 'function') {
|
|
134
|
+
await consumptionHelper.resetAll(adapter);
|
|
135
|
+
} else {
|
|
136
|
+
adapter.log.warn('[controlHelper] consumptionHelper.resetAll() nicht verfügbar');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Button wieder zurücksetzen
|
|
140
|
+
await adapter.setStateAsync('control.energy.reset', { val: false, ack: true });
|
|
141
|
+
|
|
142
|
+
// Log & Sprach-/Benachrichtigungsausgabe
|
|
143
|
+
const msg = `Energiezähler wurde am ${timestamp} vollständig zurückgesetzt.`;
|
|
144
|
+
adapter.log.info(`[controlHelper] ${msg}`);
|
|
145
|
+
|
|
146
|
+
if (notify) {
|
|
147
|
+
await sendNotification(msg);
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
adapter.log.error(`[controlHelper] Fehler beim Energiezähler-Reset: ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (err) {
|
|
154
|
+
adapter.log.error(`[controlHelper] Fehler bei State-Änderung: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Leitet Benachrichtigungen an speechHelper weiter
|
|
160
|
+
* (setzt nur speech.last_text, Versand erfolgt dort)
|
|
161
|
+
*
|
|
162
|
+
* @param {string} text - Nachrichtentext, der gesendet werden soll
|
|
163
|
+
*/
|
|
164
|
+
async function sendNotification(text) {
|
|
165
|
+
if (!text) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await adapter.setStateAsync('speech.last_text', { val: text, ack: false });
|
|
171
|
+
adapter.log.debug(`[controlHelper] Benachrichtigung an speechHelper weitergeleitet: ${text}`);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
adapter.log.warn(`[controlHelper] Fehler beim Weiterleiten der Benachrichtigung: ${err.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Stoppt Timer und räumt Ressourcen auf.
|
|
179
|
+
*/
|
|
180
|
+
function cleanup() {
|
|
181
|
+
if (backwashTimer) {
|
|
182
|
+
clearTimeout(backwashTimer);
|
|
183
|
+
backwashTimer = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = { init, handleStateChange, cleanup };
|