iobroker.poolcontrol 0.2.0 → 0.2.1
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/io-package.json +19 -1
- package/lib/helpers/consumptionHelper.js +6 -6
- package/lib/helpers/controlHelper.js +203 -50
- package/lib/helpers/debugLogHelper.js +5 -5
- package/lib/helpers/frostHelper.js +18 -2
- package/lib/helpers/pumpHelper.js +41 -8
- package/lib/helpers/runtimeHelper.js +36 -6
- package/lib/helpers/solarHelper.js +36 -12
- package/lib/helpers/speechHelper.js +68 -40
- package/lib/helpers/speechTextHelper.js +184 -0
- package/lib/helpers/statusHelper.js +2 -2
- package/lib/helpers/temperatureHelper.js +2 -2
- package/lib/helpers/timeHelper.js +10 -1
- package/lib/stateDefinitions/controlStates.js +64 -0
- package/lib/stateDefinitions/runtimeStates.js +1 -1
- package/lib/stateDefinitions/speechStates.js +58 -0
- package/lib/stateDefinitions/statusStates.js +4 -0
- package/main.js +10 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
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
|
-
//
|
|
55
|
-
this.runtimeTotal =
|
|
56
|
-
this.runtimeToday =
|
|
57
|
-
this.runtimeSeason =
|
|
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.
|
|
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 pumpStatus = (await this.adapter.getStateAsync('pump.status'))?.val || '';
|
|
37
|
+
if (pumpStatus.includes('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.
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
@@ -176,6 +176,70 @@ async function createControlStates(adapter) {
|
|
|
176
176
|
native: {},
|
|
177
177
|
});
|
|
178
178
|
await adapter.setStateAsync('control.energy.reset', { val: false, ack: true });
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------
|
|
181
|
+
// Channel: control.circulation
|
|
182
|
+
await adapter.setObjectNotExistsAsync('control.circulation', {
|
|
183
|
+
type: 'channel',
|
|
184
|
+
common: {
|
|
185
|
+
name: 'Tagesumwälzungs-Steuerung',
|
|
186
|
+
desc: 'Automatische oder manuelle Prüfung der täglichen Umwälzmenge',
|
|
187
|
+
},
|
|
188
|
+
native: {},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// State: Modus (auto/manual/notify/off)
|
|
192
|
+
await adapter.setObjectNotExistsAsync('control.circulation.mode', {
|
|
193
|
+
type: 'state',
|
|
194
|
+
common: {
|
|
195
|
+
name: 'Modus der Umwälzungsprüfung',
|
|
196
|
+
desc: 'Legt fest, ob und wie die Tagesumwälzung geprüft und ggf. nachgepumpt wird',
|
|
197
|
+
type: 'string',
|
|
198
|
+
role: 'value',
|
|
199
|
+
read: true,
|
|
200
|
+
write: true,
|
|
201
|
+
def: 'notify',
|
|
202
|
+
states: {
|
|
203
|
+
auto: 'Automatik',
|
|
204
|
+
manual: 'Manuell',
|
|
205
|
+
notify: 'Nur benachrichtigen',
|
|
206
|
+
off: 'Aus',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
native: {},
|
|
210
|
+
});
|
|
211
|
+
await adapter.setStateAsync('control.circulation.mode', { val: 'notify', ack: true });
|
|
212
|
+
|
|
213
|
+
// State: Prüfzeitpunkt
|
|
214
|
+
await adapter.setObjectNotExistsAsync('control.circulation.check_time', {
|
|
215
|
+
type: 'state',
|
|
216
|
+
common: {
|
|
217
|
+
name: 'Prüfzeitpunkt für Tagesumwälzung',
|
|
218
|
+
desc: 'Uhrzeit, zu der täglich die Tagesumwälzung geprüft und ggf. gemeldet wird (Format HH:MM)',
|
|
219
|
+
type: 'string',
|
|
220
|
+
role: 'value.time',
|
|
221
|
+
read: true,
|
|
222
|
+
write: true,
|
|
223
|
+
def: '18:00',
|
|
224
|
+
},
|
|
225
|
+
native: {},
|
|
226
|
+
});
|
|
227
|
+
await adapter.setStateAsync('control.circulation.check_time', { val: '18:00', ack: true });
|
|
228
|
+
|
|
229
|
+
// State: letzter Bericht
|
|
230
|
+
await adapter.setObjectNotExistsAsync('control.circulation.last_report', {
|
|
231
|
+
type: 'state',
|
|
232
|
+
common: {
|
|
233
|
+
name: 'Letzter Bericht zur Tagesumwälzung',
|
|
234
|
+
desc: 'Zeitstempel des letzten automatisch erzeugten Umwälzungs-Reports',
|
|
235
|
+
type: 'string',
|
|
236
|
+
role: 'date',
|
|
237
|
+
read: true,
|
|
238
|
+
write: false,
|
|
239
|
+
},
|
|
240
|
+
native: {},
|
|
241
|
+
});
|
|
242
|
+
await adapter.setStateAsync('control.circulation.last_report', { val: '', ack: true });
|
|
179
243
|
} catch (err) {
|
|
180
244
|
adapter.log.error(`[controlStates] Fehler beim Erstellen der Control-States: ${err.message}`);
|
|
181
245
|
}
|