iobroker.poolcontrol 0.5.5 → 0.6.1
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 +44 -21
- package/admin/jsonConfig.json +54 -3
- package/io-package.json +27 -27
- package/lib/helpers/migrationHelper.js +66 -22
- package/lib/helpers/photovoltaicHelper.js +305 -0
- package/lib/helpers/statisticsHelperMonth.js +20 -0
- package/lib/stateDefinitions/controlStates.js +7 -1
- package/lib/stateDefinitions/photovoltaicStates.js +161 -0
- package/lib/stateDefinitions/pumpStates.js +1 -0
- package/lib/stateDefinitions/statisticsStates.js +10 -1
- package/main.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,8 @@ Er ermöglicht die Automatisierung von Pumpen-, Temperatur- und Solarsteuerung s
|
|
|
19
19
|
## Funktionen
|
|
20
20
|
|
|
21
21
|
- **Pumpensteuerung**
|
|
22
|
-
- Betriebsmodi: Automatik, Manuell, Zeitsteuerung, Aus
|
|
22
|
+
- Betriebsmodi: Automatik, Automatik (PV), Manuell, Zeitsteuerung, Aus
|
|
23
|
+
- Automatik (PV) steuert die Pumpe abhängig vom Photovoltaik-Überschuss
|
|
23
24
|
- Fehlererkennung (kein Stromverbrauch, Leistung trotz „AUS“, Überlast)
|
|
24
25
|
- Sicherheitsfunktionen (Frostschutz, Überhitzungsschutz)
|
|
25
26
|
|
|
@@ -34,12 +35,24 @@ Er ermöglicht die Automatisierung von Pumpen-, Temperatur- und Solarsteuerung s
|
|
|
34
35
|
- Kollektor-Warnung (mit automatischer Rücksetzung bei 10 % unter der Schwelle)
|
|
35
36
|
- Optionale Sprachausgabe bei Warnung
|
|
36
37
|
|
|
38
|
+
- **Photovoltaiksteuerung (ab v0.6.0)
|
|
39
|
+
- Automatische Pumpensteuerung auf Basis von PV-Erzeugung und Hausverbrauchs
|
|
40
|
+
- Einschaltlogik: Überschuss ≥ (Pumpen-Nennleistung + Sicherheitsaufschlag)
|
|
41
|
+
- Optionaler Nachlauf bei Wolkenphasen
|
|
42
|
+
- Ignorieren bei erreichter Tagesumwälzung
|
|
43
|
+
- Konfiguration über zwei Fremd-Objekt-IDs (power_generated_id, power_house_id)
|
|
44
|
+
- Neuer Pumpenmodus „Automatik (PV)
|
|
45
|
+
|
|
37
46
|
- **Zeitsteuerung**
|
|
38
47
|
- Bis zu 3 frei konfigurierbare Zeitfenster pro Woche
|
|
39
48
|
|
|
40
49
|
- **Laufzeit & Umwälzung**
|
|
41
50
|
- Zählt Laufzeiten (heute, gesamt)
|
|
42
51
|
- Berechnet tägliche Umwälzung und Restmenge
|
|
52
|
+
- Rückspülerinnerung mit konfigurierbarem Intervall (z. B. alle 7 Tage)
|
|
53
|
+
- Anzeige der letzten Rückspülung inkl. Datum
|
|
54
|
+
- Automatische Rücksetzung nach erfolgter Rückspülung
|
|
55
|
+
- PV-Modus berücksichtigt Umwälzstatus (z. B. „Ignoriere bei Umwälzung erreicht“)
|
|
43
56
|
|
|
44
57
|
- **Verbrauch & Kosten**
|
|
45
58
|
- Auswertung eines externen kWh-Zählers
|
|
@@ -49,16 +62,19 @@ Er ermöglicht die Automatisierung von Pumpen-, Temperatur- und Solarsteuerung s
|
|
|
49
62
|
**Hinweis:**
|
|
50
63
|
Details zum Verhalten der Verbrauchs- und Kostenwerte (z. B. bei Neustarts oder beim Wechsel des Stromzählers) finden Sie in der Datei [help.md](./help.md).
|
|
51
64
|
|
|
65
|
+
- **Statistiksystem**
|
|
66
|
+
- Bereich `analytics.statistics.*` mit Tages-, Wochen- und Monatswerten
|
|
67
|
+
- Automatische Berechnung von Min-, Max-, Durchschnitts- und Laufzeitwerten
|
|
68
|
+
- Vollständig persistente Datenpunkte (Überinstallationsschutz)
|
|
69
|
+
- HTML- und JSON-Zusammenfassungen pro Sensor und Gesamtübersicht
|
|
70
|
+
|
|
52
71
|
- **Sprachausgaben**
|
|
53
72
|
- Ausgabe über Alexa oder Telegram
|
|
54
73
|
- Ansagen bei Pumpenstart/-stopp, Fehlern oder Temperaturschwellen
|
|
55
74
|
|
|
56
75
|
- **SystemCheck (Diagnosebereich)**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*Funktionen:*
|
|
61
|
-
- Auswahl des zu überwachenden Bereichs
|
|
76
|
+
- Interner Diagnosebereich für Debug- und Überwachungsfunktionen
|
|
77
|
+
- Auswahl des zu überwachenden Bereichs (z. B. Pumpe, Solar, Temperatur)
|
|
62
78
|
- Fortlaufendes Log der letzten Änderungen
|
|
63
79
|
- Manuelles Löschen des Logs möglich
|
|
64
80
|
|
|
@@ -90,26 +106,24 @@ Die Konfiguration erfolgt über Tabs im Admin-Interface:
|
|
|
90
106
|
|
|
91
107
|
## Geplante Erweiterungen
|
|
92
108
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
- Zweite Pumpe (z.B. Wärmetauscher)
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
- Steuerung von elektrischen Ventilen
|
|
105
|
-
- Steuerung von Gegenstromanlagen
|
|
109
|
+
- Erweiterte PV- und Solar-Effizienzanalyse (COP-Berechnung, Tagesnutzen, Wetterintegration)
|
|
110
|
+
- Statistik-Exportfunktion (CSV/Excel)
|
|
111
|
+
- Diagnostic-Helper zur automatischen Systemprüfung
|
|
112
|
+
- Erweiterung der Heizungs-/Wärmepumpenlogik (`heatHelper`)
|
|
113
|
+
- Drucksensor-Integration zur Kesseldruck-Überwachung
|
|
114
|
+
- Zweite Pumpe (z. B. Wärmetauscher oder Wärmepumpe)
|
|
115
|
+
- Eigene Widgets für VIS/VIS2 (grafische Pool- und Solarvisualisierung)
|
|
116
|
+
- Steuerung von Poolbeleuchtung, Ventilen und Gegenstromanlagen
|
|
117
|
+
- Integration zusätzlicher Sensorboxen (z. B. TempBox, PressureBox, LevelBox)
|
|
118
|
+
- KI- und Sprach-Assistenten-Erweiterung (Pool-Tagesbericht, Tipps, Sprachbefehle)
|
|
119
|
+
|
|
106
120
|
|
|
107
121
|
---
|
|
108
122
|
|
|
109
123
|
## Hinweis
|
|
110
124
|
|
|
111
|
-
Der Adapter befindet sich
|
|
112
|
-
Funktionen
|
|
125
|
+
Der Adapter befindet sich in aktiver Weiterentwicklung.
|
|
126
|
+
Neue Funktionen werden regelmäßig ergänzt – bitte den Changelog beachten.
|
|
113
127
|
|
|
114
128
|
---
|
|
115
129
|
|
|
@@ -121,6 +135,15 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
|
|
|
121
135
|
## Changelog
|
|
122
136
|
### **WORK IN PROGRESS**
|
|
123
137
|
|
|
138
|
+
## v0.6.0 (2025-11-03)
|
|
139
|
+
- Einführung der vollständigen Photovoltaik-Steuerung mit automatischer Pumpenlogik
|
|
140
|
+
(neuer Pumpenmodus `Automatik (PV)` unter `pump.mode`)
|
|
141
|
+
- Adapter reagiert auf PV-Überschuss basierend auf konfigurierbarer Hausverbrauchs- und Erzeugungsleistung
|
|
142
|
+
- Einschaltlogik: Pumpe EIN bei Überschuss ≥ (Nennleistung + Schwellwert)
|
|
143
|
+
- Berücksichtigung von Saisonstatus, Nachlaufzeit und optionalem „Umwälzung erreicht“-Schutz
|
|
144
|
+
- Automatische Migration ergänzt neuen Modus `auto_pv` in bestehenden Installationen
|
|
145
|
+
- Verbesserte interne Logik, Persistenz und Debug-Protokollierung
|
|
146
|
+
|
|
124
147
|
## v0.5.5 (2025-11-01)
|
|
125
148
|
- Endlosschleife in Statistik Woche und Monat behoben
|
|
126
149
|
|
package/admin/jsonConfig.json
CHANGED
|
@@ -862,7 +862,6 @@
|
|
|
862
862
|
"speech_telegram_users": {
|
|
863
863
|
"type": "text",
|
|
864
864
|
"label": "Telegram-Empfänger (Benutzernamen, Komma-getrennt / leer = an alle Benutzer)",
|
|
865
|
-
"attr": "speech_telegram_users",
|
|
866
865
|
"tooltip": "Beispiel: Dirk,Dennis (leer = an alle Benutzer senden)",
|
|
867
866
|
"default": "",
|
|
868
867
|
"xs": 12,
|
|
@@ -947,7 +946,7 @@
|
|
|
947
946
|
},
|
|
948
947
|
"consumption": {
|
|
949
948
|
"type": "panel",
|
|
950
|
-
"label": "Verbrauch & Kosten",
|
|
949
|
+
"label": "PV, Verbrauch & Kosten",
|
|
951
950
|
"items": {
|
|
952
951
|
"divider_cons1": {
|
|
953
952
|
"type": "divider",
|
|
@@ -1003,7 +1002,59 @@
|
|
|
1003
1002
|
"md": 12,
|
|
1004
1003
|
"lg": 12,
|
|
1005
1004
|
"xl": 12
|
|
1006
|
-
}
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
"divider_pv1": {
|
|
1008
|
+
"type": "divider",
|
|
1009
|
+
"newLine": true
|
|
1010
|
+
},
|
|
1011
|
+
"pv_header": {
|
|
1012
|
+
"type": "header",
|
|
1013
|
+
"text": "Photovoltaik (Überschusserkennung)",
|
|
1014
|
+
"size": 4,
|
|
1015
|
+
"newLine": true,
|
|
1016
|
+
"xs": 12,
|
|
1017
|
+
"sm": 12,
|
|
1018
|
+
"md": 12,
|
|
1019
|
+
"lg": 12,
|
|
1020
|
+
"xl": 12
|
|
1021
|
+
},
|
|
1022
|
+
"power_generated_id": {
|
|
1023
|
+
"type": "objectId",
|
|
1024
|
+
"label": "Objekt-ID PV-Erzeugungsleistung (W)",
|
|
1025
|
+
"default": "",
|
|
1026
|
+
"xs": 12,
|
|
1027
|
+
"sm": 6,
|
|
1028
|
+
"md": 6,
|
|
1029
|
+
"lg": 6,
|
|
1030
|
+
"xl": 6,
|
|
1031
|
+
"tooltip": "Datenpunkt mit aktueller PV-Leistung (Watt)"
|
|
1032
|
+
},
|
|
1033
|
+
"power_house_id": {
|
|
1034
|
+
"type": "objectId",
|
|
1035
|
+
"label": "Objekt-ID Hausverbrauch (W)",
|
|
1036
|
+
"default": "",
|
|
1037
|
+
"xs": 12,
|
|
1038
|
+
"sm": 6,
|
|
1039
|
+
"md": 6,
|
|
1040
|
+
"lg": 6,
|
|
1041
|
+
"xl": 6,
|
|
1042
|
+
"tooltip": "Datenpunkt mit aktuellem Hausverbrauch (Watt)"
|
|
1043
|
+
},
|
|
1044
|
+
"threshold_w": {
|
|
1045
|
+
"type": "number",
|
|
1046
|
+
"label": "Schwellwert für PV-Überschuss (W)",
|
|
1047
|
+
"default": 200,
|
|
1048
|
+
"min": 0,
|
|
1049
|
+
"max": 2000,
|
|
1050
|
+
"step": 50,
|
|
1051
|
+
"xs": 12,
|
|
1052
|
+
"sm": 3,
|
|
1053
|
+
"md": 3,
|
|
1054
|
+
"lg": 3,
|
|
1055
|
+
"xl": 3,
|
|
1056
|
+
"tooltip": "Ab welcher Differenz zwischen PV und Hausverbrauch ein Überschuss gilt"
|
|
1057
|
+
}
|
|
1007
1058
|
}
|
|
1008
1059
|
},
|
|
1009
1060
|
"help": {
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.6.1": {
|
|
7
|
+
"en": "Fixed false monthly reset when no last_reset date existed. Corrected real last_update timestamp in monthly statistics.",
|
|
8
|
+
"de": "Fehlerhaften Monatsreset ohne gültiges last_reset-Datum behoben. Korrigierter echter Zeitstempel last_update in der Monatsstatistik.",
|
|
9
|
+
"ru": "Исправлен неверный ежемесячный сброс без действительной даты last_reset. Исправлено обновление времени last_update в ежемесячной статистике.",
|
|
10
|
+
"pt": "Corrigido o reset mensal incorreto quando não havia data last_reset. Corrigido o timestamp real de last_update na estatística mensal.",
|
|
11
|
+
"nl": "Onjuiste maandelijkse reset opgelost wanneer er geen last_reset-datum was. Gecorrigeerde echte last_update-tijd in maandelijkse statistieken.",
|
|
12
|
+
"fr": "Correction du réinitialisation mensuelle erronée lorsqu'aucune date last_reset n'existait. Horodatage last_update réel corrigé dans les statistiques mensuelles.",
|
|
13
|
+
"it": "Corretto il reset mensile errato quando non esisteva una data last_reset. Corretto il timestamp reale last_update nelle statistiche mensili.",
|
|
14
|
+
"es": "Corregido el restablecimiento mensual erróneo cuando no existía una fecha last_reset. Corregida la marca de tiempo real last_update en las estadísticas mensuales.",
|
|
15
|
+
"pl": "Naprawiono błędny miesięczny reset, gdy nie istniała data last_reset. Poprawiono rzeczywisty znacznik czasu last_update w miesięcznych statystykach.",
|
|
16
|
+
"zh-cn": "修复了在不存在 last_reset 日期时错误的月度重置。修正了月度统计中的真实 last_update 时间戳。"
|
|
17
|
+
},
|
|
18
|
+
"0.6.0": {
|
|
19
|
+
"en": "Added complete photovoltaic control with automatic pump management (mode 'Automatik (PV)'). The adapter can now react to PV surplus based on configurable house consumption and generation values. Includes migration for existing installations. Improved system consistency and internal logging.",
|
|
20
|
+
"de": "Vollständige Photovoltaik-Steuerung mit automatischer Pumpenlogik (Modus 'Automatik (PV)') hinzugefügt. Der Adapter reagiert nun auf PV-Überschuss anhand konfigurierbarer Hausverbrauchs- und Erzeugungswerte. Migration für bestehende Installationen integriert. Systemkonsistenz und interne Protokollierung verbessert.",
|
|
21
|
+
"ru": "Добавлено полное управление фотоэлектрической системой с автоматическим управлением насосом (режим 'Automatik (PV)'). Адаптер теперь реагирует на избыток PV на основе настраиваемых значений потребления и генерации. Включена миграция для существующих установок. Улучшена системная согласованность и внутреннее ведение журнала.",
|
|
22
|
+
"pt": "Adicionado controle fotovoltaico completo com gerenciamento automático da bomba (modo 'Automatik (PV)'). O adaptador agora reage ao excedente de PV com base nos valores configuráveis de consumo e geração da casa. Inclui migração para instalações existentes. Consistência do sistema e registro interno aprimorados.",
|
|
23
|
+
"nl": "Volledige fotovoltaïsche besturing toegevoegd met automatische pompbesturing (modus 'Automatik (PV)'). De adapter reageert nu op PV-overschot op basis van configureerbare huisverbruik- en opwekkingswaarden. Migratie voor bestaande installaties inbegrepen. Verbeterde systeemconsistentie en interne logboekregistratie.",
|
|
24
|
+
"fr": "Ajout du contrôle photovoltaïque complet avec gestion automatique de la pompe (mode 'Automatik (PV)'). L'adaptateur réagit désormais au surplus photovoltaïque en fonction des valeurs configurables de consommation et de production domestiques. Migration incluse pour les installations existantes. Cohérence du système et journalisation interne améliorées.",
|
|
25
|
+
"it": "Aggiunto controllo fotovoltaico completo con gestione automatica della pompa (modalità 'Automatik (PV)'). L'adattatore ora reagisce al surplus fotovoltaico in base ai valori configurabili di consumo e generazione domestici. Migrazione inclusa per le installazioni esistenti. Migliorata la coerenza del sistema e la registrazione interna.",
|
|
26
|
+
"es": "Se añadió control fotovoltaico completo con gestión automática de la bomba (modo 'Automatik (PV)'). El adaptador ahora reacciona al excedente fotovoltaico según los valores configurables de consumo y generación de la casa. Incluye migración para instalaciones existentes. Mejorada la coherencia del sistema y el registro interno.",
|
|
27
|
+
"pl": "Dodano pełną kontrolę fotowoltaiczną z automatycznym zarządzaniem pompą (tryb 'Automatik (PV)'). Adapter reaguje teraz na nadwyżkę PV w oparciu o konfigurowalne wartości zużycia i produkcji energii. Zawiera migrację dla istniejących instalacji. Poprawiono spójność systemu i wewnętrzne logowanie.",
|
|
28
|
+
"uk": "Додано повний фотогальванічний контроль з автоматичним керуванням насосом (режим 'Automatik (PV)'). Адаптер тепер реагує на надлишок PV на основі налаштовуваних значень споживання та генерації. Включено міграцію для існуючих установок. Покращено системну узгодженість і внутрішнє журналювання.",
|
|
29
|
+
"zh-cn": "新增完整的光伏控制与自动泵管理(模式“Automatik (PV)”)。适配器现在可根据可配置的家庭耗电与发电数值响应光伏盈余。包含对现有安装的迁移。改进了系统一致性和内部日志记录。"
|
|
30
|
+
},
|
|
6
31
|
"0.5.5": {
|
|
7
32
|
"en": "Fixed remaining timer recursion issue in weekly and monthly statistics to fully prevent infinite reset loops. Cleaned up jsonConfig structure and improved timer handling.",
|
|
8
33
|
"de": "Verbleibende Timer-Rekursion in Wochen- und Monatsstatistik behoben, um Endlosschleifen beim Reset endgültig zu verhindern. jsonConfig-Struktur bereinigt und Timer-Handling verbessert.",
|
|
@@ -66,32 +91,7 @@
|
|
|
66
91
|
"pl": "Rozszerzone statystyki tygodniowe i miesięczne z trwałymi danymi, ujednoliconym formatem JSON i ochroną przed ponowną instalacją.",
|
|
67
92
|
"uk": "Розширена тижнева та місячна статистика з постійними даними, уніфікованим форматом JSON і захистом під час перевстановлення.",
|
|
68
93
|
"zh-cn": "扩展的周和月统计,具有持久数据、统一的 JSON 格式和重新安装保护。"
|
|
69
|
-
}
|
|
70
|
-
"0.5.0": {
|
|
71
|
-
"en": "Added weekly and monthly temperature statistics under analytics.statistics.temperature.week and analytics.statistics.temperature.month with automatic summaries, independent helpers and persistent data points.",
|
|
72
|
-
"de": "Wöchentliche und monatliche Temperaturstatistiken unter analytics.statistics.temperature.week und analytics.statistics.temperature.month hinzugefügt – mit automatischen Zusammenfassungen, unabhängigen Helpern und persistenten Datenpunkten.",
|
|
73
|
-
"ru": "Добавлена еженедельная и ежемесячная статистика температуры в analytics.statistics.temperature.week и analytics.statistics.temperature.month с автоматическими сводками, независимыми помощниками и постоянными точками данных.",
|
|
74
|
-
"fr": "Ajout des statistiques de température hebdomadaires et mensuelles sous analytics.statistics.temperature.week et analytics.statistics.temperature.month avec résumés automatiques, helpers indépendants et points de données persistants.",
|
|
75
|
-
"it": "Aggiunte statistiche settimanali e mensili della temperatura in analytics.statistics.temperature.week e analytics.statistics.temperature.month con riepiloghi automatici, helper indipendenti e punti dati persistenti.",
|
|
76
|
-
"es": "Se añadieron estadísticas semanales y mensuales de temperatura en analytics.statistics.temperature.week y analytics.statistics.temperature.month con resúmenes automáticos, ayudantes independientes y puntos de datos persistentes.",
|
|
77
|
-
"nl": "Wekelijkse en maandelijkse temperatuurstatistieken toegevoegd onder analytics.statistics.temperature.week en analytics.statistics.temperature.month met automatische samenvattingen, onafhankelijke helpers en persistente gegevenspunten.",
|
|
78
|
-
"pl": "Dodano tygodniowe i miesięczne statystyki temperatury w analytics.statistics.temperature.week i analytics.statistics.temperature.month z automatycznymi podsumowaniami, niezależnymi pomocnikami i trwałymi punktami danych.",
|
|
79
|
-
"uk": "Додано щотижневу та щомісячну статистику температури в analytics.statistics.temperature.week та analytics.statistics.temperature.month з автоматичними зведеннями, незалежними помічниками та постійними точками даних.",
|
|
80
|
-
"zh-cn": "在 analytics.statistics.temperature.week 和 analytics.statistics.temperature.month 中添加了每周和每月温度统计,具有自动摘要、独立助手和持久数据点。"
|
|
81
|
-
},
|
|
82
|
-
"0.4.0": {
|
|
83
|
-
"en": "Added daily temperature statistics under analytics.statistics.temperature.today with automatic min/max/average tracking, JSON and HTML summaries, and midnight reset logic.",
|
|
84
|
-
"de": "Tägliche Temperaturstatistik unter analytics.statistics.temperature.today hinzugefügt mit automatischer Erfassung von Min-/Max-/Durchschnittswerten, JSON- und HTML-Zusammenfassungen sowie Mitternachts-Reset.",
|
|
85
|
-
"ru": "Добавлена ежедневная статистика температуры в analytics.statistics.temperature.today с автоматическим отслеживанием мин./макс./средних значений, JSON и HTML сводками и логикой сброса в полночь.",
|
|
86
|
-
"pt": "Adicionadas estatísticas diárias de temperatura em analytics.statistics.temperature.today com rastreamento automático de mínimo/máximo/média, resumos em JSON e HTML e redefinição automática à meia-noite.",
|
|
87
|
-
"nl": "Dagelijkse temperatuurstatistieken toegevoegd onder analytics.statistics.temperature.today met automatische min/max/gemiddelde tracking, JSON- en HTML-samenvattingen en middernachtreset.",
|
|
88
|
-
"fr": "Ajout de statistiques quotidiennes de température sous analytics.statistics.temperature.today avec suivi automatique min/max/moyenne, résumés JSON et HTML et réinitialisation automatique à minuit.",
|
|
89
|
-
"it": "Aggiunte statistiche giornaliere della temperatura in analytics.statistics.temperature.today con monitoraggio automatico di min/max/media, riepiloghi JSON e HTML e reset automatico a mezzanotte.",
|
|
90
|
-
"es": "Se añadieron estadísticas diarias de temperatura en analytics.statistics.temperature.today con seguimiento automático de mínimos/máximos/promedios, resúmenes en JSON y HTML y reinicio automático a medianoche.",
|
|
91
|
-
"pl": "Dodano dzienne statystyki temperatury w analytics.statistics.temperature.today z automatycznym śledzeniem wartości min/max/średnich, podsumowaniami JSON i HTML oraz resetem o północy.",
|
|
92
|
-
"uk": "Додано щоденну статистику температури в analytics.statistics.temperature.today з автоматичним відстеженням мін/макс/середніх значень, JSON і HTML зведеннями та скиданням опівночі.",
|
|
93
|
-
"zh-cn": "在 analytics.statistics.temperature.today 中添加了每日温度统计,具有自动最小/最大/平均跟踪、JSON 和 HTML 摘要以及午夜重置功能。"
|
|
94
|
-
}
|
|
94
|
+
}
|
|
95
95
|
},
|
|
96
96
|
"titleLang": {
|
|
97
97
|
"en": "PoolControl",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Wird beim Adapterstart einmalig ausgeführt.
|
|
10
10
|
* - Korrigiert veraltete Definitionen (z. B. Schreibrechte, persist-Flags, etc.)
|
|
11
11
|
*
|
|
12
|
-
* Version: 1.0.
|
|
12
|
+
* Version: 1.0.3
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const migrationHelper = {
|
|
@@ -31,10 +31,8 @@ const migrationHelper = {
|
|
|
31
31
|
// ------------------------------------------------------
|
|
32
32
|
await this._fixSpeechQueue();
|
|
33
33
|
await this._fixSolarWarnActivePersist();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// await this._ensurePumpReason();
|
|
37
|
-
// await this._cleanupOldStates();
|
|
34
|
+
await this._fixPumpModeStates(); // NEU: PV-Automatik hinzufügen
|
|
35
|
+
await this._removeInvalidResetButtons(); // NEU: Entfernt Week/Month-Reset-Buttons
|
|
38
36
|
|
|
39
37
|
this.adapter.log.debug('[migrationHelper] Migration-Checks abgeschlossen.');
|
|
40
38
|
} catch (err) {
|
|
@@ -47,17 +45,11 @@ const migrationHelper = {
|
|
|
47
45
|
// ------------------------------------------------------
|
|
48
46
|
// Migration: Schreibrecht für speech.queue korrigieren
|
|
49
47
|
// ------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Prüft und korrigiert den State "speech.queue", falls er noch write:false gesetzt hat.
|
|
53
|
-
* Dadurch verschwinden Warnungen beim Schreiben (Read-only state ... written without ack).
|
|
54
|
-
*/
|
|
55
48
|
async _fixSpeechQueue() {
|
|
56
49
|
const id = 'speech.queue';
|
|
57
50
|
try {
|
|
58
51
|
const obj = await this.adapter.getObjectAsync(id);
|
|
59
52
|
if (!obj) {
|
|
60
|
-
this.adapter.log.debug(`[migrationHelper] ${id} existiert nicht – keine Anpassung nötig.`);
|
|
61
53
|
return;
|
|
62
54
|
}
|
|
63
55
|
|
|
@@ -70,8 +62,6 @@ const migrationHelper = {
|
|
|
70
62
|
desc: 'Nur intern durch den Adapter beschreibbar (nicht manuell ändern!)',
|
|
71
63
|
},
|
|
72
64
|
});
|
|
73
|
-
} else {
|
|
74
|
-
this.adapter.log.debug(`[migrationHelper] ${id} ist bereits korrekt konfiguriert.`);
|
|
75
65
|
}
|
|
76
66
|
} catch (err) {
|
|
77
67
|
this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
|
|
@@ -81,18 +71,11 @@ const migrationHelper = {
|
|
|
81
71
|
// ------------------------------------------------------
|
|
82
72
|
// Migration: persist-Flag für solar.warn_active ergänzen
|
|
83
73
|
// ------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Ergänzt persist:true bei solar.warn_active,
|
|
87
|
-
* damit die Einstellung (Warnfunktion aktivieren/deaktivieren)
|
|
88
|
-
* nach einem Neustart erhalten bleibt.
|
|
89
|
-
*/
|
|
90
74
|
async _fixSolarWarnActivePersist() {
|
|
91
75
|
const id = 'solar.warn_active';
|
|
92
76
|
try {
|
|
93
77
|
const obj = await this.adapter.getObjectAsync(id);
|
|
94
78
|
if (!obj) {
|
|
95
|
-
this.adapter.log.debug(`[migrationHelper] ${id} existiert nicht – keine Anpassung nötig.`);
|
|
96
79
|
return;
|
|
97
80
|
}
|
|
98
81
|
|
|
@@ -105,13 +88,74 @@ const migrationHelper = {
|
|
|
105
88
|
desc: `${obj.common?.desc || ''} (automatisch per Migration persistiert)`,
|
|
106
89
|
},
|
|
107
90
|
});
|
|
108
|
-
} else {
|
|
109
|
-
this.adapter.log.debug(`[migrationHelper] ${id} ist bereits mit persist:true versehen.`);
|
|
110
91
|
}
|
|
111
92
|
} catch (err) {
|
|
112
93
|
this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
|
|
113
94
|
}
|
|
114
95
|
},
|
|
96
|
+
|
|
97
|
+
// ------------------------------------------------------
|
|
98
|
+
// Migration: Ergänze neuen Pumpenmodus "Automatik (PV)"
|
|
99
|
+
// ------------------------------------------------------
|
|
100
|
+
async _fixPumpModeStates() {
|
|
101
|
+
const id = 'pump.mode';
|
|
102
|
+
try {
|
|
103
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
104
|
+
if (!obj) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const states = obj.common?.states || {};
|
|
109
|
+
if (!states.auto_pv) {
|
|
110
|
+
states.auto_pv = 'Automatik (PV)';
|
|
111
|
+
this.adapter.log.info(`[migrationHelper] Ergänze neuen Modus "Automatik (PV)" in pump.mode`);
|
|
112
|
+
await this.adapter.extendObjectAsync(id, { common: { states } });
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// FIX: Entferne versehentlich angelegte Reset-Buttons aus Wochen- und Monatsstatistik
|
|
120
|
+
async _removeInvalidResetButtons() {
|
|
121
|
+
try {
|
|
122
|
+
const allObjs = await this.adapter.getAdapterObjectsAsync();
|
|
123
|
+
const keys = Object.keys(allObjs);
|
|
124
|
+
let removed = 0;
|
|
125
|
+
|
|
126
|
+
for (const id of keys) {
|
|
127
|
+
if (
|
|
128
|
+
(id.startsWith('analytics.statistics.temperature.week.') ||
|
|
129
|
+
id.startsWith('analytics.statistics.temperature.month.')) &&
|
|
130
|
+
id.endsWith('.reset_today')
|
|
131
|
+
) {
|
|
132
|
+
try {
|
|
133
|
+
// Erst Statewert entfernen
|
|
134
|
+
await this.adapter.delStateAsync(id);
|
|
135
|
+
} catch {
|
|
136
|
+
this.adapter.log.debug(`[migrationHelper] Kein Statewert für ${id} vorhanden (Überspringe).`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Danach Objekt löschen (auch wenn persist=true)
|
|
140
|
+
try {
|
|
141
|
+
await this.adapter.delObjectAsync(id, { recursive: false });
|
|
142
|
+
this.adapter.log.info(`[migrationHelper] Veralteter Reset-Button entfernt: ${id}`);
|
|
143
|
+
removed++;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
this.adapter.log.warn(`[migrationHelper] Konnte ${id} nicht löschen: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (removed === 0) {
|
|
151
|
+
this.adapter.log.debug('[migrationHelper] Keine alten Reset-Buttons gefunden.');
|
|
152
|
+
} else {
|
|
153
|
+
this.adapter.log.info(`[migrationHelper] Insgesamt ${removed} alte Reset-Buttons entfernt.`);
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
this.adapter.log.warn(`[migrationHelper] Fehler beim Entfernen alter Reset-Buttons: ${err.message}`);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
115
159
|
};
|
|
116
160
|
|
|
117
161
|
module.exports = migrationHelper;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* photovoltaicHelper
|
|
5
|
+
* -------------------------------------------------------------
|
|
6
|
+
* - Liest PV-Erzeugung und Hausverbrauch (Foreign States aus Admin-Config)
|
|
7
|
+
* - Berechnet Überschussleistung und setzt Photovoltaik-States
|
|
8
|
+
* - Schaltet die Pumpe nur im Modus 'auto_pv' über pump.pump_switch
|
|
9
|
+
* - Respektiert Saison, Nachlaufzeit (Entprellung) und optionales "Umwälzung erreicht"
|
|
10
|
+
* - Einschaltlogik: Überschuss >= (pump_max_watt + threshold_w)
|
|
11
|
+
* -------------------------------------------------------------
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const photovoltaicHelper = {
|
|
15
|
+
adapter: null,
|
|
16
|
+
genId: null,
|
|
17
|
+
houseId: null,
|
|
18
|
+
afterrunTimer: null,
|
|
19
|
+
_pvPumpHoldUntil: 0,
|
|
20
|
+
_desiredPump: null,
|
|
21
|
+
_lastCalc: 0,
|
|
22
|
+
|
|
23
|
+
init(adapter) {
|
|
24
|
+
this.adapter = adapter;
|
|
25
|
+
|
|
26
|
+
this.genId = adapter.config?.power_generated_id || '';
|
|
27
|
+
this.houseId = adapter.config?.power_house_id || '';
|
|
28
|
+
|
|
29
|
+
if (!this.genId || !this.houseId) {
|
|
30
|
+
this.adapter.log.info(
|
|
31
|
+
'[photovoltaicHelper] PV-IDs in der Instanz-Konfiguration fehlen. Überschusserkennung bleibt passiv.',
|
|
32
|
+
);
|
|
33
|
+
} else {
|
|
34
|
+
try {
|
|
35
|
+
this.adapter.subscribeForeignStates(this.genId);
|
|
36
|
+
this.adapter.subscribeForeignStates(this.houseId);
|
|
37
|
+
this.adapter.log.info(`[photovoltaicHelper] Subscribed: PV="${this.genId}", Haus="${this.houseId}"`);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
this.adapter.log.warn(`[photovoltaicHelper] Konnte Foreign-States nicht abonnieren: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.adapter.subscribeStates('photovoltaic.afterrun_min');
|
|
44
|
+
this.adapter.subscribeStates('photovoltaic.ignore_on_circulation');
|
|
45
|
+
this.adapter.subscribeStates('status.season_active');
|
|
46
|
+
this.adapter.subscribeStates('pump.mode');
|
|
47
|
+
|
|
48
|
+
this._safeRecalc('init');
|
|
49
|
+
this.adapter.log.info('[photovoltaicHelper] Initialisierung abgeschlossen.');
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async handleStateChange(id, state) {
|
|
53
|
+
if (!state) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
if (this.genId && id === this.genId) {
|
|
58
|
+
await this._updateNumberState('photovoltaic.power_generated_w', Number(state.val) || 0);
|
|
59
|
+
await this._safeRecalc('foreign:pv');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (this.houseId && id === this.houseId) {
|
|
63
|
+
await this._updateNumberState('photovoltaic.power_house_w', Number(state.val) || 0);
|
|
64
|
+
await this._safeRecalc('foreign:house');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (id.endsWith('photovoltaic.afterrun_min')) {
|
|
69
|
+
this.adapter.log.debug(`[photovoltaicHelper] Nachlaufzeit geändert auf ${Number(state.val) || 0} min`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (id.endsWith('photovoltaic.ignore_on_circulation')) {
|
|
73
|
+
this.adapter.log.debug(
|
|
74
|
+
`[photovoltaicHelper] Flag "PV ignorieren bei Umwälzung erreicht" = ${!!state.val}`,
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (id.endsWith('status.season_active') || id.endsWith('pump.mode')) {
|
|
80
|
+
await this._safeRecalc('mode/season');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.adapter.log.warn(`[photovoltaicHelper] Fehler in handleStateChange: ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async _recalc(_sourceTag = '') {
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
if (now - this._lastCalc < 250) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this._lastCalc = now;
|
|
94
|
+
|
|
95
|
+
const seasonActive = !!(await this.adapter.getStateAsync('status.season_active'))?.val;
|
|
96
|
+
const pumpMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'auto';
|
|
97
|
+
|
|
98
|
+
const gen = Number((await this.adapter.getStateAsync('photovoltaic.power_generated_w'))?.val ?? 0) || 0;
|
|
99
|
+
const house = Number((await this.adapter.getStateAsync('photovoltaic.power_house_w'))?.val ?? 0) || 0;
|
|
100
|
+
|
|
101
|
+
// Schwelle & Pumpenleistung
|
|
102
|
+
const thresholdState = Number((await this.adapter.getStateAsync('photovoltaic.threshold_w'))?.val ?? NaN);
|
|
103
|
+
const threshold = Number.isFinite(thresholdState)
|
|
104
|
+
? thresholdState
|
|
105
|
+
: Number(this.adapter.config?.threshold_w || 200);
|
|
106
|
+
|
|
107
|
+
const pumpMax = Number((await this.adapter.getStateAsync('pump.pump_max_watt'))?.val ?? 0);
|
|
108
|
+
|
|
109
|
+
const afterrunMin = Math.max(
|
|
110
|
+
0,
|
|
111
|
+
Number((await this.adapter.getStateAsync('photovoltaic.afterrun_min'))?.val ?? 0) || 0,
|
|
112
|
+
);
|
|
113
|
+
const ignoreOnCirc = !!((await this.adapter.getStateAsync('photovoltaic.ignore_on_circulation'))?.val ?? false);
|
|
114
|
+
|
|
115
|
+
// SAFETY: Solarüberhitzungsschutz
|
|
116
|
+
try {
|
|
117
|
+
const collectorWarning = !!(await this.adapter.getStateAsync('solar.collector_warning'))?.val;
|
|
118
|
+
if (collectorWarning) {
|
|
119
|
+
this.adapter.log.warn(
|
|
120
|
+
'[photovoltaicHelper] Kollektorüberhitzung erkannt → Pumpe ZWANGSEIN (Sicherheits-Override aktiv)',
|
|
121
|
+
);
|
|
122
|
+
return this._maybeStartPump('solar_overheat_protection');
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
this.adapter.log.warn(`[photovoltaicHelper] Fehler beim Prüfen der Solarüberhitzung: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Überschussberechnung
|
|
129
|
+
const surplus = Math.max(0, gen - house);
|
|
130
|
+
await this._updateNumberState('photovoltaic.power_surplus_w', surplus);
|
|
131
|
+
|
|
132
|
+
// **NEU:** Einschaltlogik = Pumpenleistung + Sicherheitsaufschlag
|
|
133
|
+
const requiredPower = pumpMax + threshold;
|
|
134
|
+
const surplusActive = surplus >= requiredPower && seasonActive;
|
|
135
|
+
await this._updateBoolState('photovoltaic.surplus_active', surplusActive);
|
|
136
|
+
|
|
137
|
+
const txt = surplusActive
|
|
138
|
+
? `Überschuss aktiv (${surplus} W ≥ ${pumpMax}+${threshold} W)`
|
|
139
|
+
: `Kein Überschuss (${surplus} W < ${pumpMax}+${threshold} W)`;
|
|
140
|
+
await this._updateStringState('photovoltaic.status_text', txt);
|
|
141
|
+
await this._updateStringState('photovoltaic.last_update', new Date().toISOString());
|
|
142
|
+
|
|
143
|
+
// Saison/Modus prüfen
|
|
144
|
+
if (!seasonActive) {
|
|
145
|
+
this.adapter.log.debug('[photovoltaicHelper] Saison inaktiv → keine PV-Schaltlogik.');
|
|
146
|
+
return this._maybeStopPump(false, 0, 'season_inactive');
|
|
147
|
+
}
|
|
148
|
+
if (pumpMode !== 'auto_pv') {
|
|
149
|
+
this.adapter.log.debug(
|
|
150
|
+
`[photovoltaicHelper] Pumpenmodus ist '${pumpMode}' ≠ 'auto_pv' → keine PV-Schaltlogik.`,
|
|
151
|
+
);
|
|
152
|
+
return this._maybeStopPump(false, 0, 'mode_not_auto_pv');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// FIX: PV-Helfer darf nur aktiv sein, solange Umwälzung noch nicht erfüllt ist
|
|
156
|
+
if (ignoreOnCirc) {
|
|
157
|
+
try {
|
|
158
|
+
const remainingState = await this.adapter.getForeignStateAsync(
|
|
159
|
+
'poolcontrol.0.circulation.daily_remaining',
|
|
160
|
+
);
|
|
161
|
+
const remaining = Number(remainingState?.val ?? NaN);
|
|
162
|
+
|
|
163
|
+
if (Number.isFinite(remaining)) {
|
|
164
|
+
// RULE: Wenn Umwälzung bereits erfüllt → Pumpe sofort AUS (ohne Nachlauf)
|
|
165
|
+
if (remaining <= 0) {
|
|
166
|
+
this.adapter.log.info(
|
|
167
|
+
`[photovoltaicHelper] Tagesumwälzung erreicht (daily_remaining=${remaining}) → PV-Steuerung beendet, Pumpe AUS.`,
|
|
168
|
+
);
|
|
169
|
+
return this._maybeStopPump(true, 0, 'circulation_reached_force_off');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// RULE: Wenn Umwälzung noch nicht erfüllt → nur dann darf bei Überschuss eingeschaltet werden
|
|
173
|
+
if (remaining > 0 && surplusActive) {
|
|
174
|
+
this.adapter.log.debug(
|
|
175
|
+
`[photovoltaicHelper] Tagesumwälzung noch nicht erfüllt (${remaining}) → PV-Steuerung aktiv.`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch (err) {
|
|
180
|
+
this.adapter.log.debug(
|
|
181
|
+
`[photovoltaicHelper] daily_remaining konnte nicht gelesen werden: ${err.message}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// FIX: PV-Schaltlogik mit Prüfung der Umwälzung
|
|
187
|
+
if (surplusActive) {
|
|
188
|
+
try {
|
|
189
|
+
const remainingState = await this.adapter.getForeignStateAsync(
|
|
190
|
+
'poolcontrol.0.circulation.daily_remaining',
|
|
191
|
+
);
|
|
192
|
+
const remaining = Number(remainingState?.val ?? NaN);
|
|
193
|
+
|
|
194
|
+
// RULE: Einschalten nur, wenn Umwälzung noch nicht erfüllt
|
|
195
|
+
if (Number.isFinite(remaining) && remaining <= 0) {
|
|
196
|
+
this.adapter.log.info(
|
|
197
|
+
`[photovoltaicHelper] Tagesumwälzung bereits erfüllt (${remaining}) → Pumpe bleibt AUS (kein Start trotz Überschuss).`,
|
|
198
|
+
);
|
|
199
|
+
return this._maybeStopPump(true, 0, 'circulation_already_reached');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// RULE: Überschuss aktiv UND Umwälzung noch nicht erfüllt → einschalten
|
|
203
|
+
return this._maybeStartPump('pv_surplus');
|
|
204
|
+
} catch (err) {
|
|
205
|
+
this.adapter.log.warn(`[photovoltaicHelper] Fehler beim Prüfen von daily_remaining: ${err.message}`);
|
|
206
|
+
return this._maybeStartPump('pv_surplus');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// RULE: Kein Überschuss → ggf. Nachlauf/Aus
|
|
211
|
+
return this._maybeStopPump(false, afterrunMin, 'pv_ended_afterrun');
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
async _maybeStartPump(reason) {
|
|
215
|
+
if (this._desiredPump === true) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this._desiredPump = true;
|
|
219
|
+
if (this.afterrunTimer) {
|
|
220
|
+
clearTimeout(this.afterrunTimer);
|
|
221
|
+
this.afterrunTimer = null;
|
|
222
|
+
}
|
|
223
|
+
this._pvPumpHoldUntil = 0;
|
|
224
|
+
this.adapter.log.info(`[photovoltaicHelper] Pumpe EIN (Grund: ${reason})`);
|
|
225
|
+
await this._setPumpSwitch(true);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
async _maybeStopPump(immediate, afterrunMin, tag) {
|
|
229
|
+
if (immediate || !afterrunMin || afterrunMin <= 0) {
|
|
230
|
+
if (this._desiredPump === false) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this._desiredPump = false;
|
|
234
|
+
this.adapter.log.info(`[photovoltaicHelper] Pumpe AUS (${tag}, ohne Nachlauf)`);
|
|
235
|
+
await this._setPumpSwitch(false);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const holdMs = Math.round(afterrunMin * 60 * 1000);
|
|
240
|
+
this._pvPumpHoldUntil = Date.now() + holdMs;
|
|
241
|
+
if (this.afterrunTimer) {
|
|
242
|
+
clearTimeout(this.afterrunTimer);
|
|
243
|
+
}
|
|
244
|
+
this.afterrunTimer = setTimeout(async () => {
|
|
245
|
+
const active = !!(await this.adapter.getStateAsync('photovoltaic.surplus_active'))?.val;
|
|
246
|
+
if (active) {
|
|
247
|
+
this.adapter.log.debug('[photovoltaicHelper] Nachlauf abgebrochen – Überschuss wieder aktiv.');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this._desiredPump = false;
|
|
251
|
+
this.adapter.log.info('[photovoltaicHelper] Pumpe AUS (Nachlauf beendet)');
|
|
252
|
+
await this._setPumpSwitch(false);
|
|
253
|
+
}, holdMs);
|
|
254
|
+
this.adapter.log.debug(`[photovoltaicHelper] Nachlauf gestartet: ${afterrunMin} min (${tag})`);
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
async _setPumpSwitch(on) {
|
|
258
|
+
try {
|
|
259
|
+
await this.adapter.setStateAsync('pump.pump_switch', { val: !!on, ack: false });
|
|
260
|
+
} catch (err) {
|
|
261
|
+
this.adapter.log.warn(`[photovoltaicHelper] Konnte pump.pump_switch nicht setzen: ${err.message}`);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
async _updateNumberState(id, val) {
|
|
266
|
+
try {
|
|
267
|
+
await this.adapter.setStateAsync(id, { val: Number(val) || 0, ack: true });
|
|
268
|
+
} catch (e) {
|
|
269
|
+
this.adapter.log.warn(`[photovoltaicHelper] setNumber ${id} fehlgeschlagen: ${e.message}`);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
async _updateBoolState(id, val) {
|
|
273
|
+
try {
|
|
274
|
+
await this.adapter.setStateAsync(id, { val: !!val, ack: true });
|
|
275
|
+
} catch (e) {
|
|
276
|
+
this.adapter.log.warn(`[photovoltaicHelper] setBool ${id} fehlgeschlagen: ${e.message}`);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
async _updateStringState(id, val) {
|
|
280
|
+
try {
|
|
281
|
+
await this.adapter.setStateAsync(id, { val: String(val ?? ''), ack: true });
|
|
282
|
+
} catch (e) {
|
|
283
|
+
this.adapter.log.warn(`[photovoltaicHelper] setString ${id} fehlgeschlagen: ${e.message}`);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
async _safeRecalc(tag) {
|
|
288
|
+
try {
|
|
289
|
+
await this._recalc(tag);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
this.adapter.log.warn(`[photovoltaicHelper] Recalc-Fehler (${tag}): ${err.message}`);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
cleanup() {
|
|
296
|
+
if (this.afterrunTimer) {
|
|
297
|
+
clearTimeout(this.afterrunTimer);
|
|
298
|
+
this.afterrunTimer = null;
|
|
299
|
+
}
|
|
300
|
+
this._pvPumpHoldUntil = 0;
|
|
301
|
+
this._desiredPump = null;
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
module.exports = photovoltaicHelper;
|
|
@@ -250,6 +250,12 @@ const statisticsHelperMonth = {
|
|
|
250
250
|
});
|
|
251
251
|
|
|
252
252
|
await this._updateOverallSummary();
|
|
253
|
+
|
|
254
|
+
// FIX: last_update korrigieren (echtes Aktualisierungsdatum)
|
|
255
|
+
await adapter.setStateAsync(`${basePath}.last_update`, {
|
|
256
|
+
val: new Date().toISOString(),
|
|
257
|
+
ack: true,
|
|
258
|
+
});
|
|
253
259
|
},
|
|
254
260
|
|
|
255
261
|
/**
|
|
@@ -385,6 +391,20 @@ const statisticsHelperMonth = {
|
|
|
385
391
|
async _resetMonthlyTemperatureStats() {
|
|
386
392
|
const adapter = this.adapter;
|
|
387
393
|
|
|
394
|
+
// 🟢 Neuer Logikblock: Monatsreset erfolgt nur, wenn wirklich neuer Monat begonnen hat
|
|
395
|
+
try {
|
|
396
|
+
const now = new Date();
|
|
397
|
+
const currentDay = now.getDate();
|
|
398
|
+
|
|
399
|
+
// Nur am 1. Tag des Monats ausführen (00:05 Uhr geplant)
|
|
400
|
+
if (currentDay !== 1) {
|
|
401
|
+
adapter.log.debug('[statisticsHelperMonth] Kein Monatsanfang – Reset übersprungen.');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
adapter.log.warn(`[statisticsHelperMonth] Fehler bei Monatsbeginn-Prüfung: ${err.message}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
388
408
|
// 🟢 NEU: Schutz vor Endlosschleifen und Mehrfachausführung
|
|
389
409
|
if (this.isResetting) {
|
|
390
410
|
adapter.log.debug('statisticsHelperMonth: Reset bereits aktiv – übersprungen.');
|
|
@@ -151,10 +151,16 @@ async function createControlStates(adapter) {
|
|
|
151
151
|
role: 'date',
|
|
152
152
|
read: true,
|
|
153
153
|
write: false,
|
|
154
|
+
persist: true,
|
|
154
155
|
},
|
|
155
156
|
native: {},
|
|
156
157
|
});
|
|
157
|
-
|
|
158
|
+
|
|
159
|
+
// 🟢 Überinstallationsschutz – nur schreiben, wenn kein Wert existiert
|
|
160
|
+
const existingLastDate = await adapter.getStateAsync('control.pump.backwash_last_date');
|
|
161
|
+
if (existingLastDate === null || existingLastDate.val === null || existingLastDate.val === undefined) {
|
|
162
|
+
await adapter.setStateAsync('control.pump.backwash_last_date', { val: '', ack: true });
|
|
163
|
+
}
|
|
158
164
|
|
|
159
165
|
await adapter.setObjectNotExistsAsync('control.pump.backwash_required', {
|
|
160
166
|
type: 'state',
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* createPhotovoltaicStates(adapter)
|
|
5
|
+
* -------------------------------------------------------------
|
|
6
|
+
* Legt alle States für den Photovoltaik-Bereich an.
|
|
7
|
+
* - persistente States
|
|
8
|
+
* - überinstallationsgeschützt (keine Überschreibung vorhandener Werte)
|
|
9
|
+
* - konsistent im Stil der übrigen State-Dateien
|
|
10
|
+
* -------------------------------------------------------------
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} adapter - ioBroker Adapterinstanz
|
|
15
|
+
* @returns {Promise<void>}
|
|
16
|
+
*/
|
|
17
|
+
async function createPhotovoltaicStates(adapter) {
|
|
18
|
+
adapter.log.debug('[createPhotovoltaicStates] Initialisierung gestartet.');
|
|
19
|
+
|
|
20
|
+
// --- Photovoltaik-Hauptordner ---
|
|
21
|
+
await adapter.setObjectNotExistsAsync('photovoltaic', {
|
|
22
|
+
type: 'channel',
|
|
23
|
+
common: { name: 'Photovoltaik (Überschusserkennung)' },
|
|
24
|
+
native: {},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// --- State-Liste ---
|
|
28
|
+
const states = [
|
|
29
|
+
{
|
|
30
|
+
id: 'power_generated_w',
|
|
31
|
+
name: 'PV-Erzeugungsleistung (W)',
|
|
32
|
+
desc: 'Aktuell erzeugte Leistung der PV-Anlage in Watt',
|
|
33
|
+
type: 'number',
|
|
34
|
+
role: 'value.power',
|
|
35
|
+
unit: 'W',
|
|
36
|
+
read: true,
|
|
37
|
+
write: false,
|
|
38
|
+
def: 0,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'power_house_w',
|
|
42
|
+
name: 'Hausverbrauch (W)',
|
|
43
|
+
desc: 'Aktueller Stromverbrauch des Hauses in Watt',
|
|
44
|
+
type: 'number',
|
|
45
|
+
role: 'value.power',
|
|
46
|
+
unit: 'W',
|
|
47
|
+
read: true,
|
|
48
|
+
write: false,
|
|
49
|
+
def: 0,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'power_surplus_w',
|
|
53
|
+
name: 'PV-Überschussleistung (W)',
|
|
54
|
+
desc: 'Berechneter Überschuss zwischen PV-Erzeugung und Hausverbrauch',
|
|
55
|
+
type: 'number',
|
|
56
|
+
role: 'value.power',
|
|
57
|
+
unit: 'W',
|
|
58
|
+
read: true,
|
|
59
|
+
write: false,
|
|
60
|
+
def: 0,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'surplus_active',
|
|
64
|
+
name: 'PV-Überschuss aktiv',
|
|
65
|
+
desc: 'Zeigt an, ob aktuell ein PV-Überschuss vorliegt (true/false)',
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
role: 'indicator',
|
|
68
|
+
read: true,
|
|
69
|
+
write: false,
|
|
70
|
+
def: false,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'afterrun_min',
|
|
74
|
+
name: 'Nachlaufzeit (Minuten)',
|
|
75
|
+
desc: 'Dauer, wie lange die Pumpe nach Ende des PV-Überschusses weiterläuft',
|
|
76
|
+
type: 'number',
|
|
77
|
+
role: 'level',
|
|
78
|
+
unit: 'min',
|
|
79
|
+
read: true,
|
|
80
|
+
write: true,
|
|
81
|
+
def: 2,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'ignore_on_circulation',
|
|
85
|
+
name: 'PV ignorieren, wenn Umwälzmenge erreicht',
|
|
86
|
+
desc: 'Wenn aktiviert, wird PV-Steuerung deaktiviert, sobald Tagesumwälzung erfüllt ist',
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
role: 'switch.enable',
|
|
89
|
+
read: true,
|
|
90
|
+
write: true,
|
|
91
|
+
def: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'threshold_w',
|
|
95
|
+
name: 'Schwellwert für PV-Überschuss (W)',
|
|
96
|
+
desc: 'Watt-Schwelle, ab der ein Überschuss erkannt wird',
|
|
97
|
+
type: 'number',
|
|
98
|
+
role: 'value',
|
|
99
|
+
unit: 'W',
|
|
100
|
+
read: true,
|
|
101
|
+
write: false,
|
|
102
|
+
def: 200,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'status_text',
|
|
106
|
+
name: 'Statusmeldung (Text)',
|
|
107
|
+
desc: 'Klartextstatus der PV-Erkennung (z. B. Überschuss aktiv)',
|
|
108
|
+
type: 'string',
|
|
109
|
+
role: 'text',
|
|
110
|
+
read: true,
|
|
111
|
+
write: false,
|
|
112
|
+
def: '',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'last_update',
|
|
116
|
+
name: 'Letzte Aktualisierung',
|
|
117
|
+
desc: 'Zeitstempel der letzten Berechnung der PV-Werte',
|
|
118
|
+
type: 'string',
|
|
119
|
+
role: 'date',
|
|
120
|
+
read: true,
|
|
121
|
+
write: false,
|
|
122
|
+
def: '',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// --- States anlegen ---
|
|
127
|
+
for (const s of states) {
|
|
128
|
+
const id = `photovoltaic.${s.id}`;
|
|
129
|
+
|
|
130
|
+
await adapter.setObjectNotExistsAsync(id, {
|
|
131
|
+
type: 'state',
|
|
132
|
+
common: {
|
|
133
|
+
name: s.name,
|
|
134
|
+
desc: s.desc,
|
|
135
|
+
type: s.type,
|
|
136
|
+
role: s.role,
|
|
137
|
+
read: s.read,
|
|
138
|
+
write: s.write,
|
|
139
|
+
def: s.def,
|
|
140
|
+
unit: s.unit || '',
|
|
141
|
+
persist: true,
|
|
142
|
+
},
|
|
143
|
+
native: {},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Überinstallationsschutz
|
|
147
|
+
const existing = await adapter.getStateAsync(id);
|
|
148
|
+
if (existing === null || existing === undefined) {
|
|
149
|
+
await adapter.setStateAsync(id, { val: s.def, ack: true });
|
|
150
|
+
adapter.log.debug(
|
|
151
|
+
`[createPhotovoltaicStates] Neuer State '${id}' initialisiert mit Default-Wert ${s.def}.`,
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
adapter.log.debug(`[createPhotovoltaicStates] State '${id}' bereits vorhanden – Wert bleibt erhalten.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
adapter.log.info('[createPhotovoltaicStates] Initialisierung abgeschlossen.');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { createPhotovoltaicStates };
|
|
@@ -100,7 +100,6 @@ async function _createTemperatureStatsGroup(adapter, periodId, displayName) {
|
|
|
100
100
|
{ id: 'temp_avg', name: 'Durchschnittstemperatur', type: 'number', role: 'value.temperature', unit: '°C' },
|
|
101
101
|
{ id: 'data_points_count', name: 'Anzahl Messwerte', type: 'number', role: 'value' },
|
|
102
102
|
{ id: 'last_update', name: 'Letzte Aktualisierung', type: 'string', role: 'value.time' },
|
|
103
|
-
{ id: 'reset_today', name: 'Tagesstatistik zurücksetzen', type: 'boolean', role: 'button' },
|
|
104
103
|
{
|
|
105
104
|
id: 'summary_json',
|
|
106
105
|
name: `${displayName} (JSON)`,
|
|
@@ -115,6 +114,16 @@ async function _createTemperatureStatsGroup(adapter, periodId, displayName) {
|
|
|
115
114
|
},
|
|
116
115
|
];
|
|
117
116
|
|
|
117
|
+
// FIX: Reset-Button nur bei Tagesstatistik anlegen
|
|
118
|
+
if (periodId === 'today') {
|
|
119
|
+
stateDefs.push({
|
|
120
|
+
id: 'reset_today',
|
|
121
|
+
name: 'Tagesstatistik zurücksetzen',
|
|
122
|
+
type: 'boolean',
|
|
123
|
+
role: 'button',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
118
127
|
for (const def of stateDefs) {
|
|
119
128
|
await adapter.setObjectNotExistsAsync(`${basePath}.${def.id}`, {
|
|
120
129
|
type: 'state',
|
package/main.js
CHANGED
|
@@ -19,6 +19,7 @@ const consumptionHelper = require('./lib/helpers/consumptionHelper');
|
|
|
19
19
|
const solarHelper = require('./lib/helpers/solarHelper');
|
|
20
20
|
const frostHelper = require('./lib/helpers/frostHelper');
|
|
21
21
|
const statusHelper = require('./lib/helpers/statusHelper');
|
|
22
|
+
const photovoltaicHelper = require('./lib/helpers/photovoltaicHelper');
|
|
22
23
|
const controlHelper = require('./lib/helpers/controlHelper');
|
|
23
24
|
const controlHelper2 = require('./lib/helpers/controlHelper2');
|
|
24
25
|
const debugLogHelper = require('./lib/helpers/debugLogHelper');
|
|
@@ -29,6 +30,7 @@ const { createPumpStates } = require('./lib/stateDefinitions/pumpStates');
|
|
|
29
30
|
const { createPumpStates2 } = require('./lib/stateDefinitions/pumpStates2');
|
|
30
31
|
const { createPumpStates3 } = require('./lib/stateDefinitions/pumpStates3');
|
|
31
32
|
const { createSolarStates } = require('./lib/stateDefinitions/solarStates');
|
|
33
|
+
const { createPhotovoltaicStates } = require('./lib/stateDefinitions/photovoltaicStates');
|
|
32
34
|
const { createGeneralStates } = require('./lib/stateDefinitions/generalStates');
|
|
33
35
|
const { createTimeStates } = require('./lib/stateDefinitions/timeStates');
|
|
34
36
|
const { createRuntimeStates } = require('./lib/stateDefinitions/runtimeStates');
|
|
@@ -67,6 +69,9 @@ class Poolcontrol extends utils.Adapter {
|
|
|
67
69
|
// --- Solarverwaltung ---
|
|
68
70
|
await createSolarStates(this);
|
|
69
71
|
|
|
72
|
+
// --- Photovoltaik ---
|
|
73
|
+
await createPhotovoltaicStates(this);
|
|
74
|
+
|
|
70
75
|
// --- Zeitsteuerung ---
|
|
71
76
|
await createTimeStates(this);
|
|
72
77
|
|
|
@@ -113,6 +118,7 @@ class Poolcontrol extends utils.Adapter {
|
|
|
113
118
|
speechHelper.init(this);
|
|
114
119
|
consumptionHelper.init(this);
|
|
115
120
|
solarHelper.init(this);
|
|
121
|
+
photovoltaicHelper.init(this);
|
|
116
122
|
frostHelper.init(this);
|
|
117
123
|
statusHelper.init(this);
|
|
118
124
|
controlHelper.init(this);
|
|
@@ -225,6 +231,11 @@ class Poolcontrol extends utils.Adapter {
|
|
|
225
231
|
} catch (e) {
|
|
226
232
|
this.log.warn(`[consumptionHelper] Fehler in handleStateChange: ${e.message}`);
|
|
227
233
|
}
|
|
234
|
+
try {
|
|
235
|
+
photovoltaicHelper.handleStateChange(id, state);
|
|
236
|
+
} catch (e) {
|
|
237
|
+
this.log.warn(`[photovoltaicHelper] Fehler in handleStateChange: ${e.message}`);
|
|
238
|
+
}
|
|
228
239
|
try {
|
|
229
240
|
statusHelper.handleStateChange(id, state);
|
|
230
241
|
} catch (e) {
|
|
@@ -242,6 +253,17 @@ class Poolcontrol extends utils.Adapter {
|
|
|
242
253
|
controlHelper2.handleStateChange(id, state);
|
|
243
254
|
}
|
|
244
255
|
|
|
256
|
+
// --- Photovoltaik-Parameter ---
|
|
257
|
+
if (id.endsWith('photovoltaic.afterrun_min')) {
|
|
258
|
+
this.log.debug(`[onStateChange] Nachlaufzeit (PV) geändert auf ${state.val} Minuten`);
|
|
259
|
+
this.config.pv_afterrun_min = Number(state.val);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (id.endsWith('photovoltaic.ignore_on_circulation')) {
|
|
263
|
+
this.log.debug(`[onStateChange] PV-Logik ignorieren bei Umwälzmenge = ${state.val}`);
|
|
264
|
+
this.config.pv_ignore_on_circulation = !!state.val;
|
|
265
|
+
}
|
|
266
|
+
|
|
245
267
|
await debugLogHelper.handleStateChange(id, state);
|
|
246
268
|
}
|
|
247
269
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.poolcontrol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Steuerung & Automatisierung für den Pool (Pumpe, Heizung, Ventile, Sensoren).",
|
|
5
5
|
"author": "DasBo1975 <dasbo1975@outlook.de>",
|
|
6
6
|
"homepage": "https://github.com/DasBo1975/ioBroker.poolcontrol",
|