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.
- package/LICENSE +22 -0
- package/README.md +129 -0
- package/admin/i18n/de/translations.json +5 -0
- package/admin/i18n/en/translations.json +5 -0
- package/admin/i18n/es/translations.json +5 -0
- package/admin/i18n/fr/translations.json +5 -0
- package/admin/i18n/it/translations.json +5 -0
- package/admin/i18n/nl/translations.json +5 -0
- package/admin/i18n/pl/translations.json +5 -0
- package/admin/i18n/pt/translations.json +5 -0
- package/admin/i18n/ru/translations.json +5 -0
- package/admin/i18n/uk/translations.json +5 -0
- package/admin/i18n/zh-cn/translations.json +5 -0
- package/admin/jsonConfig.json +901 -0
- package/admin/poolcontrol.png +0 -0
- package/io-package.json +176 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/helpers/consumptionHelper.js +185 -0
- package/lib/helpers/frostHelper.js +94 -0
- package/lib/helpers/pumpHelper.js +224 -0
- package/lib/helpers/runtimeHelper.js +159 -0
- package/lib/helpers/solarHelper.js +138 -0
- package/lib/helpers/speechHelper.js +108 -0
- package/lib/helpers/temperatureHelper.js +227 -0
- package/lib/helpers/timeHelper.js +88 -0
- package/lib/stateDefinitions/consumptionStates.js +82 -0
- package/lib/stateDefinitions/generalStates.js +68 -0
- package/lib/stateDefinitions/pumpStates.js +184 -0
- package/lib/stateDefinitions/runtimeStates.js +113 -0
- package/lib/stateDefinitions/solarStates.js +150 -0
- package/lib/stateDefinitions/speechStates.js +104 -0
- package/lib/stateDefinitions/temperatureStates.js +182 -0
- package/lib/stateDefinitions/timeStates.js +102 -0
- package/main.js +145 -0
- 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;
|