iobroker.poolcontrol 1.3.7 → 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 +42 -10
- package/io-package.json +29 -28
- 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/helpers/solarInsightsHelper.js +29 -6
- package/lib/helpers/solarLogbookHelper.js +70 -11
- 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,48 @@ 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
|
+
|
|
222
|
+
### 1.3.9 (2026-04-24)
|
|
223
|
+
|
|
224
|
+
- Fix: solarLogbookHelper no longer creates duplicate or unnecessary log entries (improved filtering & throttling logic)
|
|
225
|
+
- Fix: Removed obsolete "no runtime today" entries once solar has actually run
|
|
226
|
+
- Fix: Improved handling of weather summary text to avoid broken or cut-off sentences
|
|
227
|
+
- Fix: solarLogbookHelper now updates `last_entry_time` only when a real log entry is written
|
|
228
|
+
- Fix: solarInsightsHelper runtime calculation improved (no incorrect time accumulation on state changes)
|
|
229
|
+
- Fix: solarInsightsHelper now correctly tracks previous solar state for more accurate active time calculation
|
|
230
|
+
- Fix: Added missing `debug.last_update` update on successful calculation
|
|
231
|
+
- Improvement: General stability and plausibility improvements in solar insights and logbook processing
|
|
232
|
+
|
|
191
233
|
### 1.3.7 (2026-04-23)
|
|
192
234
|
|
|
193
235
|
Bugfixes
|
|
@@ -208,16 +250,6 @@ frostHelper
|
|
|
208
250
|
- Fixed critical i18n issue in solarInsightsHelper and solarLogbookHelper that could lead to instability or crashes
|
|
209
251
|
- Switched translation handling to I18n.translate() for stable and consistent i18n behavior
|
|
210
252
|
|
|
211
|
-
### 1.3.4 (2026-04-19)
|
|
212
|
-
|
|
213
|
-
- Fixed critical i18n issue in solarInsightsHelper and solarLogbookHelper that could lead to instability or crashes
|
|
214
|
-
- Switched translation handling to I18n.translate() for stable and consistent i18n behavior
|
|
215
|
-
|
|
216
|
-
### 1.3.3 (2026-04-18)
|
|
217
|
-
|
|
218
|
-
- Added solar COP calculation to evaluate system efficiency
|
|
219
|
-
- Added solar logbook helper with easy-to-read daily summaries for better user understanding
|
|
220
|
-
|
|
221
253
|
## Support
|
|
222
254
|
- [ioBroker Forum](https://forum.iobroker.net/)
|
|
223
255
|
- [GitHub Issues](https://github.com/DasBo1975/ioBroker.poolcontrol/issues)
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
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
|
+
},
|
|
19
|
+
"1.3.9": {
|
|
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.",
|
|
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.",
|
|
22
|
+
"ru": "Исправьте дублирование журнала солнечной энергии и неверные записи, улучшите стабильность журнала и обработку текста о погоде; исправить расчет времени выполнения Solar Insights и обновить временную метку отладки.",
|
|
23
|
+
"pt": "Corrija a duplicação do diário de bordo solar e entradas inválidas, melhore a estabilidade do registro e o manuseio de textos meteorológicos; corrigir o cálculo do tempo de execução do Solar Insights e depurar a atualização do carimbo de data/hora.",
|
|
24
|
+
"nl": "Herstel het dupliceren van zonnelogboeken en ongeldige vermeldingen, verbeter de logstabiliteit en de verwerking van weerteksten; runtimeberekening van zonne-inzichten repareren en tijdstempelupdate debuggen.",
|
|
25
|
+
"fr": "Corrigez la duplication du journal solaire et les entrées invalides, améliorez la stabilité du journal et la gestion des textes météorologiques ; correction du calcul d'exécution des informations solaires et de la mise à jour de l'horodatage de débogage.",
|
|
26
|
+
"it": "Correggere la duplicazione del registro solare e le voci non valide, migliorare la stabilità del registro e la gestione del testo meteorologico; correggere il calcolo del runtime di Solar Insights e l'aggiornamento del timestamp di debug.",
|
|
27
|
+
"es": "Corrija la duplicación del libro de registro solar y las entradas no válidas, mejore la estabilidad del registro y el manejo de textos meteorológicos; corrige el cálculo del tiempo de ejecución de Solar Insights y la actualización de la marca de tiempo de depuración.",
|
|
28
|
+
"pl": "Napraw duplikację dziennika słonecznego i nieprawidłowe wpisy, popraw stabilność dziennika i obsługę tekstu pogodowego; napraw obliczenia czasu działania Solar Insights i aktualizację znacznika czasu debugowania.",
|
|
29
|
+
"uk": "Виправте дублювання сонячного журналу та недійсні записи, покращте стабільність журналу та обробку тексту погоди; виправити розрахунок часу виконання solar insights і налагодити оновлення часових позначок.",
|
|
30
|
+
"zh-cn": "修复太阳能日志重复和无效条目,提高日志稳定性和天气文本处理;修复 Solar Insights 运行时计算和调试时间戳更新。"
|
|
31
|
+
},
|
|
6
32
|
"1.3.7": {
|
|
7
33
|
"en": "Fix solarInsights detection for standard solar (use solar.request_active instead of non-existing solar.active). photovoltaicHelper improvements: rounded surplus value in status text and switched afterrun timer to adapter.setTimeout/clearTimeout.",
|
|
8
34
|
"de": "Fix der solarInsights-Erkennung für Standard-Solar (Verwendung von solar.request_active statt nicht vorhandenem solar.active). Verbesserungen im photovoltaicHelper: Rundung des Überschusswerts im Status-Text und Umstellung des Afterrun-Timers auf adapter.setTimeout/clearTimeout.",
|
|
@@ -41,32 +67,6 @@
|
|
|
41
67
|
"pl": "Naprawiono krytyczny problem i18n w programach solarInsightsHelper i solarLogbookHelper, który mógł prowadzić do niestabilności lub awarii. Przełączono obsługę tłumaczeń na I18n.translate(), aby zapewnić stabilne działanie.",
|
|
42
68
|
"uk": "Виправлено критичну проблему i18n у solarInsightsHelper і solarLogbookHelper, яка могла призвести до нестабільності або збоїв. Перемкнуто обробку перекладу на I18n.translate(), щоб забезпечити стабільну роботу.",
|
|
43
69
|
"zh-cn": "修复了 SolarInsightsHelper 和 SolarLogbookHelper 中可能导致不稳定或崩溃的关键 i18n 问题。将翻译处理切换为 I18n.translate() 以确保稳定运行。"
|
|
44
|
-
},
|
|
45
|
-
"1.3.4": {
|
|
46
|
-
"en": "Fixed critical i18n issue in solarInsightsHelper and solarLogbookHelper that could lead to instability or crashes. Switched translation handling to I18n.translate() to ensure stable operation.",
|
|
47
|
-
"de": "Kritischer i18n-Fehler in solarInsightsHelper und solarLogbookHelper behoben, der zu Instabilität oder Abstürzen führen konnte. Übersetzungen wurden auf I18n.translate() umgestellt, um einen stabilen Betrieb sicherzustellen.",
|
|
48
|
-
"ru": "Исправлена критическая проблема i18n в SolarInsightsHelper и SolarLogbookHelper, которая могла привести к нестабильности или сбоям. Обработку перевода переключили на I18n.translate() для обеспечения стабильной работы.",
|
|
49
|
-
"pt": "Foi corrigido um problema crítico do i18n no solarInsightsHelper e no solarLogbookHelper que poderia causar instabilidade ou travamentos. Manipulação de tradução alterada para I18n.translate() para garantir uma operação estável.",
|
|
50
|
-
"nl": "Een kritiek i18n-probleem in solarInsightsHelper en solarLogbookHelper opgelost dat tot instabiliteit of crashes kon leiden. De verwerking van de vertalingen is gewijzigd naar I18n.translate() om een stabiele werking te garanderen.",
|
|
51
|
-
"fr": "Correction d'un problème i18n critique dans solarInsightsHelper et solarLogbookHelper qui pouvait entraîner une instabilité ou des plantages. Gestion de la traduction commutée vers I18n.translate() pour garantir un fonctionnement stable.",
|
|
52
|
-
"it": "Risolto un problema critico i18n in solarInsightsHelper e solarLogbookHelper che poteva portare a instabilità o arresti anomali. Modificata la gestione della traduzione su I18n.translate() per garantire un funzionamento stabile.",
|
|
53
|
-
"es": "Se solucionó un problema crítico de i18n en solarInsightsHelper y solarLogbookHelper que podía provocar inestabilidad o fallas. Se cambió el manejo de traducción a I18n.translate() para garantizar un funcionamiento estable.",
|
|
54
|
-
"pl": "Naprawiono krytyczny problem i18n w programach solarInsightsHelper i solarLogbookHelper, który mógł prowadzić do niestabilności lub awarii. Przełączono obsługę tłumaczeń na I18n.translate(), aby zapewnić stabilne działanie.",
|
|
55
|
-
"uk": "Виправлено критичну проблему i18n у solarInsightsHelper і solarLogbookHelper, яка могла призвести до нестабільності або збоїв. Перемкнуто обробку перекладу на I18n.translate(), щоб забезпечити стабільну роботу.",
|
|
56
|
-
"zh-cn": "修复了 SolarInsightsHelper 和 SolarLogbookHelper 中可能导致不稳定或崩溃的关键 i18n 问题。将翻译处理切换为 I18n.translate() 以确保稳定运行。"
|
|
57
|
-
},
|
|
58
|
-
"1.3.3": {
|
|
59
|
-
"en": "Added solar COP calculation and solar logbook helper with human-readable daily entries based on solar insights.",
|
|
60
|
-
"de": "Solar-COP-Berechnung sowie Solar-Logbuch-Helper mit verständlichen Tages-Einträgen basierend auf Solar-Insights hinzugefügt.",
|
|
61
|
-
"ru": "Добавлен расчет солнечного КПД и помощник в журнале солнечной энергии с удобочитаемыми ежедневными записями на основе данных о солнечной энергии.",
|
|
62
|
-
"pt": "Adicionado cálculo de COP solar e auxiliar de diário de bordo solar com entradas diárias legíveis por humanos com base em insights solares.",
|
|
63
|
-
"nl": "Zonne-COP-berekening en zonne-logboekhulp toegevoegd met voor mensen leesbare dagelijkse gegevens op basis van zonne-inzichten.",
|
|
64
|
-
"fr": "Ajout du calcul du COP solaire et d'un assistant de journal de bord solaire avec des entrées quotidiennes lisibles par l'homme basées sur des informations solaires.",
|
|
65
|
-
"it": "Aggiunto il calcolo del COP solare e l'assistente del registro solare con voci giornaliere leggibili dall'uomo basate su informazioni solari.",
|
|
66
|
-
"es": "Se agregó un cálculo de COP solar y un asistente de registro solar con entradas diarias legibles por humanos basadas en conocimientos solares.",
|
|
67
|
-
"pl": "Dodano obliczenia COP energii słonecznej i pomocnika dziennika słonecznego z czytelnymi dla człowieka codziennymi wpisami opartymi na spostrzeżeniach dotyczących energii słonecznej.",
|
|
68
|
-
"uk": "Додано розрахунок COP сонячної енергії та помічник сонячного журналу із зрозумілими для людини щоденними записами на основі даних про сонячну енергію.",
|
|
69
|
-
"zh-cn": "添加了太阳能 COP 计算和太阳能日志助手,其中包含基于太阳能洞察的人类可读的每日条目。"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
"titleLang": {
|
|
@@ -133,7 +133,8 @@
|
|
|
133
133
|
{
|
|
134
134
|
"admin": ">=7.6.20"
|
|
135
135
|
}
|
|
136
|
-
]
|
|
136
|
+
],
|
|
137
|
+
"installedFrom": "file:///opt/iobroker/ioBroker.poolcontrol"
|
|
137
138
|
},
|
|
138
139
|
"native": {},
|
|
139
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);
|