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.
@@ -0,0 +1,193 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * debugLogHelper (SystemCheck-Version)
5
+ * - Überwacht nur einen auswählbaren Bereich (z. B. pump.*, solar.*, runtime.*)
6
+ * - Loggt Änderungen fortlaufend in SystemCheck.debug_logs.log
7
+ * - Kann per SystemCheck.debug_logs.clear geleert werden
8
+ * - target_area wird automatisch erkannt (dynamisch durch States-Datei)
9
+ */
10
+
11
+ const debugLogHelper = {
12
+ adapter: null,
13
+ currentTarget: 'none',
14
+ subscribedTarget: null,
15
+ lastChange: {},
16
+ thresholdMs: 2000, // Mindestzeit zwischen Änderungen (ms)
17
+ buffer: '',
18
+ bufferTimer: null,
19
+
20
+ /**
21
+ * Initialisierung des Debug-Helpers.
22
+ *
23
+ * @param {import('iobroker').Adapter} adapter - ioBroker Adapter-Instanz
24
+ */
25
+ async init(adapter) {
26
+ this.adapter = adapter;
27
+
28
+ // --- SystemCheck-Ordner sicherstellen ---
29
+ await this.adapter.setObjectNotExistsAsync('SystemCheck', {
30
+ type: 'channel',
31
+ common: { name: 'SystemCheck (Diagnose und Tools)' },
32
+ native: {},
33
+ });
34
+
35
+ // States für clear und target_area überwachen
36
+ adapter.subscribeStates('SystemCheck.debug_logs.clear');
37
+ adapter.subscribeStates('SystemCheck.debug_logs.target_area');
38
+
39
+ // Initialwert für target_area lesen
40
+ const target = (await adapter.getStateAsync('SystemCheck.debug_logs.target_area'))?.val || 'none';
41
+ this.currentTarget = target;
42
+ if (target !== 'none') {
43
+ this._subscribeTarget(target);
44
+ } else {
45
+ adapter.log.info('[debugLogHelper] Kein Bereich ausgewählt – Logger inaktiv.');
46
+ }
47
+
48
+ adapter.log.info('[debugLogHelper] Initialisierung abgeschlossen');
49
+ },
50
+
51
+ /**
52
+ * Reagiert auf State-Änderungen
53
+ *
54
+ * @param {string} id - State-ID
55
+ * @param {ioBroker.State} state - State-Wert
56
+ */
57
+ async handleStateChange(id, state) {
58
+ if (!this.adapter || !state) {
59
+ return;
60
+ }
61
+
62
+ // Umschalten des überwachten Bereichs
63
+ if (id.endsWith('SystemCheck.debug_logs.target_area')) {
64
+ const newTarget = state.val || 'none';
65
+ await this._switchTarget(newTarget);
66
+ return;
67
+ }
68
+
69
+ // Clear-Button
70
+ if (id.endsWith('SystemCheck.debug_logs.clear') && state.val === true) {
71
+ await this._clearLog();
72
+ await this.adapter.setStateAsync('SystemCheck.debug_logs.clear', { val: false, ack: true });
73
+ return;
74
+ }
75
+
76
+ // Nur loggen, wenn der Bereich aktiv ist
77
+ if (!this.subscribedTarget || this.subscribedTarget === 'none') {
78
+ return;
79
+ }
80
+
81
+ // Nur Events aus dem überwachten Bereich aufnehmen
82
+ if (!id.includes(`.${this.subscribedTarget}.`)) {
83
+ return;
84
+ }
85
+
86
+ const now = Date.now();
87
+ const last = this.lastChange[id] || 0;
88
+ this.lastChange[id] = now;
89
+
90
+ if (now - last < this.thresholdMs) {
91
+ const msg = `[${new Date().toISOString()}] ${id} änderte sich zu schnell (${now - last} ms, val=${state.val}, ack=${state.ack})\n`;
92
+ await this._appendLog(msg);
93
+ }
94
+ },
95
+
96
+ /**
97
+ * Wechselt den aktiven Überwachungsbereich
98
+ *
99
+ * @param {string} newTarget - Name des neuen Bereichs (z.B. "pump", "solar", oder "none")
100
+ */
101
+ async _switchTarget(newTarget) {
102
+ if (this.subscribedTarget === newTarget) {
103
+ return;
104
+ }
105
+ if (this.subscribedTarget && this.subscribedTarget !== 'none') {
106
+ this.adapter.unsubscribeStates(`${this.subscribedTarget}.*`);
107
+ this.adapter.log.info(`[debugLogHelper] Überwachung für Bereich "${this.subscribedTarget}" beendet.`);
108
+ }
109
+
110
+ this.subscribedTarget = newTarget;
111
+
112
+ if (newTarget === 'none') {
113
+ this.adapter.log.info('[debugLogHelper] Kein Bereich aktiv.');
114
+ return;
115
+ }
116
+
117
+ this._subscribeTarget(newTarget);
118
+ this.adapter.log.info(`[debugLogHelper] Überwachung für Bereich "${newTarget}" gestartet.`);
119
+ await this._appendLog(
120
+ `\n=== Debug-Log gestartet: Bereich "${newTarget}" @ ${new Date().toLocaleString()} ===\n`,
121
+ );
122
+ },
123
+
124
+ /**
125
+ * Abonniert States für den angegebenen Bereich
126
+ *
127
+ * @param {string} target - Name des zu überwachenden Bereichs
128
+ */
129
+ _subscribeTarget(target) {
130
+ this.adapter.subscribeStates(`${target}.*`);
131
+ this.subscribedTarget = target;
132
+ },
133
+
134
+ /**
135
+ * Log anhängen (fortlaufend)
136
+ *
137
+ * @param {string} message - Text, der in das fortlaufende Log geschrieben wird
138
+ */
139
+ async _appendLog(message) {
140
+ try {
141
+ this.buffer += message;
142
+
143
+ // Schreibe alle 5 Sekunden oder ab 2 KB
144
+ if (this.buffer.length > 2000) {
145
+ await this._flushBuffer();
146
+ } else if (!this.bufferTimer) {
147
+ this.bufferTimer = setTimeout(() => this._flushBuffer(), 5000);
148
+ }
149
+ } catch (err) {
150
+ this.adapter.log.warn(`[debugLogHelper] Fehler beim Anhängen an Log: ${err.message}`);
151
+ }
152
+ },
153
+
154
+ async _flushBuffer() {
155
+ try {
156
+ if (!this.buffer) {
157
+ return;
158
+ }
159
+ const current = (await this.adapter.getStateAsync('SystemCheck.debug_logs.log'))?.val || '';
160
+ const newVal = current + this.buffer;
161
+ await this.adapter.setStateAsync('SystemCheck.debug_logs.log', {
162
+ val: newVal.slice(-60000),
163
+ ack: true,
164
+ }); // max ~60k Zeichen
165
+ this.buffer = '';
166
+ this.bufferTimer = null;
167
+ } catch (err) {
168
+ this.adapter.log.warn(`[debugLogHelper] Fehler beim Schreiben in Log: ${err.message}`);
169
+ }
170
+ },
171
+
172
+ /**
173
+ * Löscht das Log komplett
174
+ */
175
+ async _clearLog() {
176
+ try {
177
+ await this.adapter.setStateAsync('SystemCheck.debug_logs.log', { val: '', ack: true });
178
+ this.buffer = '';
179
+ this.adapter.log.info('[debugLogHelper] Debug-Log gelöscht');
180
+ } catch (err) {
181
+ this.adapter.log.warn(`[debugLogHelper] Fehler beim Löschen des Logs: ${err.message}`);
182
+ }
183
+ },
184
+
185
+ cleanup() {
186
+ if (this.bufferTimer) {
187
+ clearTimeout(this.bufferTimer);
188
+ }
189
+ this.adapter.log.debug('[debugLogHelper] Cleanup ausgeführt');
190
+ },
191
+ };
192
+
193
+ module.exports = debugLogHelper;
@@ -13,6 +13,8 @@ const pumpHelper = {
13
13
  adapter: null,
14
14
  deviceId: null, // Objekt-ID der echten Steckdose (aus Config)
15
15
  currentPowerId: null, // Fremd-State für Leistung (aus Config)
16
+ _lastPumpStart: null, // Zeitstempel letzter Start
17
+ _lastPumpStop: null, // Zeitstempel letzter Stopp
16
18
 
17
19
  init(adapter) {
18
20
  this.adapter = adapter;
@@ -134,6 +136,13 @@ const pumpHelper = {
134
136
  ack: false,
135
137
  });
136
138
  }
