iobroker.poolcontrol 0.0.7

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.
Files changed (35) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +129 -0
  3. package/admin/i18n/de/translations.json +5 -0
  4. package/admin/i18n/en/translations.json +5 -0
  5. package/admin/i18n/es/translations.json +5 -0
  6. package/admin/i18n/fr/translations.json +5 -0
  7. package/admin/i18n/it/translations.json +5 -0
  8. package/admin/i18n/nl/translations.json +5 -0
  9. package/admin/i18n/pl/translations.json +5 -0
  10. package/admin/i18n/pt/translations.json +5 -0
  11. package/admin/i18n/ru/translations.json +5 -0
  12. package/admin/i18n/uk/translations.json +5 -0
  13. package/admin/i18n/zh-cn/translations.json +5 -0
  14. package/admin/jsonConfig.json +901 -0
  15. package/admin/poolcontrol.png +0 -0
  16. package/io-package.json +176 -0
  17. package/lib/adapter-config.d.ts +19 -0
  18. package/lib/helpers/consumptionHelper.js +185 -0
  19. package/lib/helpers/frostHelper.js +94 -0
  20. package/lib/helpers/pumpHelper.js +224 -0
  21. package/lib/helpers/runtimeHelper.js +159 -0
  22. package/lib/helpers/solarHelper.js +138 -0
  23. package/lib/helpers/speechHelper.js +108 -0
  24. package/lib/helpers/temperatureHelper.js +227 -0
  25. package/lib/helpers/timeHelper.js +88 -0
  26. package/lib/stateDefinitions/consumptionStates.js +82 -0
  27. package/lib/stateDefinitions/generalStates.js +68 -0
  28. package/lib/stateDefinitions/pumpStates.js +184 -0
  29. package/lib/stateDefinitions/runtimeStates.js +113 -0
  30. package/lib/stateDefinitions/solarStates.js +150 -0
  31. package/lib/stateDefinitions/speechStates.js +104 -0
  32. package/lib/stateDefinitions/temperatureStates.js +182 -0
  33. package/lib/stateDefinitions/timeStates.js +102 -0
  34. package/main.js +145 -0
  35. package/package.json +60 -0
