iobroker.poolcontrol 0.8.2 → 0.9.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/README.md +39 -11
- package/admin/jsonConfig.json +86 -2
- package/io-package.json +14 -14
- package/lib/helpers/heatHelper.js +450 -0
- package/lib/helpers/pumpHelper.js +9 -0
- package/lib/stateDefinitions/heatStates.js +261 -0
- package/main.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,29 @@ Er ermöglicht die Automatisierung von Pumpen-, Temperatur- und Solarsteuerung s
|
|
|
35
35
|
- Kollektor-Warnung (mit automatischer Rücksetzung bei 10 % unter der Schwelle)
|
|
36
36
|
- Optionale Sprachausgabe bei Warnung
|
|
37
37
|
|
|
38
|
+
- **Heizungs- / Wärmepumpensteuerung (neu, Testphase)**
|
|
39
|
+
- Automatische Steuerung von Heizstab oder Wärmepumpe auf Basis der Pooltemperatur
|
|
40
|
+
- Zieltemperatur und maximale Sicherheitstemperatur konfigurierbar
|
|
41
|
+
- Aktiv nur bei:
|
|
42
|
+
- aktiver Poolsaison
|
|
43
|
+
- Pumpenmodus **Automatik**
|
|
44
|
+
- nicht aktivem Wartungsmodus
|
|
45
|
+
- Vorranglogik:
|
|
46
|
+
- Wartungsmodus blockiert die Heizungssteuerung vollständig
|
|
47
|
+
- Heizung greift nicht in manuelle oder zeitgesteuerte Pumpenmodi ein
|
|
48
|
+
- Pumpen-Nachlaufzeit nach Heizende konfigurierbar
|
|
49
|
+
- Ownership-Schutz:
|
|
50
|
+
- Die Pumpe wird nur dann ausgeschaltet, wenn sie zuvor vom heatHelper selbst eingeschaltet wurde
|
|
51
|
+
- Unterstützt:
|
|
52
|
+
- schaltbare Steckdosen **oder**
|
|
53
|
+
- boolesche Steuer-States externer Heizungen
|
|
54
|
+
- Interner Status- und Diagnosebereich unter `heat.*`
|
|
55
|
+
- Rein steuernd, **keine Chemie- oder Solarlogik**
|
|
56
|
+
|
|
57
|
+
**Hinweis:**
|
|
58
|
+
Diese Funktion befindet sich aktuell in einer **Testphase**.
|
|
59
|
+
Die Logik ist vollständig implementiert, sollte aber zunächst nur mit interessierten Test-Usern eingesetzt werden.
|
|
60
|
+
|
|
38
61
|
- **Photovoltaiksteuerung (seit v0.6.0)**
|
|
39
62
|
- Automatische Pumpensteuerung auf Basis von PV-Erzeugung und Hausverbrauchs
|
|
40
63
|
- Einschaltlogik: Überschuss ≥ (Pumpen-Nennleistung + Sicherheitsaufschlag)
|
|
@@ -151,8 +174,6 @@ Die Konfiguration erfolgt über Tabs im Admin-Interface:
|
|
|
151
174
|
- Erweiterte PV- und Solar-Effizienzanalyse (COP-Berechnung, Tagesnutzen, Wetterintegration)
|
|
152
175
|
- Statistik-Exportfunktion (CSV/Excel)
|
|
153
176
|
- Diagnostic-Helper zur automatischen Systemprüfung
|
|
154
|
-
- Erweiterung der Heizungs-/Wärmepumpenlogik (`heatHelper`)
|
|
155
|
-
- Zweite Pumpe (z. B. Wärmetauscher oder Wärmepumpe)
|
|
156
177
|
- Eigene Widgets für VIS/VIS2 (grafische Pool- und Solarvisualisierung)
|
|
157
178
|
- Steuerung von Poolbeleuchtung, Ventilen und Gegenstromanlagen
|
|
158
179
|
- Integration zusätzlicher Sensorboxen (z. B. TempBox, PressureBox, LevelBox)
|
|
@@ -176,6 +197,22 @@ Neue Funktionen werden regelmäßig ergänzt – bitte den Changelog beachten.
|
|
|
176
197
|
## Changelog
|
|
177
198
|
### **WORK IN PROGRESS**
|
|
178
199
|
|
|
200
|
+
## v0.9.0 (WORK IN PROGRESS)
|
|
201
|
+
- Einführung der Heizungs- / Wärmepumpensteuerung (`heatHelper`)
|
|
202
|
+
- Automatische Heizanforderung basierend auf Pooltemperatur
|
|
203
|
+
- Ziel- und Maximaltemperatur konfigurierbar
|
|
204
|
+
- Unterstützung von:
|
|
205
|
+
- schaltbaren Steckdosen
|
|
206
|
+
- booleschen Steuer-States
|
|
207
|
+
- Pumpen-Nachlaufzeit nach Heizende
|
|
208
|
+
- Vorrangsystem:
|
|
209
|
+
- Wartungsmodus blockiert Heizungssteuerung
|
|
210
|
+
- Aktiv nur im Automatikmodus
|
|
211
|
+
- Berücksichtigung des Saisonstatus
|
|
212
|
+
- Ownership-Schutz für Pumpensteuerung
|
|
213
|
+
- Neuer interner State `heat.heating_request` für externe Auswertung
|
|
214
|
+
|
|
215
|
+
|
|
179
216
|
## v0.8.2 (2025-12-25)
|
|
180
217
|
- Neues KI-Modul **Chemie-Hilfe** (`aiChemistryHelpHelper`)
|
|
181
218
|
- Rein informatives Hilfesystem zur Poolwasserchemie
|
|
@@ -242,17 +279,8 @@ Neue Funktionen werden regelmäßig ergänzt – bitte den Changelog beachten.
|
|
|
242
279
|
- Telegram-Benutzerwahl hinzugefügt
|
|
243
280
|
|
|
244
281
|
## v0.5.2 (2025-10-30)
|
|
245
|
-
- Erweitertes Helper-Vorrangssystem: Konflikte zwischen Zeit- und Solarsteuerung behoben
|
|
246
|
-
- Frostschutz pausiert während Zeitfenster. Nun stabiles Pumpenverhalten und Verbesserte
|
|
247
|
-
Koordination zwischen den Helpern
|
|
248
282
|
|
|
249
283
|
## v0.5.0 (2025-10-28)
|
|
250
|
-
- Erweiterung der Temperaturstatistik um Wochen- und Monatsauswertung
|
|
251
|
-
(`analytics.statistics.temperature.week` / `.month`)
|
|
252
|
-
- Eigenständige, eventbasierte Helper für Woche und Monat
|
|
253
|
-
- Persistente Datenpunkte mit automatischen JSON- und HTML-Zusammenfassungen
|
|
254
|
-
- Vorbereitung für zukünftige Erweiterungen (Saison- und Jahresstatistik)
|
|
255
|
-
|
|
256
284
|
|
|
257
285
|
### **0.4.0 (26.10.2025)**
|
|
258
286
|
|
package/admin/jsonConfig.json
CHANGED
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
},
|
|
422
422
|
"solar": {
|
|
423
423
|
"type": "panel",
|
|
424
|
-
"label": "
|
|
424
|
+
"label": "Solar/Heizungsverwaltung",
|
|
425
425
|
"items": {
|
|
426
426
|
"solar_control_active_header": {
|
|
427
427
|
"type": "header",
|
|
@@ -539,7 +539,91 @@
|
|
|
539
539
|
"md": 12,
|
|
540
540
|
"lg": 12,
|
|
541
541
|
"xl": 12
|
|
542
|
-
}
|
|
542
|
+
},
|
|
543
|
+
"heat_control_header": {
|
|
544
|
+
"type": "header",
|
|
545
|
+
"text": "Heizung / Wärmepumpe",
|
|
546
|
+
"size": 4,
|
|
547
|
+
"newLine": true,
|
|
548
|
+
"xs": 12,
|
|
549
|
+
"sm": 12,
|
|
550
|
+
"md": 12,
|
|
551
|
+
"lg": 12,
|
|
552
|
+
"xl": 12
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
"heat_control_active": {
|
|
556
|
+
"type": "checkbox",
|
|
557
|
+
"label": "Heizungssteuerung aktivieren",
|
|
558
|
+
"default": false,
|
|
559
|
+
"xs": 12,
|
|
560
|
+
"sm": 3,
|
|
561
|
+
"md": 3,
|
|
562
|
+
"lg": 3,
|
|
563
|
+
"xl": 3,
|
|
564
|
+
"newLine": true
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
"heat_control_type": {
|
|
568
|
+
"type": "select",
|
|
569
|
+
"label": "Art der Heizungssteuerung",
|
|
570
|
+
"options": [
|
|
571
|
+
{ "label": "Messsteckdose schalten (Ein / Aus)", "value": "socket" },
|
|
572
|
+
{ "label": "Logischer Steuer-State (true / false)", "value": "boolean" }
|
|
573
|
+
],
|
|
574
|
+
"default": "socket",
|
|
575
|
+
"xs": 12,
|
|
576
|
+
"sm": 4,
|
|
577
|
+
"md": 4,
|
|
578
|
+
"lg": 4,
|
|
579
|
+
"xl": 4,
|
|
580
|
+
"newLine": true
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
"heat_control_object_id": {
|
|
584
|
+
"type": "objectId",
|
|
585
|
+
"label": "Objekt-ID Heizung / Wärmepumpe",
|
|
586
|
+
"default": "",
|
|
587
|
+
"xs": 12,
|
|
588
|
+
"sm": 6,
|
|
589
|
+
"md": 6,
|
|
590
|
+
"lg": 6,
|
|
591
|
+
"xl": 6
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
"heat_temp_target": {
|
|
595
|
+
"type": "number",
|
|
596
|
+
"label": "Zieltemperatur Pool (°C)",
|
|
597
|
+
"default": 26,
|
|
598
|
+
"xs": 12,
|
|
599
|
+
"sm": 3,
|
|
600
|
+
"md": 3,
|
|
601
|
+
"lg": 3,
|
|
602
|
+
"xl": 3,
|
|
603
|
+
"newLine": true
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
"heat_temp_max": {
|
|
607
|
+
"type": "number",
|
|
608
|
+
"label": "Maximale Pooltemperatur (°C – Sicherheitsabschaltung)",
|
|
609
|
+
"default": 30,
|
|
610
|
+
"xs": 12,
|
|
611
|
+
"sm": 3,
|
|
612
|
+
"md": 3,
|
|
613
|
+
"lg": 3,
|
|
614
|
+
"xl": 3
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
"heat_control_hint": {
|
|
618
|
+
"type": "staticText",
|
|
619
|
+
"text": "Hinweis: Die Heizungssteuerung arbeitet temperaturgeführt und nur, wenn die Poolsaison aktiv ist. Je nach ausgewählter Steuerungsart wird entweder eine Messsteckdose geschaltet oder ein logischer Steuer-State (true/false) gesetzt.",
|
|
620
|
+
"newLine": true,
|
|
621
|
+
"xs": 12,
|
|
622
|
+
"sm": 12,
|
|
623
|
+
"md": 12,
|
|
624
|
+
"lg": 12,
|
|
625
|
+
"xl": 12
|
|
626
|
+
}
|
|
543
627
|
}
|
|
544
628
|
},
|
|
545
629
|
"timecontrol": {
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.9.0": {
|
|
7
|
+
"en": "New heating / heat pump control (heatHelper). Automatic control based on pool temperature with configurable target and maximum temperature, pump after-run time, and full priority handling (e.g. maintenance mode). Works only in automatic pump mode and respects season status. Supports both switchable sockets and boolean control states. Includes ownership protection to avoid interfering with other pump controllers.",
|
|
8
|
+
"de": "Neue Heizungs- / Wärmepumpensteuerung (heatHelper). Automatische Regelung basierend auf der Pooltemperatur mit konfigurierbarer Ziel- und Maximaltemperatur, Pumpen-Nachlaufzeit sowie vollständiger Vorranglogik (z. B. Wartungsmodus). Arbeitet ausschließlich im Automatikmodus der Pumpe und berücksichtigt den Saisonstatus. Unterstützt schaltbare Steckdosen und boolesche Steuer-States. Enthält Ownership-Schutz, um Konflikte mit anderen Pumpensteuerungen zu vermeiden.",
|
|
9
|
+
"ru": "Новая система управления отоплением / тепловым насосом (heatHelper). Автоматическое управление на основе температуры воды с настраиваемой целевой и максимальной температурой, временем добега насоса и полной системой приоритетов (например, режим обслуживания). Работает только в автоматическом режиме насоса и учитывает сезонный статус. Поддерживает как переключаемые розетки, так и булевы управляющие состояния. Включает защиту владения для предотвращения конфликтов с другими контроллерами насоса.",
|
|
10
|
+
"pt": "Novo controlo de aquecimento / bomba de calor (heatHelper). Controlo automático baseado na temperatura da piscina com temperatura alvo e máxima configuráveis, tempo de pós-funcionamento da bomba e lógica completa de prioridades (por exemplo, modo de manutenção). Funciona apenas no modo automático da bomba e respeita o estado da época. Suporta tomadas comutáveis e estados de controlo booleanos. Inclui proteção de propriedade para evitar conflitos com outros controladores da bomba.",
|
|
11
|
+
"nl": "Nieuwe verwarmings- / warmtepompregeling (heatHelper). Automatische regeling op basis van de zwembadtemperatuur met instelbare doel- en maximumtemperatuur, pomp-nalooptijd en volledige prioriteitslogica (bijv. onderhoudsmodus). Werkt uitsluitend in de automatische pompmodus en houdt rekening met de seizoensstatus. Ondersteunt zowel schakelbare stopcontacten als booleaanse besturingsstates. Bevat eigendomsbescherming om conflicten met andere pompcontrollers te voorkomen.",
|
|
12
|
+
"fr": "Nouveau contrôle de chauffage / pompe à chaleur (heatHelper). Régulation automatique basée sur la température de la piscine avec températures cible et maximale configurables, temps de post-fonctionnement de la pompe et gestion complète des priorités (par ex. mode maintenance). Fonctionne uniquement en mode automatique de la pompe et respecte le statut de saison. Prend en charge les prises commutables et les états de commande booléens. Inclut une protection de propriété afin d’éviter les conflits avec d’autres contrôleurs de pompe.",
|
|
13
|
+
"it": "Nuovo controllo di riscaldamento / pompa di calore (heatHelper). Controllo automatico basato sulla temperatura della piscina con temperatura obiettivo e massima configurabili, tempo di post-funzionamento della pompa e gestione completa delle priorità (ad es. modalità manutenzione). Funziona solo in modalità automatica della pompa e tiene conto dello stato stagionale. Supporta prese commutabili e stati di controllo booleani. Include una protezione di proprietà per evitare conflitti con altri controllori della pompa.",
|
|
14
|
+
"es": "Nuevo control de calefacción / bomba de calor (heatHelper). Control automático basado en la temperatura de la piscina con temperatura objetivo y máxima configurables, tiempo de posfuncionamiento de la bomba y gestión completa de prioridades (p. ej., modo de mantenimiento). Funciona únicamente en modo automático de la bomba y respeta el estado de la temporada. Compatible con enchufes conmutables y estados de control booleanos. Incluye protección de propiedad para evitar conflictos con otros controladores de la bomba.",
|
|
15
|
+
"pl": "Nowy system sterowania ogrzewaniem / pompą ciepła (heatHelper). Automatyczne sterowanie na podstawie temperatury basenu z konfigurowalną temperaturą docelową i maksymalną, czasem dobiegu pompy oraz pełną obsługą priorytetów (np. tryb konserwacji). Działa wyłącznie w trybie automatycznym pompy i uwzględnia status sezonu. Obsługuje przełączalne gniazda oraz binarne stany sterujące. Zawiera ochronę własności, aby zapobiec konfliktom z innymi kontrolerami pompy.",
|
|
16
|
+
"uk": "Нова система керування опаленням / тепловим насосом (heatHelper). Автоматичне керування на основі температури басейну з налаштовуваною цільовою та максимальною температурою, часом післяроботи насоса та повною системою пріоритетів (наприклад, режим обслуговування). Працює лише в автоматичному режимі насоса та враховує сезонний статус. Підтримує комутовані розетки та булеві стани керування. Містить захист володіння для запобігання конфліктам з іншими контролерами насоса.",
|
|
17
|
+
"zh-cn": "新增加热 / 热泵控制功能(heatHelper)。基于泳池水温的自动控制,支持可配置的目标温度和最高温度、泵后运行时间以及完整的优先级处理(例如维护模式)。仅在泵的自动模式下工作,并考虑季节状态。支持可开关插座和布尔控制状态。包含所有权保护机制,避免与其他泵控制逻辑发生冲突。"
|
|
18
|
+
},
|
|
6
19
|
"0.8.2": {
|
|
7
20
|
"en": "New AI chemistry help module. Provides an interactive, purely informational help system for common pool water issues (e.g. pH too high/low, chlorine ineffective, green or cloudy water). Generates understandable explanations and solution directions without automatic dosing, product recommendations, device control or speech output.",
|
|
8
21
|
"de": "Neues KI-Chemie-Hilfesystem. Bietet eine interaktive, rein informative Unterstützung bei typischen Poolwasserproblemen (z. B. pH zu hoch/niedrig, Chlor wirkt nicht, grünes oder trübes Wasser). Erzeugt verständliche Ursachen- und Lösungshinweise ohne automatische Dosierung, Produktempfehlungen, Gerätesteuerung oder Sprachausgabe.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Dodano nowy system informacji: adapter zapisuje teraz sezonowe pozdrowienia oraz zainstalowaną wersję adaptera w stanach info.*. Zawiera automatyczne codzienne odświeżanie o 00:01 oraz pełne wyliczenie daty Wielkanocy.",
|
|
81
94
|
"uk": "Додано нову інформаційну систему: адаптер тепер записує сезонні привітання та встановлену версію адаптера в стани info.*. Містить щоденне автоматичне оновлення о 00:01 і повний розрахунок дати Великодня.",
|
|
82
95
|
"zh-cn": "新增信息系统:适配器现在会将季节性问候和已安装的适配器版本写入 info.* 状态。包括每天 00:01 的自动刷新和完整的复活节日期计算。"
|
|
83
|
-
},
|
|
84
|
-
"0.7.1": {
|
|
85
|
-
"en": "Fix for pressure sensor initialization: The adapter now correctly recognizes the configured sensor ID in pumpHelper4. Additionally improved handling of missing / invalid values.",
|
|
86
|
-
"de": "Fehlerbehebung bei der Drucksensor-Initialisierung: Der Adapter erkennt nun die konfigurierte Sensor-ID in pumpHelper4 korrekt. Außerdem wurde die Verarbeitung fehlender oder ungültiger Werte verbessert.",
|
|
87
|
-
"ru": "Исправление инициализации датчика давления: адаптер теперь корректно распознаёт настроенный идентификатор датчика в pumpHelper4. Улучшена обработка отсутствующих и недопустимых значений.",
|
|
88
|
-
"pt": "Correção na inicialização do sensor de pressão: o adaptador agora reconhece corretamente o ID configurado do sensor em pumpHelper4. Manipulação aprimorada de valores ausentes ou inválidos.",
|
|
89
|
-
"nl": "Foutoplossing voor de druksensor-initialisatie: de adapter herkent nu correct de geconfigureerde sensor-ID in pumpHelper4. Verbeterde verwerking van ontbrekende of ongeldige waarden.",
|
|
90
|
-
"fr": "Correction de l'initialisation du capteur de pression : l'adaptateur reconnaît désormais correctement l'ID du capteur configuré dans pumpHelper4. Amélioration du traitement des valeurs manquantes ou invalides.",
|
|
91
|
-
"it": "Correzione dell'inizializzazione del sensore di pressione: l'adattatore ora riconosce correttamente l'ID configurato del sensore in pumpHelper4. Migliorata anche la gestione dei valori mancanti o non validi.",
|
|
92
|
-
"es": "Corrección en la inicialización del sensor de presión: el adaptador ahora reconoce correctamente la ID del sensor configurado en pumpHelper4. Manejo mejorado de valores faltantes o inválidos.",
|
|
93
|
-
"pl": "Poprawka inicjalizacji czujnika ciśnienia: adapter poprawnie rozpoznaje teraz skonfigurowany identyfikator czujnika w pumpHelper4. Ulepszono również obsługę brakujących lub nieprawidłowych wartości.",
|
|
94
|
-
"uk": "Виправлення ініціалізації датчика тиску: адаптер тепер правильно розпізнає налаштований ID датчика в pumpHelper4. Покращено обробку відсутніх або некоректних значень.",
|
|
95
|
-
"zh-cn": "修复压力传感器初始化:适配器现在可以正确识别 pumpHelper4 中配置的传感器 ID,并改进了对缺失或无效数值的处理。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* heatHelper
|
|
5
|
+
* -------------------------------------------------------------
|
|
6
|
+
* - Steuert Heizung/Wärmepumpe basierend auf Pooltemperatur
|
|
7
|
+
* - Respektiert:
|
|
8
|
+
* - status.season_active
|
|
9
|
+
* - control.pump.maintenance_active (Vorrang / Block)
|
|
10
|
+
* - pump.mode (nur im Automatikbetrieb)
|
|
11
|
+
* - Kann entweder:
|
|
12
|
+
* - eine schaltbare Steckdose (socket) oder
|
|
13
|
+
* - einen bool Steuer-State (boolean) bedienen
|
|
14
|
+
* - Erzeugt zusätzlich ein internes Signal:
|
|
15
|
+
* - heat.heating_request (read-only) => kann extern ausgewertet werden
|
|
16
|
+
* - Pumpen-Nachlaufzeit (min) wird berücksichtigt
|
|
17
|
+
* - Ownership-Schutz: Pumpe wird nur ausgeschaltet, wenn heatHelper sie vorher selbst eingeschaltet hat
|
|
18
|
+
* -------------------------------------------------------------
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const heatHelper = {
|
|
22
|
+
adapter: null,
|
|
23
|
+
|
|
24
|
+
// dynamische Steuer-ID (foreign)
|
|
25
|
+
_heatControlForeignId: '',
|
|
26
|
+
_afterrunTimer: null,
|
|
27
|
+
|
|
28
|
+
// Ownership / Merker
|
|
29
|
+
_ownsPump: false,
|
|
30
|
+
_desiredHeat: null,
|
|
31
|
+
_lastEval: 0,
|
|
32
|
+
|
|
33
|
+
init(adapter) {
|
|
34
|
+
this.adapter = adapter;
|
|
35
|
+
|
|
36
|
+
// lokale States überwachen
|
|
37
|
+
this.adapter.subscribeStates('heat.control_active');
|
|
38
|
+
this.adapter.subscribeStates('heat.control_type');
|
|
39
|
+
this.adapter.subscribeStates('heat.control_object_id');
|
|
40
|
+
this.adapter.subscribeStates('heat.target_temperature');
|
|
41
|
+
this.adapter.subscribeStates('heat.max_temperature');
|
|
42
|
+
this.adapter.subscribeStates('heat.pump_afterrun_minutes');
|
|
43
|
+
|
|
44
|
+
// Abhängigkeiten
|
|
45
|
+
this.adapter.subscribeStates('status.season_active');
|
|
46
|
+
this.adapter.subscribeStates('pump.mode');
|
|
47
|
+
this.adapter.subscribeStates('pump.pump_switch');
|
|
48
|
+
this.adapter.subscribeStates('temperature.surface.current');
|
|
49
|
+
|
|
50
|
+
// Vorrangschaltung / Wartung
|
|
51
|
+
this.adapter.subscribeStates('control.pump.maintenance_active');
|
|
52
|
+
|
|
53
|
+
// ggf. vorhandene Foreign-ID abonnieren
|
|
54
|
+
this._refreshForeignSubscription().catch(err =>
|
|
55
|
+
this.adapter.log.warn(`[heatHelper] Foreign-Subscription Fehler: ${err.message}`),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
this._safeEvaluate('init');
|
|
59
|
+
this.adapter.log.info('[heatHelper] Initialisierung abgeschlossen.');
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async handleStateChange(id, state) {
|
|
63
|
+
if (!state) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Wenn die Ziel-Objekt-ID geändert wurde: Foreign subscription anpassen
|
|
69
|
+
if (id.endsWith('heat.control_object_id') || id.endsWith('heat.control_type')) {
|
|
70
|
+
await this._refreshForeignSubscription();
|
|
71
|
+
await this._safeEvaluate('control_target_changed');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Alles andere: neu bewerten
|
|
76
|
+
if (
|
|
77
|
+
id.endsWith('heat.control_active') ||
|
|
78
|
+
id.endsWith('heat.target_temperature') ||
|
|
79
|
+
id.endsWith('heat.max_temperature') ||
|
|
80
|
+
id.endsWith('heat.pump_afterrun_minutes') ||
|
|
81
|
+
id.endsWith('status.season_active') ||
|
|
82
|
+
id.endsWith('pump.mode') ||
|
|
83
|
+
id.endsWith('temperature.surface.current') ||
|
|
84
|
+
id.endsWith('control.pump.maintenance_active')
|
|
85
|
+
) {
|
|
86
|
+
await this._safeEvaluate('state_change');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Wenn jemand die Pumpe extern schaltet: Ownership ggf. zurücknehmen
|
|
91
|
+
if (id.endsWith('pump.pump_switch')) {
|
|
92
|
+
const pumpOn = !!state.val;
|
|
93
|
+
if (!pumpOn && this._ownsPump) {
|
|
94
|
+
// wenn Pumpe aus geht obwohl wir "ownen", Ownership verlieren
|
|
95
|
+
this._ownsPump = false;
|
|
96
|
+
}
|
|
97
|
+
await this._safeEvaluate('pump_switch_changed');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
this.adapter.log.warn(`[heatHelper] Fehler in handleStateChange: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// -------------------------------------------------------------
|
|
106
|
+
// Core
|
|
107
|
+
// -------------------------------------------------------------
|
|
108
|
+
async _evaluate(_sourceTag = '') {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
if (now - this._lastEval < 250) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this._lastEval = now;
|
|
114
|
+
|
|
115
|
+
const seasonActive = !!(await this.adapter.getStateAsync('status.season_active'))?.val;
|
|
116
|
+
const maintenanceActive = !!(await this.adapter.getStateAsync('control.pump.maintenance_active'))?.val;
|
|
117
|
+
|
|
118
|
+
const pumpMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'auto';
|
|
119
|
+
const heatEnabled = !!(await this.adapter.getStateAsync('heat.control_active'))?.val;
|
|
120
|
+
|
|
121
|
+
const poolTempRaw = (await this.adapter.getStateAsync('temperature.surface.current'))?.val;
|
|
122
|
+
const poolTemp = Number(poolTempRaw);
|
|
123
|
+
|
|
124
|
+
const targetTemp = Number((await this.adapter.getStateAsync('heat.target_temperature'))?.val ?? 26);
|
|
125
|
+
const maxTemp = Number((await this.adapter.getStateAsync('heat.max_temperature'))?.val ?? 30);
|
|
126
|
+
|
|
127
|
+
const afterrunMin = Math.max(
|
|
128
|
+
0,
|
|
129
|
+
Number((await this.adapter.getStateAsync('heat.pump_afterrun_minutes'))?.val ?? 0) || 0,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const controlType = (await this.adapter.getStateAsync('heat.control_type'))?.val || 'socket';
|
|
133
|
+
const controlObjectId = (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '';
|
|
134
|
+
|
|
135
|
+
// --- Hard conditions / Blocker ---
|
|
136
|
+
if (!seasonActive) {
|
|
137
|
+
return this._applyBlockedState('season_inactive', 'Poolsaison ist inaktiv', afterrunMin);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Vorrangschaltung: Wartung blockiert IMMER
|
|
141
|
+
if (maintenanceActive) {
|
|
142
|
+
return this._applyBlockedState(
|
|
143
|
+
'maintenance_active',
|
|
144
|
+
'Wartungsmodus aktiv (Control hat Vorrang)',
|
|
145
|
+
afterrunMin,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Heizungssteuerung deaktiviert
|
|
150
|
+
if (!heatEnabled) {
|
|
151
|
+
return this._applyOffState('heat_disabled', 'Heizungssteuerung deaktiviert', afterrunMin);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Pumpenmodus: nur Automatik
|
|
155
|
+
if (pumpMode !== 'auto') {
|
|
156
|
+
return this._applyBlockedState(
|
|
157
|
+
'mode_not_auto',
|
|
158
|
+
`Pumpenmodus ist '${pumpMode}' (Heizung arbeitet nur in Automatik)`,
|
|
159
|
+
afterrunMin,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Sensor plausibel?
|
|
164
|
+
if (!Number.isFinite(poolTemp)) {
|
|
165
|
+
return this._applyBlockedState(
|
|
166
|
+
'no_pool_temp',
|
|
167
|
+
'Keine gültige Pooltemperatur (temperature.surface.current)',
|
|
168
|
+
afterrunMin,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Sicherheitsabschaltung: MaxTemp überschritten
|
|
173
|
+
if (poolTemp >= maxTemp) {
|
|
174
|
+
return this._applyOffState(
|
|
175
|
+
'max_temp_reached',
|
|
176
|
+
`Max-Temperatur erreicht (${poolTemp.toFixed(1)} °C ≥ ${maxTemp} °C)`,
|
|
177
|
+
afterrunMin,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- Heating logic (simple hysteresis-free) ---
|
|
182
|
+
// Einschalten: unter Zieltemperatur
|
|
183
|
+
// Ausschalten: bei Zieltemperatur erreicht/überschritten
|
|
184
|
+
const shouldHeat = poolTemp < targetTemp;
|
|
185
|
+
|
|
186
|
+
if (shouldHeat) {
|
|
187
|
+
return this._startHeating({
|
|
188
|
+
reason: `Heizen: Pool ${poolTemp.toFixed(1)} °C < Ziel ${targetTemp} °C`,
|
|
189
|
+
controlType,
|
|
190
|
+
controlObjectId,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return this._stopHeating({
|
|
195
|
+
reason: `Ziel erreicht: Pool ${poolTemp.toFixed(1)} °C ≥ Ziel ${targetTemp} °C`,
|
|
196
|
+
controlType,
|
|
197
|
+
controlObjectId,
|
|
198
|
+
afterrunMin,
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// -------------------------------------------------------------
|
|
203
|
+
// State transitions
|
|
204
|
+
// -------------------------------------------------------------
|
|
205
|
+
async _startHeating({ reason, controlType, controlObjectId }) {
|
|
206
|
+
if (this._desiredHeat === true) {
|
|
207
|
+
// nur Status/Reason ggf. aktualisieren
|
|
208
|
+
await this._setHeatStates({
|
|
209
|
+
active: true,
|
|
210
|
+
blocked: false,
|
|
211
|
+
mode: 'heating',
|
|
212
|
+
reason,
|
|
213
|
+
info: `control_type=${controlType}, target=${controlObjectId || '(leer)'}`,
|
|
214
|
+
heatingRequest: true,
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this._desiredHeat = true;
|
|
220
|
+
|
|
221
|
+
// Nachlauf ggf. abbrechen
|
|
222
|
+
if (this._afterrunTimer) {
|
|
223
|
+
clearTimeout(this._afterrunTimer);
|
|
224
|
+
this._afterrunTimer = null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Heizung einschalten (wenn ID gesetzt)
|
|
228
|
+
await this._setHeatingDevice(true, controlObjectId);
|
|
229
|
+
|
|
230
|
+
// Pumpe einschalten (ownership)
|
|
231
|
+
await this._ensurePumpOn();
|
|
232
|
+
|
|
233
|
+
await this._setHeatStates({
|
|
234
|
+
active: true,
|
|
235
|
+
blocked: false,
|
|
236
|
+
mode: 'heating',
|
|
237
|
+
reason,
|
|
238
|
+
info: `Heizung EIN | control_type=${controlType}`,
|
|
239
|
+
heatingRequest: true,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
this.adapter.log.info(`[heatHelper] Heizung EIN (${reason})`);
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
async _stopHeating({ reason, controlType, controlObjectId, afterrunMin }) {
|
|
246
|
+
if (this._desiredHeat === false) {
|
|
247
|
+
// nur Status/Reason ggf. aktualisieren
|
|
248
|
+
await this._setHeatStates({
|
|
249
|
+
active: false,
|
|
250
|
+
blocked: false,
|
|
251
|
+
mode: 'off',
|
|
252
|
+
reason,
|
|
253
|
+
info: `control_type=${controlType}`,
|
|
254
|
+
heatingRequest: false,
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this._desiredHeat = false;
|
|
260
|
+
|
|
261
|
+
// Heizung aus
|
|
262
|
+
await this._setHeatingDevice(false, controlObjectId);
|
|
263
|
+
|
|
264
|
+
// Signal für andere Systeme
|
|
265
|
+
await this._setHeatStates({
|
|
266
|
+
active: false,
|
|
267
|
+
blocked: false,
|
|
268
|
+
mode: 'afterrun',
|
|
269
|
+
reason,
|
|
270
|
+
info: `Heizung AUS | Nachlauf=${afterrunMin} min | control_type=${controlType}`,
|
|
271
|
+
heatingRequest: false,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Pumpen-Nachlauf nur, wenn wir die Pumpe eingeschaltet hatten
|
|
275
|
+
await this._startAfterrunIfNeeded(afterrunMin, reason);
|
|
276
|
+
|
|
277
|
+
this.adapter.log.info(`[heatHelper] Heizung AUS (${reason})`);
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
async _applyBlockedState(mode, reason, afterrunMin) {
|
|
281
|
+
// blockiert => Heizung aus, Request false
|
|
282
|
+
await this._setHeatingDevice(false, (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '');
|
|
283
|
+
|
|
284
|
+
await this._setHeatStates({
|
|
285
|
+
active: false,
|
|
286
|
+
blocked: true,
|
|
287
|
+
mode,
|
|
288
|
+
reason,
|
|
289
|
+
info: 'Heizung blockiert',
|
|
290
|
+
heatingRequest: false,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Nachlauf ggf. (nur wenn wir ownen)
|
|
294
|
+
await this._startAfterrunIfNeeded(afterrunMin, reason);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
async _applyOffState(mode, reason, afterrunMin) {
|
|
298
|
+
// off => Heizung aus, Request false
|
|
299
|
+
await this._setHeatingDevice(false, (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '');
|
|
300
|
+
|
|
301
|
+
await this._setHeatStates({
|
|
302
|
+
active: false,
|
|
303
|
+
blocked: false,
|
|
304
|
+
mode,
|
|
305
|
+
reason,
|
|
306
|
+
info: 'Heizung AUS',
|
|
307
|
+
heatingRequest: false,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
await this._startAfterrunIfNeeded(afterrunMin, reason);
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
// -------------------------------------------------------------
|
|
314
|
+
// Pump handling (ownership protected)
|
|
315
|
+
// -------------------------------------------------------------
|
|
316
|
+
async _ensurePumpOn() {
|
|
317
|
+
try {
|
|
318
|
+
const pumpState = await this.adapter.getStateAsync('pump.pump_switch');
|
|
319
|
+
const isOn = !!pumpState?.val;
|
|
320
|
+
|
|
321
|
+
if (!isOn) {
|
|
322
|
+
// wir schalten sie ein => ownership true
|
|
323
|
+
this._ownsPump = true;
|
|
324
|
+
await this.adapter.setStateAsync('pump.pump_switch', { val: true, ack: false });
|
|
325
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: false, ack: true });
|
|
326
|
+
}
|
|
327
|
+
} catch (err) {
|
|
328
|
+
this.adapter.log.warn(`[heatHelper] Konnte Pumpe nicht einschalten: ${err.message}`);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
async _startAfterrunIfNeeded(afterrunMin, reason) {
|
|
333
|
+
// nur wenn wir die Pumpe vorher selbst eingeschaltet haben
|
|
334
|
+
if (!this._ownsPump) {
|
|
335
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: false, ack: true });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// wenn keine Nachlaufzeit: sofort aus
|
|
340
|
+
if (!afterrunMin || afterrunMin <= 0) {
|
|
341
|
+
await this._stopPumpNow('no_afterrun');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Timer neu starten
|
|
346
|
+
if (this._afterrunTimer) {
|
|
347
|
+
clearTimeout(this._afterrunTimer);
|
|
348
|
+
this._afterrunTimer = null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: true, ack: true });
|
|
352
|
+
|
|
353
|
+
const holdMs = Math.round(afterrunMin * 60 * 1000);
|
|
354
|
+
this.adapter.log.debug(`[heatHelper] Pumpen-Nachlauf gestartet: ${afterrunMin} min (${reason})`);
|
|
355
|
+
|
|
356
|
+
this._afterrunTimer = setTimeout(async () => {
|
|
357
|
+
// Wenn inzwischen wieder Heizbedarf aktiv ist -> Nachlauf abbrechen
|
|
358
|
+
if (this._desiredHeat === true) {
|
|
359
|
+
this.adapter.log.debug('[heatHelper] Nachlauf abgebrochen – Heizen wieder aktiv.');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
await this._stopPumpNow('afterrun_done');
|
|
363
|
+
}, holdMs);
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
async _stopPumpNow(tag) {
|
|
367
|
+
try {
|
|
368
|
+
await this.adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
369
|
+
} catch (err) {
|
|
370
|
+
this.adapter.log.warn(`[heatHelper] Konnte Pumpe nicht ausschalten: ${err.message}`);
|
|
371
|
+
} finally {
|
|
372
|
+
this._ownsPump = false;
|
|
373
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: false, ack: true });
|
|
374
|
+
this.adapter.log.info(`[heatHelper] Pumpe AUS (${tag})`);
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
// -------------------------------------------------------------
|
|
379
|
+
// Heating device control (foreign state)
|
|
380
|
+
// -------------------------------------------------------------
|
|
381
|
+
async _setHeatingDevice(on, foreignId) {
|
|
382
|
+
const id = String(foreignId || '').trim();
|
|
383
|
+
if (!id) {
|
|
384
|
+
// Kein Ziel => nur internes heating_request als Signal
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await this.adapter.setForeignStateAsync(id, { val: !!on, ack: false });
|
|
390
|
+
} catch (err) {
|
|
391
|
+
this.adapter.log.warn(`[heatHelper] Konnte Heizung nicht setzen (${id}): ${err.message}`);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
async _setHeatStates({ active, blocked, mode, reason, info, heatingRequest }) {
|
|
396
|
+
try {
|
|
397
|
+
await this.adapter.setStateAsync('heat.active', { val: !!active, ack: true });
|
|
398
|
+
await this.adapter.setStateAsync('heat.blocked', { val: !!blocked, ack: true });
|
|
399
|
+
await this.adapter.setStateAsync('heat.mode', { val: String(mode ?? ''), ack: true });
|
|
400
|
+
await this.adapter.setStateAsync('heat.reason', { val: String(reason ?? ''), ack: true });
|
|
401
|
+
await this.adapter.setStateAsync('heat.info', { val: String(info ?? ''), ack: true });
|
|
402
|
+
await this.adapter.setStateAsync('heat.heating_request', { val: !!heatingRequest, ack: true });
|
|
403
|
+
await this.adapter.setStateAsync('heat.last_change', { val: Date.now(), ack: true });
|
|
404
|
+
} catch (err) {
|
|
405
|
+
this.adapter.log.warn(`[heatHelper] Fehler beim Schreiben der Heat-States: ${err.message}`);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
// -------------------------------------------------------------
|
|
410
|
+
// Foreign subscribe handling
|
|
411
|
+
// -------------------------------------------------------------
|
|
412
|
+
async _refreshForeignSubscription() {
|
|
413
|
+
const id = (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '';
|
|
414
|
+
const nextId = String(id).trim();
|
|
415
|
+
|
|
416
|
+
if (nextId && nextId !== this._heatControlForeignId) {
|
|
417
|
+
// neue ID abonnieren
|
|
418
|
+
try {
|
|
419
|
+
this.adapter.subscribeForeignStates(nextId);
|
|
420
|
+
this.adapter.log.info(`[heatHelper] Subscribed Foreign-Heat-Control: "${nextId}"`);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
this.adapter.log.warn(`[heatHelper] Konnte Foreign-State nicht abonnieren (${nextId}): ${err.message}`);
|
|
423
|
+
}
|
|
424
|
+
this._heatControlForeignId = nextId;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!nextId) {
|
|
428
|
+
this._heatControlForeignId = '';
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
async _safeEvaluate(tag) {
|
|
433
|
+
try {
|
|
434
|
+
await this._evaluate(tag);
|
|
435
|
+
} catch (err) {
|
|
436
|
+
this.adapter.log.warn(`[heatHelper] Evaluate-Fehler (${tag}): ${err.message}`);
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
cleanup() {
|
|
441
|
+
if (this._afterrunTimer) {
|
|
442
|
+
clearTimeout(this._afterrunTimer);
|
|
443
|
+
this._afterrunTimer = null;
|
|
444
|
+
}
|
|
445
|
+
this._ownsPump = false;
|
|
446
|
+
this._desiredHeat = null;
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
module.exports = heatHelper;
|
|
@@ -214,6 +214,15 @@ const pumpHelper = {
|
|
|
214
214
|
status = 'EIN (Zeitsteuerung)';
|
|
215
215
|
}
|
|
216
216
|
break;
|
|
217
|
+
|
|
218
|
+
case 'heatHelper':
|
|
219
|
+
try {
|
|
220
|
+
const reason = (await this.adapter.getStateAsync('pump.reason'))?.val || '';
|
|
221
|
+
status = reason ? `EIN (${reason})` : 'EIN (Heizung)';
|
|
222
|
+
} catch {
|
|
223
|
+
status = 'EIN (Heizung)';
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
217
226
|
}
|
|
218
227
|
}
|
|
219
228
|
await this.adapter.setStateChangedAsync('pump.status', {
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Legt alle States für die Heizungs- / Wärmepumpensteuerung an
|
|
5
|
+
*
|
|
6
|
+
* @param {import("iobroker").Adapter} adapter - ioBroker Adapter-Instanz
|
|
7
|
+
*/
|
|
8
|
+
async function createHeatStates(adapter) {
|
|
9
|
+
const channelId = 'heat';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------
|
|
12
|
+
// Channel
|
|
13
|
+
// ---------------------------------------------------------
|
|
14
|
+
await adapter.setObjectNotExistsAsync(channelId, {
|
|
15
|
+
type: 'channel',
|
|
16
|
+
common: {
|
|
17
|
+
name: 'Heizung / Wärmepumpe',
|
|
18
|
+
},
|
|
19
|
+
native: {},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------
|
|
23
|
+
// Steuerung / Konfiguration (write = true, persistent)
|
|
24
|
+
// ---------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.control_active`, {
|
|
27
|
+
type: 'state',
|
|
28
|
+
common: {
|
|
29
|
+
name: 'Heizungssteuerung aktiv',
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
role: 'switch',
|
|
32
|
+
read: true,
|
|
33
|
+
write: true,
|
|
34
|
+
persist: true,
|
|
35
|
+
},
|
|
36
|
+
native: {},
|
|
37
|
+
});
|
|
38
|
+
const existingControlActive = await adapter.getStateAsync(`${channelId}.control_active`);
|
|
39
|
+
if (
|
|
40
|
+
existingControlActive === null ||
|
|
41
|
+
existingControlActive.val === null ||
|
|
42
|
+
existingControlActive.val === undefined
|
|
43
|
+
) {
|
|
44
|
+
await adapter.setStateAsync(`${channelId}.control_active`, {
|
|
45
|
+
val: adapter.config.heat_control_active ?? false,
|
|
46
|
+
ack: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.control_type`, {
|
|
51
|
+
type: 'state',
|
|
52
|
+
common: {
|
|
53
|
+
name: 'Art der Heizungssteuerung',
|
|
54
|
+
type: 'string',
|
|
55
|
+
role: 'text',
|
|
56
|
+
read: true,
|
|
57
|
+
write: true,
|
|
58
|
+
persist: true,
|
|
59
|
+
},
|
|
60
|
+
native: {},
|
|
61
|
+
});
|
|
62
|
+
const existingControlType = await adapter.getStateAsync(`${channelId}.control_type`);
|
|
63
|
+
if (existingControlType === null || existingControlType.val === null || existingControlType.val === undefined) {
|
|
64
|
+
await adapter.setStateAsync(`${channelId}.control_type`, {
|
|
65
|
+
val: adapter.config.heat_control_type ?? 'socket',
|
|
66
|
+
ack: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.control_object_id`, {
|
|
71
|
+
type: 'state',
|
|
72
|
+
common: {
|
|
73
|
+
name: 'Steuer-Objekt-ID Heizung',
|
|
74
|
+
type: 'string',
|
|
75
|
+
role: 'text',
|
|
76
|
+
read: true,
|
|
77
|
+
write: true,
|
|
78
|
+
persist: true,
|
|
79
|
+
},
|
|
80
|
+
native: {},
|
|
81
|
+
});
|
|
82
|
+
const existingObjectId = await adapter.getStateAsync(`${channelId}.control_object_id`);
|
|
83
|
+
if (existingObjectId === null || existingObjectId.val === null || existingObjectId.val === undefined) {
|
|
84
|
+
await adapter.setStateAsync(`${channelId}.control_object_id`, {
|
|
85
|
+
val: adapter.config.heat_control_object_id ?? '',
|
|
86
|
+
ack: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.target_temperature`, {
|
|
91
|
+
type: 'state',
|
|
92
|
+
common: {
|
|
93
|
+
name: 'Zieltemperatur Pool',
|
|
94
|
+
type: 'number',
|
|
95
|
+
role: 'value.temperature',
|
|
96
|
+
unit: '°C',
|
|
97
|
+
read: true,
|
|
98
|
+
write: true,
|
|
99
|
+
persist: true,
|
|
100
|
+
},
|
|
101
|
+
native: {},
|
|
102
|
+
});
|
|
103
|
+
const existingTargetTemp = await adapter.getStateAsync(`${channelId}.target_temperature`);
|
|
104
|
+
if (existingTargetTemp === null || existingTargetTemp.val === null || existingTargetTemp.val === undefined) {
|
|
105
|
+
await adapter.setStateAsync(`${channelId}.target_temperature`, {
|
|
106
|
+
val: adapter.config.heat_temp_target ?? 26,
|
|
107
|
+
ack: true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.max_temperature`, {
|
|
112
|
+
type: 'state',
|
|
113
|
+
common: {
|
|
114
|
+
name: 'Maximale Pooltemperatur (Sicherheit)',
|
|
115
|
+
type: 'number',
|
|
116
|
+
role: 'value.temperature',
|
|
117
|
+
unit: '°C',
|
|
118
|
+
read: true,
|
|
119
|
+
write: true,
|
|
120
|
+
persist: true,
|
|
121
|
+
},
|
|
122
|
+
native: {},
|
|
123
|
+
});
|
|
124
|
+
const existingMaxTemp = await adapter.getStateAsync(`${channelId}.max_temperature`);
|
|
125
|
+
if (existingMaxTemp === null || existingMaxTemp.val === null || existingMaxTemp.val === undefined) {
|
|
126
|
+
await adapter.setStateAsync(`${channelId}.max_temperature`, {
|
|
127
|
+
val: adapter.config.heat_temp_max ?? 30,
|
|
128
|
+
ack: true,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.pump_afterrun_minutes`, {
|
|
133
|
+
type: 'state',
|
|
134
|
+
common: {
|
|
135
|
+
name: 'Pumpen-Nachlaufzeit nach Heizung',
|
|
136
|
+
type: 'number',
|
|
137
|
+
role: 'value.interval',
|
|
138
|
+
unit: 'min',
|
|
139
|
+
read: true,
|
|
140
|
+
write: true,
|
|
141
|
+
persist: true,
|
|
142
|
+
},
|
|
143
|
+
native: {},
|
|
144
|
+
});
|
|
145
|
+
const existingAfterrun = await adapter.getStateAsync(`${channelId}.pump_afterrun_minutes`);
|
|
146
|
+
if (existingAfterrun === null || existingAfterrun.val === null || existingAfterrun.val === undefined) {
|
|
147
|
+
await adapter.setStateAsync(`${channelId}.pump_afterrun_minutes`, {
|
|
148
|
+
val: 5,
|
|
149
|
+
ack: true,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------
|
|
154
|
+
// Betriebsstatus (write = false)
|
|
155
|
+
// ---------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.active`, {
|
|
158
|
+
type: 'state',
|
|
159
|
+
common: {
|
|
160
|
+
name: 'Heizung aktiv',
|
|
161
|
+
type: 'boolean',
|
|
162
|
+
role: 'indicator.working',
|
|
163
|
+
read: true,
|
|
164
|
+
write: false,
|
|
165
|
+
},
|
|
166
|
+
native: {},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.mode`, {
|
|
170
|
+
type: 'state',
|
|
171
|
+
common: {
|
|
172
|
+
name: 'Heizungsmodus',
|
|
173
|
+
type: 'string',
|
|
174
|
+
role: 'text',
|
|
175
|
+
read: true,
|
|
176
|
+
write: false,
|
|
177
|
+
},
|
|
178
|
+
native: {},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.blocked`, {
|
|
182
|
+
type: 'state',
|
|
183
|
+
common: {
|
|
184
|
+
name: 'Heizung blockiert',
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
role: 'indicator.blocked',
|
|
187
|
+
read: true,
|
|
188
|
+
write: false,
|
|
189
|
+
},
|
|
190
|
+
native: {},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.afterrun_active`, {
|
|
194
|
+
type: 'state',
|
|
195
|
+
common: {
|
|
196
|
+
name: 'Pumpen-Nachlauf aktiv',
|
|
197
|
+
type: 'boolean',
|
|
198
|
+
role: 'indicator.working',
|
|
199
|
+
read: true,
|
|
200
|
+
write: false,
|
|
201
|
+
},
|
|
202
|
+
native: {},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.last_change`, {
|
|
206
|
+
type: 'state',
|
|
207
|
+
common: {
|
|
208
|
+
name: 'Letzte Statusänderung Heizung',
|
|
209
|
+
type: 'number',
|
|
210
|
+
role: 'value.time',
|
|
211
|
+
read: true,
|
|
212
|
+
write: false,
|
|
213
|
+
},
|
|
214
|
+
native: {},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.heating_request`, {
|
|
218
|
+
type: 'state',
|
|
219
|
+
common: {
|
|
220
|
+
name: 'Heizanforderung (PoolControl)',
|
|
221
|
+
type: 'boolean',
|
|
222
|
+
role: 'indicator.request',
|
|
223
|
+
read: true,
|
|
224
|
+
write: false,
|
|
225
|
+
def: false,
|
|
226
|
+
},
|
|
227
|
+
native: {},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------
|
|
231
|
+
// Transparenz / Diagnose
|
|
232
|
+
// ---------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.reason`, {
|
|
235
|
+
type: 'state',
|
|
236
|
+
common: {
|
|
237
|
+
name: 'Grund für Heizungsstatus',
|
|
238
|
+
type: 'string',
|
|
239
|
+
role: 'text',
|
|
240
|
+
read: true,
|
|
241
|
+
write: false,
|
|
242
|
+
},
|
|
243
|
+
native: {},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.info`, {
|
|
247
|
+
type: 'state',
|
|
248
|
+
common: {
|
|
249
|
+
name: 'Heizungs-Info',
|
|
250
|
+
type: 'string',
|
|
251
|
+
role: 'text',
|
|
252
|
+
read: true,
|
|
253
|
+
write: false,
|
|
254
|
+
},
|
|
255
|
+
native: {},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
createHeatStates,
|
|
261
|
+
};
|
package/main.js
CHANGED
|
@@ -30,6 +30,7 @@ const debugLogHelper = require('./lib/helpers/debugLogHelper');
|
|
|
30
30
|
const speechTextHelper = require('./lib/helpers/speechTextHelper');
|
|
31
31
|
const migrationHelper = require('./lib/helpers/migrationHelper');
|
|
32
32
|
const infoHelper = require('./lib/helpers/infoHelper');
|
|
33
|
+
const heatHelper = require('./lib/helpers/heatHelper');
|
|
33
34
|
const { createTemperatureStates } = require('./lib/stateDefinitions/temperatureStates');
|
|
34
35
|
const { createPumpStates } = require('./lib/stateDefinitions/pumpStates');
|
|
35
36
|
const { createPumpStates2 } = require('./lib/stateDefinitions/pumpStates2');
|
|
@@ -49,6 +50,7 @@ const { createDebugLogStates } = require('./lib/stateDefinitions/debugLogStates'
|
|
|
49
50
|
const { createInfoStates } = require('./lib/stateDefinitions/infoStates');
|
|
50
51
|
const { createAiStates } = require('./lib/stateDefinitions/aiStates'); // NEU: KI-States
|
|
51
52
|
const { createAiChemistryHelpStates } = require('./lib/stateDefinitions/aiChemistryHelpStates'); // NEU: KI-Chemie-Hilfe
|
|
53
|
+
const { createHeatStates } = require('./lib/stateDefinitions/heatStates');
|
|
52
54
|
|
|
53
55
|
class Poolcontrol extends utils.Adapter {
|
|
54
56
|
constructor(options) {
|
|
@@ -79,6 +81,9 @@ class Poolcontrol extends utils.Adapter {
|
|
|
79
81
|
// --- Solarverwaltung ---
|
|
80
82
|
await createSolarStates(this);
|
|
81
83
|
|
|
84
|
+
// --- Heizung / Wärmepumpe ---
|
|
85
|
+
await createHeatStates(this);
|
|
86
|
+
|
|
82
87
|
// --- Photovoltaik ---
|
|
83
88
|
await createPhotovoltaicStates(this);
|
|
84
89
|
|
|
@@ -136,6 +141,7 @@ class Poolcontrol extends utils.Adapter {
|
|
|
136
141
|
speechHelper.init(this);
|
|
137
142
|
consumptionHelper.init(this);
|
|
138
143
|
solarHelper.init(this);
|
|
144
|
+
heatHelper.init(this); // ← NEU
|
|
139
145
|
photovoltaicHelper.init(this);
|
|
140
146
|
aiHelper.init(this);
|
|
141
147
|
aiForecastHelper.init(this);
|
|
@@ -196,6 +202,9 @@ class Poolcontrol extends utils.Adapter {
|
|
|
196
202
|
if (controlHelper2.cleanup) {
|
|
197
203
|
controlHelper2.cleanup();
|
|
198
204
|
}
|
|
205
|
+
if (heatHelper.cleanup) {
|
|
206
|
+
heatHelper.cleanup();
|
|
207
|
+
}
|
|
199
208
|
if (speechTextHelper.cleanup) {
|
|
200
209
|
speechTextHelper.cleanup();
|
|
201
210
|
}
|
|
@@ -279,6 +288,11 @@ class Poolcontrol extends utils.Adapter {
|
|
|
279
288
|
} catch (e) {
|
|
280
289
|
this.log.warn(`[photovoltaicHelper] Fehler in handleStateChange: ${e.message}`);
|
|
281
290
|
}
|
|
291
|
+
try {
|
|
292
|
+
heatHelper.handleStateChange(id, state);
|
|
293
|
+
} catch (e) {
|
|
294
|
+
this.log.warn(`[heatHelper] Fehler in handleStateChange: ${e.message}`);
|
|
295
|
+
}
|
|
282
296
|
// --- AI-Helper ---
|
|
283
297
|
try {
|
|
284
298
|
aiHelper.handleStateChange(id, state);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.poolcontrol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|