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
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.2.1": {
|
|
7
|
+
"en": "Fixed the issue with invisible states for speech control and added proper internal variable handling.",
|
|
8
|
+
"de": "Behebung des Problems mit unsichtbaren States für die Sprachsteuerung und hinzugefügte korrekte Handhabung interner Variablen.",
|
|
9
|
+
"ru": "Исправлена проблема с невидимыми состояниями для управления речью и добавлена правильная обработка внутренних переменных.",
|
|
10
|
+
"pt": "Corrigido o problema com estados invisíveis para controle de fala e adicionada a manipulação adequada de variáveis internas.",
|
|
11
|
+
"nl": "Het probleem met onzichtbare toestanden voor spraakbesturing is opgelost en de juiste verwerking van interne variabelen toegevoegd.",
|
|
12
|
+
"fr": "Correction du problème des états invisibles pour le contrôle vocal et ajout d'un traitement correct des variables internes.",
|
|
13
|
+
"it": "Risolto il problema con gli stati invisibili per il controllo vocale e aggiunto un corretto trattamento delle variabili interne.",
|
|
14
|
+
"es": "Se solucionó el problema con los estados invisibles para el control de voz y se añadió el manejo adecuado de las variables internas.",
|
|
15
|
+
"pl": "Naprawiono problem z niewidzialnymi stanami dla kontroli głosowej i dodano prawidłowe przetwarzanie zmiennych wewnętrznych.",
|
|
16
|
+
"uk": "Виправлено проблему з невидимими станами для керування голосом та додано правильну обробку внутрішніх змінних.",
|
|
17
|
+
"zh-cn": "修复了语音控制的隐形状态问题,并添加了正确的内部变量处理。"
|
|
18
|
+
},
|
|
6
19
|
"0.2.0": {
|
|
7
20
|
"en": "New diagnostic area 'SystemCheck' for internal debug logs and future analysis tools.",
|
|
8
21
|
"de": "Neuer Diagnosebereich 'SystemCheck' für interne Debug-Logs und künftige Analysefunktionen.",
|
|
@@ -115,6 +128,11 @@
|
|
|
115
128
|
"extIcon": "https://raw.githubusercontent.com/DasBo1975/ioBroker.poolcontrol/main/admin/poolcontrol.png",
|
|
116
129
|
"readme": "https://github.com/DasBo1975/ioBroker.poolcontrol/blob/main/README.md",
|
|
117
130
|
"loglevel": "info",
|
|
131
|
+
"ignoreLogs": [
|
|
132
|
+
"State .*speech\\.solar_active.* has no existing object",
|
|
133
|
+
"State .*speech\\.frost_active.* has no existing object",
|
|
134
|
+
"State .*speech\\.time_active.* has no existing object"
|
|
135
|
+
],
|
|
118
136
|
"tier": 3,
|
|
119
137
|
"mode": "daemon",
|
|
120
138
|
"type": "climate-control",
|
|
@@ -29,13 +29,13 @@ const consumptionHelper = {
|
|
|
29
29
|
this.price = parseFloat(String(adapter.config.energy_price_eur_kwh).replace(',', '.')) || 0;
|
|
30
30
|
this.lastKnownPrice = this.price;
|
|
31
31
|
|
|
32
|
-
this.adapter.log.
|
|
32
|
+
this.adapter.log.debug(`[consumptionHelper] Strompreis: ${this.price} €/kWh`);
|
|
33
33
|
|
|
34
34
|
if (this.energyId) {
|
|
35
35
|
adapter.subscribeForeignStates(this.energyId);
|
|
36
|
-
adapter.log.
|
|
36
|
+
adapter.log.debug(`[consumptionHelper] Überwache externen kWh-Zähler: ${this.energyId}`);
|
|
37
37
|
} else {
|
|
38
|
-
adapter.log.
|
|
38
|
+
adapter.log.debug('[consumptionHelper] Kein externer kWh-Zähler konfiguriert → Verbrauchslogik inaktiv.');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
this._scheduleDailyReset();
|
|
@@ -51,7 +51,7 @@ const consumptionHelper = {
|
|
|
51
51
|
this.baseTotalKwh = totalKwh;
|
|
52
52
|
this.baseTotalEur = totalEur;
|
|
53
53
|
|
|
54
|
-
this.adapter.log.
|
|
54
|
+
this.adapter.log.debug(
|
|
55
55
|
`[consumptionHelper] Kosten-Basis geladen → ${this.baseTotalEur.toFixed(
|
|
56
56
|
2,
|
|
57
57
|
)} € bei ${this.baseTotalKwh.toFixed(3)} kWh`,
|
|
@@ -169,7 +169,7 @@ const consumptionHelper = {
|
|
|
169
169
|
this.baselines.month = totalNow - (month || 0);
|
|
170
170
|
this.baselines.year = totalNow - (year || 0);
|
|
171
171
|
|
|
172
|
-
this.adapter.log.
|
|
172
|
+
this.adapter.log.debug(`[consumptionHelper] Baselines geladen: ${JSON.stringify(this.baselines)}`);
|
|
173
173
|
},
|
|
174
174
|
|
|
175
175
|
// NEU: Baselines aus States rekonstruieren (beim Adapterstart)
|
|
@@ -186,7 +186,7 @@ const consumptionHelper = {
|
|
|
186
186
|
this.baselines.month = totalNow - month;
|
|
187
187
|
this.baselines.year = totalNow - year;
|
|
188
188
|
|
|
189
|
-
this.adapter.log.
|
|
189
|
+
this.adapter.log.debug(`[consumptionHelper] Bestehende Verbrauchswerte wiederhergestellt.`);
|
|
190
190
|
} catch (err) {
|
|
191
191
|
this.adapter.log.warn(
|
|
192
192
|
`[consumptionHelper] Fehler beim Wiederherstellen der Verbrauchsstände: ${err.message}`,
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* controlHelper
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
5
|
+
* - Steuert Wartungsmodus, Rückspülung, Energie-Reset, Saison
|
|
6
|
+
* - Führt tägliche Umwälzprüfung (z. B. 18:00 Uhr) durch
|
|
7
|
+
* - Automatisches Nachpumpen, wenn Tagesziel nicht erreicht
|
|
8
|
+
* - Sendet Statusmeldungen über speech.queue
|
|
9
|
+
* - Nutzt Vorrangsteuerung über pump.mode = "controlHelper"
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
let adapter;
|
|
12
13
|
let backwashTimer = null;
|
|
14
|
+
let dailyTimer = null;
|
|
15
|
+
let previousPumpMode = null;
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Initialisiert den Control-Helper.
|
|
@@ -24,15 +27,154 @@ function init(a) {
|
|
|
24
27
|
adapter.subscribeStates('control.season.active');
|
|
25
28
|
adapter.subscribeStates('control.pump.backwash_start');
|
|
26
29
|
adapter.subscribeStates('control.pump.maintenance_active');
|
|
27
|
-
adapter.subscribeStates('control.energy.reset');
|
|
30
|
+
adapter.subscribeStates('control.energy.reset');
|
|
31
|
+
adapter.subscribeStates('control.circulation.check_time');
|
|
32
|
+
|
|
33
|
+
// Täglichen Check planen
|
|
34
|
+
_scheduleDailyCheck().catch(err =>
|
|
35
|
+
adapter.log.error(`[controlHelper] Fehler bei _scheduleDailyCheck(): ${err.message}`),
|
|
36
|
+
);
|
|
28
37
|
|
|
29
38
|
adapter.log.debug('[controlHelper] Überwachung der Control-States aktiviert');
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Plant den täglichen Umwälzungscheck neu.
|
|
43
|
+
*/
|
|
44
|
+
async function _scheduleDailyCheck() {
|
|
45
|
+
try {
|
|
46
|
+
if (dailyTimer) {
|
|
47
|
+
clearTimeout(dailyTimer);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const timeStr = (await adapter.getStateAsync('control.circulation.check_time'))?.val || '18:00';
|
|
51
|
+
const [hours, minutes] = timeStr.split(':').map(x => parseInt(x, 10));
|
|
52
|
+
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const next = new Date();
|
|
55
|
+
next.setHours(hours, minutes, 0, 0);
|
|
56
|
+
if (next <= now) {
|
|
57
|
+
next.setDate(next.getDate() + 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const diffMs = next - now;
|
|
61
|
+
adapter.log.debug(`[controlHelper] Nächster Tages-Umwälzungscheck geplant für ${next.toLocaleTimeString()}`);
|
|
62
|
+
|
|
63
|
+
dailyTimer = setTimeout(async () => {
|
|
64
|
+
await _runDailyCirculationCheck();
|
|
65
|
+
await _scheduleDailyCheck();
|
|
66
|
+
}, diffMs);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
adapter.log.error(`[controlHelper] Fehler bei _scheduleDailyCheck(): ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Führt den täglichen Umwälzungsbericht und ggf. Nachpumpen aus.
|
|
74
|
+
*/
|
|
75
|
+
async function _runDailyCirculationCheck() {
|
|
76
|
+
try {
|
|
77
|
+
adapter.log.debug('[controlHelper] Starte Tages-Umwälzungscheck ...');
|
|
78
|
+
|
|
79
|
+
const seasonActive = (await adapter.getStateAsync('status.season_active'))?.val;
|
|
80
|
+
const mode = (await adapter.getStateAsync('control.circulation.mode'))?.val || 'off';
|
|
81
|
+
const dailyTotal = Math.round((await adapter.getStateAsync('circulation.daily_total'))?.val || 0);
|
|
82
|
+
const dailyRequired = Math.round((await adapter.getStateAsync('circulation.daily_required'))?.val || 0);
|
|
83
|
+
const collector = Number((await adapter.getStateAsync('temperature.collector.current'))?.val || 0);
|
|
84
|
+
const pool = Number((await adapter.getStateAsync('temperature.surface.current'))?.val || 0);
|
|
85
|
+
|
|
86
|
+
if (!seasonActive) {
|
|
87
|
+
adapter.log.debug('[controlHelper] Saison inaktiv – Tagesprüfung übersprungen.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!dailyRequired || dailyRequired <= 0) {
|
|
92
|
+
await _sendSpeech('Keine Zielumwälzmenge festgelegt – Tagesbericht übersprungen.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const percent = Math.min(100, Math.round((dailyTotal / dailyRequired) * 100));
|
|
97
|
+
const missing = Math.max(0, Math.round(dailyRequired - dailyTotal));
|
|
98
|
+
let message = '';
|
|
99
|
+
|
|
100
|
+
switch (mode) {
|
|
101
|
+
case 'notify':
|
|
102
|
+
message = `Heutige Umwälzung: ${dailyTotal} l (${percent} %). Es fehlen noch ${missing} l. Bitte ggf. manuell nachpumpen.`;
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'manual':
|
|
106
|
+
message = `Heutige Umwälzung: ${dailyTotal} l (${percent} %). Es fehlen noch ${missing} l. Bitte Pumpe manuell einschalten.`;
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case 'auto':
|
|
110
|
+
if (percent >= 100) {
|
|
111
|
+
message = `Tagesumwälzung abgeschlossen: ${dailyTotal} l (${percent} %). Kein Nachpumpen erforderlich.`;
|
|
112
|
+
} else if (collector > pool) {
|
|
113
|
+
message = `Heutige Umwälzung: ${dailyTotal} l (${percent} %). Es fehlen ${missing} l. Nachpumpen startet automatisch (Kollektor wärmer).`;
|
|
114
|
+
await _startAutoPumping(missing);
|
|
115
|
+
return;
|
|
116
|
+
} else {
|
|
117
|
+
message = `Heutige Umwälzung: ${dailyTotal} l (${percent} %). Kein automatisches Nachpumpen, Kollektor kälter als Pool.`;
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
adapter.log.debug(`[controlHelper] Modus '${mode}' → keine Aktion.`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await _sendSpeech(message);
|
|
127
|
+
await adapter.setStateAsync('control.circulation.last_report', { val: new Date().toISOString(), ack: true });
|
|
128
|
+
} catch (err) {
|
|
129
|
+
adapter.log.error(`[controlHelper] Fehler beim Tagescheck: ${err.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Startet automatisches Nachpumpen.
|
|
135
|
+
*
|
|
136
|
+
* @param {number} missingLiter - Fehlende Umwälzmenge in Litern
|
|
137
|
+
*/
|
|
138
|
+
async function _startAutoPumping(missingLiter) {
|
|
139
|
+
try {
|
|
140
|
+
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
141
|
+
|
|
142
|
+
previousPumpMode = (await adapter.getStateAsync('pump.mode'))?.val || 'auto';
|
|
143
|
+
await adapter.setStateAsync('pump.mode', { val: 'controlHelper', ack: true });
|
|
144
|
+
await adapter.setStateAsync('pump.reason', { val: 'nachpumpen', ack: true });
|
|
145
|
+
await adapter.setStateAsync('pump.pump_switch', { val: true, ack: false });
|
|
146
|
+
|
|
147
|
+
adapter.log.info(`[controlHelper] Automatisches Nachpumpen gestartet (${missingLiter} l fehlen).`);
|
|
148
|
+
if (notify) {
|
|
149
|
+
await _sendSpeech(`Automatisches Nachpumpen gestartet. Es fehlen ${missingLiter} Liter.`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const interval = setInterval(async () => {
|
|
153
|
+
const total = Math.round((await adapter.getStateAsync('circulation.daily_total'))?.val || 0);
|
|
154
|
+
const required = Math.round((await adapter.getStateAsync('circulation.daily_required'))?.val || 0);
|
|
155
|
+
|
|
156
|
+
if (total >= required) {
|
|
157
|
+
clearInterval(interval);
|
|
158
|
+
await adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
159
|
+
await adapter.setStateAsync('pump.mode', { val: previousPumpMode, ack: true });
|
|
160
|
+
await adapter.setStateAsync('pump.reason', { val: '', ack: true });
|
|
161
|
+
previousPumpMode = null;
|
|
162
|
+
|
|
163
|
+
adapter.log.info('[controlHelper] Nachpumpen abgeschlossen – Tagesziel erreicht.');
|
|
164
|
+
if (notify) {
|
|
165
|
+
await _sendSpeech('Nachpumpen abgeschlossen. Tagesziel erreicht.');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}, 60 * 1000);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
adapter.log.error(`[controlHelper] Fehler beim automatischen Nachpumpen: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
32
174
|
/**
|
|
33
175
|
* Reagiert auf Änderungen der States im Bereich control.*
|
|
34
176
|
*
|
|
35
|
-
* @param {string} id - ID des geänderten States
|
|
177
|
+
* @param {string} id - Objekt-ID des geänderten States
|
|
36
178
|
* @param {ioBroker.State} state - Neuer State-Wert
|
|
37
179
|
*/
|
|
38
180
|
async function handleStateChange(id, state) {
|
|
@@ -41,39 +183,41 @@ async function handleStateChange(id, state) {
|
|
|
41
183
|
return;
|
|
42
184
|
}
|
|
43
185
|
|
|
44
|
-
// === SAISONSTATUS
|
|
186
|
+
// === SAISONSTATUS ===
|
|
45
187
|
if (id.endsWith('control.season.active')) {
|
|
46
188
|
const newVal = !!state.val;
|
|
47
189
|
adapter.log.info(`[controlHelper] Poolsaison wurde ${newVal ? 'aktiviert' : 'deaktiviert'}.`);
|
|
48
190
|
await adapter.setStateAsync('status.season_active', { val: newVal, ack: true });
|
|
49
191
|
}
|
|
50
192
|
|
|
51
|
-
// === WARTUNGSMODUS
|
|
193
|
+
// === WARTUNGSMODUS ===
|
|
52
194
|
if (id.endsWith('control.pump.maintenance_active')) {
|
|
53
195
|
const active = !!state.val;
|
|
54
196
|
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
55
197
|
|
|
56
198
|
if (active) {
|
|
57
|
-
await adapter.
|
|
199
|
+
previousPumpMode = (await adapter.getStateAsync('pump.mode'))?.val || 'auto';
|
|
200
|
+
await adapter.setStateAsync('pump.mode', { val: 'controlHelper', ack: true });
|
|
201
|
+
await adapter.setStateAsync('pump.reason', { val: 'wartung', ack: true });
|
|
58
202
|
await adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
59
203
|
adapter.log.info('[controlHelper] Wartungsmodus aktiviert. Automatik pausiert.');
|
|
60
204
|
|
|
61
205
|
if (notify) {
|
|
62
|
-
await
|
|
63
|
-
'Wartungsmodus aktiviert. Automatikfunktionen sind vorübergehend deaktiviert.',
|
|
64
|
-
);
|
|
206
|
+
await _sendSpeech('Wartungsmodus aktiviert. Automatikfunktionen deaktiviert.');
|
|
65
207
|
}
|
|
66
208
|
} else {
|
|
67
|
-
await adapter.setStateAsync('pump.mode', { val:
|
|
68
|
-
adapter.
|
|
209
|
+
await adapter.setStateAsync('pump.mode', { val: previousPumpMode, ack: true });
|
|
210
|
+
await adapter.setStateAsync('pump.reason', { val: '', ack: true });
|
|
211
|
+
previousPumpMode = null;
|
|
69
212
|
|
|
213
|
+
adapter.log.info('[controlHelper] Wartungsmodus beendet. Automatik wieder aktiv.');
|
|
70
214
|
if (notify) {
|
|
71
|
-
await
|
|
215
|
+
await _sendSpeech('Wartungsmodus beendet. Automatikbetrieb wieder aktiv.');
|
|
72
216
|
}
|
|
73
217
|
}
|
|
74
218
|
}
|
|
75
219
|
|
|
76
|
-
// === RÜCKSPÜLUNG
|
|
220
|
+
// === RÜCKSPÜLUNG ===
|
|
77
221
|
if (id.endsWith('control.pump.backwash_start') && state.val === true) {
|
|
78
222
|
const duration = (await adapter.getStateAsync('control.pump.backwash_duration'))?.val || 1;
|
|
79
223
|
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
@@ -86,14 +230,16 @@ async function handleStateChange(id, state) {
|
|
|
86
230
|
}
|
|
87
231
|
|
|
88
232
|
await adapter.setStateAsync('control.pump.backwash_active', { val: true, ack: true });
|
|
89
|
-
await adapter.setStateAsync('pump.
|
|
233
|
+
await adapter.setStateAsync('control.pump.backwash_start', { val: false, ack: true });
|
|
234
|
+
await adapter.setStateAsync('pump.mode', { val: 'controlHelper', ack: true });
|
|
235
|
+
await adapter.setStateAsync('pump.reason', { val: 'rückspülen', ack: true });
|
|
90
236
|
await adapter.setStateAsync('pump.pump_switch', { val: true, ack: false });
|
|
91
237
|
|
|
92
238
|
const durationText = duration === 1 ? 'eine Minute' : `${duration} Minuten`;
|
|
93
|
-
adapter.log.info(`[controlHelper] Rückspülung gestartet (
|
|
239
|
+
adapter.log.info(`[controlHelper] Rückspülung gestartet (${duration} Minuten).`);
|
|
94
240
|
|
|
95
241
|
if (notify) {
|
|
96
|
-
await
|
|
242
|
+
await _sendSpeech(`Rückspülung gestartet. Dauer ${durationText}.`);
|
|
97
243
|
}
|
|
98
244
|
|
|
99
245
|
if (backwashTimer) {
|
|
@@ -104,12 +250,12 @@ async function handleStateChange(id, state) {
|
|
|
104
250
|
try {
|
|
105
251
|
await adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
106
252
|
await adapter.setStateAsync('pump.mode', { val: prevMode, ack: true });
|
|
253
|
+
await adapter.setStateAsync('pump.reason', { val: '', ack: true });
|
|
107
254
|
await adapter.setStateAsync('control.pump.backwash_active', { val: false, ack: true });
|
|
108
255
|
|
|
109
256
|
adapter.log.info('[controlHelper] Rückspülung beendet. Automatik wieder aktiv.');
|
|
110
|
-
|
|
111
257
|
if (notify) {
|
|
112
|
-
await
|
|
258
|
+
await _sendSpeech('Rückspülung abgeschlossen. Automatikmodus wieder aktiv.');
|
|
113
259
|
}
|
|
114
260
|
} catch (err) {
|
|
115
261
|
adapter.log.warn(`[controlHelper] Fehler beim Beenden der Rückspülung: ${err.message}`);
|
|
@@ -119,35 +265,39 @@ async function handleStateChange(id, state) {
|
|
|
119
265
|
);
|
|
120
266
|
}
|
|
121
267
|
|
|
122
|
-
// === ENERGIEZÄHLER RESET
|
|
268
|
+
// === ENERGIEZÄHLER RESET ===
|
|
123
269
|
if (id.endsWith('control.energy.reset') && state.val === true) {
|
|
124
270
|
const now = new Date();
|
|
125
271
|
const timestamp = now.toLocaleString('de-DE');
|
|
126
272
|
const notify = (await adapter.getStateAsync('control.pump.notifications_enabled'))?.val;
|
|
127
273
|
|
|
128
|
-
adapter.log.info(`[controlHelper] Energiezähler
|
|
274
|
+
adapter.log.info(`[controlHelper] Energiezähler wird vollständig zurückgesetzt (${timestamp}).`);
|
|
129
275
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
276
|
+
const consStates = [
|
|
277
|
+
'consumption.total_kwh',
|
|
278
|
+
'consumption.day_kwh',
|
|
279
|
+
'consumption.week_kwh',
|
|
280
|
+
'consumption.month_kwh',
|
|
281
|
+
'consumption.year_kwh',
|
|
282
|
+
'consumption.last_total_kwh',
|
|
283
|
+
'consumption.offset_kwh',
|
|
284
|
+
'costs.total_eur',
|
|
285
|
+
'costs.day_eur',
|
|
286
|
+
'costs.week_eur',
|
|
287
|
+
'costs.month_eur',
|
|
288
|
+
'costs.year_eur',
|
|
289
|
+
];
|
|
138
290
|
|
|
139
|
-
|
|
140
|
-
await adapter.setStateAsync(
|
|
291
|
+
for (const sid of consStates) {
|
|
292
|
+
await adapter.setStateAsync(sid, { val: 0, ack: true });
|
|
293
|
+
}
|
|
141
294
|
|
|
142
|
-
|
|
143
|
-
const msg = `Energiezähler wurde am ${timestamp} vollständig zurückgesetzt.`;
|
|
144
|
-
adapter.log.info(`[controlHelper] ${msg}`);
|
|
295
|
+
await adapter.setStateAsync('control.energy.reset', { val: false, ack: true });
|
|
145
296
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
adapter.log.error(`[controlHelper] Fehler beim Energiezähler-Reset: ${err.message}`);
|
|
297
|
+
const msg = `Energiezähler und Kosten wurden am ${timestamp} vollständig zurückgesetzt.`;
|
|
298
|
+
adapter.log.info(`[controlHelper] ${msg}`);
|
|
299
|
+
if (notify) {
|
|
300
|
+
await _sendSpeech(msg);
|
|
151
301
|
}
|
|
152
302
|
}
|
|
153
303
|
} catch (err) {
|
|
@@ -156,32 +306,35 @@ async function handleStateChange(id, state) {
|
|
|
156
306
|
}
|
|
157
307
|
|
|
158
308
|
/**
|
|
159
|
-
*
|
|
160
|
-
* (setzt nur speech.last_text, Versand erfolgt dort)
|
|
309
|
+
* Sendet Text an speech.queue
|
|
161
310
|
*
|
|
162
|
-
* @param {string} text -
|
|
311
|
+
* @param {string} text - Nachricht, die an speech.queue gesendet werden soll
|
|
163
312
|
*/
|
|
164
|
-
async function
|
|
313
|
+
async function _sendSpeech(text) {
|
|
165
314
|
if (!text) {
|
|
166
315
|
return;
|
|
167
316
|
}
|
|
168
|
-
|
|
169
317
|
try {
|
|
170
|
-
await adapter.setStateAsync('speech.
|
|
171
|
-
adapter.log.debug(`[controlHelper]
|
|
318
|
+
await adapter.setStateAsync('speech.queue', { val: text, ack: false });
|
|
319
|
+
adapter.log.debug(`[controlHelper] Nachricht an speech.queue: ${text}`);
|
|
172
320
|
} catch (err) {
|
|
173
|
-
adapter.log.warn(`[controlHelper] Fehler beim
|
|
321
|
+
adapter.log.warn(`[controlHelper] Fehler beim Senden an speech.queue: ${err.message}`);
|
|
174
322
|
}
|
|
175
323
|
}
|
|
176
324
|
|
|
177
325
|
/**
|
|
178
|
-
*
|
|
326
|
+
* Aufräumen
|
|
179
327
|
*/
|
|
180
328
|
function cleanup() {
|
|
181
329
|
if (backwashTimer) {
|
|
182
330
|
clearTimeout(backwashTimer);
|
|
183
331
|
backwashTimer = null;
|
|
184
332
|
}
|
|
333
|
+
if (dailyTimer) {
|
|
334
|
+
clearTimeout(dailyTimer);
|
|
335
|
+
dailyTimer = null;
|
|
336
|
+
}
|
|
337
|
+
previousPumpMode = null;
|
|
185
338
|
}
|
|
186
339
|
|
|
187
340
|
module.exports = { init, handleStateChange, cleanup };
|
|
@@ -42,10 +42,10 @@ const debugLogHelper = {
|
|
|
42
42
|
if (target !== 'none') {
|
|
43
43
|
this._subscribeTarget(target);
|
|
44
44
|
} else {
|
|
45
|
-
adapter.log.
|
|
45
|
+
adapter.log.debug('[debugLogHelper] Kein Bereich ausgewählt – Logger inaktiv.');
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
adapter.log.
|
|
48
|
+
adapter.log.debug('[debugLogHelper] Initialisierung abgeschlossen');
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -104,18 +104,18 @@ const debugLogHelper = {
|
|
|
104
104
|
}
|
|
105
105
|
if (this.subscribedTarget && this.subscribedTarget !== 'none') {
|
|
106
106
|
this.adapter.unsubscribeStates(`${this.subscribedTarget}.*`);
|
|
107
|
-
this.adapter.log.
|
|
107
|
+
this.adapter.log.debug(`[debugLogHelper] Überwachung für Bereich "${this.subscribedTarget}" beendet.`);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
this.subscribedTarget = newTarget;
|
|
111
111
|
|
|
112
112
|
if (newTarget === 'none') {
|
|
113
|
-
this.adapter.log.
|
|
113
|
+
this.adapter.log.debug('[debugLogHelper] Kein Bereich aktiv.');
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
this._subscribeTarget(newTarget);
|
|
118
|
-
this.adapter.log.
|
|
118
|
+
this.adapter.log.debug(`[debugLogHelper] Überwachung für Bereich "${newTarget}" gestartet.`);
|
|
119
119
|
await this._appendLog(
|
|
120
120
|
`\n=== Debug-Log gestartet: Bereich "${newTarget}" @ ${new Date().toLocaleString()} ===\n`,
|
|
121
121
|
);
|
|
@@ -18,7 +18,7 @@ const frostHelper = {
|
|
|
18
18
|
// Minütlicher Check
|
|
19
19
|
this._scheduleCheck();
|
|
20
20
|
|
|
21
|
-
this.adapter.log.
|
|
21
|
+
this.adapter.log.debug('[frostHelper] initialisiert (Prüfung alle 60s)');
|
|
22
22
|
},
|
|
23
23
|
|
|
24
24
|
_scheduleCheck() {
|
|
@@ -32,6 +32,13 @@ const frostHelper = {
|
|
|
32
32
|
|
|
33
33
|
async _checkFrost() {
|
|
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('[frostHelper] Vorrang durch ControlHelper aktiv – Frostschutz pausiert.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
// Nur aktiv im AUTO-Modus
|
|
36
43
|
const mode = (await this.adapter.getStateAsync('pump.mode'))?.val;
|
|
37
44
|
if (mode !== 'auto') {
|
|
@@ -68,13 +75,22 @@ const frostHelper = {
|
|
|
68
75
|
shouldRun = false;
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
// --- NEU: Sprachsignal für Frostschutz setzen ---
|
|
79
|
+
const oldVal = (await this.adapter.getStateAsync('speech.frost_active'))?.val;
|
|
80
|
+
if (oldVal !== shouldRun) {
|
|
81
|
+
await this.adapter.setStateChangedAsync('speech.frost_active', {
|
|
82
|
+
val: shouldRun,
|
|
83
|
+
ack: true,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
// Schalten nur, wenn sich etwas ändert
|
|
72
88
|
if (shouldRun !== pumpActive) {
|
|
73
89
|
await this.adapter.setStateAsync('pump.pump_switch', {
|
|
74
90
|
val: shouldRun,
|
|
75
91
|
ack: false,
|
|
76
92
|
});
|
|
77
|
-
this.adapter.log.
|
|
93
|
+
this.adapter.log.debug(
|
|
78
94
|
`[frostHelper] Frostschutz → Pumpe ${shouldRun ? 'EIN' : 'AUS'} (Außen=${outside}°C, Grenze=${frostTemp}°C)`,
|
|
79
95
|
);
|
|
80
96
|
}
|
|
@@ -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.
|
|
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.
|
|
46
|
+
this.adapter.log.debug(`[pumpHelper] Überwache Leistung von ${this.currentPowerId}`);
|
|
47
47
|
});
|
|
48
48
|
} else {
|
|
49
|
-
this.adapter.log.
|
|
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.
|
|
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.
|
|
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.
|
|
71
|
+
this.adapter.log.debug('[pumpHelper] Keine Objekt-ID für Pumpen-Steckdose konfiguriert');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
this.adapter.log.
|
|
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.
|
|
212
|
+
await this.adapter.setStateChangedAsync('pump.status', {
|
|
180
213
|
val: status,
|
|
181
214
|
ack: true,
|
|
182
215
|
});
|