iobroker.poolcontrol 1.3.9 → 1.3.10
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 +31 -5
- package/io-package.json +16 -2
- package/lib/helpers/photovoltaicHelper.js +54 -18
- package/lib/helpers/photovoltaicInsightsHelper.js +306 -0
- package/lib/helpers/solarExtendedHelper.js +39 -0
- package/lib/helpers/solarHelper.js +39 -0
- package/lib/i18n/de.json +18 -1
- package/lib/i18n/en.json +18 -1
- package/lib/i18n/es.json +18 -2
- package/lib/i18n/fr.json +18 -2
- package/lib/i18n/it.json +18 -2
- package/lib/i18n/nl.json +18 -2
- package/lib/i18n/pl.json +18 -2
- package/lib/i18n/pt.json +18 -2
- package/lib/i18n/ru.json +18 -2
- package/lib/i18n/uk.json +18 -2
- package/lib/i18n/zh-cn.json +18 -2
- package/lib/stateDefinitions/photovoltaicInsightsStates.js +444 -0
- package/main.js +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -188,6 +188,37 @@ New features are added regularly – please refer to the changelog.
|
|
|
188
188
|
---
|
|
189
189
|
|
|
190
190
|
## Changelog
|
|
191
|
+
### 1.3.10 (2026-05-01)
|
|
192
|
+
|
|
193
|
+
New: Photovoltaic Insights
|
|
194
|
+
- Introduced a new analytics module `analytics.insights.photovoltaic`
|
|
195
|
+
- Tracks PV-based pump runtime, energy usage and estimated savings
|
|
196
|
+
- New helper: `photovoltaicInsightsHelper`
|
|
197
|
+
- New states: inputs, calculation, results, debug
|
|
198
|
+
- Includes summary outputs (text, JSON, HTML)
|
|
199
|
+
- Fully integrated with i18n translations
|
|
200
|
+
|
|
201
|
+
Improvement: Active Helper Handling
|
|
202
|
+
- Added consistent `pump.active_helper` ownership handling for:
|
|
203
|
+
- photovoltaicHelper
|
|
204
|
+
- solarHelper
|
|
205
|
+
- solarExtendedHelper
|
|
206
|
+
- Each helper now:
|
|
207
|
+
- sets its own identifier when controlling the pump
|
|
208
|
+
- releases it when stopping
|
|
209
|
+
- does not override other active helpers
|
|
210
|
+
- Ensures correct priority handling and prevents conflicts
|
|
211
|
+
|
|
212
|
+
Improvement: PV Runtime Evaluation
|
|
213
|
+
- Photovoltaic runtime is now only counted when:
|
|
214
|
+
- PV surplus is active
|
|
215
|
+
- AND photovoltaicHelper actually owns the pump
|
|
216
|
+
- Enables accurate runtime, energy and savings calculation
|
|
217
|
+
|
|
218
|
+
Fix: PV Circulation Logic
|
|
219
|
+
- Fixed issue where pump stopped despite `photovoltaic.ignore_on_circulation = false`
|
|
220
|
+
- Circulation check is now only applied when explicitly enabled
|
|
221
|
+
|
|
191
222
|
### 1.3.9 (2026-04-24)
|
|
192
223
|
|
|
193
224
|
- Fix: solarLogbookHelper no longer creates duplicate or unnecessary log entries (improved filtering & throttling logic)
|
|
@@ -219,11 +250,6 @@ frostHelper
|
|
|
219
250
|
- Fixed critical i18n issue in solarInsightsHelper and solarLogbookHelper that could lead to instability or crashes
|
|
220
251
|
- Switched translation handling to I18n.translate() for stable and consistent i18n behavior
|
|
221
252
|
|
|
222
|
-
### 1.3.4 (2026-04-19)
|
|
223
|
-
|
|
224
|
-
- Fixed critical i18n issue in solarInsightsHelper and solarLogbookHelper that could lead to instability or crashes
|
|
225
|
-
- Switched translation handling to I18n.translate() for stable and consistent i18n behavior
|
|
226
|
-
|
|
227
253
|
## Support
|
|
228
254
|
- [ioBroker Forum](https://forum.iobroker.net/)
|
|
229
255
|
- [GitHub Issues](https://github.com/DasBo1975/ioBroker.poolcontrol/issues)
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.10",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.3.10": {
|
|
7
|
+
"en": "Added photovoltaic insights (runtime, energy, savings) with new analytics states and helper. Improved active_helper ownership handling across photovoltaic, solar and extended solar helpers. Fixed PV circulation logic when ignore_on_circulation is disabled.",
|
|
8
|
+
"de": "Photovoltaik-Einblicke (Laufzeit, Energie, Einsparungen) mit neuen Analysezuständen und Hilfsfunktionen hinzugefügt. Verbesserte Handhabung des Active_Helper-Besitzes für Photovoltaik-, Solar- und erweiterte Solar-Helfer. Die PV-Zirkulationslogik wurde korrigiert, wenn „ignore_on_circulation“ deaktiviert ist.",
|
|
9
|
+
"ru": "Добавлены данные по фотоэлектрической энергии (время работы, энергия, экономия) с новыми аналитическими состояниями и помощником. Улучшено управление владением active_helper для фотоэлектрических, солнечных и расширенных солнечных помощников. Исправлена логика циркуляции PV, когда ignore_on_circulation отключен.",
|
|
10
|
+
"pt": "Adicionados insights fotovoltaicos (tempo de execução, energia, economia) com novos estados analíticos e auxiliares. Melhor gerenciamento de propriedade de active_helper em ajudantes fotovoltaicos, solares e solares estendidos. Lógica de circulação fotovoltaica corrigida quando ignore_on_circulation está desativado.",
|
|
11
|
+
"nl": "Fotovoltaïsche inzichten toegevoegd (runtime, energie, besparingen) met nieuwe analysestatussen en helper. Verbeterde afhandeling van active_helper-eigendom voor fotovoltaïsche, zonne-energie en uitgebreide zonne-helpers. Vaste PV-circulatielogica wanneer negeer_on_circulatie is uitgeschakeld.",
|
|
12
|
+
"fr": "Ajout d'informations photovoltaïques (durée de fonctionnement, énergie, économies) avec de nouveaux états d'analyse et une nouvelle aide. Amélioration de la gestion de la propriété active_helper pour les assistants photovoltaïques, solaires et solaires étendus. Correction de la logique de circulation PV lorsque ignore_on_circulation est désactivé.",
|
|
13
|
+
"it": "Aggiunti approfondimenti sul fotovoltaico (autonomia, energia, risparmio) con nuovi stati di analisi e supporto. Migliorata la gestione della proprietà di active_helper tra gli helper fotovoltaici, solari e solari estesi. Risolta la logica di circolazione FV quando ignore_on_circulation è disabilitato.",
|
|
14
|
+
"es": "Se agregaron conocimientos fotovoltaicos (tiempo de ejecución, energía, ahorros) con nuevos estados analíticos y ayuda. Se mejoró el manejo de la propiedad de active_helper en los asistentes fotovoltaicos, solares y solares extendidos. Se corrigió la lógica de circulación de PV cuando ignore_on_circulation estaba deshabilitado.",
|
|
15
|
+
"pl": "Dodano statystyki fotowoltaiczne (czas pracy, energia, oszczędności) z nowymi stanami analitycznymi i pomocą. Ulepszona obsługa własności active_helper w przypadku fotowoltaiki, energii słonecznej i rozszerzonych pomocników energii słonecznej. Naprawiono logikę cyrkulacji PV, gdy ignorowanie_cyrkulacji jest wyłączone.",
|
|
16
|
+
"uk": "Додано статистику фотоелектричної системи (час роботи, енергія, економія) з новими аналітичними станами та помічником. Покращено керування правами власності active_helper на фотоелектричні, сонячні та розширені сонячні помічники. Виправлена логіка циркуляції PV, коли ignore_on_circulation вимкнено.",
|
|
17
|
+
"zh-cn": "通过新的分析状态和帮助器添加了光伏见解(运行时间、能源、节省)。改进了跨光伏、太阳能和扩展太阳能助手的 active_helper 所有权处理。修复了禁用ignore_on_circulation时的PV循环逻辑。"
|
|
18
|
+
},
|
|
6
19
|
"1.3.9": {
|
|
7
20
|
"en": "Fix solar logbook duplication and invalid entries, improve log stability and weather text handling; fix solar insights runtime calculation and debug timestamp update.",
|
|
8
21
|
"de": "Behebung von doppelten und ungültigen Solar-Logbuch-Einträgen, Verbesserung der Log-Stabilität und Wettertext-Verarbeitung; Korrektur der Laufzeitberechnung im Solar-Insights-Bereich sowie Aktualisierung des Debug-Zeitstempels.",
|
|
@@ -120,7 +133,8 @@
|
|
|
120
133
|
{
|
|
121
134
|
"admin": ">=7.6.20"
|
|
122
135
|
}
|
|
123
|
-
]
|
|
136
|
+
],
|
|
137
|
+
"installedFrom": "file:///opt/iobroker/ioBroker.poolcontrol"
|
|
124
138
|
},
|
|
125
139
|
"native": {},
|
|
126
140
|
"objects": [],
|
|
@@ -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;
|
|
@@ -235,10 +235,18 @@ const solarExtendedHelper = {
|
|
|
235
235
|
// wenn Solar Extended überhaupt der zuständige Solarmodus ist
|
|
236
236
|
// und keine höhere Fremdpriorität aktiv ist.
|
|
237
237
|
if (isExtendedMode && !isControlPriority && !isTimePriority) {
|
|
238
|
+
if (speechSolarActive) {
|
|
239
|
+
await this._setActiveHelperIfAllowed('solarExtendedHelper');
|
|
240
|
+
} else {
|
|
241
|
+
await this._releaseActiveHelperIfOwned();
|
|
242
|
+
}
|
|
243
|
+
|
|
238
244
|
await this.adapter.setStateChangedAsync('pump.pump_switch', {
|
|
239
245
|
val: speechSolarActive,
|
|
240
246
|
ack: false,
|
|
241
247
|
});
|
|
248
|
+
} else {
|
|
249
|
+
await this._releaseActiveHelperIfOwned();
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
await this.adapter.setStateChangedAsync('solar.extended.enabled_by_master', {
|
|
@@ -309,6 +317,37 @@ const solarExtendedHelper = {
|
|
|
309
317
|
}
|
|
310
318
|
},
|
|
311
319
|
|
|
320
|
+
async _setActiveHelperIfAllowed(helperName) {
|
|
321
|
+
try {
|
|
322
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
323
|
+
|
|
324
|
+
if (activeHelper && activeHelper !== helperName) {
|
|
325
|
+
this.adapter.log.debug(
|
|
326
|
+
`[solarExtendedHelper] Active helper not changed because '${activeHelper}' currently owns the pump.`,
|
|
327
|
+
);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: helperName, ack: true });
|
|
332
|
+
} catch (err) {
|
|
333
|
+
this.adapter.log.warn(`[solarExtendedHelper] Could not set pump.active_helper: ${err.message}`);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
async _releaseActiveHelperIfOwned() {
|
|
338
|
+
try {
|
|
339
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
340
|
+
|
|
341
|
+
if (activeHelper !== 'solarExtendedHelper') {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
await this.adapter.setStateAsync('pump.active_helper', { val: '', ack: true });
|
|
346
|
+
} catch (err) {
|
|
347
|
+
this.adapter.log.warn(`[solarExtendedHelper] Could not release pump.active_helper: ${err.message}`);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
|
|
312
351
|
cleanup() {
|
|
313
352
|
if (this.checkTimer) {
|
|
314
353
|
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,22 @@
|
|
|
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."
|
|
171
188
|
}
|
package/lib/i18n/en.json
CHANGED
|
@@ -239,5 +239,22 @@
|
|
|
239
239
|
"solar_log_text_weather_supported_with_text": "Weather-based plausibility is active. Current weather summary: %s",
|
|
240
240
|
"solar_log_text_weather_supported": "Weather-based plausibility is active for this assessment.",
|
|
241
241
|
"solar_log_text_small_gain_detail": "The currently estimated daily gain is about %s Wh, which corresponds to about %s kWh.",
|
|
242
|
-
"solar_log_text_quality_level": "The current quality level of the assessment is %s."
|
|
242
|
+
"solar_log_text_quality_level": "The current quality level of the assessment is %s.",
|
|
243
|
+
|
|
244
|
+
"photovoltaic_insights_calculation_mode_daily": "Daily PV surplus analysis",
|
|
245
|
+
"photovoltaic_insights_price_source_adapter_config": "Adapter configuration",
|
|
246
|
+
"photovoltaic_insights_calculation_note_block_2": "The calculation block prepares the source information for the photovoltaic insights analysis.",
|
|
247
|
+
"photovoltaic_insights_summary_text_block_3": "PV ran today for %s minutes, used %s kWh and saved approximately %s €.",
|
|
248
|
+
"photovoltaic_insights_label_mode": "Mode",
|
|
249
|
+
"photovoltaic_insights_label_pv_surplus_active": "PV surplus active",
|
|
250
|
+
"photovoltaic_insights_label_pv_controls_pump": "PV controls pump",
|
|
251
|
+
"photovoltaic_insights_label_runtime_today": "Runtime today",
|
|
252
|
+
"photovoltaic_insights_label_energy_used_today": "Energy used today",
|
|
253
|
+
"photovoltaic_insights_label_savings_today": "Savings today",
|
|
254
|
+
"photovoltaic_insights_label_starts_today": "Starts today",
|
|
255
|
+
"photovoltaic_insights_label_summary": "Summary",
|
|
256
|
+
"photovoltaic_insights_debug_reason_pv_runtime_active": "PV surplus operation active",
|
|
257
|
+
"photovoltaic_insights_debug_reason_no_pv_runtime": "No active PV surplus operation",
|
|
258
|
+
"photovoltaic_insights_debug_text_pv_runtime_active": "PV surplus is active and the photovoltaic helper currently owns the pump.",
|
|
259
|
+
"photovoltaic_insights_debug_text_no_pv_runtime": "No PV runtime is currently counted because either no PV surplus is active or the photovoltaic helper does not own the pump."
|
|
243
260
|
}
|