iobroker.poolcontrol 1.3.9 → 1.3.11
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 +58 -10
- package/io-package.json +29 -15
- package/lib/helpers/chemistryPhHelper.js +703 -0
- package/lib/helpers/chemistryTdsHelper.js +830 -0
- package/lib/helpers/photovoltaicHelper.js +54 -18
- package/lib/helpers/photovoltaicInsightsHelper.js +306 -0
- package/lib/helpers/solarExtendedHelper.js +53 -6
- package/lib/helpers/solarHelper.js +39 -0
- package/lib/i18n/de.json +77 -1
- package/lib/i18n/en.json +78 -2
- package/lib/i18n/es.json +77 -2
- package/lib/i18n/fr.json +77 -2
- package/lib/i18n/it.json +77 -2
- package/lib/i18n/nl.json +77 -2
- package/lib/i18n/pl.json +77 -2
- package/lib/i18n/pt.json +77 -2
- package/lib/i18n/ru.json +77 -2
- package/lib/i18n/uk.json +77 -2
- package/lib/i18n/zh-cn.json +77 -2
- package/lib/stateDefinitions/chemistryPhStates.js +701 -0
- package/lib/stateDefinitions/chemistryTdsStates.js +889 -0
- package/lib/stateDefinitions/photovoltaicInsightsStates.js +444 -0
- package/main.js +45 -0
- package/package.json +1 -1
|
@@ -207,28 +207,29 @@ const photovoltaicHelper = {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// FIX: PV
|
|
210
|
+
// FIX: PV switching logic must only block on fulfilled circulation target if ignoreOnCirc is enabled
|
|
211
211
|
if (surplusActive) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const remaining = Number(remainingState?.val ?? NaN);
|
|
217
|
-
|
|
218
|
-
// RULE: Einschalten nur, wenn Umwälzung noch nicht erfüllt
|
|
219
|
-
if (Number.isFinite(remaining) && remaining <= 0) {
|
|
220
|
-
this.adapter.log.info(
|
|
221
|
-
`[photovoltaicHelper] Daily circulation target already reached (${remaining}) → pump stays OFF (no start despite surplus).`,
|
|
212
|
+
if (ignoreOnCirc) {
|
|
213
|
+
try {
|
|
214
|
+
const remainingState = await this.adapter.getForeignStateAsync(
|
|
215
|
+
'poolcontrol.0.circulation.daily_remaining',
|
|
222
216
|
);
|
|
223
|
-
|
|
224
|
-
}
|
|
217
|
+
const remaining = Number(remainingState?.val ?? NaN);
|
|
225
218
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
219
|
+
// RULE: Start only if circulation target is not fulfilled yet
|
|
220
|
+
if (Number.isFinite(remaining) && remaining <= 0) {
|
|
221
|
+
this.adapter.log.info(
|
|
222
|
+
`[photovoltaicHelper] Daily circulation target already reached (${remaining}) → pump stays OFF (no start despite surplus).`,
|
|
223
|
+
);
|
|
224
|
+
return this._maybeStopPump(true, 0, 'circulation_already_reached');
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
this.adapter.log.warn(`[photovoltaicHelper] Error while checking daily_remaining: ${err.message}`);
|
|
228
|
+
}
|
|
231
229
|
}
|
|
230
|
+
|
|
231
|
+
// RULE: Surplus active and no circulation blocking active → switch on
|
|
232
|
+
return this._maybeStartPump('pv_surplus');
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
// RULE: Kein Überschuss → ggf. Nachlauf/Aus
|
|
@@ -249,6 +250,8 @@ const photovoltaicHelper = {
|
|
|
249
250
|
}
|
|
250
251
|
this._pvPumpHoldUntil = 0;
|
|
251
252
|
|
|
253
|
+
await this._setActiveHelperIfAllowed('photovoltaicHelper');
|
|
254
|
+
|
|
252
255
|
if (this._desiredPump === true) {
|
|
253
256
|
return;
|
|
254
257
|
}
|
|
@@ -287,6 +290,7 @@ const photovoltaicHelper = {
|
|
|
287
290
|
this._desiredPump = false;
|
|
288
291
|
this.adapter.log.info(`[photovoltaicHelper] Pump OFF (${tag}, no afterrun)`);
|
|
289
292
|
await this._setPumpSwitch(false);
|
|
293
|
+
await this._releaseActiveHelperIfOwned();
|
|
290
294
|
return;
|
|
291
295
|
}
|
|
292
296
|
|
|
@@ -321,6 +325,7 @@ const photovoltaicHelper = {
|
|
|
321
325
|
this._desiredPump = false;
|
|
322
326
|
this.adapter.log.info('[photovoltaicHelper] Pump OFF (afterrun finished)');
|
|
323
327
|
await this._setPumpSwitch(false);
|
|
328
|
+
await this._releaseActiveHelperIfOwned();
|
|
324
329
|
}, holdMs);
|
|
325
330
|
|
|
326
331
|
this.adapter.log.debug(`[photovoltaicHelper] Afterrun started: ${afterrunMin} min (${tag})`);
|
|
@@ -334,6 +339,37 @@ const photovoltaicHelper = {
|
|
|
334
339
|
}
|
|
335
340
|
},
|
|
336
341
|
|
|
342
|
+
async _setActiveHelperIfAllowed(helperName) {
|
|
343
|
+
try {
|
|
344
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
345
|
+
|
|
346
|
+
if (activeHelper && activeHelper !== helperName) {
|
|
347
|
+
this.adapter.log.debug(
|
|
348
|
+
`[photovoltaicHelper] Active helper not changed because '${activeHelper}' currently owns the pump.`,
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: helperName, ack: true });
|
|
354
|
+
} catch (err) {
|
|
355
|
+
this.adapter.log.warn(`[photovoltaicHelper] Could not set pump.active_helper: ${err.message}`);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
async _releaseActiveHelperIfOwned() {
|
|
360
|
+
try {
|
|
361
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
362
|
+
|
|
363
|
+
if (activeHelper !== 'photovoltaicHelper') {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: '', ack: true });
|
|
368
|
+
} catch (err) {
|
|
369
|
+
this.adapter.log.warn(`[photovoltaicHelper] Could not release pump.active_helper: ${err.message}`);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
|
|
337
373
|
async _updateNumberState(id, val) {
|
|
338
374
|
try {
|
|
339
375
|
await this.adapter.setStateAsync(id, { val: Number(val) || 0, ack: true });
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { I18n } = require('@iobroker/adapter-core');
|
|
4
|
+
|
|
5
|
+
const photovoltaicInsightsHelper = {
|
|
6
|
+
adapter: null,
|
|
7
|
+
checkTimer: null,
|
|
8
|
+
lastResultTimestamp: null,
|
|
9
|
+
lastPvRuntimeActive: false,
|
|
10
|
+
|
|
11
|
+
init(adapter) {
|
|
12
|
+
this.adapter = adapter;
|
|
13
|
+
|
|
14
|
+
void this._subscribeStates();
|
|
15
|
+
this._scheduleCheck(0);
|
|
16
|
+
|
|
17
|
+
this.adapter.log.debug(
|
|
18
|
+
'[photovoltaicInsightsHelper] Initialized (Block 1 inputs, Block 2 calculation, Block 3 results, Block 4 debug)',
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
handleStateChange(id, state) {
|
|
23
|
+
if (!state || state.ack !== true) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!this._isRelevantState(id)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this._scheduleCheck(200);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
_scheduleCheck(delayMs = 0) {
|
|
35
|
+
if (this.checkTimer) {
|
|
36
|
+
this.adapter.clearTimeout(this.checkTimer);
|
|
37
|
+
this.checkTimer = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.checkTimer = this.adapter.setTimeout(() => {
|
|
41
|
+
this.checkTimer = null;
|
|
42
|
+
void this._updateInputs();
|
|
43
|
+
}, delayMs);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async _subscribeStates() {
|
|
47
|
+
const ids = [
|
|
48
|
+
'photovoltaic.power_surplus_w',
|
|
49
|
+
'photovoltaic.surplus_active',
|
|
50
|
+
'pump.live.current_power_w',
|
|
51
|
+
'pump.active_helper',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
for (const id of ids) {
|
|
55
|
+
await this.adapter.subscribeStatesAsync(id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.adapter.log.debug('[photovoltaicInsightsHelper] Subscribed to input states');
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
_isRelevantState(id) {
|
|
62
|
+
const ids = [
|
|
63
|
+
'photovoltaic.power_surplus_w',
|
|
64
|
+
'photovoltaic.surplus_active',
|
|
65
|
+
'pump.live.current_power_w',
|
|
66
|
+
'pump.active_helper',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return ids.some(relevantId => id === relevantId || id.endsWith(`.${relevantId}`));
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async _updateInputs() {
|
|
73
|
+
try {
|
|
74
|
+
const pvSurplusW = await this._readNumber('photovoltaic.power_surplus_w');
|
|
75
|
+
const pvSurplusActive = await this._readBoolean('photovoltaic.surplus_active');
|
|
76
|
+
|
|
77
|
+
const pumpPowerW = await this._readNumber('pump.live.current_power_w');
|
|
78
|
+
|
|
79
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.inputs.pv_surplus_w', {
|
|
80
|
+
val: Number.isFinite(pvSurplusW) ? pvSurplusW : 0,
|
|
81
|
+
ack: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.inputs.pv_surplus_active', {
|
|
85
|
+
val: pvSurplusActive,
|
|
86
|
+
ack: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.inputs.pump_power_w', {
|
|
90
|
+
val: Number.isFinite(pumpPowerW) ? pumpPowerW : 0,
|
|
91
|
+
ack: true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await this._updateCalculation();
|
|
95
|
+
await this._updateResults();
|
|
96
|
+
|
|
97
|
+
this.adapter.log.debug('[photovoltaicInsightsHelper] Inputs, calculation and results updated');
|
|
98
|
+
} catch (err) {
|
|
99
|
+
this.adapter.log.warn(`[photovoltaicInsightsHelper] Error updating inputs: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async _updateCalculation() {
|
|
104
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.calculation.mode', {
|
|
105
|
+
val: I18n.translate('photovoltaic_insights_calculation_mode_daily'),
|
|
106
|
+
ack: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.calculation.price_source', {
|
|
110
|
+
val: I18n.translate('photovoltaic_insights_price_source_adapter_config'),
|
|
111
|
+
ack: true,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.calculation.note', {
|
|
115
|
+
val: I18n.translate('photovoltaic_insights_calculation_note_block_2'),
|
|
116
|
+
ack: true,
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async _updateResults() {
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
|
|
123
|
+
const pvSurplusActive = await this._readBoolean('photovoltaic.surplus_active');
|
|
124
|
+
const pumpPowerW = await this._readNumber('pump.live.current_power_w');
|
|
125
|
+
const activeHelper = await this._readString('pump.active_helper');
|
|
126
|
+
const electricityPriceEurKwh = Number(this.adapter.config.energy_price_eur_kwh);
|
|
127
|
+
|
|
128
|
+
const pvControlsPump = activeHelper === 'photovoltaicHelper';
|
|
129
|
+
const pvRuntimeActive = pvSurplusActive && pvControlsPump;
|
|
130
|
+
|
|
131
|
+
let runtimeTodayMin = await this._readNumber('analytics.insights.photovoltaic.results.runtime_today_min');
|
|
132
|
+
let energyUsedTodayKwh = await this._readNumber(
|
|
133
|
+
'analytics.insights.photovoltaic.results.energy_used_today_kwh',
|
|
134
|
+
);
|
|
135
|
+
let savingsTodayEur = await this._readNumber('analytics.insights.photovoltaic.results.savings_today_eur');
|
|
136
|
+
let startsToday = await this._readNumber('analytics.insights.photovoltaic.results.starts_today');
|
|
137
|
+
|
|
138
|
+
runtimeTodayMin = Number.isFinite(runtimeTodayMin) ? runtimeTodayMin : 0;
|
|
139
|
+
energyUsedTodayKwh = Number.isFinite(energyUsedTodayKwh) ? energyUsedTodayKwh : 0;
|
|
140
|
+
savingsTodayEur = Number.isFinite(savingsTodayEur) ? savingsTodayEur : 0;
|
|
141
|
+
startsToday = Number.isFinite(startsToday) ? startsToday : 0;
|
|
142
|
+
|
|
143
|
+
if (pvRuntimeActive && !this.lastPvRuntimeActive) {
|
|
144
|
+
startsToday += 1;
|
|
145
|
+
this.lastResultTimestamp = now;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (pvRuntimeActive && this.lastResultTimestamp && Number.isFinite(pumpPowerW) && pumpPowerW > 0) {
|
|
149
|
+
const deltaHours = (now - this.lastResultTimestamp) / 3600000;
|
|
150
|
+
|
|
151
|
+
if (deltaHours > 0 && deltaHours <= 0.5) {
|
|
152
|
+
const energyDeltaKwh = (pumpPowerW * deltaHours) / 1000;
|
|
153
|
+
|
|
154
|
+
runtimeTodayMin = Number((runtimeTodayMin + deltaHours * 60).toFixed(2));
|
|
155
|
+
energyUsedTodayKwh = Number((energyUsedTodayKwh + energyDeltaKwh).toFixed(4));
|
|
156
|
+
|
|
157
|
+
if (Number.isFinite(electricityPriceEurKwh) && electricityPriceEurKwh > 0) {
|
|
158
|
+
savingsTodayEur = Number((savingsTodayEur + energyDeltaKwh * electricityPriceEurKwh).toFixed(2));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (pvRuntimeActive) {
|
|
164
|
+
this.lastResultTimestamp = now;
|
|
165
|
+
} else {
|
|
166
|
+
this.lastResultTimestamp = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.lastPvRuntimeActive = pvRuntimeActive;
|
|
170
|
+
|
|
171
|
+
const activeToday = runtimeTodayMin > 0 || startsToday > 0;
|
|
172
|
+
|
|
173
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.active_today', {
|
|
174
|
+
val: activeToday,
|
|
175
|
+
ack: true,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.runtime_today_min', {
|
|
179
|
+
val: runtimeTodayMin,
|
|
180
|
+
ack: true,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.energy_used_today_kwh', {
|
|
184
|
+
val: energyUsedTodayKwh,
|
|
185
|
+
ack: true,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.savings_today_eur', {
|
|
189
|
+
val: savingsTodayEur,
|
|
190
|
+
ack: true,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.starts_today', {
|
|
194
|
+
val: startsToday,
|
|
195
|
+
ack: true,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const summaryText = I18n.translate('photovoltaic_insights_summary_text_block_3')
|
|
199
|
+
.replace('%s', runtimeTodayMin.toFixed(0))
|
|
200
|
+
.replace('%s', energyUsedTodayKwh.toFixed(2))
|
|
201
|
+
.replace('%s', savingsTodayEur.toFixed(2));
|
|
202
|
+
|
|
203
|
+
const summaryJson = {
|
|
204
|
+
mode: 'daily_pv_surplus_analysis',
|
|
205
|
+
pv_surplus_active: pvSurplusActive,
|
|
206
|
+
pv_controls_pump: pvControlsPump,
|
|
207
|
+
pv_runtime_active: pvRuntimeActive,
|
|
208
|
+
active_today: activeToday,
|
|
209
|
+
values: {
|
|
210
|
+
pv_surplus_w: await this._readNumber('photovoltaic.power_surplus_w'),
|
|
211
|
+
pump_power_w: Number.isFinite(pumpPowerW) ? pumpPowerW : null,
|
|
212
|
+
electricity_price_eur_kwh: Number.isFinite(electricityPriceEurKwh) ? electricityPriceEurKwh : null,
|
|
213
|
+
runtime_today_min: runtimeTodayMin,
|
|
214
|
+
energy_used_today_kwh: energyUsedTodayKwh,
|
|
215
|
+
savings_today_eur: savingsTodayEur,
|
|
216
|
+
starts_today: startsToday,
|
|
217
|
+
},
|
|
218
|
+
note: I18n.translate('photovoltaic_insights_calculation_note_block_2'),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const summaryHtml = [
|
|
222
|
+
'<div>',
|
|
223
|
+
`<b>${I18n.translate('photovoltaic_insights_label_mode')}:</b> ${I18n.translate('photovoltaic_insights_calculation_mode_daily')}<br>`,
|
|
224
|
+
`<b>${I18n.translate('photovoltaic_insights_label_pv_surplus_active')}:</b> ${pvSurplusActive}<br>`,
|
|
225
|
+
`<b>${I18n.translate('photovoltaic_insights_label_pv_controls_pump')}:</b> ${pvControlsPump}<br>`,
|
|
226
|
+
`<b>${I18n.translate('photovoltaic_insights_label_runtime_today')}:</b> ${runtimeTodayMin.toFixed(2)} min<br>`,
|
|
227
|
+
`<b>${I18n.translate('photovoltaic_insights_label_energy_used_today')}:</b> ${energyUsedTodayKwh.toFixed(4)} kWh<br>`,
|
|
228
|
+
`<b>${I18n.translate('photovoltaic_insights_label_savings_today')}:</b> ${savingsTodayEur.toFixed(2)} €<br>`,
|
|
229
|
+
`<b>${I18n.translate('photovoltaic_insights_label_starts_today')}:</b> ${startsToday}<br>`,
|
|
230
|
+
`<b>${I18n.translate('photovoltaic_insights_label_summary')}:</b> ${summaryText}`,
|
|
231
|
+
'</div>',
|
|
232
|
+
].join('');
|
|
233
|
+
|
|
234
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.summary_text', {
|
|
235
|
+
val: summaryText,
|
|
236
|
+
ack: true,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.summary_json', {
|
|
240
|
+
val: JSON.stringify(summaryJson),
|
|
241
|
+
ack: true,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.results.summary_html', {
|
|
245
|
+
val: summaryHtml,
|
|
246
|
+
ack: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.debug.last_update', {
|
|
250
|
+
val: new Date().toISOString(),
|
|
251
|
+
ack: true,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.debug.last_recalculation_reason', {
|
|
255
|
+
val: pvRuntimeActive
|
|
256
|
+
? I18n.translate('photovoltaic_insights_debug_reason_pv_runtime_active')
|
|
257
|
+
: I18n.translate('photovoltaic_insights_debug_reason_no_pv_runtime'),
|
|
258
|
+
ack: true,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await this.adapter.setStateChangedAsync('analytics.insights.photovoltaic.debug.debug_text', {
|
|
262
|
+
val: pvRuntimeActive
|
|
263
|
+
? I18n.translate('photovoltaic_insights_debug_text_pv_runtime_active')
|
|
264
|
+
: I18n.translate('photovoltaic_insights_debug_text_no_pv_runtime'),
|
|
265
|
+
ack: true,
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
async _readState(id) {
|
|
270
|
+
try {
|
|
271
|
+
return await this.adapter.getStateAsync(id);
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
async _readBoolean(id) {
|
|
278
|
+
const state = await this._readState(id);
|
|
279
|
+
return state ? state.val === true : false;
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
async _readNumber(id) {
|
|
283
|
+
const state = await this._readState(id);
|
|
284
|
+
|
|
285
|
+
if (!state || state.val === null || state.val === undefined || state.val === '') {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const value = Number(state.val);
|
|
290
|
+
return Number.isFinite(value) ? value : null;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
async _readString(id) {
|
|
294
|
+
const state = await this._readState(id);
|
|
295
|
+
return state && state.val !== undefined ? String(state.val) : '';
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
cleanup() {
|
|
299
|
+
if (this.checkTimer) {
|
|
300
|
+
this.adapter.clearTimeout(this.checkTimer);
|
|
301
|
+
this.checkTimer = null;
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
module.exports = photovoltaicInsightsHelper;
|
|
@@ -222,12 +222,20 @@ const solarExtendedHelper = {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const speechSolarActive = requestActive && !blocked;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
|
|
226
|
+
// FIX:
|
|
227
|
+
// speech.solar_active darf vom solarExtendedHelper nur im Extended-Modus beschrieben werden.
|
|
228
|
+
// Im Standard-Modus ist der normale solarHelper zuständig, sonst überschreibt Extended
|
|
229
|
+
// dessen Solar-Sprachstatus minütlich wieder auf false.
|
|
230
|
+
if (isExtendedMode) {
|
|
231
|
+
const oldSpeechSolarActive = (await this.adapter.getStateAsync('speech.solar_active'))?.val;
|
|
232
|
+
|
|
233
|
+
if (oldSpeechSolarActive !== speechSolarActive) {
|
|
234
|
+
await this.adapter.setStateChangedAsync('speech.solar_active', {
|
|
235
|
+
val: speechSolarActive,
|
|
236
|
+
ack: true,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
231
239
|
}
|
|
232
240
|
|
|
233
241
|
// FIX:
|
|
@@ -235,10 +243,18 @@ const solarExtendedHelper = {
|
|
|
235
243
|
// wenn Solar Extended überhaupt der zuständige Solarmodus ist
|
|
236
244
|
// und keine höhere Fremdpriorität aktiv ist.
|
|
237
245
|
if (isExtendedMode && !isControlPriority && !isTimePriority) {
|
|
246
|
+
if (speechSolarActive) {
|
|
247
|
+
await this._setActiveHelperIfAllowed('solarExtendedHelper');
|
|
248
|
+
} else {
|
|
249
|
+
await this._releaseActiveHelperIfOwned();
|
|
250
|
+
}
|
|
251
|
+
|
|
238
252
|
await this.adapter.setStateChangedAsync('pump.pump_switch', {
|
|
239
253
|
val: speechSolarActive,
|
|
240
254
|
ack: false,
|
|
241
255
|
});
|
|
256
|
+
} else {
|
|
257
|
+
await this._releaseActiveHelperIfOwned();
|
|
242
258
|
}
|
|
243
259
|
|
|
244
260
|
await this.adapter.setStateChangedAsync('solar.extended.enabled_by_master', {
|
|
@@ -309,6 +325,37 @@ const solarExtendedHelper = {
|
|
|
309
325
|
}
|
|
310
326
|
},
|
|
311
327
|
|
|
328
|
+
async _setActiveHelperIfAllowed(helperName) {
|
|
329
|
+
try {
|
|
330
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
331
|
+
|
|
332
|
+
if (activeHelper && activeHelper !== helperName) {
|
|
333
|
+
this.adapter.log.debug(
|
|
334
|
+
`[solarExtendedHelper] Active helper not changed because '${activeHelper}' currently owns the pump.`,
|
|
335
|
+
);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: helperName, ack: true });
|
|
340
|
+
} catch (err) {
|
|
341
|
+
this.adapter.log.warn(`[solarExtendedHelper] Could not set pump.active_helper: ${err.message}`);
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
async _releaseActiveHelperIfOwned() {
|
|
346
|
+
try {
|
|
347
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
348
|
+
|
|
349
|
+
if (activeHelper !== 'solarExtendedHelper') {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: '', ack: true });
|
|
354
|
+
} catch (err) {
|
|
355
|
+
this.adapter.log.warn(`[solarExtendedHelper] Could not release pump.active_helper: ${err.message}`);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
|
|
312
359
|
cleanup() {
|
|
313
360
|
if (this.checkTimer) {
|
|
314
361
|
this.adapter.clearInterval(this.checkTimer);
|
|
@@ -131,6 +131,12 @@ const solarHelper = {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
// ZENTRAL: Pumpe über Bool-Schalter setzen
|
|
134
|
+
if (shouldRun) {
|
|
135
|
+
await this._setActiveHelperIfAllowed('solarHelper');
|
|
136
|
+
} else {
|
|
137
|
+
await this._releaseActiveHelperIfOwned();
|
|
138
|
+
}
|
|
139
|
+
|
|
134
140
|
await this.adapter.setStateChangedAsync('pump.pump_switch', {
|
|
135
141
|
val: shouldRun,
|
|
136
142
|
ack: false,
|
|
@@ -145,6 +151,8 @@ const solarHelper = {
|
|
|
145
151
|
ack: true,
|
|
146
152
|
});
|
|
147
153
|
|
|
154
|
+
await this._releaseActiveHelperIfOwned();
|
|
155
|
+
|
|
148
156
|
// Keine Schaltung – Grund protokollieren
|
|
149
157
|
const reason = !season
|
|
150
158
|
? 'Saison inaktiv'
|
|
@@ -209,6 +217,37 @@ const solarHelper = {
|
|
|
209
217
|
}
|
|
210
218
|
},
|
|
211
219
|
|
|
220
|
+
async _setActiveHelperIfAllowed(helperName) {
|
|
221
|
+
try {
|
|
222
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
223
|
+
|
|
224
|
+
if (activeHelper && activeHelper !== helperName) {
|
|
225
|
+
this.adapter.log.debug(
|
|
226
|
+
`[solarHelper] Active helper not changed because '${activeHelper}' currently owns the pump.`,
|
|
227
|
+
);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: helperName, ack: true });
|
|
232
|
+
} catch (err) {
|
|
233
|
+
this.adapter.log.warn(`[solarHelper] Could not set pump.active_helper: ${err.message}`);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
async _releaseActiveHelperIfOwned() {
|
|
238
|
+
try {
|
|
239
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
240
|
+
|
|
241
|
+
if (activeHelper !== 'solarHelper') {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: '', ack: true });
|
|
246
|
+
} catch (err) {
|
|
247
|
+
this.adapter.log.warn(`[solarHelper] Could not release pump.active_helper: ${err.message}`);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
|
|
212
251
|
cleanup() {
|
|
213
252
|
if (this.checkTimer) {
|
|
214
253
|
this.adapter.clearInterval(this.checkTimer);
|
package/lib/i18n/de.json
CHANGED
|
@@ -167,5 +167,81 @@
|
|
|
167
167
|
"solar_log_text_weather_supported_with_text": "Wetterbasierte Plausibilisierung ist aktiv. Aktuelle Wetterzusammenfassung: %s",
|
|
168
168
|
"solar_log_text_weather_supported": "Für diese Bewertung ist eine wetterbasierte Plausibilisierung aktiv.",
|
|
169
169
|
"solar_log_text_small_gain_detail": "Der aktuell geschätzte Tagesertrag liegt bei etwa %s Wh, das entspricht etwa %s kWh.",
|
|
170
|
-
"solar_log_text_quality_level": "Die aktuelle Qualitätsstufe der Bewertung ist %s."
|
|
170
|
+
"solar_log_text_quality_level": "Die aktuelle Qualitätsstufe der Bewertung ist %s.",
|
|
171
|
+
|
|
172
|
+
"photovoltaic_insights_calculation_mode_daily": "Tägliche PV-Überschussanalyse",
|
|
173
|
+
"photovoltaic_insights_price_source_adapter_config": "Adapter-Konfiguration",
|
|
174
|
+
"photovoltaic_insights_calculation_note_block_2": "Der Berechnungsblock bereitet die Quelleninformationen für die Photovoltaik-Insights-Analyse vor.",
|
|
175
|
+
"photovoltaic_insights_summary_text_block_3": "PV-Überschuss hat die Pumpe heute %s Minuten betrieben, dabei etwa %s kWh genutzt und ungefähr %s € eingespart.",
|
|
176
|
+
"photovoltaic_insights_label_mode": "Modus",
|
|
177
|
+
"photovoltaic_insights_label_pv_surplus_active": "PV-Überschuss aktiv",
|
|
178
|
+
"photovoltaic_insights_label_pv_controls_pump": "PV steuert Pumpe",
|
|
179
|
+
"photovoltaic_insights_label_runtime_today": "Laufzeit heute",
|
|
180
|
+
"photovoltaic_insights_label_energy_used_today": "Genutzte Energie heute",
|
|
181
|
+
"photovoltaic_insights_label_savings_today": "Einsparung heute",
|
|
182
|
+
"photovoltaic_insights_label_starts_today": "Starts heute",
|
|
183
|
+
"photovoltaic_insights_label_summary": "Zusammenfassung",
|
|
184
|
+
"photovoltaic_insights_debug_reason_pv_runtime_active": "PV-Überschussbetrieb aktiv",
|
|
185
|
+
"photovoltaic_insights_debug_reason_no_pv_runtime": "Kein aktiver PV-Überschussbetrieb",
|
|
186
|
+
"photovoltaic_insights_debug_text_pv_runtime_active": "PV-Überschuss ist aktiv und der Photovoltaik-Helper besitzt aktuell die Pumpe.",
|
|
187
|
+
"photovoltaic_insights_debug_text_no_pv_runtime": "Aktuell wird keine PV-Laufzeit gezählt, weil entweder kein PV-Überschuss aktiv ist oder der Photovoltaik-Helper die Pumpe nicht besitzt.",
|
|
188
|
+
|
|
189
|
+
"pH source state configured.": "pH-Quell-Datenpunkt konfiguriert.",
|
|
190
|
+
"pH source state could not be subscribed.": "pH-Quell-Datenpunkt konnte nicht abonniert werden.",
|
|
191
|
+
"No pH source state configured.": "Kein pH-Quell-Datenpunkt konfiguriert.",
|
|
192
|
+
"pH evaluation is disabled.": "pH-Auswertung ist deaktiviert.",
|
|
193
|
+
"pH input is disabled.": "pH-Eingang ist deaktiviert.",
|
|
194
|
+
"No pH input source is active.": "Keine pH-Eingangsquelle ist aktiv.",
|
|
195
|
+
"No valid pH source is configured.": "Keine gültige pH-Quelle ist konfiguriert.",
|
|
196
|
+
"pH source state could not be read.": "pH-Quell-Datenpunkt konnte nicht gelesen werden.",
|
|
197
|
+
"The configured pH source could not be read.": "Die konfigurierte pH-Quelle konnte nicht gelesen werden.",
|
|
198
|
+
"pH source state does not exist.": "pH-Quell-Datenpunkt existiert nicht.",
|
|
199
|
+
"The configured pH source state does not exist.": "Der konfigurierte pH-Quell-Datenpunkt existiert nicht.",
|
|
200
|
+
"Unknown pH source mode.": "Unbekannter pH-Quellenmodus.",
|
|
201
|
+
"Manual pH value is used.": "Manueller pH-Wert wird verwendet.",
|
|
202
|
+
"External pH source is valid.": "Externe pH-Quelle ist gültig.",
|
|
203
|
+
"The pH value is invalid. Please check the measurement or sensor.": "Der pH-Wert ist ungültig. Bitte Messung oder Sensor prüfen.",
|
|
204
|
+
"pH evaluation is waiting for the pool pump because the sensor is in a measurement section.": "pH-Auswertung wartet auf die Poolpumpe, weil der Sensor in einer Messstrecke sitzt.",
|
|
205
|
+
"pH evaluation is waiting until the measurement section has stabilized after pump start.": "pH-Auswertung wartet, bis die Messstrecke nach Pumpenstart stabilisiert ist.",
|
|
206
|
+
"pH value is too low. Check whether pH plus should be added according to the product instructions. Then circulate and measure again.": "pH-Wert ist zu niedrig. Bitte prüfen, ob pH-Plus nach Herstellerangabe zugegeben werden sollte. Danach umwälzen und erneut messen.",
|
|
207
|
+
"pH value is too high. Check whether pH minus should be added according to the product instructions. Then circulate and measure again.": "pH-Wert ist zu hoch. Bitte prüfen, ob pH-Minus nach Herstellerangabe zugegeben werden sollte. Danach umwälzen und erneut messen.",
|
|
208
|
+
"pH value is within the target range. No action is required.": "pH-Wert liegt im Zielbereich. Keine Aktion erforderlich.",
|
|
209
|
+
"Mixing run was not started because pH evaluation is disabled.": "Mischlauf wurde nicht gestartet, weil die pH-Auswertung deaktiviert ist.",
|
|
210
|
+
"Mixing run was not started because the pool season is inactive.": "Mischlauf wurde nicht gestartet, weil die Poolsaison inaktiv ist.",
|
|
211
|
+
"Mixing run was not started because no runtime is configured.": "Mischlauf wurde nicht gestartet, weil keine Laufzeit eingestellt ist.",
|
|
212
|
+
"Mixing run was not started because another helper currently controls the pump.": "Mischlauf wurde nicht gestartet, weil aktuell ein anderer Helper die Pumpe steuert.",
|
|
213
|
+
"pH mixing run started. No chemicals are dosed automatically.": "pH-Mischlauf gestartet. Es wird keine Chemie automatisch dosiert.",
|
|
214
|
+
"pH mixing run finished. Pump was switched off by the pH helper.": "pH-Mischlauf beendet. Die Pumpe wurde vom pH-Helper ausgeschaltet.",
|
|
215
|
+
"pH mixing run finished. Pump was not switched off because another helper is active.": "pH-Mischlauf beendet. Die Pumpe wurde nicht ausgeschaltet, weil ein anderer Helper aktiv ist.",
|
|
216
|
+
"pH mixing run finished. Pump was already running and was not switched off by the pH helper.": "pH-Mischlauf beendet. Die Pumpe lief bereits und wurde vom pH-Helper nicht ausgeschaltet.",
|
|
217
|
+
|
|
218
|
+
"No TDS source state configured.": "Kein TDS-Quell-Datenpunkt konfiguriert.",
|
|
219
|
+
"TDS source state configured.": "TDS-Quell-Datenpunkt konfiguriert.",
|
|
220
|
+
"TDS source state could not be subscribed.": "TDS-Quell-Datenpunkt konnte nicht abonniert werden.",
|
|
221
|
+
"TDS input is disabled.": "TDS-Eingang ist deaktiviert.",
|
|
222
|
+
"TDS evaluation is disabled.": "TDS-Auswertung ist deaktiviert.",
|
|
223
|
+
"No valid TDS source is configured.": "Keine gültige TDS-Quelle ist konfiguriert.",
|
|
224
|
+
"The configured TDS source state does not exist.": "Der konfigurierte TDS-Quell-Datenpunkt existiert nicht.",
|
|
225
|
+
"The configured TDS source could not be read.": "Die konfigurierte TDS-Quelle konnte nicht gelesen werden.",
|
|
226
|
+
"Unknown TDS source mode.": "Unbekannter TDS-Quellenmodus.",
|
|
227
|
+
"Manual TDS value is used.": "Manueller TDS-Wert wird verwendet.",
|
|
228
|
+
"External TDS source is valid.": "Externe TDS-Quelle ist gültig.",
|
|
229
|
+
"The TDS value is invalid. Please check the measurement or sensor.": "Der TDS-Wert ist ungültig. Bitte Messung oder Sensor prüfen.",
|
|
230
|
+
"TDS evaluation is waiting for the pool pump because the sensor is in a measurement section.": "TDS-Auswertung wartet auf die Poolpumpe, weil der Sensor in einer Messstrecke sitzt.",
|
|
231
|
+
"TDS evaluation is waiting until the measurement section has stabilized after pump start.": "TDS-Auswertung wartet, bis die Messstrecke nach Pumpenstart stabilisiert ist.",
|
|
232
|
+
"TDS value and trend are currently unremarkable.": "TDS-Wert und Trend sind aktuell unauffällig.",
|
|
233
|
+
"TDS is very high. Check water care and seriously consider a partial water change.": "TDS ist sehr hoch. Bitte Wasserpflege prüfen und einen Teilwasserwechsel ernsthaft in Betracht ziehen.",
|
|
234
|
+
"TDS is high or rising quickly. Check water load, chemical usage and consider fresh water or a partial water change.": "TDS ist hoch oder steigt schnell. Bitte Wasserbelastung, Chemikalieneinsatz und Frischwasser bzw. Teilwasserwechsel prüfen.",
|
|
235
|
+
"TDS is elevated or rising noticeably. Observe the trend and check water load and chemical additions.": "TDS ist erhöht oder steigt auffällig. Trend beobachten und Wasserbelastung sowie Chemikalienzugaben prüfen.",
|
|
236
|
+
"Not enough TDS history is available yet. Collect more valid measurements.": "Es sind noch nicht genügend TDS-Verlaufsdaten vorhanden. Weitere gültige Messwerte sammeln.",
|
|
237
|
+
"TDS is falling. This can be plausible after fresh water or a partial water change.": "TDS sinkt. Das kann nach Frischwasser oder einem Teilwasserwechsel plausibel sein.",
|
|
238
|
+
"Current TDS value": "Aktueller TDS-Wert",
|
|
239
|
+
"not enough data": "nicht genug Daten",
|
|
240
|
+
"Initial reference": "Initialer Referenzwert",
|
|
241
|
+
"not set": "nicht gesetzt",
|
|
242
|
+
"Delta since reference": "Differenz seit Referenzwert",
|
|
243
|
+
"not available": "nicht verfügbar",
|
|
244
|
+
"Trend status": "Trendstatus",
|
|
245
|
+
"Overall status": "Gesamtstatus",
|
|
246
|
+
"Recommendation": "Empfehlung"
|
|
171
247
|
}
|