iobroker.poolcontrol 0.8.2 → 0.9.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 +39 -11
- package/admin/jsonConfig.json +86 -2
- package/io-package.json +27 -27
- package/lib/helpers/heatHelper.js +513 -0
- package/lib/helpers/pumpHelper.js +9 -0
- package/lib/stateDefinitions/generalStates.js +9 -0
- package/lib/stateDefinitions/heatStates.js +296 -0
- package/lib/stateDefinitions/pumpStates.js +9 -0
- package/lib/stateDefinitions/solarStates.js +9 -0
- package/lib/stateDefinitions/timeStates.js +9 -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,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "poolcontrol",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.9.1": {
|
|
7
|
+
"en": "Extended heating / heat pump control (heatHelper) with configurable pump pre-run before heating start. The pump can now be started a defined time before the heater is activated to ensure sufficient water flow and pressure (e.g. for heat pumps). Includes new states for pre-run timing and status, fully integrated into the existing priority, ownership, and safety logic.",
|
|
8
|
+
"de": "Erweiterte Heizungs- / Wärmepumpensteuerung (heatHelper) um eine konfigurierbare Pumpen-Vorlaufzeit vor Heizstart. Die Pumpe kann nun für eine definierte Zeit vor dem Einschalten der Heizung gestartet werden, um ausreichend Wasserdurchfluss und Druck sicherzustellen (z. B. bei Wärmepumpen). Enthält neue Datenpunkte für Vorlaufzeit und Vorlaufstatus, vollständig integriert in die bestehende Vorrangs-, Ownership- und Sicherheitslogik.",
|
|
9
|
+
"ru": "Расширенная система управления отоплением / тепловым насосом (heatHelper) с настраиваемым предварительным запуском насоса перед началом нагрева. Насос может запускаться за заданное время до включения отопления для обеспечения достаточного потока и давления воды (например, для тепловых насосов). Добавлены новые состояния для времени предварительного запуска и его статуса, полностью интегрированные в существующую логику приоритетов, владения и безопасности.",
|
|
10
|
+
"pt": "Controlo de aquecimento / bomba de calor (heatHelper) alargado com pré-funcionamento configurável da bomba antes do início do aquecimento. A bomba pode agora ser ligada durante um período definido antes da ativação do aquecedor para garantir caudal e pressão de água suficientes (por exemplo, em bombas de calor). Inclui novos estados para tempo e estado do pré-funcionamento, totalmente integrados na lógica existente de prioridades, propriedade e segurança.",
|
|
11
|
+
"nl": "Uitgebreide verwarmings- / warmtepompregeling (heatHelper) met configureerbare pomp-voorloop vóór de start van de verwarming. De pomp kan nu een ingestelde tijd vóór het inschakelen van de verwarming worden gestart om voldoende waterdoorstroming en druk te garanderen (bijv. voor warmtepompen). Bevat nieuwe states voor voorlooptijd en -status, volledig geïntegreerd in de bestaande prioriteits-, eigendoms- en veiligheidslogica.",
|
|
12
|
+
"fr": "Extension du contrôle de chauffage / pompe à chaleur (heatHelper) avec un préfonctionnement configurable de la pompe avant le démarrage du chauffage. La pompe peut désormais être démarrée un temps défini avant l’activation du chauffage afin d’assurer un débit et une pression d’eau suffisants (par ex. pour les pompes à chaleur). Ajout de nouveaux états pour la durée et le statut du préfonctionnement, entièrement intégrés à la logique existante de priorité, de propriété et de sécurité.",
|
|
13
|
+
"it": "Controllo di riscaldamento / pompa di calore (heatHelper) esteso con pre-avviamento configurabile della pompa prima dell’inizio del riscaldamento. La pompa può ora essere avviata per un periodo definito prima dell’attivazione del riscaldamento per garantire un flusso e una pressione dell’acqua sufficienti (ad es. per le pompe di calore). Include nuovi stati per il tempo e lo stato del pre-avviamento, completamente integrati nella logica esistente di priorità, proprietà e sicurezza.",
|
|
14
|
+
"es": "Control de calefacción / bomba de calor (heatHelper) ampliado con prefuncionamiento configurable de la bomba antes del inicio de la calefacción. La bomba puede iniciarse ahora durante un tiempo definido antes de activar la calefacción para garantizar un caudal y una presión de agua suficientes (p. ej., para bombas de calor). Incluye nuevos estados para el tiempo y el estado del prefuncionamiento, totalmente integrados en la lógica existente de prioridades, propiedad y seguridad.",
|
|
15
|
+
"pl": "Rozszerzony system sterowania ogrzewaniem / pompą ciepła (heatHelper) o konfigurowalny wstępny bieg pompy przed rozpoczęciem grzania. Pompa może być uruchamiana na określony czas przed włączeniem ogrzewania w celu zapewnienia odpowiedniego przepływu i ciśnienia wody (np. dla pomp ciepła). Dodano nowe stany dla czasu i statusu biegu wstępnego, w pełni zintegrowane z istniejącą logiką priorytetów, własności i bezpieczeństwa.",
|
|
16
|
+
"uk": "Розширене керування опаленням / тепловим насосом (heatHelper) з налаштовуваним попереднім запуском насоса перед початком нагріву. Насос може запускатися за визначений час до увімкнення опалення для забезпечення достатнього потоку та тиску води (наприклад, для теплових насосів). Додано нові стани для часу та статусу попереднього запуску, повністю інтегровані в існуючу логіку пріоритетів, володіння та безпеки.",
|
|
17
|
+
"zh-cn": "扩展了加热 / 热泵控制功能(heatHelper),新增可配置的加热前水泵预运行时间。水泵可在加热设备启动前提前运行一段时间,以确保足够的水流和水压(例如用于热泵)。新增了预运行时间和状态的数据点,已完全集成到现有的优先级、所有权和安全逻辑中。"
|
|
18
|
+
},
|
|
19
|
+
"0.9.0": {
|
|
20
|
+
"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.",
|
|
21
|
+
"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.",
|
|
22
|
+
"ru": "Новая система управления отоплением / тепловым насосом (heatHelper). Автоматическое управление на основе температуры воды с настраиваемой целевой и максимальной температурой, временем добега насоса и полной системой приоритетов (например, режим обслуживания). Работает только в автоматическом режиме насоса и учитывает сезонный статус. Поддерживает как переключаемые розетки, так и булевы управляющие состояния. Включает защиту владения для предотвращения конфликтов с другими контроллерами насоса.",
|
|
23
|
+
"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.",
|
|
24
|
+
"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.",
|
|
25
|
+
"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.",
|
|
26
|
+
"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.",
|
|
27
|
+
"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.",
|
|
28
|
+
"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.",
|
|
29
|
+
"uk": "Нова система керування опаленням / тепловим насосом (heatHelper). Автоматичне керування на основі температури басейну з налаштовуваною цільовою та максимальною температурою, часом післяроботи насоса та повною системою пріоритетів (наприклад, режим обслуговування). Працює лише в автоматичному режимі насоса та враховує сезонний статус. Підтримує комутовані розетки та булеві стани керування. Містить захист володіння для запобігання конфліктам з іншими контролерами насоса.",
|
|
30
|
+
"zh-cn": "新增加热 / 热泵控制功能(heatHelper)。基于泳池水温的自动控制,支持可配置的目标温度和最高温度、泵后运行时间以及完整的优先级处理(例如维护模式)。仅在泵的自动模式下工作,并考虑季节状态。支持可开关插座和布尔控制状态。包含所有权保护机制,避免与其他泵控制逻辑发生冲突。"
|
|
31
|
+
},
|
|
6
32
|
"0.8.2": {
|
|
7
33
|
"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
34
|
"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.",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Poprawka pozdrowień sezonowych: życzenia świąteczne pojawiają się teraz prawidłowo tylko między 20 a 27 grudnia. Błędna przedwczesna aktywacja została naprawiona.",
|
|
68
94
|
"uk": "Виправлено сезонні вітання: Різдвяне привітання тепер з’являється лише з 20 по 27 грудня. Помилкове передчасне спрацьовування виправлено.",
|
|
69
95
|
"zh-cn": "修复季节性问候:圣诞问候现在仅在 12 月 20 日至 27 日之间显示。已修复错误的提前触发问题。"
|
|
70
|
-
},
|
|
71
|
-
"0.7.2": {
|
|
72
|
-
"en": "Added new info system: The adapter now writes seasonal greetings and the installed adapter version to info.* states. Includes automatic daily refresh at 00:01 and full Easter date calculation.",
|
|
73
|
-
"de": "Neues Info-System hinzugefügt: Der Adapter schreibt nun saisonale Grüße und die installierte Adapterversion in die info.* States. Enthält eine automatische tägliche Aktualisierung um 00:01 sowie eine vollständige Oster-Berechnung.",
|
|
74
|
-
"ru": "Добавлена новая информационная система: адаптер теперь записывает сезонные поздравления и установленную версию адаптера в состояния info.*. Включает ежедневное обновление в 00:01 и полный расчёт даты Пасхи.",
|
|
75
|
-
"pt": "Novo sistema de informações adicionado: o adaptador agora grava saudações sazonais e a versão instalada do adaptador nos estados info.*. Inclui atualização diária automática às 00:01 e cálculo completo da data da Páscoa.",
|
|
76
|
-
"nl": "Nieuw infosysteem toegevoegd: de adapter schrijft nu seizoensgroeten en de geïnstalleerde adapterversie naar info.*-states. Inclusief automatische dagelijkse update om 00:01 en volledige berekening van Pasen.",
|
|
77
|
-
"fr": "Nouveau système d'informations ajouté : l'adaptateur écrit désormais des messages saisonniers et la version installée dans les états info.*. Comprend une mise à jour quotidienne automatique à 00:01 et un calcul complet de la date de Pâques.",
|
|
78
|
-
"it": "Aggiunto nuovo sistema informativo: l'adattatore ora scrive i saluti stagionali e la versione installata negli stati info.*. Include aggiornamento giornaliero automatico alle 00:01 e calcolo completo della data di Pasqua.",
|
|
79
|
-
"es": "Nuevo sistema de información añadido: el adaptador ahora escribe saludos estacionales y la versión instalada en los estados info.*. Incluye actualización automática diaria a las 00:01 y cálculo completo de la fecha de Pascua.",
|
|
80
|
-
"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
|
-
"uk": "Додано нову інформаційну систему: адаптер тепер записує сезонні привітання та встановлену версію адаптера в стани info.*. Містить щоденне автоматичне оновлення о 00:01 і повний розрахунок дати Великодня.",
|
|
82
|
-
"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,513 @@
|
|
|
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
|
+
_prerunTimer: null, // NEU: Pumpen-Vorlauf vor Heizstart
|
|
28
|
+
|
|
29
|
+
// Ownership / Merker
|
|
30
|
+
_ownsPump: false,
|
|
31
|
+
_desiredHeat: null,
|
|
32
|
+
_lastEval: 0,
|
|
33
|
+
|
|
34
|
+
init(adapter) {
|
|
35
|
+
this.adapter = adapter;
|
|
36
|
+
|
|
37
|
+
// lokale States überwachen
|
|
38
|
+
this.adapter.subscribeStates('heat.control_active');
|
|
39
|
+
this.adapter.subscribeStates('heat.control_type');
|
|
40
|
+
this.adapter.subscribeStates('heat.control_object_id');
|
|
41
|
+
this.adapter.subscribeStates('heat.target_temperature');
|
|
42
|
+
this.adapter.subscribeStates('heat.max_temperature');
|
|
43
|
+
this.adapter.subscribeStates('heat.pump_afterrun_minutes');
|
|
44
|
+
this.adapter.subscribeStates('heat.pump_prerun_minutes'); // NEU
|
|
45
|
+
|
|
46
|
+
// Abhängigkeiten
|
|
47
|
+
this.adapter.subscribeStates('status.season_active');
|
|
48
|
+
this.adapter.subscribeStates('pump.mode');
|
|
49
|
+
this.adapter.subscribeStates('pump.pump_switch');
|
|
50
|
+
this.adapter.subscribeStates('temperature.surface.current');
|
|
51
|
+
|
|
52
|
+
// Vorrangschaltung / Wartung
|
|
53
|
+
this.adapter.subscribeStates('control.pump.maintenance_active');
|
|
54
|
+
|
|
55
|
+
// ggf. vorhandene Foreign-ID abonnieren
|
|
56
|
+
this._refreshForeignSubscription().catch(err =>
|
|
57
|
+
this.adapter.log.warn(`[heatHelper] Foreign-Subscription Fehler: ${err.message}`),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
this._safeEvaluate('init');
|
|
61
|
+
this.adapter.log.info('[heatHelper] Initialisierung abgeschlossen.');
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
async handleStateChange(id, state) {
|
|
65
|
+
if (!state) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Wenn die Ziel-Objekt-ID geändert wurde: Foreign subscription anpassen
|
|
71
|
+
if (id.endsWith('heat.control_object_id') || id.endsWith('heat.control_type')) {
|
|
72
|
+
await this._refreshForeignSubscription();
|
|
73
|
+
await this._safeEvaluate('control_target_changed');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Alles andere: neu bewerten
|
|
78
|
+
if (
|
|
79
|
+
id.endsWith('heat.control_active') ||
|
|
80
|
+
id.endsWith('heat.target_temperature') ||
|
|
81
|
+
id.endsWith('heat.max_temperature') ||
|
|
82
|
+
id.endsWith('heat.pump_afterrun_minutes') ||
|
|
83
|
+
id.endsWith('status.season_active') ||
|
|
84
|
+
id.endsWith('pump.mode') ||
|
|
85
|
+
id.endsWith('temperature.surface.current') ||
|
|
86
|
+
id.endsWith('control.pump.maintenance_active') ||
|
|
87
|
+
id.endsWith('heat.pump_prerun_minutes')
|
|
88
|
+
) {
|
|
89
|
+
await this._safeEvaluate('state_change');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Wenn jemand die Pumpe extern schaltet: Ownership ggf. zurücknehmen
|
|
94
|
+
if (id.endsWith('pump.pump_switch')) {
|
|
95
|
+
const pumpOn = !!state.val;
|
|
96
|
+
if (!pumpOn && this._ownsPump) {
|
|
97
|
+
// wenn Pumpe aus geht obwohl wir "ownen", Ownership verlieren
|
|
98
|
+
this._ownsPump = false;
|
|
99
|
+
}
|
|
100
|
+
await this._safeEvaluate('pump_switch_changed');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
this.adapter.log.warn(`[heatHelper] Fehler in handleStateChange: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// -------------------------------------------------------------
|
|
109
|
+
// Core
|
|
110
|
+
// -------------------------------------------------------------
|
|
111
|
+
async _evaluate(_sourceTag = '') {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
if (now - this._lastEval < 250) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this._lastEval = now;
|
|
117
|
+
|
|
118
|
+
const seasonActive = !!(await this.adapter.getStateAsync('status.season_active'))?.val;
|
|
119
|
+
const maintenanceActive = !!(await this.adapter.getStateAsync('control.pump.maintenance_active'))?.val;
|
|
120
|
+
|
|
121
|
+
const pumpMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'auto';
|
|
122
|
+
const heatEnabled = !!(await this.adapter.getStateAsync('heat.control_active'))?.val;
|
|
123
|
+
|
|
124
|
+
const poolTempRaw = (await this.adapter.getStateAsync('temperature.surface.current'))?.val;
|
|
125
|
+
const poolTemp = Number(poolTempRaw);
|
|
126
|
+
|
|
127
|
+
const targetTemp = Number((await this.adapter.getStateAsync('heat.target_temperature'))?.val ?? 26);
|
|
128
|
+
const maxTemp = Number((await this.adapter.getStateAsync('heat.max_temperature'))?.val ?? 30);
|
|
129
|
+
|
|
130
|
+
const afterrunMin = Math.max(
|
|
131
|
+
0,
|
|
132
|
+
Number((await this.adapter.getStateAsync('heat.pump_afterrun_minutes'))?.val ?? 0) || 0,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const controlType = (await this.adapter.getStateAsync('heat.control_type'))?.val || 'socket';
|
|
136
|
+
const controlObjectId = (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '';
|
|
137
|
+
|
|
138
|
+
// --- Hard conditions / Blocker ---
|
|
139
|
+
if (!seasonActive) {
|
|
140
|
+
return this._applyBlockedState('season_inactive', 'Poolsaison ist inaktiv', afterrunMin);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Vorrangschaltung: Wartung blockiert IMMER
|
|
144
|
+
if (maintenanceActive) {
|
|
145
|
+
return this._applyBlockedState(
|
|
146
|
+
'maintenance_active',
|
|
147
|
+
'Wartungsmodus aktiv (Control hat Vorrang)',
|
|
148
|
+
afterrunMin,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Heizungssteuerung deaktiviert
|
|
153
|
+
if (!heatEnabled) {
|
|
154
|
+
return this._applyOffState('heat_disabled', 'Heizungssteuerung deaktiviert', afterrunMin);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Pumpenmodus: nur Automatik
|
|
158
|
+
if (pumpMode !== 'auto') {
|
|
159
|
+
return this._applyBlockedState(
|
|
160
|
+
'mode_not_auto',
|
|
161
|
+
`Pumpenmodus ist '${pumpMode}' (Heizung arbeitet nur in Automatik)`,
|
|
162
|
+
afterrunMin,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Sensor plausibel?
|
|
167
|
+
if (!Number.isFinite(poolTemp)) {
|
|
168
|
+
return this._applyBlockedState(
|
|
169
|
+
'no_pool_temp',
|
|
170
|
+
'Keine gültige Pooltemperatur (temperature.surface.current)',
|
|
171
|
+
afterrunMin,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Sicherheitsabschaltung: MaxTemp überschritten
|
|
176
|
+
if (poolTemp >= maxTemp) {
|
|
177
|
+
return this._applyOffState(
|
|
178
|
+
'max_temp_reached',
|
|
179
|
+
`Max-Temperatur erreicht (${poolTemp.toFixed(1)} °C ≥ ${maxTemp} °C)`,
|
|
180
|
+
afterrunMin,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- Heating logic (simple hysteresis-free) ---
|
|
185
|
+
// Einschalten: unter Zieltemperatur
|
|
186
|
+
// Ausschalten: bei Zieltemperatur erreicht/überschritten
|
|
187
|
+
const shouldHeat = poolTemp < targetTemp;
|
|
188
|
+
|
|
189
|
+
if (shouldHeat) {
|
|
190
|
+
return this._startHeating({
|
|
191
|
+
reason: `Heizen: Pool ${poolTemp.toFixed(1)} °C < Ziel ${targetTemp} °C`,
|
|
192
|
+
controlType,
|
|
193
|
+
controlObjectId,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return this._stopHeating({
|
|
198
|
+
reason: `Ziel erreicht: Pool ${poolTemp.toFixed(1)} °C ≥ Ziel ${targetTemp} °C`,
|
|
199
|
+
controlType,
|
|
200
|
+
controlObjectId,
|
|
201
|
+
afterrunMin,
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
// -------------------------------------------------------------
|
|
206
|
+
// State transitions
|
|
207
|
+
// -------------------------------------------------------------
|
|
208
|
+
async _startHeating({ reason, controlType, controlObjectId }) {
|
|
209
|
+
if (this._desiredHeat === true) {
|
|
210
|
+
// nur Status/Reason ggf. aktualisieren
|
|
211
|
+
await this._setHeatStates({
|
|
212
|
+
active: true,
|
|
213
|
+
blocked: false,
|
|
214
|
+
mode: 'heating',
|
|
215
|
+
reason,
|
|
216
|
+
info: `control_type=${controlType}, target=${controlObjectId || '(leer)'}`,
|
|
217
|
+
heatingRequest: true,
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// -------------------------------------------------
|
|
223
|
+
// NEU: Pumpen-Prerun vor Heizstart
|
|
224
|
+
// -------------------------------------------------
|
|
225
|
+
const prerunMin = Math.max(
|
|
226
|
+
0,
|
|
227
|
+
Number((await this.adapter.getStateAsync('heat.pump_prerun_minutes'))?.val ?? 0) || 0,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const pumpState = await this.adapter.getStateAsync('pump.pump_switch');
|
|
231
|
+
const pumpIsOn = !!pumpState?.val;
|
|
232
|
+
|
|
233
|
+
// Prerun nur, wenn:
|
|
234
|
+
// - Zeit > 0
|
|
235
|
+
// - Pumpe aktuell AUS
|
|
236
|
+
// - kein Prerun aktiv
|
|
237
|
+
if (prerunMin > 0 && !pumpIsOn && !this._prerunTimer) {
|
|
238
|
+
this.adapter.log.info(`[heatHelper] Starte Pumpen-Prerun (${prerunMin} min)`);
|
|
239
|
+
|
|
240
|
+
// Pumpe einschalten + Ownership übernehmen
|
|
241
|
+
this._ownsPump = true;
|
|
242
|
+
await this.adapter.setStateAsync('pump.pump_switch', { val: true, ack: false });
|
|
243
|
+
|
|
244
|
+
// Prerun-Status setzen
|
|
245
|
+
await this.adapter.setStateAsync('heat.prerun_active', { val: true, ack: true });
|
|
246
|
+
|
|
247
|
+
const holdMs = Math.round(prerunMin * 60 * 1000);
|
|
248
|
+
|
|
249
|
+
this._prerunTimer = setTimeout(async () => {
|
|
250
|
+
this._prerunTimer = null;
|
|
251
|
+
|
|
252
|
+
await this.adapter.setStateAsync('heat.prerun_active', { val: false, ack: true });
|
|
253
|
+
|
|
254
|
+
// Nach Prerun erneut bewerten → Heizung darf jetzt starten
|
|
255
|
+
await this._safeEvaluate('prerun_done');
|
|
256
|
+
}, holdMs);
|
|
257
|
+
|
|
258
|
+
// WICHTIG: Heizung JETZT noch NICHT einschalten
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this._desiredHeat = true;
|
|
263
|
+
|
|
264
|
+
// Nachlauf ggf. abbrechen
|
|
265
|
+
if (this._afterrunTimer) {
|
|
266
|
+
clearTimeout(this._afterrunTimer);
|
|
267
|
+
this._afterrunTimer = null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Heizung einschalten (wenn ID gesetzt)
|
|
271
|
+
await this._setHeatingDevice(true, controlObjectId);
|
|
272
|
+
|
|
273
|
+
// Pumpe einschalten (ownership)
|
|
274
|
+
await this._ensurePumpOn();
|
|
275
|
+
|
|
276
|
+
await this._setHeatStates({
|
|
277
|
+
active: true,
|
|
278
|
+
blocked: false,
|
|
279
|
+
mode: 'heating',
|
|
280
|
+
reason,
|
|
281
|
+
info: `Heizung EIN | control_type=${controlType}`,
|
|
282
|
+
heatingRequest: true,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
this.adapter.log.info(`[heatHelper] Heizung EIN (${reason})`);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async _stopHeating({ reason, controlType, controlObjectId, afterrunMin }) {
|
|
289
|
+
if (this._desiredHeat === false) {
|
|
290
|
+
// nur Status/Reason ggf. aktualisieren
|
|
291
|
+
await this._setHeatStates({
|
|
292
|
+
active: false,
|
|
293
|
+
blocked: false,
|
|
294
|
+
mode: 'off',
|
|
295
|
+
reason,
|
|
296
|
+
info: `control_type=${controlType}`,
|
|
297
|
+
heatingRequest: false,
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this._desiredHeat = false;
|
|
303
|
+
|
|
304
|
+
// Heizung aus
|
|
305
|
+
await this._setHeatingDevice(false, controlObjectId);
|
|
306
|
+
|
|
307
|
+
// Signal für andere Systeme
|
|
308
|
+
await this._setHeatStates({
|
|
309
|
+
active: false,
|
|
310
|
+
blocked: false,
|
|
311
|
+
mode: 'afterrun',
|
|
312
|
+
reason,
|
|
313
|
+
info: `Heizung AUS | Nachlauf=${afterrunMin} min | control_type=${controlType}`,
|
|
314
|
+
heatingRequest: false,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Pumpen-Nachlauf nur, wenn wir die Pumpe eingeschaltet hatten
|
|
318
|
+
await this._startAfterrunIfNeeded(afterrunMin, reason);
|
|
319
|
+
|
|
320
|
+
this.adapter.log.info(`[heatHelper] Heizung AUS (${reason})`);
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
async _applyBlockedState(mode, reason, afterrunMin) {
|
|
324
|
+
// NEU: laufenden Prerun abbrechen
|
|
325
|
+
if (this._prerunTimer) {
|
|
326
|
+
clearTimeout(this._prerunTimer);
|
|
327
|
+
this._prerunTimer = null;
|
|
328
|
+
await this.adapter.setStateAsync('heat.prerun_active', { val: false, ack: true });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// blockiert => Heizung aus, Request false
|
|
332
|
+
await this._setHeatingDevice(false, (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '');
|
|
333
|
+
|
|
334
|
+
await this._setHeatStates({
|
|
335
|
+
active: false,
|
|
336
|
+
blocked: true,
|
|
337
|
+
mode,
|
|
338
|
+
reason,
|
|
339
|
+
info: 'Heizung blockiert',
|
|
340
|
+
heatingRequest: false,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Nachlauf ggf. (nur wenn wir ownen)
|
|
344
|
+
await this._startAfterrunIfNeeded(afterrunMin, reason);
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
async _applyOffState(mode, reason, afterrunMin) {
|
|
348
|
+
// NEU: laufenden Prerun abbrechen
|
|
349
|
+
if (this._prerunTimer) {
|
|
350
|
+
clearTimeout(this._prerunTimer);
|
|
351
|
+
this._prerunTimer = null;
|
|
352
|
+
await this.adapter.setStateAsync('heat.prerun_active', { val: false, ack: true });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// off => Heizung aus, Request false
|
|
356
|
+
await this._setHeatingDevice(false, (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '');
|
|
357
|
+
|
|
358
|
+
await this._setHeatStates({
|
|
359
|
+
active: false,
|
|
360
|
+
blocked: false,
|
|
361
|
+
mode,
|
|
362
|
+
reason,
|
|
363
|
+
info: 'Heizung AUS',
|
|
364
|
+
heatingRequest: false,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await this._startAfterrunIfNeeded(afterrunMin, reason);
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
// -------------------------------------------------------------
|
|
371
|
+
// Pump handling (ownership protected)
|
|
372
|
+
// -------------------------------------------------------------
|
|
373
|
+
async _ensurePumpOn() {
|
|
374
|
+
try {
|
|
375
|
+
const pumpState = await this.adapter.getStateAsync('pump.pump_switch');
|
|
376
|
+
const isOn = !!pumpState?.val;
|
|
377
|
+
|
|
378
|
+
if (!isOn) {
|
|
379
|
+
// wir schalten sie ein => ownership true
|
|
380
|
+
this._ownsPump = true;
|
|
381
|
+
await this.adapter.setStateAsync('pump.pump_switch', { val: true, ack: false });
|
|
382
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: false, ack: true });
|
|
383
|
+
}
|
|
384
|
+
} catch (err) {
|
|
385
|
+
this.adapter.log.warn(`[heatHelper] Konnte Pumpe nicht einschalten: ${err.message}`);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
async _startAfterrunIfNeeded(afterrunMin, reason) {
|
|
390
|
+
// nur wenn wir die Pumpe vorher selbst eingeschaltet haben
|
|
391
|
+
if (!this._ownsPump) {
|
|
392
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: false, ack: true });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// wenn keine Nachlaufzeit: sofort aus
|
|
397
|
+
if (!afterrunMin || afterrunMin <= 0) {
|
|
398
|
+
await this._stopPumpNow('no_afterrun');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Timer neu starten
|
|
403
|
+
if (this._afterrunTimer) {
|
|
404
|
+
clearTimeout(this._afterrunTimer);
|
|
405
|
+
this._afterrunTimer = null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: true, ack: true });
|
|
409
|
+
|
|
410
|
+
const holdMs = Math.round(afterrunMin * 60 * 1000);
|
|
411
|
+
this.adapter.log.debug(`[heatHelper] Pumpen-Nachlauf gestartet: ${afterrunMin} min (${reason})`);
|
|
412
|
+
|
|
413
|
+
this._afterrunTimer = setTimeout(async () => {
|
|
414
|
+
// Wenn inzwischen wieder Heizbedarf aktiv ist -> Nachlauf abbrechen
|
|
415
|
+
if (this._desiredHeat === true) {
|
|
416
|
+
this.adapter.log.debug('[heatHelper] Nachlauf abgebrochen – Heizen wieder aktiv.');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
await this._stopPumpNow('afterrun_done');
|
|
420
|
+
}, holdMs);
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
async _stopPumpNow(tag) {
|
|
424
|
+
try {
|
|
425
|
+
await this.adapter.setStateAsync('pump.pump_switch', { val: false, ack: false });
|
|
426
|
+
} catch (err) {
|
|
427
|
+
this.adapter.log.warn(`[heatHelper] Konnte Pumpe nicht ausschalten: ${err.message}`);
|
|
428
|
+
} finally {
|
|
429
|
+
this._ownsPump = false;
|
|
430
|
+
await this.adapter.setStateAsync('heat.afterrun_active', { val: false, ack: true });
|
|
431
|
+
await this.adapter.setStateAsync('heat.prerun_active', { val: false, ack: true });
|
|
432
|
+
this.adapter.log.info(`[heatHelper] Pumpe AUS (${tag})`);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// -------------------------------------------------------------
|
|
437
|
+
// Heating device control (foreign state)
|
|
438
|
+
// -------------------------------------------------------------
|
|
439
|
+
async _setHeatingDevice(on, foreignId) {
|
|
440
|
+
const id = String(foreignId || '').trim();
|
|
441
|
+
if (!id) {
|
|
442
|
+
// Kein Ziel => nur internes heating_request als Signal
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
await this.adapter.setForeignStateAsync(id, { val: !!on, ack: false });
|
|
448
|
+
} catch (err) {
|
|
449
|
+
this.adapter.log.warn(`[heatHelper] Konnte Heizung nicht setzen (${id}): ${err.message}`);
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
async _setHeatStates({ active, blocked, mode, reason, info, heatingRequest }) {
|
|
454
|
+
try {
|
|
455
|
+
await this.adapter.setStateAsync('heat.active', { val: !!active, ack: true });
|
|
456
|
+
await this.adapter.setStateAsync('heat.blocked', { val: !!blocked, ack: true });
|
|
457
|
+
await this.adapter.setStateAsync('heat.mode', { val: String(mode ?? ''), ack: true });
|
|
458
|
+
await this.adapter.setStateAsync('heat.reason', { val: String(reason ?? ''), ack: true });
|
|
459
|
+
await this.adapter.setStateAsync('heat.info', { val: String(info ?? ''), ack: true });
|
|
460
|
+
await this.adapter.setStateAsync('heat.heating_request', { val: !!heatingRequest, ack: true });
|
|
461
|
+
await this.adapter.setStateAsync('heat.last_change', { val: Date.now(), ack: true });
|
|
462
|
+
} catch (err) {
|
|
463
|
+
this.adapter.log.warn(`[heatHelper] Fehler beim Schreiben der Heat-States: ${err.message}`);
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// -------------------------------------------------------------
|
|
468
|
+
// Foreign subscribe handling
|
|
469
|
+
// -------------------------------------------------------------
|
|
470
|
+
async _refreshForeignSubscription() {
|
|
471
|
+
const id = (await this.adapter.getStateAsync('heat.control_object_id'))?.val || '';
|
|
472
|
+
const nextId = String(id).trim();
|
|
473
|
+
|
|
474
|
+
if (nextId && nextId !== this._heatControlForeignId) {
|
|
475
|
+
// neue ID abonnieren
|
|
476
|
+
try {
|
|
477
|
+
this.adapter.subscribeForeignStates(nextId);
|
|
478
|
+
this.adapter.log.info(`[heatHelper] Subscribed Foreign-Heat-Control: "${nextId}"`);
|
|
479
|
+
} catch (err) {
|
|
480
|
+
this.adapter.log.warn(`[heatHelper] Konnte Foreign-State nicht abonnieren (${nextId}): ${err.message}`);
|
|
481
|
+
}
|
|
482
|
+
this._heatControlForeignId = nextId;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!nextId) {
|
|
486
|
+
this._heatControlForeignId = '';
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
async _safeEvaluate(tag) {
|
|
491
|
+
try {
|
|
492
|
+
await this._evaluate(tag);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
this.adapter.log.warn(`[heatHelper] Evaluate-Fehler (${tag}): ${err.message}`);
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
cleanup() {
|
|
499
|
+
if (this._afterrunTimer) {
|
|
500
|
+
clearTimeout(this._afterrunTimer);
|
|
501
|
+
this._afterrunTimer = null;
|
|
502
|
+
}
|
|
503
|
+
if (this._prerunTimer) {
|
|
504
|
+
clearTimeout(this._prerunTimer);
|
|
505
|
+
this._prerunTimer = null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
this._ownsPump = false;
|
|
509
|
+
this._desiredHeat = null;
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
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', {
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
* @param {import("iobroker").Adapter} adapter - ioBroker Adapter-Instanz
|
|
10
10
|
*/
|
|
11
11
|
async function createGeneralStates(adapter) {
|
|
12
|
+
// Channel: Allgemein
|
|
13
|
+
await adapter.setObjectNotExistsAsync('general', {
|
|
14
|
+
type: 'channel',
|
|
15
|
+
common: {
|
|
16
|
+
name: 'Allgemeine Einstellungen',
|
|
17
|
+
},
|
|
18
|
+
native: {},
|
|
19
|
+
});
|
|
20
|
+
|
|
12
21
|
// Poolname
|
|
13
22
|
await adapter.setObjectNotExistsAsync('general.pool_name', {
|
|
14
23
|
type: 'state',
|
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
// --- NEU: Pumpen-Vorlaufzeit vor Heizstart ---
|
|
133
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.pump_prerun_minutes`, {
|
|
134
|
+
type: 'state',
|
|
135
|
+
common: {
|
|
136
|
+
name: 'Pumpen-Vorlaufzeit vor Heizung',
|
|
137
|
+
type: 'number',
|
|
138
|
+
role: 'value.interval',
|
|
139
|
+
unit: 'min',
|
|
140
|
+
read: true,
|
|
141
|
+
write: true,
|
|
142
|
+
persist: true,
|
|
143
|
+
},
|
|
144
|
+
native: {},
|
|
145
|
+
});
|
|
146
|
+
const existingPrerun = await adapter.getStateAsync(`${channelId}.pump_prerun_minutes`);
|
|
147
|
+
if (existingPrerun === null || existingPrerun.val === null || existingPrerun.val === undefined) {
|
|
148
|
+
await adapter.setStateAsync(`${channelId}.pump_prerun_minutes`, {
|
|
149
|
+
val: 0,
|
|
150
|
+
ack: true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.pump_afterrun_minutes`, {
|
|
155
|
+
type: 'state',
|
|
156
|
+
common: {
|
|
157
|
+
name: 'Pumpen-Nachlaufzeit nach Heizung',
|
|
158
|
+
type: 'number',
|
|
159
|
+
role: 'value.interval',
|
|
160
|
+
unit: 'min',
|
|
161
|
+
read: true,
|
|
162
|
+
write: true,
|
|
163
|
+
persist: true,
|
|
164
|
+
},
|
|
165
|
+
native: {},
|
|
166
|
+
});
|
|
167
|
+
const existingAfterrun = await adapter.getStateAsync(`${channelId}.pump_afterrun_minutes`);
|
|
168
|
+
if (existingAfterrun === null || existingAfterrun.val === null || existingAfterrun.val === undefined) {
|
|
169
|
+
await adapter.setStateAsync(`${channelId}.pump_afterrun_minutes`, {
|
|
170
|
+
val: 5,
|
|
171
|
+
ack: true,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------
|
|
176
|
+
// Betriebsstatus (write = false)
|
|
177
|
+
// ---------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.active`, {
|
|
180
|
+
type: 'state',
|
|
181
|
+
common: {
|
|
182
|
+
name: 'Heizung aktiv',
|
|
183
|
+
type: 'boolean',
|
|
184
|
+
role: 'indicator.working',
|
|
185
|
+
read: true,
|
|
186
|
+
write: false,
|
|
187
|
+
},
|
|
188
|
+
native: {},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.mode`, {
|
|
192
|
+
type: 'state',
|
|
193
|
+
common: {
|
|
194
|
+
name: 'Heizungsmodus',
|
|
195
|
+
type: 'string',
|
|
196
|
+
role: 'text',
|
|
197
|
+
read: true,
|
|
198
|
+
write: false,
|
|
199
|
+
},
|
|
200
|
+
native: {},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.blocked`, {
|
|
204
|
+
type: 'state',
|
|
205
|
+
common: {
|
|
206
|
+
name: 'Heizung blockiert',
|
|
207
|
+
type: 'boolean',
|
|
208
|
+
role: 'indicator.blocked',
|
|
209
|
+
read: true,
|
|
210
|
+
write: false,
|
|
211
|
+
},
|
|
212
|
+
native: {},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// --- NEU: Pumpen-Vorlauf aktiv ---
|
|
216
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.prerun_active`, {
|
|
217
|
+
type: 'state',
|
|
218
|
+
common: {
|
|
219
|
+
name: 'Pumpen-Vorlauf aktiv',
|
|
220
|
+
type: 'boolean',
|
|
221
|
+
role: 'indicator.working',
|
|
222
|
+
read: true,
|
|
223
|
+
write: false,
|
|
224
|
+
},
|
|
225
|
+
native: {},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.afterrun_active`, {
|
|
229
|
+
type: 'state',
|
|
230
|
+
common: {
|
|
231
|
+
name: 'Pumpen-Nachlauf aktiv',
|
|
232
|
+
type: 'boolean',
|
|
233
|
+
role: 'indicator.working',
|
|
234
|
+
read: true,
|
|
235
|
+
write: false,
|
|
236
|
+
},
|
|
237
|
+
native: {},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.last_change`, {
|
|
241
|
+
type: 'state',
|
|
242
|
+
common: {
|
|
243
|
+
name: 'Letzte Statusänderung Heizung',
|
|
244
|
+
type: 'number',
|
|
245
|
+
role: 'value.time',
|
|
246
|
+
read: true,
|
|
247
|
+
write: false,
|
|
248
|
+
},
|
|
249
|
+
native: {},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.heating_request`, {
|
|
253
|
+
type: 'state',
|
|
254
|
+
common: {
|
|
255
|
+
name: 'Heizanforderung (PoolControl)',
|
|
256
|
+
type: 'boolean',
|
|
257
|
+
role: 'indicator.request',
|
|
258
|
+
read: true,
|
|
259
|
+
write: false,
|
|
260
|
+
def: false,
|
|
261
|
+
},
|
|
262
|
+
native: {},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ---------------------------------------------------------
|
|
266
|
+
// Transparenz / Diagnose
|
|
267
|
+
// ---------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.reason`, {
|
|
270
|
+
type: 'state',
|
|
271
|
+
common: {
|
|
272
|
+
name: 'Grund für Heizungsstatus',
|
|
273
|
+
type: 'string',
|
|
274
|
+
role: 'text',
|
|
275
|
+
read: true,
|
|
276
|
+
write: false,
|
|
277
|
+
},
|
|
278
|
+
native: {},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
await adapter.setObjectNotExistsAsync(`${channelId}.info`, {
|
|
282
|
+
type: 'state',
|
|
283
|
+
common: {
|
|
284
|
+
name: 'Heizungs-Info',
|
|
285
|
+
type: 'string',
|
|
286
|
+
role: 'text',
|
|
287
|
+
read: true,
|
|
288
|
+
write: false,
|
|
289
|
+
},
|
|
290
|
+
native: {},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = {
|
|
295
|
+
createHeatStates,
|
|
296
|
+
};
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
* @param {import("iobroker").Adapter} adapter - ioBroker Adapter-Instanz
|
|
14
14
|
*/
|
|
15
15
|
async function createPumpStates(adapter) {
|
|
16
|
+
// Channel: Pumpe
|
|
17
|
+
await adapter.setObjectNotExistsAsync('pump', {
|
|
18
|
+
type: 'channel',
|
|
19
|
+
common: {
|
|
20
|
+
name: 'Pumpe',
|
|
21
|
+
},
|
|
22
|
+
native: {},
|
|
23
|
+
});
|
|
24
|
+
|
|
16
25
|
// Max. Pumpenleistung (W)
|
|
17
26
|
await adapter.setObjectNotExistsAsync('pump.pump_max_watt', {
|
|
18
27
|
type: 'state',
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
* @param {import("iobroker").Adapter} adapter - ioBroker Adapter-Instanz
|
|
15
15
|
*/
|
|
16
16
|
async function createSolarStates(adapter) {
|
|
17
|
+
// Channel: Solar
|
|
18
|
+
await adapter.setObjectNotExistsAsync('solar', {
|
|
19
|
+
type: 'channel',
|
|
20
|
+
common: {
|
|
21
|
+
name: 'Solar',
|
|
22
|
+
},
|
|
23
|
+
native: {},
|
|
24
|
+
});
|
|
25
|
+
|
|
17
26
|
// Solarsteuerung aktiv (mit Persist-Schutz)
|
|
18
27
|
await adapter.setObjectNotExistsAsync('solar.solar_control_active', {
|
|
19
28
|
type: 'state',
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
* @param {import("iobroker").Adapter} adapter - ioBroker Adapter-Instanz
|
|
10
10
|
*/
|
|
11
11
|
async function createTimeStates(adapter) {
|
|
12
|
+
// Channel: Zeitsteuerung
|
|
13
|
+
await adapter.setObjectNotExistsAsync('timecontrol', {
|
|
14
|
+
type: 'channel',
|
|
15
|
+
common: {
|
|
16
|
+
name: 'Zeitsteuerung',
|
|
17
|
+
},
|
|
18
|
+
native: {},
|
|
19
|
+
});
|
|
20
|
+
|
|
12
21
|
async function createTimeWindow(prefix, label) {
|
|
13
22
|
// Aktiv
|
|
14
23
|
await adapter.setObjectNotExistsAsync(`timecontrol.${prefix}_active`, {
|
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.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",
|