iobroker.poolcontrol 0.2.0 → 0.2.2

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.
@@ -38,21 +38,21 @@ const pumpHelper = {
38
38
  .then(s => {
39
39
  const val = this._parseNumber(s?.val);
40
40
  this.adapter.setStateAsync('pump.current_power', { val, ack: true });
41
- this.adapter.log.info(
41
+ this.adapter.log.debug(
42
42
  `[pumpHelper] Überwache Leistung von ${this.currentPowerId} (Startwert: ${val})`,
43
43
  );
44
44
  })
45
45
  .catch(() => {
46
- this.adapter.log.info(`[pumpHelper] Überwache Leistung von ${this.currentPowerId}`);
46
+ this.adapter.log.debug(`[pumpHelper] Überwache Leistung von ${this.currentPowerId}`);
47
47
  });
48
48
  } else {
49
- this.adapter.log.info('[pumpHelper] Keine Objekt-ID für aktuelle Leistung konfiguriert');
49
+ this.adapter.log.debug('[pumpHelper] Keine Objekt-ID für aktuelle Leistung konfiguriert');
50
50
  }
51
51
 
52
52
  // Echte Steckdose beobachten (Status-Spiegelung)
53
53
  if (this.deviceId) {
54
54
  this.adapter.subscribeForeignStates(this.deviceId);
55
- this.adapter.log.info(`[pumpHelper] Überwache Steckdose: ${this.deviceId}`);
55
+ this.adapter.log.debug(`[pumpHelper] Überwache Steckdose: ${this.deviceId}`);
56
56
 
57
57
  // NEU: Initialwert übernehmen
58
58
  this.adapter
@@ -61,17 +61,17 @@ const pumpHelper = {
61
61
  if (s) {
62
62
  const val = !!s.val;
63
63
  this.adapter.setStateAsync('pump.pump_switch', { val, ack: true });
64
- this.adapter.log.info(`[pumpHelper] Initialer Pumpenstatus von Steckdose übernommen: ${val}`);
64
+ this.adapter.log.debug(`[pumpHelper] Initialer Pumpenstatus von Steckdose übernommen: ${val}`);
65
65
  }
66
66
  })
67
67
  .catch(err =>
68
68
  this.adapter.log.warn(`[pumpHelper] Konnte initialen Pumpenstatus nicht laden: ${err.message}`),
69
69
  );
70
70
  } else {
71
- this.adapter.log.info('[pumpHelper] Keine Objekt-ID für Pumpen-Steckdose konfiguriert');
71
+ this.adapter.log.debug('[pumpHelper] Keine Objekt-ID für Pumpen-Steckdose konfiguriert');
72
72
  }
73
73
 
74
- this.adapter.log.info('[pumpHelper] initialisiert');
74
+ this.adapter.log.debug('[pumpHelper] initialisiert');
75
75
  // Initialer Status
76
76
  this._updateStatus().catch(err =>
77
77
  this.adapter.log.warn(`[pumpHelper] Initiales Status-Update fehlgeschlagen: ${err.message}`),
@@ -171,12 +171,45 @@ const pumpHelper = {
171
171
  case 'auto':
172
172
  status = 'EIN (automatik)';
173
173
  break;
174
+ case 'controlHelper':
175
+ try {
176
+ const reason = (await this.adapter.getStateAsync('pump.reason'))?.val || '';
177
+ status = reason ? `EIN (${reason})` : 'EIN (Systemsteuerung)';
178
+ } catch {
179
+ status = 'EIN (Systemsteuerung)';
180
+ }
181
+ break;
182
+ case 'speechTextHelper':
183
+ try {
184
+ const reason = (await this.adapter.getStateAsync('pump.reason'))?.val || '';
185
+ status = reason ? `EIN (${reason})` : 'EIN (Sprachsteuerung)';
186
+ } catch {
187
+ status = 'EIN (Sprachsteuerung)';
188
+ }
189
+ break;
174
190
  default:
175
191
  status = 'EIN';
176
192
  break;
193
+ case 'frostHelper':
194
+ try {
195
+ const reason = (await this.adapter.getStateAsync('pump.reason'))?.val || '';
196
+ status = reason ? `EIN (${reason})` : 'EIN (Frostschutz)';
197
+ } catch {
198
+ status = 'EIN (Frostschutz)';
199
+ }
200
+ break;
201
+
202
+ case 'timeHelper':
203
+ try {
204
+ const reason = (await this.adapter.getStateAsync('pump.reason'))?.val || '';
205
+ status = reason ? `EIN (${reason})` : 'EIN (Zeitsteuerung)';
206
+ } catch {
207
+ status = 'EIN (Zeitsteuerung)';
208
+ }
209
+ break;
177
210
  }
178
211
  }