@@ -0,0 +1,224 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * pumpHelper
5
+ * - Spiegelt aktuelle Leistung aus Fremd-State nach pump.current_power
6
+ * - Prüft Fehlerkriterien → setzt pump.error
7
+ * - Bei Überlast: Pumpe ausschalten + Modus blockieren
8
+ * - Leitet pump.status ab aus mode, switch, error
9
+ * - NEU: Synchronisiert zentralen Schalter (pump.pump_switch, boolean) mit der realen Steckdose (adapter.config.pump_switch)
10
+ */
11
+
12
+ const pumpHelper = {
13
+ adapter: null,
14
+ deviceId: null, // Objekt-ID der echten Steckdose (aus Config)
15
+ currentPowerId: null, // Fremd-State für Leistung (aus Config)
16
+
17
+ init(adapter) {
18
+ this.adapter = adapter;
19
+
20
+ // States aus Config lesen
21
+ this.deviceId = (adapter.config.pump_switch || '').trim() || null;
22
+ this.currentPowerId = (adapter.config.pump_current_power_id || '').trim() || null;
23
+
24
+ // Relevante eigenen States beobachten
25
+ this.adapter.subscribeStates('pump.mode');
26
+ this.adapter.subscribeStates('pump.pump_switch');
27
+ this.adapter.subscribeStates('pump.error');
28
+
29
+ // Fremdleistung beobachten
30
+ if (this.currentPowerId) {
31
+ this.adapter.subscribeForeignStates(this.currentPowerId);
32
+
33
+ // Initialwert ziehen
34
+ this.adapter
35
+ .getForeignStateAsync(this.currentPowerId)
36
+ .then(s => {
37
+ const val = this._parseNumber(s?.val);
38
+ this.adapter.setStateAsync('pump.current_power', { val, ack: true });
39
+ this.adapter.log.info(
40
+ `[pumpHelper] Überwache Leistung von ${this.currentPowerId} (Startwert: ${val})`,
41
+ );
42
+ })
43
+ .catch(() => {
44
+ this.adapter.log.info(`[pumpHelper] Überwache Leistung von ${this.currentPowerId}`);
45
+ });
46
+ } else {
47
+ this.adapter.log.info('[pumpHelper] Keine Objekt-ID für aktuelle Leistung konfiguriert');
48
+ }
49
+
50
+ // Echte Steckdose beobachten (Status-Spiegelung)
51
+ if (this.deviceId) {
52
+ this.adapter.subscribeForeignStates(this.deviceId);
53
+ this.adapter.log.info(`[pumpHelper] Überwache Steckdose: ${this.deviceId}`);
54
+ } else {
55
+ this.adapter.log.info('[pumpHelper] Keine Objekt-ID für Pumpen-Steckdose konfiguriert');
56
+ }
57
+
58
+ this.adapter.log.info('[pumpHelper] initialisiert');
59
+ // Initialer Status
60
+ this._updateStatus().catch(err =>
61
+ this.adapter.log.warn(`[pumpHelper] Initiales Status-Update fehlgeschlagen: ${err.message}`),
62
+ );
63
+ },
64
+
65
+ // Hilfsfunktion: Zahlen robust parsen
66
+ _parseNumber(x) {
67
+ if (typeof x === 'number' && Number.isFinite(x)) {
68
+ return x;
69
+ }
70
+ const m = String(x ?? '')
71
+ .replace(',', '.')
72
+ .match(/-?\d+(\.\d+)?/);
73
+ return m ? Number(m[0]) : 0;
74
+ },
75
+
76
+ async handleStateChange(id, state) {
77
+ if (!state) {
78
+ return;
79
+ }
80
+
81
+ // 1) Leistung aus Fremd-State spiegeln
82
+ if (this.currentPowerId && id === this.currentPowerId) {
83
+ const val = this._parseNumber(state.val);
84
+ await this.adapter.setStateAsync('pump.current_power', {
85
+ val,
86
+ ack: true,
87
+ });
88
+ await this._checkErrorConditions();
89
+ return;
90
+ }
91
+
92
+ // 2) Fremde Steckdose hat sich verändert → in unseren bool-Schalter spiegeln
93
+ if (this.deviceId && id === this.deviceId) {
94
+ const val = !!state.val;
95
+ await this.adapter.setStateAsync('pump.pump_switch', { val, ack: true });
96
+ await this._updateStatus();
97
+ await this._checkErrorConditions();
98
+ return;
99
+ }
100
+
101
+ // 3) Eigene Pumpen-States geändert
102
+ if (id.endsWith('pump.mode') || id.endsWith('pump.pump_switch') || id.endsWith('pump.error')) {
103
+ if (id.endsWith('pump.pump_switch') && this.deviceId) {
104
+ await this.adapter.setForeignStateAsync(this.deviceId, {
105
+ val: !!state.val,
106
+ ack: false,
107
+ });
108
+ }
109
+
110
+ await this._updateStatus();
111
+ await this._checkErrorConditions();
112
+ return;
113
+ }
114
+ },
115
+
116
+ async _updateStatus() {
117
+ try {
118
+ const mode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'unknown';
119
+ const active = (await this.adapter.getStateAsync('pump.pump_switch'))?.val;
120
+ const error = (await this.adapter.getStateAsync('pump.error'))?.val;
121
+
122
+ let status = 'AUS';
123
+ if (error) {
124
+ status = 'FEHLER';
125
+ } else if (active) {
126
+ switch (mode) {
127
+ case 'manual':
128
+ status = 'EIN (manuell)';
129
+ break;
130
+ case 'time':
131
+ status = 'EIN (zeit)';
132
+ break;
133
+ case 'auto':
134
+ status = 'EIN (automatik)';
135
+ break;
136
+ default:
137
+ status = 'EIN';
138
+ break;
139
+ }
140
+ }
141
+ await this.adapter.setStateAsync('pump.status', {
142
+ val: status,
143
+ ack: true,
144
+ });
145
+ } catch (err) {
146
+ this.adapter.log.warn(`[pumpHelper] Fehler beim Setzen von pump.status: ${err.message}`);
147
+ }
148
+ },
149
+
150
+ async _checkErrorConditions() {
151
+ try {
152
+ const pumpSwitchId = (this.adapter.config.pump_switch || '').trim();
153
+ let active = null;
154
+
155
+ if (pumpSwitchId) {
156
+ active = !!(await this.adapter.getForeignStateAsync(pumpSwitchId))?.val;
157
+ } else {
158
+ const v = (await this.adapter.getStateAsync('pump.pump_switch'))?.val;
159
+ if (typeof v === 'boolean') {
160
+ active = v;
161
+ }
162
+ }
163
+
164
+ const errorOld = (await this.adapter.getStateAsync('pump.error'))?.val;
165
+ const power = this._parseNumber((await this.adapter.getStateAsync('pump.current_power'))?.val);
166
+ const maxWatt = this._parseNumber((await this.adapter.getStateAsync('pump.pump_max_watt'))?.val);
167
+
168
+ let error = false;
169
+ let errorMsg = '';
170
+
171
+ if (active === true && power < 5) {
172
+ error = true;
173
+ errorMsg = 'Fehler: Pumpe EIN, aber keine Leistung!';
174
+ }
175
+
176
+ if (active === false && power > 10) {
177
+ error = true;
178
+ errorMsg = 'Fehler: Pumpe AUS, aber Leistung vorhanden!';
179
+ }
180
+
181
+ if (active === true && maxWatt > 0 && power > maxWatt) {
182
+ error = true;
183
+ errorMsg = `Überlast: ${power} W > Maximalwert ${maxWatt} W → Pumpe wird abgeschaltet!`;
184
+
185
+ if (pumpSwitchId) {
186
+ await this.adapter.setForeignStateAsync(pumpSwitchId, {
187
+ val: false,
188
+ ack: false,
189
+ });
190
+ }
191
+
192
+ await this.adapter.setStateAsync('pump.mode', {
193
+ val: 'off',
194
+ ack: true,
195
+ });
196
+ }
197
+
198
+ if (error !== errorOld) {
199
+ await this.adapter.setStateAsync('pump.error', {
200
+ val: error,
201
+ ack: true,
202
+ });
203
+ await this._updateStatus();
204
+
205
+ if (error && errorMsg) {
206
+ this.adapter.log.warn(`[pumpHelper] ${errorMsg}`);
207
+ } else if (!error && errorOld) {
208
+ this.adapter.log.info('[pumpHelper] Pumpenfehler behoben');
209
+ }
210
+ }
211
+ } catch (err) {
212
+ this.adapter.log.warn(`[pumpHelper] Fehler bei Error-Check: ${err.message}`);
213
+ }
214
+ },
215
+
216
+ cleanup() {
217
+ if (this.checkTimer) {
218
+ clearInterval(this.checkTimer);
219
+ this.checkTimer = null;
220
+ }
221
+ },
222
+ };
223
+
224
+ module.exports = pumpHelper;
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * runtimeHelper
5
+ * - Zählt Pumpenlaufzeit
6
+ * - Berechnet tägliche Umwälzmenge
7
+ * - Schreibt Werte in die States (Objekte werden in runtimeStates.js angelegt)
8
+ * - Nutzt den zentralen Boolean pump.pump_switch
9
+ */
10
+
11
+ const runtimeHelper = {
12
+ adapter: null,
13
+ isRunning: false,
14
+ lastOn: null,
15
+ runtimeTotal: 0, // Gesamtzeit (s)
16
+ runtimeToday: 0, // Tageszeit (s)
17
+ resetTimer: null,
18
+ liveTimer: null, // Timer für Live-Updates
19
+
20
+ init(adapter) {
21
+ this.adapter = adapter;
22
+
23
+ // Pumpenschalter überwachen
24
+ this.adapter.subscribeStates('pump.pump_switch');
25
+
26
+ // Tagesreset einplanen
27
+ this._scheduleDailyReset();
28
+
29
+ // Gleich beim Start einmal berechnen
30
+ this._updateStates();
31
+
32
+ this.adapter.log.info('[runtimeHelper] initialisiert');
33
+ },
34
+
35
+ async handleStateChange(id, state) {
36
+ if (!state) {
37
+ return;
38
+ }
39
+
40
+ if (id.endsWith('pump.pump_switch')) {
41
+ if (state.val && !this.isRunning) {
42
+ // Pumpe startet
43
+ this.isRunning = true;
44
+ this.lastOn = Date.now();
45
+
46
+ // Live-Timer starten (jede Minute)
47
+ this._startLiveTimer();
48
+ } else if (!state.val && this.isRunning) {
49
+ // Pumpe stoppt
50
+ const delta = Math.floor((Date.now() - this.lastOn) / 1000);
51
+ this.runtimeToday += delta;
52
+ this.runtimeTotal += delta;
53
+ this.isRunning = false;
54
+ this.lastOn = null;
55
+
56
+ // Live-Timer stoppen
57
+ this._stopLiveTimer();
58
+
59
+ // States final aktualisieren
60
+ await this._updateStates();
61
+ }
62
+ }
63
+ },
64
+
65
+ async _updateStates() {
66
+ try {
67
+ // Falls Pumpe läuft → temporäre Laufzeit seit lastOn einrechnen
68
+ let effectiveToday = this.runtimeToday;
69
+ if (this.isRunning && this.lastOn) {
70
+ const delta = Math.floor((Date.now() - this.lastOn) / 1000);
71
+ effectiveToday += delta;
72
+ }
73
+
74
+ // Laufzeit-States setzen
75
+ await this.adapter.setStateAsync('runtime.total', {
76
+ val: this.runtimeTotal,
77
+ ack: true,
78
+ });
79
+ await this.adapter.setStateAsync('runtime.today', {
80
+ val: effectiveToday,
81
+ ack: true,
82
+ });
83
+ await this.adapter.setStateAsync('runtime.formatted', {
84
+ val: this._formatTime(effectiveToday),
85
+ ack: true,
86
+ });
87
+
88
+ // Umwälzmenge berechnen
89
+ const pumpLph = (await this.adapter.getStateAsync('pump.pump_power_lph'))?.val || 0;
90
+ const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
91
+ const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
92
+
93
+ const dailyTotal = Math.round((effectiveToday / 3600) * pumpLph);
94
+ const dailyRequired = Math.round(poolSize * minCirc);
95
+ const dailyRemaining = Math.max(dailyRequired - dailyTotal, 0);
96
+
97
+ await this.adapter.setStateAsync('circulation.daily_total', {
98
+ val: dailyTotal,
99
+ ack: true,
100
+ });
101
+ await this.adapter.setStateAsync('circulation.daily_required', {
102
+ val: dailyRequired,
103
+ ack: true,
104
+ });
105
+ await this.adapter.setStateAsync('circulation.daily_remaining', {
106
+ val: dailyRemaining,
107
+ ack: true,
108
+ });
109
+ } catch (err) {
110
+ this.adapter.log.warn(`[runtimeHelper] Fehler beim Update der States: ${err.message}`);
111
+ }
112
+ },
113
+
114
+ _formatTime(seconds) {
115
+ const h = Math.floor(seconds / 3600);
116
+ const m = Math.floor((seconds % 3600) / 60);
117
+ return `${h}h ${m}m`;
118
+ },
119
+
120
+ _scheduleDailyReset() {
121
+ const now = new Date();
122
+ const nextMidnight = new Date(now);
123
+ nextMidnight.setHours(24, 0, 0, 0);
124
+ const msUntilMidnight = nextMidnight - now;
125
+
126
+ this.resetTimer = setTimeout(() => {
127
+ this.runtimeToday = 0;
128
+ this.lastOn = this.isRunning ? Date.now() : null;
129
+ this._updateStates();
130
+ this._scheduleDailyReset();
131
+ }, msUntilMidnight);
132
+ },
133
+
134
+ _startLiveTimer() {
135
+ if (this.liveTimer) {
136
+ clearInterval(this.liveTimer);
137
+ }
138
+ this.liveTimer = setInterval(() => this._updateStates(), 60 * 1000);
139
+ this.adapter.log.debug('[runtimeHelper] Live-Timer gestartet (Updates jede Minute)');
140
+ },
141
+
142
+ _stopLiveTimer() {
143
+ if (this.liveTimer) {
144
+ clearInterval(this.liveTimer);
145
+ this.liveTimer = null;
146
+ this.adapter.log.debug('[runtimeHelper] Live-Timer gestoppt');
147
+ }
148
+ },
149
+
150
+ cleanup() {
151
+ if (this.resetTimer) {
152
+ clearTimeout(this.resetTimer);
153
+ this.resetTimer = null;
154
+ }
155
+ this._stopLiveTimer();
156
+ },
157
+ };
158
+
159
+ module.exports = runtimeHelper;
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * solarHelper
5
+ * - Prüft Kollektor-Temperaturen und schaltet Pumpe entsprechend
6
+ * - Nutzt States aus solarStates.js und temperatureStates.js
7
+ * - Setzt Warnung bei Übertemperatur (mit automatischer Rücksetzung bei 10 % darunter)
8
+ * - Schaltet über den zentralen Bool-State pump.pump_switch
9
+ */
10
+
11
+ const solarHelper = {
12
+ adapter: null,
13
+ checkTimer: null,
14
+
15
+ init(adapter) {
16
+ this.adapter = adapter;
17
+
18
+ // Minütlicher Check
19
+ this._scheduleCheck();
20
+
21
+ this.adapter.log.info('[solarHelper] initialisiert (Prüfung alle 60s)');
22
+ },
23
+
24
+ _scheduleCheck() {
25
+ if (this.checkTimer) {
26
+ clearInterval(this.checkTimer);
27
+ }
28
+ this.checkTimer = setInterval(() => this._checkSolar(), 60 * 1000);
29
+ // Beim Start sofort prüfen
30
+ this._checkSolar();
31
+ },
32
+
33
+ async _checkSolar() {
34
+ try {
35
+ // Solarsteuerung aktiv?
36
+ const active = (await this.adapter.getStateAsync('solar.solar_control_active'))?.val;
37
+ if (!active) {
38
+ return;
39
+ }
40
+
41
+ // Pumpenmodus muss AUTO sein
42
+ const mode = (await this.adapter.getStateAsync('pump.mode'))?.val;
43
+ if (mode !== 'auto') {
44
+ return;
45
+ }
46
+
47
+ // Grenzwerte laden
48
+ const tempOn = (await this.adapter.getStateAsync('solar.temp_on'))?.val;
49
+ const tempOff = (await this.adapter.getStateAsync('solar.temp_off'))?.val;
50
+ const hysteresis = (await this.adapter.getStateAsync('solar.hysteresis_active'))?.val;
51
+
52
+ // Temperaturen laden
53
+ const collector = (await this.adapter.getStateAsync('temperature.collector.current'))?.val;
54
+ const pool = (await this.adapter.getStateAsync('temperature.surface.current'))?.val; // Oberfläche = Pooltemp
55
+
56
+ if (collector == null || pool == null) {
57
+ this.adapter.log.debug('[solarHelper] Keine gültigen Temperaturen verfügbar');
58
+ return;
59
+ }
60
+
61
+ let shouldRun = false;
62
+ const delta = collector - pool;
63
+
64
+ // Logik: Einschalten, wenn Collector > tempOn und Delta > 0
65
+ if (collector >= tempOn && delta > 0) {
66
+ shouldRun = true;
67
+ }
68
+
69
+ // Ausschalten, wenn Collector < tempOff oder Delta <= 0
70
+ if (collector <= tempOff || delta <= 0) {
71
+ shouldRun = false;
72
+ }
73
+
74
+ // Optional Hysterese (kann später erweitert werden)
75
+ if (hysteresis) {
76
+ // z. B. Ausschaltgrenze etwas absenken
77
+ }
78
+
79
+ // ZENTRAL: Pumpe über Bool-Schalter setzen
80
+ await this.adapter.setStateAsync('pump.pump_switch', {
81
+ val: shouldRun,
82
+ ack: false,
83
+ });
84
+ this.adapter.log.debug(
85
+ `[solarHelper] Solarregelung → Pumpe ${shouldRun ? 'EIN' : 'AUS'} (Collector=${collector}°C, Pool=${pool}°C, Delta=${delta}°C)`,
86
+ );
87
+
88
+ // --- Kollektor-Warnung ---
89
+ const warnActive = (await this.adapter.getStateAsync('solar.warn_active'))?.val;
90
+ if (warnActive) {
91
+ const warnTemp = Number((await this.adapter.getStateAsync('solar.warn_temp'))?.val) || 0;
92
+ const speechEnabled = (await this.adapter.getStateAsync('solar.warn_speech'))?.val;
93
+ const currentWarning = (await this.adapter.getStateAsync('solar.collector_warning'))?.val || false;
94
+
95
+ // Neue Warnung, wenn Collector >= warnTemp
96
+ if (collector >= warnTemp && !currentWarning) {
97
+ await this.adapter.setStateAsync('solar.collector_warning', {
98
+ val: true,
99
+ ack: true,
100
+ });
101
+ this.adapter.log.warn(
102
+ `[solarHelper] WARNUNG: Kollektortemperatur ${collector}°C >= ${warnTemp}°C!`,
103
+ );
104
+
105
+ // Sprachausgabe bei Aktivierung
106
+ if (speechEnabled) {
107
+ await this.adapter.setStateAsync('speech.last_text', {
108
+ val: `Warnung: Kollektortemperatur ${collector} Grad erreicht.`,
109
+ ack: true,
110
+ });
111
+ }
112
+ }
113
+
114
+ // Warnung zurücksetzen, wenn Collector <= 90 % von warnTemp
115
+ if (collector <= warnTemp * 0.9 && currentWarning) {
116
+ await this.adapter.setStateAsync('solar.collector_warning', {
117
+ val: false,
118
+ ack: true,
119
+ });
120
+ this.adapter.log.info(
121
+ `[solarHelper] Kollektorwarnung zurückgesetzt: ${collector}°C <= ${warnTemp * 0.9}°C`,
122
+ );
123
+ }
124
+ }
125
+ } catch (err) {
126
+ this.adapter.log.warn(`[solarHelper] Fehler im Check: ${err.message}`);
127
+ }
128
+ },
129
+
130
+ cleanup() {
131
+ if (this.checkTimer) {
132
+ clearInterval(this.checkTimer);
133
+ this.checkTimer = null;
134
+ }
135
+ },
136
+ };
137
+
138
+ module.exports = solarHelper;
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * speechHelper
5
+ * - Sendet Texte an Alexa und/oder Telegram
6
+ * - Verwendet Config (jsonConfig) + States aus speechStates.js
7
+ */
8
+
9
+ const speechHelper = {
10
+ adapter: null,
11
+
12
+ init(adapter) {
13
+ this.adapter = adapter;
14
+
15
+ // States überwachen, die Textänderungen triggern
16
+ this.adapter.subscribeStates('speech.start_text');
17
+ this.adapter.subscribeStates('speech.end_text');
18
+ this.adapter.subscribeStates('speech.texts.*');
19
+ this.adapter.subscribeStates('pump.error'); // Fehleransagen
20
+ this.adapter.subscribeStates('temperature.*.current'); // Temp-Trigger
21
+
22
+ this.adapter.log.info('[speechHelper] initialisiert');
23
+ },
24
+
25
+ async handleStateChange(id, state) {
26
+ if (!state) {
27
+ return;
28
+ }
29
+
30
+ // Globale Aktivierung prüfen
31
+ const active = (await this.adapter.getStateAsync('speech.active'))?.val;
32
+ if (!active) {
33
+ return;
34
+ }
35
+
36
+ // Fehleransagen
37
+ if (id.endsWith('pump.error') && state.val) {
38
+ const includeErrors = this.adapter.config.speech_include_errors;
39
+ if (includeErrors) {
40
+ await this._speak('Achtung: Pumpenfehler erkannt!');
41
+ }
42
+ return;
43
+ }
44
+
45
+ // Beispiel: Pumpenstart / -stop
46
+ 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);
51
+ } else {
52
+ const txt =
53
+ (await this.adapter.getStateAsync('speech.end_text'))?.val || 'Die Poolpumpe wurde gestoppt.';
54
+ await this._speak(txt);
55
+ }
56
+ return;
57
+ }
58
+
59
+ // Temperatur-Trigger: über Config-Schwelle
60
+ if (id.includes('.temperature.') && id.endsWith('.current')) {
61
+ const threshold = this.adapter.config.speech_temp_threshold || 0;
62
+ const val = Number(state.val);
63
+ if (val >= threshold && threshold > 0) {
64
+ await this._speak(`Der Pool hat ${val} Grad erreicht.`);
65
+ }
66
+ return;
67
+ }
68
+ },
69
+
70
+ async _speak(text) {
71
+ try {
72
+ if (!text) {
73
+ return;
74
+ }
75
+
76
+ // Letzten Text speichern
77
+ await this.adapter.setStateAsync('speech.last_text', {
78
+ val: text,
79
+ ack: true,
80
+ });
81
+
82
+ // Alexa-Ausgabe
83
+ if (this.adapter.config.speech_alexa_enabled && this.adapter.config.speech_alexa_device) {
84
+ await this.adapter.setForeignStateAsync(this.adapter.config.speech_alexa_device, text);
85
+ this.adapter.log.info(`[speechHelper] Alexa sagt: ${text}`);
86
+ }
87
+
88
+ // Telegram-Ausgabe
89
+ if (this.adapter.config.speech_telegram_enabled && this.adapter.config.speech_telegram_instance) {
90
+ 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}`);
97
+ }
98
+ } catch (err) {
99
+ this.adapter.log.warn(`[speechHelper] Fehler beim Sprechen: ${err.message}`);
100
+ }
101
+ },
102
+
103
+ cleanup() {
104
+ // nichts nötig bisher
105
+ },
106
+ };
107
+
108
+ module.exports = speechHelper;