iobroker.poolcontrol 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -26
- package/README.md +29 -1
- package/admin/jsonConfig.json +68 -1
- package/io-package.json +31 -34
- package/lib/helpers/pumpHelper.js +9 -0
- package/lib/helpers/solarHelper.js +39 -29
- package/lib/helpers/speechHelper.js +19 -1
- package/lib/helpers/statusHelper.js +226 -0
- package/lib/stateDefinitions/statusStates.js +185 -0
- package/main.js +29 -1
- package/package.json +2 -7
package/LICENSE
CHANGED
|
@@ -19,29 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
MIT-Lizenz
|
|
26
|
-
|
|
27
|
-
Copyright (c) 2025 DasBo1975
|
|
28
|
-
|
|
29
|
-
Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der
|
|
30
|
-
zugehörigen Dokumentationen (die "Software") erhält, die Erlaubnis erteilt,
|
|
31
|
-
sie uneingeschränkt zu nutzen, einschließlich und ohne Ausnahme des Rechts,
|
|
32
|
-
sie zu verwenden, zu kopieren, zu ändern, zusammenzuführen, zu veröffentlichen,
|
|
33
|
-
zu verbreiten, zu unterlizenzieren und/oder zu verkaufen, und Personen, denen
|
|
34
|
-
diese Software überlassen wird, diese Rechte zu verschaffen, unter den
|
|
35
|
-
folgenden Bedingungen:
|
|
36
|
-
|
|
37
|
-
Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen
|
|
38
|
-
Kopien oder Teilkopien der Software beizulegen.
|
|
39
|
-
|
|
40
|
-
DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER STILLSCHWEIGENDE GARANTIE
|
|
41
|
-
BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE DER MARKTREIFE, DER EIGNUNG FÜR
|
|
42
|
-
EINEN BESTIMMTEN ZWECK UND DER NICHTVERLETZUNG VON RECHTEN DRITTER.
|
|
43
|
-
IN KEINEM FALL SIND DIE AUTOREN ODER URHEBERRECHTSINHABER FÜR ANSPRÜCHE,
|
|
44
|
-
SCHÄDEN ODER SONSTIGE VERPFLICHTUNGEN HAFTBAR ZU MACHEN, OB AUS EINEM
|
|
45
|
-
VERTRAG, EINER UNERLAUBTEN HANDLUNG ODER ANDERWEITIG ENTSTANDEN, DIE SICH AUS
|
|
46
|
-
ODER IN VERBINDUNG MIT DER SOFTWARE ODER DER VERWENDUNG ODER SONSTIGEN
|
|
47
|
-
HANDLUNGEN IN DER SOFTWARE ERGEBEN.
|
package/README.md
CHANGED
|
@@ -94,6 +94,33 @@ Funktionen können sich ändern, bitte regelmäßig den Changelog beachten.
|
|
|
94
94
|
## Changelog
|
|
95
95
|
Auszug, vollständige Liste siehe `io-package.json`:
|
|
96
96
|
|
|
97
|
+
### 0.1.0
|
|
98
|
+
- Sprachausgabe über **E-Mail** hinzugefügt (konfigurierbar: Instanz, Empfänger, Betreff).
|
|
99
|
+
- Erweiterung der Instanz-Konfiguration im Tab „Sprachausgaben“.
|
|
100
|
+
**Bugfixes**
|
|
101
|
+
- Kleinere Korrekturen und Optimierungen in der Dokumentation (`help.md`).
|
|
102
|
+
- Logging in `speechHelper` verbessert.
|
|
103
|
+
|
|
104
|
+
### 0.0.10
|
|
105
|
+
|
|
106
|
+
Statusübersicht
|
|
107
|
+
Ab Version 0.0.10 gibt es einen eigenen Bereich `status.*` mit folgenden Datenpunkten:
|
|
108
|
+
|
|
109
|
+
- **status.summary** → Textübersicht (Pumpe, Modus, Temperaturen, Laufzeit, Umwälzung)
|
|
110
|
+
- **status.overview_json** → Übersicht als JSON (maschinenlesbar)
|
|
111
|
+
- **status.last_summary_update** → Zeitpunkt der letzten Aktualisierung
|
|
112
|
+
- **status.pump_last_start** → Letzter Pumpenstart (Zeitstempel)
|
|
113
|
+
- **status.pump_last_stop** → Letztes Pumpenende (Zeitstempel)
|
|
114
|
+
- **status.pump_was_on_today** → Boolean, ob die Pumpe heute lief
|
|
115
|
+
- **status.pump_today_count** → Anzahl der Starts heute (Reset um Mitternacht)
|
|
116
|
+
- **status.system_ok** → Boolean, ob das System fehlerfrei läuft
|
|
117
|
+
- **status.system_warning** → Boolean, wenn eine Warnung aktiv ist
|
|
118
|
+
- **status.system_warning_text** → Beschreibung der aktiven Warnung
|
|
119
|
+
- **status.season_active** → Anzeige, ob die Poolsaison aktiv ist
|
|
120
|
+
|
|
121
|
+
Diese Datenpunkte sind besonders für **VIS/vis2, Alexa- oder Telegram-Ausgaben** gedacht, da sie eine schnelle Übersicht über den aktuellen Poolstatus bieten.
|
|
122
|
+
|
|
123
|
+
|
|
97
124
|
### 0.0.9
|
|
98
125
|
- Laufzeit-, Umwälz-, Verbrauch-/Kosten- und Temperatur-Min/Max-States sind jetzt persistent
|
|
99
126
|
(Werte bleiben nach Adapter-Neustart oder Stromausfall erhalten)
|
|
@@ -137,5 +164,6 @@ Der Nutzer ist für die **sichere Installation und den Betrieb seiner Hardware**
|
|
|
137
164
|
---
|
|
138
165
|
|
|
139
166
|
## License
|
|
140
|
-
|
|
167
|
+
Copyright (c) 2025 DasBo1975 <dasbo1975@outlook.de>
|
|
141
168
|
|
|
169
|
+
MIT License
|
package/admin/jsonConfig.json
CHANGED
|
@@ -49,6 +49,21 @@
|
|
|
49
49
|
"lg": 3,
|
|
50
50
|
"xl": 3,
|
|
51
51
|
"newLine": true
|
|
52
|
+
},
|
|
53
|
+
"divider_season1": {
|
|
54
|
+
"type": "divider",
|
|
55
|
+
"newLine": true
|
|
56
|
+
},
|
|
57
|
+
"season_active": {
|
|
58
|
+
"type": "checkbox",
|
|
59
|
+
"label": "Poolsaison aktiv",
|
|
60
|
+
"default": false,
|
|
61
|
+
"xs": 12,
|
|
62
|
+
"sm": 3,
|
|
63
|
+
"md": 3,
|
|
64
|
+
"lg": 3,
|
|
65
|
+
"xl": 3,
|
|
66
|
+
"newLine": true
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
69
|
},
|
|
@@ -831,6 +846,53 @@
|
|
|
831
846
|
"lg": 6,
|
|
832
847
|
"xl": 6
|
|
833
848
|
},
|
|
849
|
+
"divider_speech_email": {
|
|
850
|
+
"type": "divider",
|
|
851
|
+
"newLine": true
|
|
852
|
+
},
|
|
853
|
+
"speech_email_enabled": {
|
|
854
|
+
"type": "checkbox",
|
|
855
|
+
"label": "Ausgabe über E-Mail aktivieren",
|
|
856
|
+
"default": false,
|
|
857
|
+
"xs": 12,
|
|
858
|
+
"sm": 3,
|
|
859
|
+
"md": 3,
|
|
860
|
+
"lg": 3,
|
|
861
|
+
"xl": 3,
|
|
862
|
+
"newLine": true
|
|
863
|
+
},
|
|
864
|
+
"speech_email_instance": {
|
|
865
|
+
"type": "instance",
|
|
866
|
+
"adapter": "email",
|
|
867
|
+
"label": "E-Mail Adapter-Instanz",
|
|
868
|
+
"default": "email.0",
|
|
869
|
+
"xs": 12,
|
|
870
|
+
"sm": 6,
|
|
871
|
+
"md": 6,
|
|
872
|
+
"lg": 6,
|
|
873
|
+
"xl": 6
|
|
874
|
+
},
|
|
875
|
+
"speech_email_recipient": {
|
|
876
|
+
"type": "text",
|
|
877
|
+
"label": "Empfänger-Adresse",
|
|
878
|
+
"default": "meineadresse@example.com",
|
|
879
|
+
"xs": 12,
|
|
880
|
+
"sm": 6,
|
|
881
|
+
"md": 6,
|
|
882
|
+
"lg": 6,
|
|
883
|
+
"xl": 6,
|
|
884
|
+
"newLine": true
|
|
885
|
+
},
|
|
886
|
+
"speech_email_subject": {
|
|
887
|
+
"type": "text",
|
|
888
|
+
"label": "E-Mail Betreff",
|
|
889
|
+
"default": "PoolControl Nachricht",
|
|
890
|
+
"xs": 12,
|
|
891
|
+
"sm": 6,
|
|
892
|
+
"md": 6,
|
|
893
|
+
"lg": 6,
|
|
894
|
+
"xl": 6
|
|
895
|
+
},
|
|
834
896
|
"divider_speech4": {
|
|
835
897
|
"type": "divider",
|
|
836
898
|
"newLine": true
|
|
@@ -938,7 +1000,12 @@
|
|
|
938
1000
|
},
|
|
939
1001
|
"helpText": {
|
|
940
1002
|
"type": "staticText",
|
|
941
|
-
"text": "Hier finden Sie die vollständige Dokumentation und Hinweise zu allen Einstellungen. Weitere Hinweise zu künftigen Versionen folgen."
|
|
1003
|
+
"text": "Hier finden Sie die vollständige Dokumentation und Hinweise zu allen Einstellungen. Weitere Hinweise zu künftigen Versionen folgen.",
|
|
1004
|
+
"xs": 12,
|
|
1005
|
+
"sm": 12,
|
|
1006
|
+
"md": 12,
|
|
1007
|
+
"lg": 12,
|
|
1008
|
+
"xl": 12
|
|
942
1009
|
}
|
|
943
1010
|
}
|
|
944
1011
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.0": {
|
|
7
|
+
"en": "Added speech output via E-Mail (configurable: instance, recipient, subject)",
|
|
8
|
+
"de": "Sprachausgabe per E-Mail hinzugefügt (konfigurierbar: Instanz, Empfänger, Betreff)",
|
|
9
|
+
"ru": "Добавлен вывод речи по электронной почте (настраивается: экземпляр, получатель, тема)",
|
|
10
|
+
"pt": "Adicionada saída de fala por e-mail (configurável: instância, destinatário, assunto)",
|
|
11
|
+
"nl": "Spraakuitvoer via e-mail toegevoegd (configureerbaar: instantie, ontvanger, onderwerp)",
|
|
12
|
+
"fr": "Ajout de la sortie vocale par e-mail (configurable : instance, destinataire, sujet)",
|
|
13
|
+
"it": "Aggiunta uscita vocale via e-mail (configurabile: istanza, destinatario, oggetto)",
|
|
14
|
+
"es": "Se agregó salida de voz por correo electrónico (configurable: instancia, destinatario, asunto)",
|
|
15
|
+
"pl": "Dodano wyjście głosowe przez e-mail (konfigurowalne: instancja, odbiorca, temat)",
|
|
16
|
+
"uk": "Додано озвучення електронною поштою (налаштовується: екземпляр, одержувач, тема)",
|
|
17
|
+
"zh-cn": "新增通过电子邮件的语音输出(可配置:实例、收件人、主题)"
|
|
18
|
+
},
|
|
19
|
+
"0.0.10": {
|
|
20
|
+
"en": "Added extended status overview with pump statistics, system flags and JSON summary",
|
|
21
|
+
"de": "Erweiterte Statusübersicht mit Pumpenstatistiken, Systemanzeigen und JSON-Zusammenfassung hinzugefügt",
|
|
22
|
+
"ru": "Добавлен расширенный обзор состояния с насосной статистикой, системными флагами и JSON-резюме",
|
|
23
|
+
"pt": "Adicionada visão geral de status estendida com estatísticas da bomba, sinalizadores do sistema e resumo em JSON",
|
|
24
|
+
"nl": "Uitgebreide statusoverzicht toegevoegd met pompstatistieken, systeemindicatoren en JSON-samenvatting",
|
|
25
|
+
"fr": "Aperçu du statut étendu ajouté avec statistiques de la pompe, indicateurs système et résumé JSON",
|
|
26
|
+
"it": "Aggiunta panoramica dello stato estesa con statistiche della pompa, indicatori di sistema e riepilogo JSON",
|
|
27
|
+
"es": "Añadida vista general de estado extendida con estadísticas de la bomba, indicadores del sistema y resumen en JSON",
|
|
28
|
+
"pl": "Dodano rozszerzony przegląd statusu ze statystykami pompy, flagami systemowymi i podsumowaniem JSON",
|
|
29
|
+
"uk": "Додано розширений огляд статусу зі статистикою насоса, системними прапорцями та зведенням JSON",
|
|
30
|
+
"zh-cn": "新增扩展状态概览,包括泵统计、系统标志和 JSON 摘要"
|
|
31
|
+
},
|
|
6
32
|
"0.0.9": {
|
|
7
33
|
"en": "Made runtime, circulation, consumption/costs and temperature min/max states persistent across restarts",
|
|
8
34
|
"de": "Laufzeit-, Umwälz-, Verbrauch-/Kosten- und Temperatur-Min/Max-States bleiben jetzt über Neustarts erhalten",
|
|
@@ -15,32 +41,6 @@
|
|
|
15
41
|
"pl": "Stany czasu pracy, cyrkulacji, zużycia/kosztów i temp. min/max są teraz zachowane po restartach",
|
|
16
42
|
"uk": "Стан часу роботи, циркуляції, споживання/вартості та мін/макс температури тепер зберігаються після перезапусків",
|
|
17
43
|
"zh-cn": "运行时间、循环、消耗/成本和温度最小/最大状态现在在重启后保持"
|
|
18
|
-
},
|
|
19
|
-
"0.0.8": {
|
|
20
|
-
"en": "Added Help tab in instance configuration with link to GitHub documentation",
|
|
21
|
-
"de": "Hilfetab in der Instanzkonfiguration hinzugefügt mit Link zur GitHub-Dokumentation",
|
|
22
|
-
"ru": "Добавлена вкладка помощи в настройке экземпляра со ссылкой на документацию GitHub",
|
|
23
|
-
"pt": "Guia de ajuda adicionado na configuração da instância com link para a documentação do GitHub",
|
|
24
|
-
"nl": "Help-tab toegevoegd in de instance-configuratie met link naar GitHub-documentatie",
|
|
25
|
-
"fr": "Onglet d'aide ajouté dans la configuration de l'instance avec lien vers la documentation GitHub",
|
|
26
|
-
"it": "Aggiunta scheda di aiuto nella configurazione dell'istanza con collegamento alla documentazione GitHub",
|
|
27
|
-
"es": "Se agregó pestaña de ayuda en la configuración de la instancia con enlace a la documentación de GitHub",
|
|
28
|
-
"pl": "Dodano kartę pomocy w konfiguracji instancji z linkiem do dokumentacji GitHub",
|
|
29
|
-
"uk": "Додано вкладку довідки в конфігурацію екземпляра з посиланням на документацію GitHub",
|
|
30
|
-
"zh-cn": "在实例配置中添加了帮助选项卡,并链接到 документацию GitHub"
|
|
31
|
-
},
|
|
32
|
-
"0.0.7": {
|
|
33
|
-
"en": "Added help file (help.md) and first README version",
|
|
34
|
-
"de": "Hilfedatei (help.md) und erste README-Version hinzugefügt",
|
|
35
|
-
"ru": "Добавлен файл справки (help.md) и первая версия README",
|
|
36
|
-
"pt": "Adicionado arquivo de ajuda (help.md) e primeira versão do README",
|
|
37
|
-
"nl": "Helpbestand (help.md) en eerste README-versie toegevoegd",
|
|
38
|
-
"fr": "Ajout du fichier d'aide (help.md) et première version du README",
|
|
39
|
-
"it": "Aggiunto file di aiuto (help.md) e prima versione di README",
|
|
40
|
-
"es": "Se agregó archivo de ayuda (help.md) y primera versión de README",
|
|
41
|
-
"pl": "Dodano plik pomocy (help.md) i pierwszą wersję README",
|
|
42
|
-
"uk": "Додано файл довідки (help.md) та першу версію README",
|
|
43
|
-
"zh-cn": "添加了帮助文件 (help.md) 和第一个 README 版本"
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"titleLang": {
|
|
@@ -103,14 +103,11 @@
|
|
|
103
103
|
"js-controller": ">=6.0.11"
|
|
104
104
|
}
|
|
105
105
|
],
|
|
106
|
-
|
|
106
|
+
"globalDependencies": [
|
|
107
|
+
{
|
|
107
108
|
"admin": ">=7.6.17"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"donate": {
|
|
111
|
-
"paypal": "https://www.paypal.com/donate?business=dirk.bertin%40t-online.de"
|
|
112
|
-
}
|
|
113
|
-
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
114
111
|
},
|
|
115
112
|
"native": {
|
|
116
113
|
"option1": true,
|
|
@@ -92,6 +92,15 @@ const pumpHelper = {
|
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// Saisonprüfung
|
|
96
|
+
const season = (await this.adapter.getStateAsync('status.season_active'))?.val;
|
|
97
|
+
if (!season) {
|
|
98
|
+
this.adapter.log.debug(
|
|
99
|
+
'[pumpHelper] Saison inaktiv – Pumpenlogik übersprungen (Frostschutz läuft separat)',
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
95
104
|
// 1) Leistung aus Fremd-State spiegeln
|
|
96
105
|
if (this.currentPowerId && id === this.currentPowerId) {
|
|
97
106
|
const val = this._parseNumber(state.val);
|
|
@@ -32,17 +32,14 @@ const solarHelper = {
|
|
|
32
32
|
|
|
33
33
|
async _checkSolar() {
|
|
34
34
|
try {
|
|
35
|
+
// --- NEU: Saisonstatus ---
|
|
36
|
+
const season = (await this.adapter.getStateAsync('status.season_active'))?.val;
|
|
37
|
+
|
|
35
38
|
// Solarsteuerung aktiv?
|
|
36
39
|
const active = (await this.adapter.getStateAsync('solar.solar_control_active'))?.val;
|
|
37
|
-
if (!active) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
40
|
|
|
41
|
-
// Pumpenmodus
|
|
41
|
+
// Pumpenmodus
|
|
42
42
|
const mode = (await this.adapter.getStateAsync('pump.mode'))?.val;
|
|
43
|
-
if (mode !== 'auto') {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
43
|
|
|
47
44
|
// Grenzwerte laden
|
|
48
45
|
const tempOn = (await this.adapter.getStateAsync('solar.temp_on'))?.val;
|
|
@@ -58,32 +55,45 @@ const solarHelper = {
|
|
|
58
55
|
return;
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
// --- Schaltlogik nur ausführen, wenn Saison aktiv, Solar aktiv und Modus AUTO ---
|
|
59
|
+
if (season && active && mode === 'auto') {
|
|
60
|
+
let shouldRun = false;
|
|
61
|
+
const delta = collector - pool;
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
// Logik: Einschalten, wenn Collector > tempOn und Delta > 0
|
|
64
|
+
if (collector >= tempOn && delta > 0) {
|
|
65
|
+
shouldRun = true;
|
|
66
|
+
}
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
// Ausschalten, wenn Collector < tempOff oder Delta <= 0
|
|
69
|
+
if (collector <= tempOff || delta <= 0) {
|
|
70
|
+
shouldRun = false;
|
|
71
|
+
}
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
// Optional Hysterese (kann später erweitert werden)
|
|
74
|
+
if (hysteresis) {
|
|
75
|
+
// z. B. Ausschaltgrenze etwas absenken
|
|
76
|
+
}
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
// ZENTRAL: Pumpe über Bool-Schalter setzen
|
|
79
|
+
await this.adapter.setStateAsync('pump.pump_switch', {
|
|
80
|
+
val: shouldRun,
|
|
81
|
+
ack: false,
|
|
82
|
+
});
|
|
83
|
+
this.adapter.log.debug(
|
|
84
|
+
`[solarHelper] Solarregelung → Pumpe ${shouldRun ? 'EIN' : 'AUS'} (Collector=${collector}°C, Pool=${pool}°C, Delta=${delta}°C)`,
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
// Keine Schaltung – Grund protokollieren
|
|
88
|
+
const reason = !season
|
|
89
|
+
? 'Saison inaktiv'
|
|
90
|
+
: !active
|
|
91
|
+
? 'Solarsteuerung aus'
|
|
92
|
+
: mode !== 'auto'
|
|
93
|
+
? 'Pumpenmodus != auto'
|
|
94
|
+
: 'unbekannt';
|
|
95
|
+
this.adapter.log.debug(`[solarHelper] Solarregelung übersprungen (${reason})`);
|
|
96
|
+
}
|
|
87
97
|
|
|
88
98
|
// --- Kollektor-Warnung ---
|
|
89
99
|
const warnActive = (await this.adapter.getStateAsync('solar.warn_active'))?.val;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* speechHelper
|
|
5
|
-
* - Sendet Texte an Alexa und
|
|
5
|
+
* - Sendet Texte an Alexa, Telegram und optional per E-Mail
|
|
6
6
|
* - Verwendet Config (jsonConfig) + States aus speechStates.js
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -95,6 +95,24 @@ const speechHelper = {
|
|
|
95
95
|
});
|
|
96
96
|
this.adapter.log.info(`[speechHelper] Telegram sendet: ${text}`);
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
// E-Mail-Ausgabe
|
|
100
|
+
if (this.adapter.config.speech_email_enabled && this.adapter.config.speech_email_instance) {
|
|
101
|
+
const instance = this.adapter.config.speech_email_instance;
|
|
102
|
+
const sendState = `${instance}.mail`;
|
|
103
|
+
|
|
104
|
+
await this.adapter.setForeignStateAsync(sendState, {
|
|
105
|
+
val: {
|
|
106
|
+
to: this.adapter.config.speech_email_recipient,
|
|
107
|
+
subject: this.adapter.config.speech_email_subject || 'PoolControl Nachricht',
|
|
108
|
+
text: text,
|
|
109
|
+
},
|
|
110
|
+
ack: false,
|
|
111
|
+
});
|
|
112
|
+
this.adapter.log.info(
|
|
113
|
+
`[speechHelper] E-Mail gesendet an ${this.adapter.config.speech_email_recipient}: ${text}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
98
116
|
} catch (err) {
|
|
99
117
|
this.adapter.log.warn(`[speechHelper] Fehler beim Sprechen: ${err.message}`);
|
|
100
118
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* statusHelper
|
|
5
|
+
* - Erstellt Text- und JSON-Zusammenfassungen
|
|
6
|
+
* - Schreibt Pumpenstart/Stop-Zeiten, Zähler, Indikatoren
|
|
7
|
+
* - Hält systemweite OK/Warning-Flags aktuell
|
|
8
|
+
* - Führt Tagesreset um Mitternacht durch
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const statusHelper = {
|
|
12
|
+
adapter: null,
|
|
13
|
+
midnightTimer: null,
|
|
14
|
+
pumpOn: null, // interner Merker für Pumpenstatus
|
|
15
|
+
|
|
16
|
+
init(adapter) {
|
|
17
|
+
this.adapter = adapter;
|
|
18
|
+
|
|
19
|
+
// Relevante States überwachen
|
|
20
|
+
this.adapter.subscribeStates('pump.status');
|
|
21
|
+
this.adapter.subscribeStates('pump.mode');
|
|
22
|
+
this.adapter.subscribeStates('pump.pump_switch'); // wichtig für Start/Stop
|
|
23
|
+
this.adapter.subscribeStates('temperature.surface.current');
|
|
24
|
+
this.adapter.subscribeStates('temperature.collector.current');
|
|
25
|
+
this.adapter.subscribeStates('temperature.outside.current');
|
|
26
|
+
this.adapter.subscribeStates('runtime.today');
|
|
27
|
+
this.adapter.subscribeStates('circulation.daily_total');
|
|
28
|
+
this.adapter.subscribeStates('circulation.daily_required');
|
|
29
|
+
|
|
30
|
+
// System-Warnungen überwachen
|
|
31
|
+
this.adapter.subscribeStates('solar.collector_warning');
|
|
32
|
+
this.adapter.subscribeStates('pump.error');
|
|
33
|
+
|
|
34
|
+
// Aktuellen Pumpenstatus laden
|
|
35
|
+
this.adapter
|
|
36
|
+
.getStateAsync('pump.pump_switch')
|
|
37
|
+
.then(s => {
|
|
38
|
+
this.pumpOn = !!s?.val;
|
|
39
|
+
})
|
|
40
|
+
.catch(() => {
|
|
41
|
+
this.pumpOn = false;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Beim Start initiale Updates
|
|
45
|
+
this.updateSummary().catch(err =>
|
|
46
|
+
this.adapter.log.warn(`[statusHelper] Initial-Update fehlgeschlagen: ${err.message}`),
|
|
47
|
+
);
|
|
48
|
+
this.updateSystemStatus().catch(err =>
|
|
49
|
+
this.adapter.log.warn(`[statusHelper] Initial-Systemstatus fehlgeschlagen: ${err.message}`),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Mitternacht-Reset einplanen
|
|
53
|
+
this.scheduleMidnightReset();
|
|
54
|
+
|
|
55
|
+
this.adapter.log.info('[statusHelper] initialisiert');
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async handleStateChange(id, state) {
|
|
59
|
+
if (!state || state.ack !== true) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Pumpenstart/-stop
|
|
64
|
+
if (id.endsWith('pump.pump_switch')) {
|
|
65
|
+
const nowOn = !!state.val;
|
|
66
|
+
|
|
67
|
+
// steigende Flanke
|
|
68
|
+
if (nowOn && this.pumpOn !== true) {
|
|
69
|
+
await this.adapter.setStateAsync('status.pump_last_start', {
|
|
70
|
+
val: new Date().toISOString(),
|
|
71
|
+
ack: true,
|
|
72
|
+
});
|
|
73
|
+
const currentCount = (await this.adapter.getStateAsync('status.pump_today_count'))?.val || 0;
|
|
74
|
+
await this.adapter.setStateAsync('status.pump_today_count', { val: currentCount + 1, ack: true });
|
|
75
|
+
await this.adapter.setStateAsync('status.pump_was_on_today', { val: true, ack: true });
|
|
76
|
+
this.pumpOn = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// fallende Flanke
|
|
80
|
+
if (!nowOn && this.pumpOn !== false) {
|
|
81
|
+
await this.adapter.setStateAsync('status.pump_last_stop', {
|
|
82
|
+
val: new Date().toISOString(),
|
|
83
|
+
ack: true,
|
|
84
|
+
});
|
|
85
|
+
this.pumpOn = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// System-Warnungen
|
|
90
|
+
if (id.endsWith('solar.collector_warning') || id.endsWith('pump.error')) {
|
|
91
|
+
await this.updateSystemStatus();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Allgemeines Update
|
|
95
|
+
await this.updateSummary();
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async updateSummary() {
|
|
99
|
+
try {
|
|
100
|
+
// Werte laden
|
|
101
|
+
const pumpStatus = (await this.adapter.getStateAsync('pump.status'))?.val || 'unbekannt';
|
|
102
|
+
const pumpMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'unknown';
|
|
103
|
+
|
|
104
|
+
const poolTemp = (await this.adapter.getStateAsync('temperature.surface.current'))?.val;
|
|
105
|
+
const collectorTemp = (await this.adapter.getStateAsync('temperature.collector.current'))?.val;
|
|
106
|
+
const outsideTemp = (await this.adapter.getStateAsync('temperature.outside.current'))?.val;
|
|
107
|
+
|
|
108
|
+
const runtimeToday = (await this.adapter.getStateAsync('runtime.today'))?.val || 0;
|
|
109
|
+
const dailyTotal = (await this.adapter.getStateAsync('circulation.daily_total'))?.val || 0;
|
|
110
|
+
const dailyRequired = (await this.adapter.getStateAsync('circulation.daily_required'))?.val || 0;
|
|
111
|
+
|
|
112
|
+
// Laufzeit formatieren
|
|
113
|
+
const h = Math.floor(runtimeToday / 3600);
|
|
114
|
+
const m = Math.floor((runtimeToday % 3600) / 60);
|
|
115
|
+
const runtimeFormatted = `${h}h ${m}m`;
|
|
116
|
+
|
|
117
|
+
// Umwälzungsquote
|
|
118
|
+
let circulationPct = 0;
|
|
119
|
+
if (dailyRequired > 0) {
|
|
120
|
+
circulationPct = Math.round((dailyTotal / dailyRequired) * 100);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Text bauen
|
|
124
|
+
let text = `Pumpe: ${pumpStatus}`;
|
|
125
|
+
if (pumpMode && pumpMode !== 'unknown') {
|
|
126
|
+
text += ` (Modus: ${pumpMode})`;
|
|
127
|
+
}
|
|
128
|
+
if (poolTemp != null) {
|
|
129
|
+
text += `. Pool: ${poolTemp.toFixed(1)} °C`;
|
|
130
|
+
}
|
|
131
|
+
if (collectorTemp != null) {
|
|
132
|
+
text += `, Kollektor: ${collectorTemp.toFixed(1)} °C`;
|
|
133
|
+
}
|
|
134
|
+
if (outsideTemp != null) {
|
|
135
|
+
text += `, Außentemperatur: ${outsideTemp.toFixed(1)} °C`;
|
|
136
|
+
}
|
|
137
|
+
text += `. Tageslaufzeit: ${runtimeFormatted} (${circulationPct}% der Soll-Umwälzung).`;
|
|
138
|
+
|
|
139
|
+
// In States schreiben
|
|
140
|
+
await this.adapter.setStateAsync('status.summary', { val: text, ack: true });
|
|
141
|
+
await this.adapter.setStateAsync('status.last_summary_update', {
|
|
142
|
+
val: new Date().toISOString(),
|
|
143
|
+
ack: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// JSON-Übersicht bauen
|
|
147
|
+
const json = {
|
|
148
|
+
pump: pumpStatus,
|
|
149
|
+
mode: pumpMode,
|
|
150
|
+
pool: poolTemp,
|
|
151
|
+
collector: collectorTemp,
|
|
152
|
+
outside: outsideTemp,
|
|
153
|
+
runtime_today: runtimeToday,
|
|
154
|
+
runtime_formatted: runtimeFormatted,
|
|
155
|
+
circulation_pct: circulationPct,
|
|
156
|
+
};
|
|
157
|
+
await this.adapter.setStateAsync('status.overview_json', {
|
|
158
|
+
val: JSON.stringify(json),
|
|
159
|
+
ack: true,
|
|
160
|
+
});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
this.adapter.log.warn(`[statusHelper] Fehler beim Update: ${err.message}`);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async updateSystemStatus() {
|
|
167
|
+
try {
|
|
168
|
+
const pumpError = (await this.adapter.getStateAsync('pump.error'))?.val;
|
|
169
|
+
const collectorWarning = (await this.adapter.getStateAsync('solar.collector_warning'))?.val;
|
|
170
|
+
|
|
171
|
+
let warningActive = false;
|
|
172
|
+
let warningText = '';
|
|
173
|
+
|
|
174
|
+
if (pumpError) {
|
|
175
|
+
warningActive = true;
|
|
176
|
+
warningText += 'Pumpenfehler ';
|
|
177
|
+
}
|
|
178
|
+
if (collectorWarning) {
|
|
179
|
+
warningActive = true;
|
|
180
|
+
warningText += 'Kollektorwarnung ';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await this.adapter.setStateAsync('status.system_warning', { val: warningActive, ack: true });
|
|
184
|
+
await this.adapter.setStateAsync('status.system_warning_text', { val: warningText.trim(), ack: true });
|
|
185
|
+
await this.adapter.setStateAsync('status.system_ok', { val: !warningActive, ack: true });
|
|
186
|
+
} catch (err) {
|
|
187
|
+
this.adapter.log.warn(`[statusHelper] Fehler beim Systemstatus: ${err.message}`);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
scheduleMidnightReset() {
|
|
192
|
+
if (this.midnightTimer) {
|
|
193
|
+
clearTimeout(this.midnightTimer);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const now = new Date();
|
|
197
|
+
const nextMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 5, 0);
|
|
198
|
+
const msToMidnight = nextMidnight.getTime() - now.getTime();
|
|
199
|
+
|
|
200
|
+
this.midnightTimer = setTimeout(async () => {
|
|
201
|
+
await this.doMidnightReset();
|
|
202
|
+
this.scheduleMidnightReset(); // neu einplanen
|
|
203
|
+
}, msToMidnight);
|
|
204
|
+
|
|
205
|
+
this.adapter.log.debug(`[statusHelper] Tagesreset geplant in ${Math.round(msToMidnight / 1000)}s`);
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
async doMidnightReset() {
|
|
209
|
+
try {
|
|
210
|
+
await this.adapter.setStateAsync('status.pump_today_count', { val: 0, ack: true });
|
|
211
|
+
await this.adapter.setStateAsync('status.pump_was_on_today', { val: false, ack: true });
|
|
212
|
+
this.adapter.log.info('[statusHelper] Tagesreset durchgeführt');
|
|
213
|
+
} catch (err) {
|
|
214
|
+
this.adapter.log.warn(`[statusHelper] Fehler beim Tagesreset: ${err.message}`);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
cleanup() {
|
|
219
|
+
if (this.midnightTimer) {
|
|
220
|
+
clearTimeout(this.midnightTimer);
|
|
221
|
+
this.midnightTimer = null;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
module.exports = statusHelper;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Legt alle States für Status-Übersichten an:
|
|
5
|
+
* - status.summary (string, Textzusammenfassung)
|
|
6
|
+
* - status.overview_json (string, JSON-Zusammenfassung)
|
|
7
|
+
* - status.last_summary_update (string, Zeitstempel)
|
|
8
|
+
* - status.pump_last_start (string, Zeitstempel)
|
|
9
|
+
* - status.pump_last_stop (string, Zeitstempel)
|
|
10
|
+
* - status.pump_was_on_today (boolean)
|
|
11
|
+
* - status.pump_today_count (number)
|
|
12
|
+
* - status.system_ok (boolean)
|
|
13
|
+
* - status.system_warning (boolean)
|
|
14
|
+
* - status.system_warning_text (string)
|
|
15
|
+
* - status.season_active (boolean)
|
|
16
|
+
*
|
|
17
|
+
* @param {import("iobroker").Adapter} adapter - ioBroker Adapter-Instanz
|
|
18
|
+
*/
|
|
19
|
+
async function createStatusStates(adapter) {
|
|
20
|
+
// Root-Kanal "status"
|
|
21
|
+
await adapter.setObjectNotExistsAsync('status', {
|
|
22
|
+
type: 'channel',
|
|
23
|
+
common: { name: 'Statusübersicht' },
|
|
24
|
+
native: {},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Zusammenfassung als Text
|
|
28
|
+
await adapter.setObjectNotExistsAsync('status.summary', {
|
|
29
|
+
type: 'state',
|
|
30
|
+
common: {
|
|
31
|
+
name: 'Zusammenfassung als Text',
|
|
32
|
+
type: 'string',
|
|
33
|
+
role: 'text',
|
|
34
|
+
read: true,
|
|
35
|
+
write: false,
|
|
36
|
+
// bewusst kein persist: true, da nur Live-Daten
|
|
37
|
+
},
|
|
38
|
+
native: {},
|
|
39
|
+
});
|
|
40
|
+
await adapter.setStateAsync('status.summary', { val: '', ack: true });
|
|
41
|
+
|
|
42
|
+
// JSON-Zusammenfassung
|
|
43
|
+
await adapter.setObjectNotExistsAsync('status.overview_json', {
|
|
44
|
+
type: 'state',
|
|
45
|
+
common: {
|
|
46
|
+
name: 'Übersicht als JSON',
|
|
47
|
+
type: 'string',
|
|
48
|
+
role: 'json',
|
|
49
|
+
read: true,
|
|
50
|
+
write: false,
|
|
51
|
+
},
|
|
52
|
+
native: {},
|
|
53
|
+
});
|
|
54
|
+
await adapter.setStateAsync('status.overview_json', { val: '{}', ack: true });
|
|
55
|
+
|
|
56
|
+
// Letzte Aktualisierung der Summary
|
|
57
|
+
await adapter.setObjectNotExistsAsync('status.last_summary_update', {
|
|
58
|
+
type: 'state',
|
|
59
|
+
common: {
|
|
60
|
+
name: 'Letzte Aktualisierung der Zusammenfassung',
|
|
61
|
+
type: 'string',
|
|
62
|
+
role: 'date',
|
|
63
|
+
read: true,
|
|
64
|
+
write: false,
|
|
65
|
+
},
|
|
66
|
+
native: {},
|
|
67
|
+
});
|
|
68
|
+
await adapter.setStateAsync('status.last_summary_update', { val: '', ack: true });
|
|
69
|
+
|
|
70
|
+
// Pumpen-Status: letzter Start
|
|
71
|
+
await adapter.setObjectNotExistsAsync('status.pump_last_start', {
|
|
72
|
+
type: 'state',
|
|
73
|
+
common: {
|
|
74
|
+
name: 'Letzter Pumpenstart',
|
|
75
|
+
type: 'string',
|
|
76
|
+
role: 'date',
|
|
77
|
+
read: true,
|
|
78
|
+
write: false,
|
|
79
|
+
},
|
|
80
|
+
native: {},
|
|
81
|
+
});
|
|
82
|
+
await adapter.setStateAsync('status.pump_last_start', { val: '', ack: true });
|
|
83
|
+
|
|
84
|
+
// Pumpen-Status: letzter Stopp
|
|
85
|
+
await adapter.setObjectNotExistsAsync('status.pump_last_stop', {
|
|
86
|
+
type: 'state',
|
|
87
|
+
common: {
|
|
88
|
+
name: 'Letztes Pumpenende',
|
|
89
|
+
type: 'string',
|
|
90
|
+
role: 'date',
|
|
91
|
+
read: true,
|
|
92
|
+
write: false,
|
|
93
|
+
},
|
|
94
|
+
native: {},
|
|
95
|
+
});
|
|
96
|
+
await adapter.setStateAsync('status.pump_last_stop', { val: '', ack: true });
|
|
97
|
+
|
|
98
|
+
// Pumpen-Status: heute eingeschaltet
|
|
99
|
+
await adapter.setObjectNotExistsAsync('status.pump_was_on_today', {
|
|
100
|
+
type: 'state',
|
|
101
|
+
common: {
|
|
102
|
+
name: 'Pumpe war heute eingeschaltet',
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
role: 'indicator',
|
|
105
|
+
read: true,
|
|
106
|
+
write: false,
|
|
107
|
+
},
|
|
108
|
+
native: {},
|
|
109
|
+
});
|
|
110
|
+
await adapter.setStateAsync('status.pump_was_on_today', { val: false, ack: true });
|
|
111
|
+
|
|
112
|
+
// Pumpen-Status: Anzahl Starts heute
|
|
113
|
+
await adapter.setObjectNotExistsAsync('status.pump_today_count', {
|
|
114
|
+
type: 'state',
|
|
115
|
+
common: {
|
|
116
|
+
name: 'Pumpenstarts heute',
|
|
117
|
+
type: 'number',
|
|
118
|
+
role: 'value',
|
|
119
|
+
read: true,
|
|
120
|
+
write: false,
|
|
121
|
+
},
|
|
122
|
+
native: {},
|
|
123
|
+
});
|
|
124
|
+
await adapter.setStateAsync('status.pump_today_count', { val: 0, ack: true });
|
|
125
|
+
|
|
126
|
+
// Systemstatus: OK
|
|
127
|
+
await adapter.setObjectNotExistsAsync('status.system_ok', {
|
|
128
|
+
type: 'state',
|
|
129
|
+
common: {
|
|
130
|
+
name: 'System OK',
|
|
131
|
+
type: 'boolean',
|
|
132
|
+
role: 'indicator',
|
|
133
|
+
read: true,
|
|
134
|
+
write: false,
|
|
135
|
+
},
|
|
136
|
+
native: {},
|
|
137
|
+
});
|
|
138
|
+
await adapter.setStateAsync('status.system_ok', { val: true, ack: true });
|
|
139
|
+
|
|
140
|
+
// Systemstatus: Warnung aktiv
|
|
141
|
+
await adapter.setObjectNotExistsAsync('status.system_warning', {
|
|
142
|
+
type: 'state',
|
|
143
|
+
common: {
|
|
144
|
+
name: 'System-Warnung aktiv',
|
|
145
|
+
type: 'boolean',
|
|
146
|
+
role: 'indicator',
|
|
147
|
+
read: true,
|
|
148
|
+
write: false,
|
|
149
|
+
},
|
|
150
|
+
native: {},
|
|
151
|
+
});
|
|
152
|
+
await adapter.setStateAsync('status.system_warning', { val: false, ack: true });
|
|
153
|
+
|
|
154
|
+
// Systemstatus: Warnungstext
|
|
155
|
+
await adapter.setObjectNotExistsAsync('status.system_warning_text', {
|
|
156
|
+
type: 'state',
|
|
157
|
+
common: {
|
|
158
|
+
name: 'Beschreibung der Systemwarnung',
|
|
159
|
+
type: 'string',
|
|
160
|
+
role: 'text',
|
|
161
|
+
read: true,
|
|
162
|
+
write: false,
|
|
163
|
+
},
|
|
164
|
+
native: {},
|
|
165
|
+
});
|
|
166
|
+
await adapter.setStateAsync('status.system_warning_text', { val: '', ack: true });
|
|
167
|
+
|
|
168
|
+
// Saisonstatus
|
|
169
|
+
await adapter.setObjectNotExistsAsync('status.season_active', {
|
|
170
|
+
type: 'state',
|
|
171
|
+
common: {
|
|
172
|
+
name: 'Poolsaison aktiv',
|
|
173
|
+
type: 'boolean',
|
|
174
|
+
role: 'switch',
|
|
175
|
+
read: true,
|
|
176
|
+
write: true,
|
|
177
|
+
},
|
|
178
|
+
native: {},
|
|
179
|
+
});
|
|
180
|
+
await adapter.setStateAsync('status.season_active', { val: false, ack: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
createStatusStates,
|
|
185
|
+
};
|
package/main.js
CHANGED
|
@@ -13,6 +13,7 @@ const speechHelper = require('./lib/helpers/speechHelper');
|
|
|
13
13
|
const consumptionHelper = require('./lib/helpers/consumptionHelper');
|
|
14
14
|
const solarHelper = require('./lib/helpers/solarHelper');
|
|
15
15
|
const frostHelper = require('./lib/helpers/frostHelper');
|
|
16
|
+
const statusHelper = require('./lib/helpers/statusHelper');
|
|
16
17
|
const { createTemperatureStates } = require('./lib/stateDefinitions/temperatureStates');
|
|
17
18
|
const { createPumpStates } = require('./lib/stateDefinitions/pumpStates');
|
|
18
19
|
const { createSolarStates } = require('./lib/stateDefinitions/solarStates');
|
|
@@ -21,6 +22,7 @@ const { createTimeStates } = require('./lib/stateDefinitions/timeStates');
|
|
|
21
22
|
const { createRuntimeStates } = require('./lib/stateDefinitions/runtimeStates');
|
|
22
23
|
const { createSpeechStates } = require('./lib/stateDefinitions/speechStates');
|
|
23
24
|
const { createConsumptionStates } = require('./lib/stateDefinitions/consumptionStates');
|
|
25
|
+
const { createStatusStates } = require('./lib/stateDefinitions/statusStates');
|
|
24
26
|
|
|
25
27
|
class Poolcontrol extends utils.Adapter {
|
|
26
28
|
constructor(options) {
|
|
@@ -60,6 +62,15 @@ class Poolcontrol extends utils.Adapter {
|
|
|
60
62
|
// --- Verbrauch & Kosten ---
|
|
61
63
|
await createConsumptionStates(this);
|
|
62
64
|
|
|
65
|
+
// --- Statusübersicht ---
|
|
66
|
+
await createStatusStates(this);
|
|
67
|
+
|
|
68
|
+
// Saisonstatus aus Config übernehmen
|
|
69
|
+
await this.setStateAsync('status.season_active', {
|
|
70
|
+
val: this.config.season_active,
|
|
71
|
+
ack: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
63
74
|
// --- Helper starten ---
|
|
64
75
|
temperatureHelper.init(this);
|
|
65
76
|
timeHelper.init(this);
|
|
@@ -69,6 +80,7 @@ class Poolcontrol extends utils.Adapter {
|
|
|
69
80
|
consumptionHelper.init(this);
|
|
70
81
|
solarHelper.init(this);
|
|
71
82
|
frostHelper.init(this);
|
|
83
|
+
statusHelper.init(this);
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
onUnload(callback) {
|
|
@@ -97,6 +109,9 @@ class Poolcontrol extends utils.Adapter {
|
|
|
97
109
|
if (frostHelper.cleanup) {
|
|
98
110
|
frostHelper.cleanup();
|
|
99
111
|
}
|
|
112
|
+
if (statusHelper.cleanup) {
|
|
113
|
+
statusHelper.cleanup();
|
|
114
|
+
}
|
|
100
115
|
} catch (e) {
|
|
101
116
|
this.log.warn(`[onUnload] Fehler beim Cleanup: ${e.message}`);
|
|
102
117
|
} finally {
|
|
@@ -104,12 +119,20 @@ class Poolcontrol extends utils.Adapter {
|
|
|
104
119
|
}
|
|
105
120
|
}
|
|
106
121
|
|
|
107
|
-
onStateChange(id, state) {
|
|
122
|
+
async onStateChange(id, state) {
|
|
108
123
|
if (state) {
|
|
109
124
|
this.log.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
110
125
|
} else {
|
|
111
126
|
this.log.debug(`state ${id} deleted`);
|
|
112
127
|
}
|
|
128
|
+
|
|
129
|
+
// Saisonstatus manuell ändern (z.B. über VIS)
|
|
130
|
+
if (id.endsWith('status.season_active') && state && state.ack === false) {
|
|
131
|
+
this.log.info(`[main] Saisonstatus geändert: ${state.val}`);
|
|
132
|
+
await this.setStateAsync('status.season_active', { val: state.val, ack: true });
|
|
133
|
+
return; // danach keine Helper mehr aufrufen
|
|
134
|
+
}
|
|
135
|
+
|
|
113
136
|
try {
|
|
114
137
|
temperatureHelper.handleStateChange(id, state);
|
|
115
138
|
} catch (e) {
|
|
@@ -135,6 +158,11 @@ class Poolcontrol extends utils.Adapter {
|
|
|
135
158
|
} catch (e) {
|
|
136
159
|
this.log.warn(`[consumptionHelper] Fehler in handleStateChange: ${e.message}`);
|
|
137
160
|
}
|
|
161
|
+
try {
|
|
162
|
+
statusHelper.handleStateChange(id, state);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
this.log.warn(`[statusHelper] Fehler in handleStateChange: ${e.message}`);
|
|
165
|
+
}
|
|
138
166
|
}
|
|
139
167
|
}
|
|
140
168
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.poolcontrol",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
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",
|
|
@@ -27,14 +27,9 @@
|
|
|
27
27
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
28
28
|
"@iobroker/eslint-config": "^2.2.0",
|
|
29
29
|
"@iobroker/testing": "^5.1.1",
|
|
30
|
-
"chai": "^4.5.0",
|
|
31
|
-
"chai-as-promised": "^7.1.2",
|
|
32
30
|
"eslint": "^9.36.0",
|
|
33
|
-
"mocha": "^11.7.2",
|
|
34
31
|
"prettier": "^3.6.2",
|
|
35
|
-
"proxyquire": "^2.1.3"
|
|
36
|
-
"sinon": "^21.0.0",
|
|
37
|
-
"sinon-chai": "^3.7.0"
|
|
32
|
+
"proxyquire": "^2.1.3"
|
|
38
33
|
},
|
|
39
34
|
"main": "main.js",
|
|
40
35
|
"files": [
|