iobroker.poolcontrol 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -121,6 +121,24 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
121
121
  ## Changelog
122
122
  ### **WORK IN PROGRESS**
123
123
 
124
+ ### 0.3.0 (12.10.2025)
125
+ **Neu:** Intelligentes Pumpen-Monitoring & Lernsystem
126
+
127
+ - Hinzugefügt: **Reelle Durchflussberechnung** auf Basis der tatsächlichen Leistungsaufnahme.
128
+ - Neuer Bereich **`pump.live`** zur Live-Überwachung von Leistung, Durchfluss und prozentualer Auslastung.
129
+ - Die **tägliche Umwälzberechnung** verwendet nun den realen Durchflusswert anstelle eines Fixwerts.
130
+ - Neuer Lernbereich **`pump.learning`**:
131
+ - Lernt automatisch die durchschnittlichen Leistungs- und Durchflusswerte.
132
+ - Bestimmt daraus einen dynamischen **Normalbereich (± 15 %)**.
133
+ - Berechnet prozentuale Abweichungen und erstellt **textbasierte Statusmeldungen**.
134
+ - Alle Lernwerte werden **persistent** gespeichert und bleiben auch nach Neustart erhalten.
135
+ - Vollständig **ereignisgesteuerte Logik** ohne zusätzliche Timer oder Polling-Zyklen.
136
+
137
+ > Mit dieser Version beginnt die lernfähige Phase des PoolControl-Adapters:
138
+ > Deine Pumpe weiß jetzt selbst, was für sie „normal“ ist.
139
+
140
+ ---
141
+
124
142
  ### 0.2.2 (2025-10-08)
125
143
  - Einführung einer **automatischen Rückspülerinnerung** (neuer Bereich im ControlHelper2)
126
144
  - Erinnerung mit Intervall in Tagen und automatischer Rücksetzung nach erfolgter Rückspülung