139
+
140
+ // Zeitstempel für Kulanzzeiten setzen
141
+ if (state.val === true) {
142
+ this._lastPumpStart = Date.now();
143
+ } else {
144
+ this._lastPumpStop = Date.now();
145
+ }
137
146
  }
138
147
 
139
148
  await this._updateStatus();
@@ -194,6 +203,22 @@ const pumpHelper = {
194
203
  const power = this._parseNumber((await this.adapter.getStateAsync('pump.current_power'))?.val);
195
204
  const maxWatt = this._parseNumber((await this.adapter.getStateAsync('pump.pump_max_watt'))?.val);
196
205
 
206
+ // --- NEU: Kulanzzeiten für Start/Stop ---
207
+ const graceOnMs = 5000; // 5 Sekunden nach Start ignorieren
208
+ const graceOffMs = 5000; // 5 Sekunden nach Stop ignorieren
209
+ const now = Date.now();
210
+
211
+ if (active === true && this._lastPumpStart && now - this._lastPumpStart < graceOnMs) {
212
+ this.adapter.log.debug('[pumpHelper] Innerhalb der Start-Kulanzzeit – Fehlerprüfung übersprungen');
213
+ return;
214
+ }
215
+
216
+ if (active === false && this._lastPumpStop && now - this._lastPumpStop < graceOffMs) {
217
+ this.adapter.log.debug('[pumpHelper] Innerhalb der Stopp-Kulanzzeit – Fehlerprüfung übersprungen');
218
+ return;
219
+ }
220
+ // --- Ende Kulanzzeiten ---
221
+
197
222
  let error = false;
198
223
  let errorMsg = '';
199
224
 
@@ -6,6 +6,7 @@
6
6
  * - Berechnet tägliche Umwälzmenge
7
7
  * - Schreibt Werte in die States (Objekte werden in runtimeStates.js angelegt)
8
8
  * - Nutzt den zentralen Boolean pump.pump_switch
9
+ * - NEU: zählt Starts, aktuelle Laufzeit, Saisonlaufzeit
9
10
  */
10
11
 
11
12
  const runtimeHelper = {
@@ -14,6 +15,8 @@ const runtimeHelper = {
14
15
  lastOn: null,
15
16
  runtimeTotal: 0, // Gesamtzeit (s)
16
17
  runtimeToday: 0, // Tageszeit (s)
18
+ runtimeSeason: 0, // Laufzeit der aktuellen Saison (s)
19
+ startCountToday: 0, // Anzahl Starts heute
17
20
  resetTimer: null,
18
21
  liveTimer: null, // Timer für Live-Updates
19
22
 
@@ -43,15 +46,16 @@ const runtimeHelper = {
43
46
  },
44
47
 
45
48
  async _restoreFromStates() {
46
- const total = Number((await this.adapter.getStateAsync('runtime.total'))?.val);
47
- const today = Number((await this.adapter.getStateAsync('runtime.today'))?.val);
49
+ const totalRaw = (await this.adapter.getStateAsync('runtime.total'))?.val;
50
+ const todayRaw = (await this.adapter.getStateAsync('runtime.today'))?.val;
51
+ const seasonRaw = (await this.adapter.getStateAsync('runtime.season_total'))?.val;
52
+ const countRaw = (await this.adapter.getStateAsync('runtime.start_count_today'))?.val;
48
53
 
49
- if (Number.isFinite(total)) {
50
- this.runtimeTotal = total;
51
- }
52
- if (Number.isFinite(today)) {
53
- this.runtimeToday = today;
54
- }
54
+ // Alle formatierten Werte werden intern wieder auf 0 gesetzt, Laufzeiten starten neu
55
+ this.runtimeTotal = Number(totalRaw) || 0;
56
+ this.runtimeToday = Number(todayRaw) || 0;
57
+ this.runtimeSeason = Number(seasonRaw) || 0;
58
+ this.startCountToday = Number(countRaw) || 0;
55
59
 
56
60
  // Falls Pumpe gerade läuft → Status wiederherstellen
57
61
  const active = !!(await this.adapter.getStateAsync('pump.pump_switch'))?.val;
@@ -72,14 +76,25 @@ const runtimeHelper = {
72
76
  // Pumpe startet
73
77
  this.isRunning = true;
74
78
  this.lastOn = Date.now();
79
+ this.startCountToday += 1;
75
80
 
76
81
  // Live-Timer starten (jede Minute)
77
82
  this._startLiveTimer();
83
+
84
+ // Start sofort in State schreiben
85
+ await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
78
86
  } else if (!state.val && this.isRunning) {
79
87
  // Pumpe stoppt
80
88
  const delta = Math.floor((Date.now() - this.lastOn) / 1000);
81
89
  this.runtimeToday += delta;
82
90
  this.runtimeTotal += delta;
91
+
92
+ // Saisonlaufzeit nur zählen, wenn aktiv
93
+ const seasonActive = !!(await this.adapter.getStateAsync('control.season.active'))?.val;
94
+ if (seasonActive) {
95
+ this.runtimeSeason += delta;
96
+ }
97
+
83
98
  this.isRunning = false;
84
99
  this.lastOn = null;
85
100
 
@@ -96,24 +111,27 @@ const runtimeHelper = {
96
111
  try {
97
112
  // Falls Pumpe läuft → temporäre Laufzeit seit lastOn einrechnen
98
113
  let effectiveToday = this.runtimeToday;
114
+ let effectiveSeason = this.runtimeSeason;
115
+ let currentSessionSeconds = 0;
116
+
99
117
  if (this.isRunning && this.lastOn) {
100
118
  const delta = Math.floor((Date.now() - this.lastOn) / 1000);
101
119
  effectiveToday += delta;
120
+ effectiveSeason += delta;
121
+ currentSessionSeconds = delta;
102
122
  }
103
123
 
104
- // Laufzeit-States setzen
105
- await this.adapter.setStateAsync('runtime.total', {
106
- val: this.runtimeTotal,
107
- ack: true,
108
- });
109
- await this.adapter.setStateAsync('runtime.today', {
110
- val: effectiveToday,
111
- ack: true,
112
- });
113
- await this.adapter.setStateAsync('runtime.formatted', {
114
- val: this._formatTime(effectiveToday),
115
- ack: true,
116
- });
124
+ // Formatiert schreiben
125
+ const formattedToday = this._formatTime(effectiveToday);
126
+ const formattedTotal = this._formatTime(this.runtimeTotal);
127
+ const formattedSeason = this._formatTime(effectiveSeason);
128
+ const formattedCurrent = this._formatTime(currentSessionSeconds);
129
+
130
+ await this.adapter.setStateAsync('runtime.total', { val: formattedTotal, ack: true });
131
+ await this.adapter.setStateAsync('runtime.today', { val: formattedToday, ack: true });
132
+ await this.adapter.setStateAsync('runtime.current_session', { val: formattedCurrent, ack: true });
133
+ await this.adapter.setStateAsync('runtime.season_total', { val: formattedSeason, ack: true });
134
+ await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
117
135
 
118
136
  // Umwälzmenge berechnen
119
137
  const pumpLph = (await this.adapter.getStateAsync('pump.pump_power_lph'))?.val || 0;
@@ -124,27 +142,20 @@ const runtimeHelper = {
124
142
  const dailyRequired = Math.round(poolSize * minCirc);
125
143
  const dailyRemaining = Math.max(dailyRequired - dailyTotal, 0);
126
144
 
127
- await this.adapter.setStateAsync('circulation.daily_total', {
128
- val: dailyTotal,
129
- ack: true,
130
- });
131
- await this.adapter.setStateAsync('circulation.daily_required', {
132
- val: dailyRequired,
133
- ack: true,
134
- });
135
- await this.adapter.setStateAsync('circulation.daily_remaining', {
136
- val: dailyRemaining,
137
- ack: true,
138
- });
145
+ await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
146
+ await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
147
+ await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });
139
148
  } catch (err) {
140
149
  this.adapter.log.warn(`[runtimeHelper] Fehler beim Update der States: ${err.message}`);
141
150
  }
142
151
  },
143
152
 
144
153
  _formatTime(seconds) {
154
+ seconds = Math.max(0, Math.floor(seconds));
145
155
  const h = Math.floor(seconds / 3600);
146
156
  const m = Math.floor((seconds % 3600) / 60);
147
- return `${h}h ${m}m`;
157
+ const s = seconds % 60;
158
+ return `${h}h ${m}m ${s}s`;
148
159
  },
149
160
 
150
161
  _scheduleDailyReset() {
@@ -155,6 +166,7 @@ const runtimeHelper = {
155
166
 
156
167
  this.resetTimer = setTimeout(() => {
157
168
  this.runtimeToday = 0;
169
+ this.startCountToday = 0;
158
170
  this.lastOn = this.isRunning ? Date.now() : null;
159
171
  this._updateStates();
160
172
  this._scheduleDailyReset();
@@ -8,6 +8,8 @@
8
8
 
9
9
  const speechHelper = {
10
10
  adapter: null,
11
+ lastTempNotify: {}, // Cooldown-Speicher pro Sensor
12
+ lastPumpState: null, // interner Speicher für letzten Pumpenzustand
11
13
 
12
14
  init(adapter) {
13
15
  this.adapter = adapter;
@@ -18,6 +20,10 @@ const speechHelper = {
18
20
  this.adapter.subscribeStates('speech.texts.*');
19
21
  this.adapter.subscribeStates('pump.error'); // Fehleransagen
20
22
  this.adapter.subscribeStates('temperature.*.current'); // Temp-Trigger
23
+ this.adapter.subscribeStates('pump.pump_switch'); // wichtig für Flankenerkennung
24
+
25
+ // NEU: auch speech.last_text überwachen (z. B. von controlHelper gesendet)
26
+ this.adapter.subscribeStates('speech.last_text');
21
27
 
22
28
  this.adapter.log.info('[speechHelper] initialisiert');
23
29
  },
@@ -33,6 +39,15 @@ const speechHelper = {
33
39
  return;
34
40
  }
35
41
 
42
+ // NEU: Direktnachricht von controlHelper über speech.last_text
43
+ if (id.endsWith('speech.last_text') && state.ack === false) {
44
+ const txt = String(state.val || '').trim();
45
+ if (txt) {
46
+ await this._speak(txt);
47
+ return;
48
+ }
49
+ }
50
+
36
51
  // Fehleransagen
37
52
  if (id.endsWith('pump.error') && state.val) {
38
53
  const includeErrors = this.adapter.config.speech_include_errors;
@@ -42,16 +57,26 @@ const speechHelper = {
42
57
  return;
43
58
  }
44
59
 
45
- // Beispiel: Pumpenstart / -stop
60
+ // === Pumpenstart / -stop nur bei Zustandswechsel ===
46
61
  if (id.endsWith('pump.pump_switch')) {
47
- if (state.val) {
48
- const txt =
49
- (await this.adapter.getStateAsync('speech.start_text'))?.val || 'Die Poolpumpe wurde gestartet.';
50
- await this._speak(txt);
62
+ const newVal = !!state.val;
63
+
64
+ // Nur wenn sich der Zustand wirklich geändert hat
65
+ if (this.lastPumpState !== newVal) {
66
+ this.lastPumpState = newVal;
67
+
68
+ if (newVal) {
69
+ const txt =
70
+ (await this.adapter.getStateAsync('speech.start_text'))?.val ||
71
+ 'Die Poolpumpe wurde gestartet.';
72
+ await this._speak(txt);
73
+ } else {
74
+ const txt =
75
+ (await this.adapter.getStateAsync('speech.end_text'))?.val || 'Die Poolpumpe wurde gestoppt.';
76
+ await this._speak(txt);
77
+ }
51
78
  } else {
52
- const txt =
53
- (await this.adapter.getStateAsync('speech.end_text'))?.val || 'Die Poolpumpe wurde gestoppt.';
54
- await this._speak(txt);
79
+ this.adapter.log.debug('[speechHelper] Ignoriere Pumpenmeldung – kein Zustandswechsel.');
55
80
  }
56
81
  return;
57
82
  }
@@ -61,7 +86,16 @@ const speechHelper = {
61
86
  const threshold = this.adapter.config.speech_temp_threshold || 0;
62
87
  const val = Number(state.val);
63
88
  if (val >= threshold && threshold > 0) {
64
- await this._speak(`Der Pool hat ${val} Grad erreicht.`);
89
+ const now = Date.now();
90
+ const last = this.lastTempNotify[id] || 0;
91
+
92
+ // Nur einmal pro Stunde pro Sensor
93
+ if (now - last > 60 * 60 * 1000) {
94
+ await this._speak(`Der Pool hat ${val} Grad erreicht.`);
95
+ this.lastTempNotify[id] = now;
96
+ } else {
97
+ this.adapter.log.debug(`[speechHelper] Temperaturansage für ${id} unterdrückt (Cooldown aktiv).`);
98
+ }
65
99
  }
66
100
  return;
67
101
  }
@@ -74,10 +108,7 @@ const speechHelper = {
74
108
  }
75
109
 
76
110
  // Letzten Text speichern
77
- await this.adapter.setStateAsync('speech.last_text', {
78
- val: text,
79
- ack: true,
80
- });
111
+ await this.adapter.setStateAsync('speech.last_text', { val: text, ack: true });
81
112
 
82
113
  // Alexa-Ausgabe
83
114
  if (this.adapter.config.speech_alexa_enabled && this.adapter.config.speech_alexa_device) {
@@ -85,33 +116,34 @@ const speechHelper = {
85
116
  this.adapter.log.info(`[speechHelper] Alexa sagt: ${text}`);
86
117
  }
87
118
 
88
- // Telegram-Ausgabe
119
+ // Telegram-Ausgabe (modern über sendTo)
89
120
  if (this.adapter.config.speech_telegram_enabled && this.adapter.config.speech_telegram_instance) {
90
121
  const instance = this.adapter.config.speech_telegram_instance;
91
- const sendState = `${instance}.communicate.sendMessage`;
92
- await this.adapter.setForeignStateAsync(sendState, {
93
- val: text,
94
- ack: false,
95
- });
96
- this.adapter.log.info(`[speechHelper] Telegram sendet: ${text}`);
122
+ try {
123
+ this.adapter.sendTo(instance, { text, parse_mode: 'Markdown' });
124
+ this.adapter.log.info(`[speechHelper] Telegram sendet: ${text}`);
125
+ } catch (err) {
126
+ this.adapter.log.warn(
127
+ `[speechHelper] Telegram-Versand fehlgeschlagen (${instance}): ${err.message}`,
128
+ );
129
+ }
97
130
  }
98
131
 
99
- // E-Mail-Ausgabe
132
+ // E-Mail-Ausgabe (modern über sendTo)
100
133
  if (this.adapter.config.speech_email_enabled && this.adapter.config.speech_email_instance) {
101
- const instance = this.adapter.config.speech_email_instance;
102
- const sendState = `${instance}.mail`;
103
-
104
- await this.adapter.setForeignStateAsync(sendState, {
105
- val: {
134
+ const instance = this.adapter.config.speech_email_instance; // z. B. "email.0"
135
+ try {
136
+ this.adapter.sendTo(instance, {
106
137
  to: this.adapter.config.speech_email_recipient,
107
138
  subject: this.adapter.config.speech_email_subject || 'PoolControl Nachricht',
108
- text: text,
109
- },
110
- ack: false,
111
- });
112
- this.adapter.log.info(
113
- `[speechHelper] E-Mail gesendet an ${this.adapter.config.speech_email_recipient}: ${text}`,
114
- );
139
+ text,
140
+ });
141
+ this.adapter.log.info(
142
+ `[speechHelper] E-Mail gesendet über ${instance} an ${this.adapter.config.speech_email_recipient}: ${text}`,
143
+ );
144
+ } catch (err) {
145
+ this.adapter.log.warn(`[speechHelper] E-Mail-Versand fehlgeschlagen (${instance}): ${err.message}`);
146
+ }
115
147
  }
116
148
  } catch (err) {
117
149
  this.adapter.log.warn(`[speechHelper] Fehler beim Sprechen: ${err.message}`);
@@ -30,6 +30,35 @@ const temperatureHelper = {
30
30
  adapter.subscribeForeignStates(id);
31
31
  }
32
32
 
33
+ // >>> NEU: Initialwerte aller aktiven Sensoren einlesen
34
+ (async () => {
35
+ for (const [key, id] of Object.entries(this.sensors)) {
36
+ try {
37
+ const state = await adapter.getForeignStateAsync(id);
38
+ if (state && state.val !== null && state.val !== undefined && !isNaN(Number(state.val))) {
39
+ const val = Number(state.val);
40
+ this.values[key] = val;
41
+ await this._setCurrentValue(key, val);
42
+ await this._updateMinMax(key, val);
43
+ adapter.log.debug(`[temperatureHelper] Initialwert für ${key}: ${val} °C`);
44
+ } else {
45
+ adapter.log.debug(`[temperatureHelper] Kein gültiger Initialwert für ${key}`);
46
+ }
47
+ } catch (err) {
48
+ adapter.log.warn(`[temperatureHelper] Fehler beim Initial-Read ${key}: ${err.message}`);
49
+ }
50
+ }
51
+
52
+ // Nach Setzen der ersten Werte gleich Deltas prüfen
53
+ await this._maybeWriteDelta(
54
+ 'temperature.delta.collector_outside',
55
+ this.values.collector,
56
+ this.values.outside,
57
+ );
58
+ await this._maybeWriteDelta('temperature.delta.surface_ground', this.values.surface, this.values.ground);
59
+ await this._maybeWriteDelta('temperature.delta.flow_return', this.values.flow, this.values.return);
60
+ })();
61
+
33
62
  // >>> NEU: Alte Min/Max-Werte wiederherstellen
34
63
  this._restoreMinMaxFromStates().catch(err =>
35
64
  this.adapter.log.warn(`[temperatureHelper] Restore Min/Max fehlgeschlagen: ${err.message}`),