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 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
 
@@ -421,7 +421,7 @@
421
421
  },
422
422
  "solar": {
423
423
  "type": "panel",
424
- "label": "Solarverwaltung",
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.8.2",
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äte­steuerung 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.8.2",
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",