package/io-package.json CHANGED
@@ -1,8 +1,20 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "poolcontrol",
4
- "version": "0.2.2",
4
+ "version": "0.3.0",
5
5
  "news": {
6
+ "0.3.0": {
7
+ "en": "Added real pump flow calculation, live monitoring, and self-learning normal range system for pump analysis.",
8
+ "de": "Reelle Durchflussberechnung, Liveüberwachung und selbstlernendes Normalbereich-System zur Pumpenanalyse hinzugefügt.",
9
+ "ru": "Добавлен расчет реального расхода насоса, живой мониторинг и самообучающаяся система нормальных диапазонов для анализа насоса.",
10
+ "pt": "Adicionada cálculo de fluxo real da bomba, monitoramento ao vivo e sistema autoaprendente de faixa normal para análise da bomba.",
11
+ "nl": "Reële pompdebietberekening, livebewaking en zelflerend normaalbereiksysteem voor pompanalyse toegevoegd.",
12
+ "fr": "Ajout du calcul du débit réel de la pompe, de la surveillance en direct et d’un système de plage normale auto-apprenant pour l’analyse de la pompe.",
13
+ "it": "Aggiunto calcolo del flusso reale della pompa, monitoraggio live e sistema autoapprendente di intervallo normale per l'analisi della pompa.",
14
+ "es": "Se añadió el cálculo del caudal real de la bomba, la monitorización en vivo y un sistema de rango normal autoaprendente para el análisis de la bomba.",
15
+ "pl": "Dodano obliczanie rzeczywistego przepływu pompy, monitorowanie na żywo i samouczący się system normalnego zakresu do analizy pompy.",
16
+ "zh-cn": "添加了实际泵流量计算、实时监控以及用于泵分析的自学习正常范围系统。"
17
+ },
6
18
  "0.2.2": {
7
19
  "en": "Added automatic backwash reminder with speech and log notifications.",
8
20
  "de": "Automatische Rückspülerinnerung mit Sprach- und Log-Benachrichtigung hinzugefügt.",
@@ -40,33 +52,7 @@
40
52
  "pl": "Nowy obszar diagnostyczny 'SystemCheck' dla wewnętrznych dzienników debugowania i przyszłych narzędzi analitycznych.",
41
53
  "uk": "Нова діагностична область 'SystemCheck' для внутрішніх журналів налагодження та майбутніх інструментів аналізу.",
42
54
  "zh-cn": "新的诊断区域“SystemCheck”,用于内部调试日志和未来的分析工具。"
43
- },
44
- "0.1.2": {
45
- "en": "Improved: The last known temperature values of all active sensors are now read at adapter start. Sensors that report rarely are now displayed correctly.",
46
- "de": "Verbesserung: Beim Adapterstart werden nun die letzten bekannten Temperaturwerte aller aktiven Sensoren übernommen. Sensoren, die nur selten Werte senden, werden jetzt korrekt angezeigt.",
47
- "ru": "Улучшение: При запуске адаптера теперь считываются последние известные значения температуры всех активных датчиков.",
48
- "pt": "Melhoria: Os últimos valores de temperatura conhecidos de todos os sensores ativos agora são lidos ao iniciar o adaptador.",
49
- "nl": "Verbetering: Bij het starten van de adapter worden nu de laatst bekende temperatuurwaarden van alle actieve sensoren opgehaald.",
50
- "fr": "Amélioration : les dernières valeurs de température connues de tous les capteurs actifs sont désormais lues au démarrage de l’adaptateur.",
51
- "it": "Miglioramento: all'avvio dell'adattatore vengono ora letti gli ultimi valori di temperatura noti di tutti i sensori attivi.",
52
- "es": "Mejora: ahora se leen al iniciar el adaptador los últimos valores de temperatura conocidos de todos los sensores activos.",
53
- "pl": "Ulepszenie: podczas uruchamiania adaptera odczytywane są ostatnie znane wartości temperatury wszystkich aktywnych czujników.",
54
- "uk": "Покращення: під час запуску адаптера тепер зчитуються останні відомі значення температури всіх активних датчиків.",
55
- "zh-cn": "改进:适配器启动时现在会读取所有活动传感器的最后已知温度值。"
56
- },
57
- "0.1.1": {
58
- "en": "Fixed endless loop between pump_switch and deviceId for some smart sockets",
59
- "de": "Endlosschleife zwischen pump_switch und Steckdose (deviceId) behoben",
60
- "ru": "Исправлен бесконечный цикл между pump_switch и deviceId для некоторых умных розеток",
61
- "pt": "Corrigido loop infinito entre pump_switch e deviceId em algumas tomadas inteligentes",
62
- "nl": "Eindeloze lus tussen pump_switch en deviceId voor sommige slimme stopcontacten opgelost",
63
- "fr": "Correction d'une boucle infinie entre pump_switch et deviceId pour certaines prises intelligentes",
64
- "it": "Corretto loop infinito tra pump_switch e deviceId per alcune prese intelligenti",
65
- "es": "Corregido bucle infinito entre pump_switch y deviceId en algunos enchufes inteligentes",
66
- "pl": "Naprawiono nieskończoną pętlę między pump_switch a deviceId dla niektórych inteligentnych gniazdek",
67
- "uk": "Виправлено нескінченний цикл між pump_switch і deviceId для деяких розумних розеток",
68
- "zh-cn": "修复了某些智能插座中 pump_switch 与 deviceId 之间的无限循环"
69
- }
55
+ }
70
56
  },
