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