179
- await this.adapter.setStateAsync('pump.status', {
212
+ await this.adapter.setStateChangedAsync('pump.status', {
180
213
  val: status,
181
214
  ack: true,
182
215
  });
@@ -7,6 +7,7 @@
7
7
  * - Schreibt Werte in die States (Objekte werden in runtimeStates.js angelegt)
8
8
  * - Nutzt den zentralen Boolean pump.pump_switch
9
9
  * - NEU: zählt Starts, aktuelle Laufzeit, Saisonlaufzeit
10
+ * - NEU: stellt formatiert gespeicherte Laufzeiten nach Neustart korrekt wieder her
10
11
  */
11
12
 
12
13
  const runtimeHelper = {
@@ -35,13 +36,13 @@ const runtimeHelper = {
35
36
  // Erst nach Restore einmal berechnen
36
37
  this._updateStates();
37
38
 
38
- this.adapter.log.info('[runtimeHelper] initialisiert (mit Restore)');
39
+ this.adapter.log.debug('[runtimeHelper] initialisiert (mit Restore)');
39
40
  })
40
41
  .catch(err => {
41
42
  this.adapter.log.warn(`[runtimeHelper] Restore fehlgeschlagen: ${err.message}`);
42
43
  this._scheduleDailyReset();
43
44
  this._updateStates();
44
- this.adapter.log.info('[runtimeHelper] initialisiert (ohne Restore)');
45
+ this.adapter.log.debug('[runtimeHelper] initialisiert (ohne Restore)');
45
46
  });
46
47
  },
47
48
 
@@ -51,10 +52,10 @@ const runtimeHelper = {
51
52
  const seasonRaw = (await this.adapter.getStateAsync('runtime.season_total'))?.val;
52
53
  const countRaw = (await this.adapter.getStateAsync('runtime.start_count_today'))?.val;
53
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;
55
+ // >>> NEU: Formatierten Text (z. B. "3h 12m 5s") in Sekunden umwandeln
56
+ this.runtimeTotal = this._parseFormattedTimeToSeconds(totalRaw);
57
+ this.runtimeToday = this._parseFormattedTimeToSeconds(todayRaw);
58
+ this.runtimeSeason = this._parseFormattedTimeToSeconds(seasonRaw);
58
59
  this.startCountToday = Number(countRaw) || 0;
59
60
 
60
61
  // Falls Pumpe gerade läuft → Status wiederherstellen
@@ -158,6 +159,35 @@ const runtimeHelper = {
158
159
  return `${h}h ${m}m ${s}s`;
159
160
  },
160
161
 
162
+ // >>> NEU: formatierten Text (z. B. "3h 12m 5s") in Sekunden zurückrechnen
163
+ _parseFormattedTimeToSeconds(value) {
164
+ if (typeof value === 'number' && Number.isFinite(value)) {
165
+ return value;
166
+ } // bereits Sekunden
167
+ const str = String(value ?? '').trim();
168
+ if (!str) {
169
+ return 0;
170
+ }
171
+
172
+ let h = 0,
173
+ m = 0,
174
+ s = 0;
175
+ const mh = str.match(/(\d+)\s*h/);
176
+ if (mh) {
177
+ h = parseInt(mh[1], 10);
178
+ }
179
+ const mm = str.match(/(\d+)\s*m/);
180
+ if (mm) {
181
+ m = parseInt(mm[1], 10);
182
+ }
183
+ const ms = str.match(/(\d+)\s*s/);
184
+ if (ms) {
185
+ s = parseInt(ms[1], 10);
186
+ }
187
+
188
+ return h * 3600 + m * 60 + s;
189
+ },
190
+
161
191
  _scheduleDailyReset() {
162
192
  const now = new Date();
163
193
  const nextMidnight = new Date(now);
@@ -18,7 +18,7 @@ const solarHelper = {
18
18
  // Minütlicher Check
19
19
  this._scheduleCheck();
20
20
 
21
- this.adapter.log.info('[solarHelper] initialisiert (Prüfung alle 60s)');
21
+ this.adapter.log.debug('[solarHelper] initialisiert (Prüfung alle 60s)');
22
22
  },
23
23
 
24
24
  _scheduleCheck() {
@@ -32,6 +32,13 @@ const solarHelper = {
32
32
 
33
33
  async _checkSolar() {
34
34
  try {
35
+ // --- NEU: Vorrangprüfung durch ControlHelper ---
36
+ const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
37
+ if (activeHelper === 'controlHelper') {
38
+ this.adapter.log.debug('[solarHelper] Vorrang durch ControlHelper aktiv – Solarregelung pausiert.');
39
+ return;
40
+ }
41
+
35
42
  // --- NEU: Saisonstatus ---
36
43
  const season = (await this.adapter.getStateAsync('status.season_active'))?.val;
37
44
 
@@ -47,8 +54,8 @@ const solarHelper = {
47
54
  const hysteresis = (await this.adapter.getStateAsync('solar.hysteresis_active'))?.val;
48
55
 
49
56
  // Temperaturen laden
50
- const collector = (await this.adapter.getStateAsync('temperature.collector.current'))?.val;
51
- const pool = (await this.adapter.getStateAsync('temperature.surface.current'))?.val; // Oberfläche = Pooltemp
57
+ const collector = Number((await this.adapter.getStateAsync('temperature.collector.current'))?.val);
58
+ const pool = Number((await this.adapter.getStateAsync('temperature.surface.current'))?.val); // Oberfläche = Pooltemp
52
59
 
53
60
  if (collector == null || pool == null) {
54
61
  this.adapter.log.debug('[solarHelper] Keine gültigen Temperaturen verfügbar');
@@ -75,11 +82,21 @@ const solarHelper = {
75
82
  // z. B. Ausschaltgrenze etwas absenken
76
83
  }
77
84
 
85
+ // --- NEU: Sprachvariable für Solarsteuerung setzen ---
86
+ const oldVal = (await this.adapter.getStateAsync('speech.solar_active'))?.val;
87
+ if (oldVal !== shouldRun) {
88
+ await this.adapter.setStateChangedAsync('speech.solar_active', {
89
+ val: shouldRun,
90
+ ack: true,
91
+ });
92
+ }
93
+
78
94
  // ZENTRAL: Pumpe über Bool-Schalter setzen
79
- await this.adapter.setStateAsync('pump.pump_switch', {
95
+ await this.adapter.setStateChangedAsync('pump.pump_switch', {
80
96
  val: shouldRun,
81
97
  ack: false,
82
98
  });
99
+
83
100
  this.adapter.log.debug(
84
101
  `[solarHelper] Solarregelung → Pumpe ${shouldRun ? 'EIN' : 'AUS'} (Collector=${collector}°C, Pool=${pool}°C, Delta=${delta}°C)`,
85
102
  );
@@ -99,7 +116,10 @@ const solarHelper = {
99
116
  const warnActive = (await this.adapter.getStateAsync('solar.warn_active'))?.val;
100
117
  if (warnActive) {
101
118
  const warnTemp = Number((await this.adapter.getStateAsync('solar.warn_temp'))?.val) || 0;
119
+
120
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
102
121
  const speechEnabled = (await this.adapter.getStateAsync('solar.warn_speech'))?.val;
122
+
103
123
  const currentWarning = (await this.adapter.getStateAsync('solar.collector_warning'))?.val || false;
104
124
 
105
125
  // Neue Warnung, wenn Collector >= warnTemp
@@ -112,13 +132,17 @@ const solarHelper = {
112
132
  `[solarHelper] WARNUNG: Kollektortemperatur ${collector}°C >= ${warnTemp}°C!`,
113
133
  );
114
134
 
115
- // Sprachausgabe bei Aktivierung
116
- if (speechEnabled) {
117
- await this.adapter.setStateAsync('speech.last_text', {
118
- val: `Warnung: Kollektortemperatur ${collector} Grad erreicht.`,
119
- ack: true,
120
- });
121
- }
135
+ /*
136
+ * Deaktiviert, ersetzt durch SpeechTextHelper
137
+ *
138
+ * // Sprachausgabe bei Aktivierung
139
+ * if (speechEnabled) {
140
+ * await this.adapter.setStateAsync('speech.last_text', {
141
+ * val: `Warnung: Kollektortemperatur ${collector} Grad erreicht.`,
142
+ * ack: true,
143
+ * });
144
+ * }
145
+ */
122
146
  }
123
147
 
124
148
  // Warnung zurücksetzen, wenn Collector <= 90 % von warnTemp
@@ -127,7 +151,7 @@ const solarHelper = {
127
151
  val: false,
128
152
  ack: true,
129
153
  });
130
- this.adapter.log.info(
154
+ this.adapter.log.debug(
131
155
  `[solarHelper] Kollektorwarnung zurückgesetzt: ${collector}°C <= ${warnTemp * 0.9}°C`,
132
156
  );
133
157
  }
@@ -21,11 +21,10 @@ const speechHelper = {
21
21
  this.adapter.subscribeStates('pump.error'); // Fehleransagen
22
22
  this.adapter.subscribeStates('temperature.*.current'); // Temp-Trigger
23
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
24
  this.adapter.subscribeStates('speech.last_text');
25
+ this.adapter.subscribeStates('speech.queue'); // <<< NEU: zentrale Nachrichtenwarteschlange
27
26
 
28
- this.adapter.log.info('[speechHelper] initialisiert');
27
+ this.adapter.log.debug('[speechHelper] initialisiert');
29
28
  },
30
29
 
31
30
  async handleStateChange(id, state) {
@@ -39,6 +38,16 @@ const speechHelper = {
39
38
  return;
40
39
  }
41
40
 
41
+ // NEU: Nachricht aus zentraler speech.queue
42
+ if (id.endsWith('speech.queue') && state.ack === false) {
43
+ const txt = String(state.val || '').trim();
44
+ if (txt) {
45
+ await this._speak(txt);
46
+ await this.adapter.setStateAsync('speech.queue', { val: '', ack: true });
47
+ }
48
+ return;
49
+ }
50
+
42
51
  // NEU: Direktnachricht von controlHelper über speech.last_text
43
52
  if (id.endsWith('speech.last_text') && state.ack === false) {
44
53
  const txt = String(state.val || '').trim();
@@ -57,48 +66,67 @@ const speechHelper = {
57
66
  return;
58
67
  }
59
68
 
60
- // === Pumpenstart / -stop nur bei Zustandswechsel ===
61
- if (id.endsWith('pump.pump_switch')) {
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
- }
78
- } else {
79
- this.adapter.log.debug('[speechHelper] Ignoriere Pumpenmeldung kein Zustandswechsel.');
80
- }
69
+ /*
70
+ *
71
+ * Deaktiviert, ersetzt durch speechTextHelper
72
+ *
73
+ * // === Pumpenstart / -stop nur bei Zustandswechsel ===
74
+ * if (id.endsWith('pump.pump_switch')) {
75
+ * const newVal = !!state.val;
76
+ *
77
+ * // Nur wenn sich der Zustand wirklich geändert hat
78
+ * if (this.lastPumpState !== newVal) {
79
+ * this.lastPumpState = newVal;
80
+ *
81
+ * if (newVal) {
82
+ * const txt =
83
+ * (await this.adapter.getStateAsync('speech.start_text'))?.val ||
84
+ * 'Die Poolpumpe wurde gestartet.';
85
+ * await this._speak(txt);
86
+ * } else {
87
+ * const txt =
88
+ * (await this.adapter.getStateAsync('speech.end_text'))?.val || 'Die Poolpumpe wurde gestoppt.';
89
+ * await this._speak(txt);
90
+ * }
91
+ * } else {
92
+ * this.adapter.log.debug('[speechHelper] Ignoriere Pumpenmeldung – kein Zustandswechsel.');
93
+ * }
94
+ * return;
95
+ * }
96
+ */
97
+
98
+ // Nur Pool-Oberflächentemperatur berücksichtigen
99
+ if (!id.includes('temperature.surface')) {
81
100
  return;
82
101
  }
83
102
 
84
- // Temperatur-Trigger: über Config-Schwelle
85
- if (id.includes('.temperature.') && id.endsWith('.current')) {
86
- const threshold = this.adapter.config.speech_temp_threshold || 0;
87
- const val = Number(state.val);
88
- if (val >= threshold && threshold > 0) {
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
- }
103
+ const threshold = this.adapter.config.speech_temp_threshold || 0;
104
+ const val = Number(state.val);
105
+
106
+ if (val >= threshold && threshold > 0) {
107
+ const now = Date.now();
108
+ const lastInfo = this.lastTempNotify[id] || { time: 0, temp: 0, date: null };
109
+
110
+ const lastDate = lastInfo.date;
111
+ const today = new Date().toDateString();
112
+ const tempDiff = Math.abs(val - lastInfo.temp);
113
+
114
+ // Prüfen: neuer Tag oder Temperatur mindestens +2°C höher
115
+ const isNewDay = lastDate !== today;
116
+ const significantChange = tempDiff >= 2;
117
+
118
+ if (isNewDay || significantChange) {
119
+ await this._speak(`Der Pool hat jetzt ${val} Grad erreicht.`);
120
+ this.lastTempNotify[id] = { time: now, temp: val, date: today };
121
+ } else {
122
+ this.adapter.log.debug(
123
+ `[speechHelper] Temperaturansage unterdrückt (tempDiff=${tempDiff.toFixed(
124
+ 1,
125
+ )}°C, letzter Wert=${lastInfo.temp}°C).`,
126
+ );
99
127
  }
100
- return;
101
128
  }
129
+ return;
102
130
  },
103
131
 
104
132
  async _speak(text) {
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * speechTextHelper
5
+ * - Erzeugt situationsabhängige Sprachtexte (z. B. Zeitmodus, Solar, PV, Wartung usw.)
6
+ * - Sendet fertige Texte an speech.queue (ack: false)
7
+ * - Beeinflusst keine Steuerlogik und keine anderen Helper
8
+ * - Wird nach und nach um Textbausteine erweitert (Schritt-für-Schritt pro Datei)
9
+ *
10
+ * @module speechTextHelper
11
+ * @version 1.0.3
12
+ */
13
+
14
+ const speechTextHelper = {
15
+ // @type {ioBroker.Adapter}
16
+ adapter: null,
17
+
18
+ /**
19
+ * Initialisiert den Helper und abonniert relevante States.
20
+ *
21
+ * @param {ioBroker.Adapter} adapter - ioBroker Adapterinstanz
22
+ */
23
+ init(adapter) {
24
+ this.adapter = adapter;
25
+
26
+ // Relevante States abonnieren (Pumpenlogik + Status)
27
+ this.adapter.subscribeStates('pump.pump_switch');
28
+ this.adapter.subscribeStates('pump.mode');
29
+ this.adapter.subscribeStates('pump.reason');
30
+ this.adapter.subscribeStates('pump.status'); // zentrale Statusüberwachung
31
+
32
+ // --- NEU: Solar-Warnung überwachen ---
33
+ this.adapter.subscribeStates('solar.collector_warning');
34
+
35
+ // --- NEU: Solarsteuerung überwachen ---
36
+ this.adapter.subscribeStates('speech.solar_active');
37
+
38
+ // --- NEU: Zeitsteuerung überwachen ---
39
+ this.adapter.subscribeStates('speech.time_active');
40
+
41
+ // Später erweiterbar:
42
+ // this.adapter.subscribeStates('solar.solar_control_active');
43
+ // this.adapter.subscribeStates('control.pump.backwash_active');
44
+
45
+ this.adapter.log.debug(
46
+ '[speechTextHelper] initialisiert (Grundstruktur aktiv, inkl. Solar-Warnung, keine weiteren Textlogiken)',
47
+ );
48
+ },
49
+
50
+ /**
51
+ * Reagiert auf State-Änderungen.
52
+ * Hier werden nach und nach die jeweiligen Textausgaben ergänzt.
53
+ *
54
+ * @param {string} id - Objekt-ID des geänderten States
55
+ * @param {ioBroker.State} state - Neuer Statewert
56
+ */
57
+ async handleStateChange(id, state) {
58
+ try {
59
+ if (!state) {
60
+ return;
61
+ }
62
+
63
+ // --- Pumpenstatusänderung ---
64
+ if (id.endsWith('pump.status')) {
65
+ const status = String(state.val || '').toLowerCase();
66
+ this.adapter.log.silly(`[speechTextHelper] Pumpenstatus geändert: ${status}`);
67
+ return;
68
+ }
69
+
70
+ // --- Pumpenereignisse ---
71
+ if (id.endsWith('pump.pump_switch') || id.endsWith('pump.mode') || id.endsWith('pump.reason')) {
72
+ this.adapter.log.silly(`[speechTextHelper] Pumpen-Event erkannt: ${id} = ${state.val}`);
73
+ return;
74
+ }
75
+
76
+ // --- NEU: Solar-Warnung ---
77
+ if (id.endsWith('solar.collector_warning')) {
78
+ const val = !!state.val;
79
+
80
+ if (val) {
81
+ // Neue Warnung aktiv
82
+ const collectorTemp = Number(
83
+ (await this.adapter.getStateAsync('temperature.collector.current'))?.val,
84
+ );
85
+ const warnTemp = Number((await this.adapter.getStateAsync('solar.warn_temp'))?.val);
86
+ const text = `Warnung: Kollektortemperatur ${collectorTemp} Grad erreicht (Warnschwelle ${warnTemp}°C).`;
87
+ await this._sendSpeech(text);
88
+ this.adapter.log.debug(`[speechTextHelper] Solar-Warnung gesendet: ${text}`);
89
+ } else {
90
+ // Warnung aufgehoben
91
+ const text = 'Kollektorwarnung aufgehoben.';
92
+ await this._sendSpeech(text);
93
+ this.adapter.log.debug('[speechTextHelper] Solar-Warnung aufgehoben.');
94
+ }
95
+
96
+ // ersetzt SolarHelper Textausgabe
97
+ return;
98
+ }
99
+
100
+ // --- NEU: Reaktion auf Solarsteuerung ---
101
+ if (id.endsWith('speech.solar_active')) {
102
+ const val = !!state.val;
103
+ // Pumpenstatus aktualisieren, damit auch im VIS korrekt sichtbar
104
+ if (val) {
105
+ await this.adapter.setStateAsync('pump.status', {
106
+ val: 'EIN (Solarsteuerung)',
107
+ ack: true,
108
+ });
109
+ } else {
110
+ await this.adapter.setStateAsync('pump.status', {
111
+ val: 'AUS (Solarsteuerung beendet)',
112
+ ack: true,
113
+ });
114
+ }
115
+ if (val) {
116
+ const text = 'Die Poolpumpe wurde durch die Solarsteuerung eingeschaltet.';
117
+ await this._sendSpeech(text);
118
+ this.adapter.log.debug('[speechTextHelper] Solarsteuerung aktiviert → Ansage gesendet.');
119
+ } else {
120
+ const text = 'Solarsteuerung beendet – Poolpumpe ausgeschaltet.';
121
+ await this._sendSpeech(text);
122
+ this.adapter.log.debug('[speechTextHelper] Solarsteuerung deaktiviert → Ansage gesendet.');
123
+ }
124
+ return;
125
+ }
126
+
127
+ // --- NEU: Reaktion auf Zeitsteuerung ---
128
+ if (id.endsWith('speech.time_active')) {
129
+ const val = !!state.val;
130
+
131
+ // Pumpenstatus mitpflegen
132
+ if (val) {
133
+ await this.adapter.setStateAsync('pump.status', {
134
+ val: 'EIN (Zeitsteuerung)',
135
+ ack: true,
136
+ });
137
+ const text = 'Die Poolpumpe wurde durch die Zeitsteuerung eingeschaltet.';
138
+ await this._sendSpeech(text);
139
+ this.adapter.log.debug('[speechTextHelper] Zeitsteuerung aktiviert → Ansage gesendet.');
140
+ } else {
141
+ await this.adapter.setStateAsync('pump.status', {
142
+ val: 'AUS (Zeitsteuerung beendet)',
143
+ ack: true,
144
+ });
145
+ const text = 'Zeitsteuerung beendet – Poolpumpe ausgeschaltet.';
146
+ await this._sendSpeech(text);
147
+ this.adapter.log.debug('[speechTextHelper] Zeitsteuerung deaktiviert → Ansage gesendet.');
148
+ }
149
+ return;
150
+ }
151
+
152
+ // Weitere Blöcke (z. B. Zeitmodus, Wartung usw.) folgen später hier
153
+ } catch (err) {
154
+ this.adapter.log.warn(`[speechTextHelper] Fehler bei handleStateChange: ${err.message}`);
155
+ }
156
+ },
157
+
158
+ /**
159
+ * Sendet Text an speech.queue.
160
+ *
161
+ * @param {string} text - Der zu sendende Text
162
+ */
163
+ async _sendSpeech(text) {
164
+ if (!text) {
165
+ return;
166
+ }
167
+ try {
168
+ await this.adapter.setStateAsync('speech.queue', { val: text, ack: false });
169
+ this.adapter.log.debug(`[speechTextHelper] Text gesendet: ${text}`);
170
+ } catch (err) {
171
+ this.adapter.log.warn(`[speechTextHelper] Fehler beim Senden an speech.queue: ${err.message}`);
172
+ }
173
+ },
174
+
175
+ /**
176
+ * Aufräumen (z. B. Timer beenden)
177
+ */
178
+ cleanup() {
179
+ // Aktuell keine Ressourcen
180
+ this.adapter.log.debug('[speechTextHelper] cleanup ausgeführt');
181
+ },
182
+ };
183
+
184
+ module.exports = speechTextHelper;
@@ -52,7 +52,7 @@ const statusHelper = {
52
52
  // Mitternacht-Reset einplanen
53
53
  this.scheduleMidnightReset();
54
54
 
55
- this.adapter.log.info('[statusHelper] initialisiert');
55
+ this.adapter.log.debug('[statusHelper] initialisiert');
56
56
  },
57
57
 
58
58
  async handleStateChange(id, state) {
@@ -209,7 +209,7 @@ const statusHelper = {
209
209
  try {
210
210
  await this.adapter.setStateAsync('status.pump_today_count', { val: 0, ack: true });
211
211
  await this.adapter.setStateAsync('status.pump_was_on_today', { val: false, ack: true });
212
- this.adapter.log.info('[statusHelper] Tagesreset durchgeführt');
212
+ this.adapter.log.debug('[statusHelper] Tagesreset durchgeführt');
213
213
  } catch (err) {
214
214
  this.adapter.log.warn(`[statusHelper] Fehler beim Tagesreset: ${err.message}`);
215
215
  }
@@ -67,7 +67,7 @@ const temperatureHelper = {
67
67
  // Reset um Mitternacht
68
68
  this._scheduleDailyReset();
69
69
 
70
- adapter.log.info(
70
+ adapter.log.debug(
71
71
  `[temperatureHelper] Aktiv: ${
72
72
  Object.keys(this.sensors).length
73
73
  ? Object.entries(this.sensors)
@@ -230,7 +230,7 @@ const temperatureHelper = {
230
230
  },
231
231
 
232
232
  async _resetMinMax() {
233
- this.adapter.log.info('[temperatureHelper] Setze Tages-Min/Max zurück');
233
+ this.adapter.log.debug('[temperatureHelper] Setze Tages-Min/Max zurück');
234
234
  for (const key of Object.keys(this.sensors)) {
235
235
  // Bugfix: statt leeres Objekt → löschen, damit Neu-Init greift
236
236
  delete this.minMax[key];
@@ -17,7 +17,7 @@ const timeHelper = {
17
17
  // Minütlicher Check
18
18
  this._scheduleCheck();
19
19
 
20
- this.adapter.log.info('[timeHelper] initialisiert (Prüfung alle 60s)');
20
+ this.adapter.log.debug('[timeHelper] initialisiert (Prüfung alle 60s)');
21
21
  },
22
22
 
23
23
  _scheduleCheck() {
@@ -59,6 +59,15 @@ const timeHelper = {
59
59
  }
60
60
  }
61
61
 
62
+ // --- NEU: Sprachsignal für Zeitsteuerung setzen ---
63
+ const oldVal = (await this.adapter.getStateAsync('speech.time_active'))?.val;
64
+ if (oldVal !== shouldRun) {
65
+ await this.adapter.setStateAsync('speech.time_active', {
66
+ val: shouldRun,
67
+ ack: true,
68
+ });
69
+ }
70
+
62
71
  // Pumpe über die echte Steckdosen-ID schalten
63
72
  await this.adapter.setForeignStateAsync(pumpSwitchId, {
64
73
  val: shouldRun,