71
57
  "titleLang": {
72
58
  "en": "PoolControl",
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * migrationHelper
5
+ * --------------------------------------------------
6
+ * Führt nachträgliche Struktur- oder State-Anpassungen
7
+ * für bestehende Installationen durch.
8
+ *
9
+ * - Wird beim Adapterstart einmalig ausgeführt.
10
+ * - Korrigiert veraltete Definitionen (z. B. Schreibrechte, persist-Flags, etc.)
11
+ *
12
+ * Version: 1.0.1
13
+ */
14
+
15
+ const migrationHelper = {
16
+ adapter: null,
17
+
18
+ /**
19
+ * Initialisiert den Migration-Helper.
20
+ * Wird einmalig beim Adapterstart aufgerufen.
21
+ *
22
+ * @param {ioBroker.Adapter} adapter - Die aktive Adapterinstanz
23
+ */
24
+ async init(adapter) {
25
+ this.adapter = adapter;
26
+ this.adapter.log.info('[migrationHelper] Starte Migration-Check ...');
27
+
28
+ try {
29
+ // ------------------------------------------------------
30
+ // Hier alle Migrationsroutinen nacheinander aufrufen
31
+ // ------------------------------------------------------
32
+ await this._fixSpeechQueue();
33
+ await this._fixSolarWarnActivePersist();
34
+
35
+ // Weitere Routinen folgen hier später:
36
+ // await this._ensurePumpReason();
37
+ // await this._cleanupOldStates();
38
+
39
+ this.adapter.log.debug('[migrationHelper] Migration-Checks abgeschlossen.');
40
+ } catch (err) {
41
+ this.adapter.log.warn(`[migrationHelper] Fehler beim Migration-Check: ${err.message}`);
42
+ }
43
+
44
+ this.adapter.log.info('[migrationHelper] Migration-Helper beendet.');
45
+ },
46
+
47
+ // ------------------------------------------------------
48
+ // Migration: Schreibrecht für speech.queue korrigieren
49
+ // ------------------------------------------------------
50
+
51
+ /**
52
+ * Prüft und korrigiert den State "speech.queue", falls er noch write:false gesetzt hat.
53
+ * Dadurch verschwinden Warnungen beim Schreiben (Read-only state ... written without ack).
54
+ */
55
+ async _fixSpeechQueue() {
56
+ const id = 'speech.queue';
57
+ try {
58
+ const obj = await this.adapter.getObjectAsync(id);
59
+ if (!obj) {
60
+ this.adapter.log.debug(`[migrationHelper] ${id} existiert nicht – keine Anpassung nötig.`);
61
+ return;
62
+ }
63
+
64
+ const isReadOnly = obj.common?.write === false;
65
+ if (isReadOnly) {
66
+ this.adapter.log.info(`[migrationHelper] Aktualisiere Schreibrecht für ${id} → write:true`);
67
+ await this.adapter.extendObjectAsync(id, {
68
+ common: {
69
+ write: true,
70
+ desc: 'Nur intern durch den Adapter beschreibbar (nicht manuell ändern!)',
71
+ },
72
+ });
73
+ } else {
74
+ this.adapter.log.debug(`[migrationHelper] ${id} ist bereits korrekt konfiguriert.`);
75
+ }
76
+ } catch (err) {
77
+ this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
78
+ }
79
+ },
80
+
81
+ // ------------------------------------------------------
82
+ // Migration: persist-Flag für solar.warn_active ergänzen
83
+ // ------------------------------------------------------
84
+
85
+ /**
86
+ * Ergänzt persist:true bei solar.warn_active,
87
+ * damit die Einstellung (Warnfunktion aktivieren/deaktivieren)
88
+ * nach einem Neustart erhalten bleibt.
89
+ */
90
+ async _fixSolarWarnActivePersist() {
91
+ const id = 'solar.warn_active';
92
+ try {
93
+ const obj = await this.adapter.getObjectAsync(id);
94
+ if (!obj) {
95
+ this.adapter.log.debug(`[migrationHelper] ${id} existiert nicht – keine Anpassung nötig.`);
96
+ return;
97
+ }
98
+
99
+ const hasPersist = obj.common?.persist === true;
100
+ if (!hasPersist) {
101
+ this.adapter.log.info(`[migrationHelper] Ergänze persist:true für ${id}`);
102
+ await this.adapter.extendObjectAsync(id, {
103
+ common: {
104
+ persist: true,
105
+ desc: `${obj.common?.desc || ''} (automatisch per Migration persistiert)`,
106
+ },
107
+ });
108
+ } else {
109
+ this.adapter.log.debug(`[migrationHelper] ${id} ist bereits mit persist:true versehen.`);
110
+ }
111
+ } catch (err) {
112
+ this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
113
+ }
114
+ },
115
+ };
116
+
117
+ module.exports = migrationHelper;
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * pumpHelper2.js
5
+ * ----------------------------------------------------------
6
+ * Ergänzende Berechnungslogik für Pumpen-Livewerte
7
+ * (reeller Durchfluss, Prozentleistung, letzter Durchflusswert).
8
+ *
9
+ * Verwendet vorhandene States:
10
+ * - pump.current_power
11
+ * - pump.pump_max_watt
12
+ * - pump.pump_power_lph
13
+ * - pump.pump_switch
14
+ *
15
+ * Schreibt neue Werte in:
16
+ * - pump.live.current_power_w
17
+ * - pump.live.flow_current_lh
18
+ * - pump.live.flow_percent
19
+ * - pump.live.last_flow_lh
20
+ *
21
+ * Alle Zielstates sind persistent (siehe pumpStates2.js).
22
+ * ----------------------------------------------------------
23
+ * Version: 1.0.0
24
+ */
25
+
26
+ const pumpHelper2 = {
27
+ adapter: null,
28
+
29
+ /**
30
+ * Initialisiert den PumpHelper2
31
+ *
32
+ * @param {ioBroker.Adapter} adapter - Aktive ioBroker Adapterinstanz
33
+ */
34
+ async init(adapter) {
35
+ this.adapter = adapter;
36
+ this.adapter.log.info('[pumpHelper2] Initialisierung gestartet');
37
+
38
+ // Relevante States überwachen
39
+ this.adapter.subscribeStates('pump.current_power');
40
+ this.adapter.subscribeStates('pump.pump_switch');
41
+
42
+ // Initialwerte berechnen
43
+ await this._updateLiveValues();
44
+
45
+ this.adapter.log.info('[pumpHelper2] Erfolgreich initialisiert');
46
+ },
47
+
48
+ /**
49
+ * StateChange-Verarbeitung
50
+ *
51
+ * @param {string} id - State-ID
52
+ * @param {ioBroker.State | null | undefined} state - Neuer Statewert
53
+ */
54
+ async handleStateChange(id, state) {
55
+ if (!state || state.ack === false) {
56
+ return;
57
+ }
58
+
59
+ // Leistungsänderung → reelle Durchflusswerte aktualisieren
60
+ if (id.endsWith('pump.current_power')) {
61
+ await this._updateLiveValues();
62
+ }
63
+
64
+ // Pumpenstatus-Änderung → ggf. letzten Durchflusswert speichern
65
+ if (id.endsWith('pump.pump_switch')) {
66
+ const pumpOn = state.val === true;
67
+ if (!pumpOn) {
68
+ await this._storeLastFlowValue();
69
+ }
70
+ }
71
+ },
72
+
73
+ /**
74
+ * Führt die Berechnung der Livewerte durch.
75
+ * (Nur wenn gültige Basiswerte vorhanden sind.)
76
+ */
77
+ async _updateLiveValues() {
78
+ try {
79
+ const [currentPower, maxPower, nominalFlow] = await Promise.all([
80
+ this._getNumber('pump.current_power'),
81
+ this._getNumber('pump.pump_max_watt'),
82
+ this._getNumber('pump.pump_power_lph'),
83
+ ]);
84
+
85
+ // Schutz gegen ungültige Werte
86
+ if (maxPower <= 0 || nominalFlow <= 0) {
87
+ this.adapter.log.debug('[pumpHelper2] Ungültige Basiswerte, Berechnung übersprungen');
88
+ return;
89
+ }
90
+
91
+ // Prozentuale Auslastung
92
+ const flowPercent = Math.min(Math.max((currentPower / maxPower) * 100, 0), 100);
93
+
94
+ // Reeller Durchfluss
95
+ const flowCurrentLh = Math.round(nominalFlow * (currentPower / maxPower) * 10) / 10; // 1 Nachkommastelle
96
+
97
+ // In States schreiben
98
+ await this._setIfChanged('pump.live.current_power_w', currentPower);
99
+ await this._setIfChanged('pump.live.flow_current_lh', flowCurrentLh);
100
+ await this._setIfChanged('pump.live.flow_percent', flowPercent);
101
+
102
+ this.adapter.log.debug(
103
+ `[pumpHelper2] Reeller Durchfluss aktualisiert: ${flowCurrentLh} l/h (${flowPercent.toFixed(1)}%)`,
104
+ );
105
+ } catch (err) {
106
+ this.adapter.log.warn(`[pumpHelper2] Fehler bei _updateLiveValues: ${err.message}`);
107
+ }
108
+ },
109
+
110
+ /**
111
+ * Speichert den letzten bekannten Durchflusswert beim Pumpen-Stopp.
112
+ */
113
+ async _storeLastFlowValue() {
114
+ try {
115
+ const currentFlow = await this._getNumber('pump.live.flow_current_lh');
116
+ await this._setIfChanged('pump.live.last_flow_lh', currentFlow);
117
+ this.adapter.log.debug(`[pumpHelper2] Letzter Durchflusswert gespeichert: ${currentFlow} l/h`);
118
+ } catch (err) {
119
+ this.adapter.log.warn(`[pumpHelper2] Fehler bei _storeLastFlowValue: ${err.message}`);
120
+ }
121
+ },
122
+
123
+ /**
124
+ * Liest einen numerischen Statewert (oder 0 bei Fehler).
125
+ *
126
+ * @param {string} id - Objekt-ID des zu lesenden States
127
+ * @returns {Promise<number>} - Aktueller numerischer Wert oder 0
128
+ */
129
+ async _getNumber(id) {
130
+ const state = await this.adapter.getStateAsync(id);
131
+ const val = Number(state?.val);
132
+ return isNaN(val) ? 0 : val;
133
+ },
134
+
135
+ /**
136
+ * Schreibt neuen Wert nur, wenn er sich geändert hat.
137
+ *
138
+ * @param {string} id - Objekt-ID des zu schreibenden States
139
+ * @param {number} newVal - Neuer Wert, des gesetzt werden soll
140
+ */
141
+ async _setIfChanged(id, newVal) {
142
+ const current = await this.adapter.getStateAsync(id);
143
+ if (current && Number(current.val) === newVal) {
144
+ return;
145
+ }
146
+ await this.adapter.setStateAsync(id, { val: newVal, ack: true });
147
+ },
148
+
149
+ /**
150
+ * Cleanup bei Adapter-Unload
151
+ * Wird aktuell nur als Platzhalter verwendet.
152
+ */
153
+ cleanup() {
154
+ // Derzeit keine Timer oder Intervalle vorhanden
155
+ // Platzhalter für zukünftige Erweiterungen
156
+ this.adapter?.log.debug('[pumpHelper2] Cleanup ausgeführt.');
157
+ },
158
+ };
159
+
160
+ module.exports = pumpHelper2;
@@ -0,0 +1,259 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * pumpHelper3.js
5
+ * ----------------------------------------------------------
6
+ * Lern- und Analysemodul für die Pumpe
7
+ * - Ermittelt Durchschnitts- und Normalwerte aus realen Laufzyklen
8
+ * - Berechnet Abweichungen (Leistung & Durchfluss)
9
+ * - Liefert textbasierte Statusbewertung ("im Normalbereich" etc.)
10
+ * - Keine Sprach- oder Queue-Ausgabe
11
+ *
12
+ * Verwaltet:
13
+ * pump.learning.*
14
+ *
15
+ * Abhängigkeiten:
16
+ * - pump.live.current_power_w
17
+ * - pump.live.flow_current_lh
18
+ * - pump.pump_switch
19
+ *
20
+ * Version: 1.0.0
21
+ */
22
+
23
+ const pumpHelper3 = {
24
+ adapter: null,
25
+
26
+ /** interner Speicher */
27
+ currentSessionValues: {
28
+ power: [],
29
+ flow: [],
30
+ },
31
+
32
+ /**
33
+ * Initialisiert den Helper
34
+ *
35
+ * @param {ioBroker.Adapter} adapter - aktive Adapterinstanz
36
+ */
37
+ async init(adapter) {
38
+ this.adapter = adapter;
39
+ this.adapter.log.info('[pumpHelper3] Initialisierung gestartet');
40
+
41
+ // Relevante States überwachen
42
+ this.adapter.subscribeStates('pump.pump_switch');
43
+ this.adapter.subscribeStates('pump.live.current_power_w');
44
+ this.adapter.subscribeStates('pump.live.flow_current_lh');
45
+
46
+ this.adapter.log.info('[pumpHelper3] Erfolgreich initialisiert');
47
+ },
48
+
49
+ /**
50
+ * Verarbeitet State-Änderungen
51
+ *
52
+ * @param {string} id - State-ID
53
+ * @param {ioBroker.State} state - neuer Statewert
54
+ */
55
+ async handleStateChange(id, state) {
56
+ if (!state || state.ack === false) {
57
+ return;
58
+ }
59
+
60
+ try {
61
+ if (id.endsWith('pump.pump_switch')) {
62
+ const isOn = state.val === true;
63
+ if (isOn) {
64
+ // Pumpe startet → neue Sitzung
65
+ this.currentSessionValues.power = [];
66
+ this.currentSessionValues.flow = [];
67
+ this.adapter.log.debug('[pumpHelper3] Neue Lern-Session gestartet');
68
+ } else {
69
+ // Pumpe stoppt → Lernwerte aktualisieren
70
+ await this._finalizeLearningCycle();
71
+ }
72
+ }
73
+
74
+ // Wenn Pumpe läuft, aktuelle Werte sammeln
75
+ const pumpRunning = !!(await this.adapter.getStateAsync('pump.pump_switch'))?.val;
76
+ if (pumpRunning) {
77
+ if (id.endsWith('pump.live.current_power_w')) {
78
+ this._pushValue('power', state.val);
79
+ }
80
+ if (id.endsWith('pump.live.flow_current_lh')) {
81
+ this._pushValue('flow', state.val);
82
+ }
83
+ }
84
+
85
+ // Bei jeder Änderung der Livewerte aktuelle Abweichung bewerten
86
+ await this._updateDeviationAndStatus();
87
+ } catch (err) {
88
+ this.adapter.log.warn(`[pumpHelper3] Fehler bei handleStateChange: ${err.message}`);
89
+ }
90
+ },
91
+
92
+ /**
93
+ * Fügt einen neuen Wert in den temporären Speicher ein.
94
+ *
95
+ * @param {"power"|"flow"} type - Art des Wertes
96
+ * @param {number} value - neuer Messwert
97
+ */
98
+ _pushValue(type, value) {
99
+ if (!this.currentSessionValues[type]) {
100
+ this.currentSessionValues[type] = [];
101
+ }
102
+ if (typeof value === 'number' && value > 0) {
103
+ this.currentSessionValues[type].push(value);
104
+ }
105
+ },
106
+
107
+ /**
108
+ * Wird aufgerufen, wenn ein Pumpenlauf endet.
109
+ * Berechnet Durchschnittswerte des Laufs und aktualisiert Lernfelder.
110
+ */
111
+ async _finalizeLearningCycle() {
112
+ try {
113
+ const { power, flow } = this.currentSessionValues;
114
+ if (power.length === 0 || flow.length === 0) {
115
+ this.adapter.log.debug('[pumpHelper3] Keine Werte zum Lernen vorhanden, Zyklus übersprungen');
116
+ return;
117
+ }
118
+
119
+ const avgPower = this._average(power);
120
+ const avgFlow = this._average(flow);
121
+
122
+ // Vorhandene Lernwerte lesen
123
+ const learnedPower = (await this._getNumber('pump.learning.learned_avg_power_w')) || 0;
124
+ const learnedFlow = (await this._getNumber('pump.learning.learned_avg_flow_lh')) || 0;
125
+ const cycles = (await this._getNumber('pump.learning.learning_cycles_total')) || 0;
126
+
127
+ // Gleitenden Durchschnitt berechnen
128
+ const newCycles = cycles + 1;
129
+ const newAvgPower = (learnedPower * cycles + avgPower) / newCycles;
130
+ const newAvgFlow = (learnedFlow * cycles + avgFlow) / newCycles;
131
+
132
+ // Normalbereiche ±15 %
133
+ const rangePowerLow = Math.round(newAvgPower * 0.85);
134
+ const rangePowerHigh = Math.round(newAvgPower * 1.15);
135
+ const rangeFlowLow = Math.round(newAvgFlow * 0.85);
136
+ const rangeFlowHigh = Math.round(newAvgFlow * 1.15);
137
+
138
+ // States schreiben
139
+ await this.adapter.setStateAsync('pump.learning.learned_avg_power_w', {
140
+ val: Math.round(newAvgPower),
141
+ ack: true,
142
+ });
143
+ await this.adapter.setStateAsync('pump.learning.learned_avg_flow_lh', {
144
+ val: Math.round(newAvgFlow),
145
+ ack: true,
146
+ });
147
+ await this.adapter.setStateAsync('pump.learning.normal_range_power_low', { val: rangePowerLow, ack: true });
148
+ await this.adapter.setStateAsync('pump.learning.normal_range_power_high', {
149
+ val: rangePowerHigh,
150
+ ack: true,
151
+ });
152
+ await this.adapter.setStateAsync('pump.learning.normal_range_flow_low', { val: rangeFlowLow, ack: true });
153
+ await this.adapter.setStateAsync('pump.learning.normal_range_flow_high', { val: rangeFlowHigh, ack: true });
154
+ await this.adapter.setStateAsync('pump.learning.learning_cycles_total', { val: newCycles, ack: true });
155
+
156
+ this.adapter.log.debug(
157
+ `[pumpHelper3] Lernzyklus #${newCycles} abgeschlossen (Power Ø${avgPower}W, Flow Ø${avgFlow}l/h)`,
158
+ );
159
+
160
+ // Speicher leeren
161
+ this.currentSessionValues.power = [];
162
+ this.currentSessionValues.flow = [];
163
+ } catch (err) {
164
+ this.adapter.log.warn(`[pumpHelper3] Fehler bei _finalizeLearningCycle: ${err.message}`);
165
+ }
166
+ },
167
+
168
+ /**
169
+ * Bewertet aktuelle Abweichungen und schreibt Status.
170
+ */
171
+ async _updateDeviationAndStatus() {
172
+ try {
173
+ const currentPower = await this._getNumber('pump.live.current_power_w');
174
+ const currentFlow = await this._getNumber('pump.live.flow_current_lh');
175
+ const avgPower = await this._getNumber('pump.learning.learned_avg_power_w');
176
+ const avgFlow = await this._getNumber('pump.learning.learned_avg_flow_lh');
177
+
178
+ if (avgPower <= 0 || avgFlow <= 0) {
179
+ return;
180
+ } // Noch kein Lernwert vorhanden
181
+
182
+ // Prozentuale Abweichungen
183
+ const deviationPower = ((currentPower - avgPower) / avgPower) * 100;
184
+ const deviationFlow = ((currentFlow - avgFlow) / avgFlow) * 100;
185
+
186
+ await this.adapter.setStateAsync('pump.learning.deviation_power_percent', {
187
+ val: Math.round(deviationPower * 10) / 10,
188
+ ack: true,
189
+ });
190
+ await this.adapter.setStateAsync('pump.learning.deviation_flow_percent', {
191
+ val: Math.round(deviationFlow * 10) / 10,
192
+ ack: true,
193
+ });
194
+
195
+ // Bewertung
196
+ const statusText = this._getStatusText(deviationPower, deviationFlow);
197
+ await this.adapter.setStateAsync('pump.learning.status_text', { val: statusText, ack: true });
198
+ } catch (err) {
199
+ this.adapter.log.warn(`[pumpHelper3] Fehler bei _updateDeviationAndStatus: ${err.message}`);
200
+ }
201
+ },
202
+
203
+ /**
204
+ * Liefert textbasierte Bewertung anhand der Abweichung.
205
+ *
206
+ * @param devPower - Aktuelle Abweichung der Leistung (W)
207
+ * @param devFlow - Aktuelle Abweichung des Durchflusses (L/H)
208
+ */
209
+ _getStatusText(devPower, devFlow) {
210
+ const absPower = Math.abs(devPower);
211
+ const absFlow = Math.abs(devFlow);
212
+
213
+ if (absPower <= 15 && absFlow <= 15) {
214
+ return 'Pumpe läuft im Normalbereich';
215
+ }
216
+ if (devPower < -15 || devFlow < -15) {
217
+ return 'Pumpe läuft unterhalb des Normalbereichs (möglicher Filterdruck)';
218
+ }
219
+ if (devPower > 15 || devFlow > 15) {
220
+ return 'Pumpe läuft oberhalb des Normalbereichs (möglicher Luftstau)';
221
+ }
222
+ return 'Pumpe außerhalb des bekannten Bereichs';
223
+ },
224
+
225
+ /**
226
+ * Erzeugt Durchschnittswerte aus einem Array numerischer Werte.
227
+ *
228
+ * @param {number[]} arr - Wertearray
229
+ * @returns {number} - Durchschnittswert
230
+ */
231
+ _average(arr) {
232
+ if (!arr || arr.length === 0) {
233
+ return 0;
234
+ }
235
+ return arr.reduce((a, b) => a + b, 0) / arr.length;
236
+ },
237
+
238
+ /**
239
+ * Liest einen numerischen Statewert oder 0 bei Fehler.
240
+ *
241
+ * @param {string} id - Objekt-ID
242
+ * @returns {Promise<number>} - Gelesener Wert oder 0
243
+ */
244
+ async _getNumber(id) {
245
+ const state = await this.adapter.getStateAsync(id);
246
+ const val = Number(state?.val);
247
+ return isNaN(val) ? 0 : val;
248
+ },
249
+
250
+ /**
251
+ * Cleanup bei Adapter-Unload
252
+ */
253
+ cleanup() {
254
+ this.currentSessionValues = { power: [], flow: [] };
255
+ this.adapter?.log.debug('[pumpHelper3] Cleanup ausgeführt.');
256
+ },
257
+ };
258
+
259
+ module.exports = pumpHelper3;
@@ -135,14 +135,24 @@ const runtimeHelper = {
135
135
  await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
136
136
 
137
137
  // Umwälzmenge berechnen
138
- const pumpLph = (await this.adapter.getStateAsync('pump.pump_power_lph'))?.val || 0;
138
+ // Reeller Durchflusswert aus pump.live.flow_current_lh
139
+ const liveFlowLh = (await this.adapter.getStateAsync('pump.live.flow_current_lh'))?.val || 0;
140
+
141
+ if (liveFlowLh <= 0) {
142
+ this.adapter.log.debug('[runtimeHelper] Kein Live-Durchflusswert vorhanden, Berechnung übersprungen');
143
+ return;
144
+ }
145
+
146
+ // Poolparameter laden
139
147
  const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
140
148
  const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
141
149
 
142
- const dailyTotal = Math.round((effectiveToday / 3600) * pumpLph);
150
+ // Berechnung der realen Tagesumwälzung (Liter)
151
+ const dailyTotal = Math.round((effectiveToday / 3600) * liveFlowLh);
143
152
  const dailyRequired = Math.round(poolSize * minCirc);
144
153
  const dailyRemaining = Math.max(dailyRequired - dailyTotal, 0);
145
154
 
155
+ // Werte schreiben
146
156
  await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
147
157
  await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
148
158
  await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });