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 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.
@@ -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.1.1",
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, // Objekt-ID des externen kWh-Zählers
14
- price: 0, // Strompreis €/kWh
15
- baselines: {}, // { day, week, month, year }
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
- this.price = adapter.config.energy_price_eur_kwh || 0;
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 handleStateChange(id, state) {
35
- if (!state) {
36
- return;
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
- if (id !== this.energyId) {
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
- // Prüfen: Reset oder neues Gerät?
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
- // Aktuellen Gesamtwert setzen
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 abfangen (bei Reset)
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
- // In States schreiben
99
- await this.adapter.setStateAsync('consumption.day_kwh', {
100
- val: Number(values.day.toFixed(3)),
101
- ack: true,
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
- val: Number((values.day * this.price).toFixed(2)),
120
- ack: true,
121
- });
122
- await this.adapter.setStateAsync('costs.week_eur', {
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 Stand des externen Zählers merken
141
- await this.adapter.setStateAsync('consumption.last_total_kwh', {
142
- val: totalNowRaw,
143
- ack: true,
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 };