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.
@@ -21,9 +21,24 @@ const runtimeHelper = {
21
21
  resetTimer: null,
22
22
  liveTimer: null, // Timer für Live-Updates
23
23
 
24
- init(adapter) {
24
+ /**
25
+ * Initialisiert den Runtime-Helper.
26
+ * Führt eine kurze Startverzögerung ein, um sicherzustellen,
27
+ * dass persistente States nach einer Überinstallation korrekt geladen werden.
28
+ *
29
+ * @param {ioBroker.Adapter} adapter - Aktive ioBroker-Adapterinstanz.
30
+ * @returns {Promise<void>}
31
+ */
32
+ async init(adapter) {
25
33
  this.adapter = adapter;
26
34
 
35
+ // ------------------------------------------------------
36
+ // NEU: Kurze Startverzögerung, damit ioBroker persistente States
37
+ // vollständig aus der Datenbank laden kann (Überinstallationsschutz)
38
+ // ------------------------------------------------------
39
+ this.adapter.log.debug('[runtimeHelper] Warte 3 Sekunden, um persistente States zu laden ...');
40
+ await new Promise(resolve => setTimeout(resolve, 3000));
41
+
27
42
  // Pumpenschalter überwachen
28
43
  this.adapter.subscribeStates('pump.pump_switch');
29
44
 
@@ -52,6 +67,13 @@ const runtimeHelper = {
52
67
  const seasonRaw = (await this.adapter.getStateAsync('runtime.season_total'))?.val;
53
68
  const countRaw = (await this.adapter.getStateAsync('runtime.start_count_today'))?.val;
54
69
 
70
+ // FIX: Falls States leer oder neu angelegt sind, Warnhinweis ausgeben und Werte nicht überschreiben
71
+ if (!totalRaw && !seasonRaw) {
72
+ this.adapter.log.info(
73
+ '[runtimeHelper] Keine gespeicherten Laufzeiten gefunden – möglicherweise neue oder überinstallierte Instanz. Laufzeiten starten bei 0.',
74
+ );
75
+ }
76
+
55
77
  // >>> NEU: Formatierten Text (z. B. "3h 12m 5s") in Sekunden umwandeln
56
78
  this.runtimeTotal = this._parseFormattedTimeToSeconds(totalRaw);
57
79
  this.runtimeToday = this._parseFormattedTimeToSeconds(todayRaw);
@@ -72,38 +94,72 @@ const runtimeHelper = {
72
94
  return;
73
95
  }
74
96
 
97
+ // FIX: Robuste Start-/Stop-Logik für pump.pump_switch
75
98
  if (id.endsWith('pump.pump_switch')) {
76
- if (state.val && !this.isRunning) {
77
- // Pumpe startet
78
- this.isRunning = true;
79
- this.lastOn = Date.now();
80
- this.startCountToday += 1;
81
-
82
- // Live-Timer starten (jede Minute)
83
- this._startLiveTimer();
84
-
85
- // Start sofort in State schreiben
86
- await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
87
- } else if (!state.val && this.isRunning) {
88
- // Pumpe stoppt
89
- const delta = Math.floor((Date.now() - this.lastOn) / 1000);
90
- this.runtimeToday += delta;
91
- this.runtimeTotal += delta;
92
-
93
- // Saisonlaufzeit nur zählen, wenn aktiv
94
- const seasonActive = !!(await this.adapter.getStateAsync('control.season.active'))?.val;
95
- if (seasonActive) {
96
- this.runtimeSeason += delta;
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;
97
162
  }
98
-
99
- this.isRunning = false;
100
- this.lastOn = null;
101
-
102
- // Live-Timer stoppen
103
- this._stopLiveTimer();
104
-
105
- // States final aktualisieren
106
- await this._updateStates();
107
163
  }
108
164
  }
109
165
  },
@@ -134,6 +190,16 @@ const runtimeHelper = {
134
190
  await this.adapter.setStateAsync('runtime.season_total', { val: formattedSeason, ack: true });
135
191
  await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
136
192
 
193
+ // Poolparameter laden (vor Durchflussprüfung!)
194
+ const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
195
+ const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
196
+
197
+ // daily_required immer direkt setzen – auch ohne Durchfluss
198
+ const dailyRequired = Math.round(poolSize * minCirc);
199
+ if (dailyRequired > 0) {
200
+ await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
201
+ }
202
+
137
203
  // Umwälzmenge berechnen
138
204
  // Reeller Durchflusswert aus pump.live.flow_current_lh
139
205
  const liveFlowLh = (await this.adapter.getStateAsync('pump.live.flow_current_lh'))?.val || 0;
@@ -143,19 +209,26 @@ const runtimeHelper = {
143
209
  return;
144
210
  }
145
211
 
146
- // Poolparameter laden
147
- const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
148
- const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
149
-
150
212
  // Berechnung der realen Tagesumwälzung (Liter)
151
213
  const dailyTotal = Math.round((effectiveToday / 3600) * liveFlowLh);
152
- const dailyRequired = Math.round(poolSize * minCirc);
153
214
  const dailyRemaining = Math.max(dailyRequired - dailyTotal, 0);
154
215
 
155
- // Werte schreiben
156
- await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
157
- await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
158
- await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });
216
+ // Bestehende Werte für Total/Remaining laden
217
+ const oldTotal = (await this.adapter.getStateAsync('circulation.daily_total'))?.val || 0;
218
+ const oldRemaining = (await this.adapter.getStateAsync('circulation.daily_remaining'))?.val || 0;
219
+
220
+ // Nur schreiben, wenn tatsächlich sinnvolle Livewerte vorliegen
221
+ if (liveFlowLh > 0 && dailyTotal > 0) {
222
+ await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
223
+ await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });
224
+ this.adapter.log.debug(
225
+ `[runtimeHelper] Circulation-Werte aktualisiert (Total=${dailyTotal}, Required=${dailyRequired}, Remaining=${dailyRemaining})`,
226
+ );
227
+ } else {
228
+ this.adapter.log.debug(
229
+ `[runtimeHelper] Keine gültigen Live-Daten – bestehende Werte bleiben erhalten (Total=${oldTotal}, Required=${dailyRequired}, Remaining=${oldRemaining})`,
230
+ );
231
+ }
159
232
  } catch (err) {
160
233
  this.adapter.log.warn(`[runtimeHelper] Fehler beim Update der States: ${err.message}`);
161
234
  }
@@ -204,12 +277,37 @@ const runtimeHelper = {
204
277
  nextMidnight.setHours(24, 0, 0, 0);
205
278
  const msUntilMidnight = nextMidnight - now;
206
279
 
207
- this.resetTimer = setTimeout(() => {
280
+ this.resetTimer = setTimeout(async () => {
208
281
  this.runtimeToday = 0;
209
282
  this.startCountToday = 0;
210
283
  this.lastOn = this.isRunning ? Date.now() : null;
284
+
285
+ // Laufzeiten zurücksetzen
211
286
  this._updateStates();
287
+
288
+ // --- NEU: Circulation-Werte um Mitternacht zurücksetzen ---
289
+ await this.adapter.setStateAsync('circulation.daily_total', { val: 0, ack: true });
290
+
291
+ // daily_required neu berechnen (optional, falls sich Poolgröße geändert hat)
292
+ const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
293
+ const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
294
+ const dailyRequired = Math.round(poolSize * minCirc);
295
+ await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
296
+
297
+ // 👉 daily_remaining neue berechnen auf Grundlage von daily_required
298
+ await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRequired, ack: true });
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
+
307
+ // Nächsten Reset planen
212
308
  this._scheduleDailyReset();
309
+
310
+ this.adapter.log.debug('[runtimeHelper] Tagesreset (Runtime + Circulation) ausgeführt.');
213
311
  }, msUntilMidnight);
214
312
  },
215
313
 
@@ -217,8 +315,8 @@ const runtimeHelper = {
217
315
  if (this.liveTimer) {
218
316
  clearInterval(this.liveTimer);
219
317
  }
220
- this.liveTimer = setInterval(() => this._updateStates(), 60 * 1000);
221
- this.adapter.log.debug('[runtimeHelper] Live-Timer gestartet (Updates jede Minute)');
318
+ this.liveTimer = setInterval(() => this._updateStates(), 10 * 1000);
319
+ this.adapter.log.debug('[runtimeHelper] Live-Timer gestartet (Updates alle 10 Sekunden)');
222
320
  },
223
321
 
224
322
  _stopLiveTimer() {