iobroker.poolcontrol 0.3.0 → 0.4.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,38 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
121
121
  ## Changelog
122
122
  ### **WORK IN PROGRESS**
123
123
 
124
+ ### **0.4.0 (26.10.2025)**
125
+
126
+ **Neue Funktionen**
127
+ - Einführung des neuen Statistik-Systems unter `analytics.statistics.temperature.today`
128
+ - Automatische Erfassung von **Min-, Max- und Durchschnittswerten** aller aktiven Temperatursensoren
129
+ - Pro Sensor: JSON- und HTML-Zusammenfassungen mit laufender Aktualisierung
130
+ - Gesamtausgabe aller Sensoren (Tabelle) unter
131
+ `analytics.statistics.temperature.today.outputs.summary_all_html`
132
+ - Vollständig **persistente Datenpunkte** mit Überinstallationsschutz
133
+ - **Automatischer Mitternachts-Reset** zur Tagesrücksetzung inkl. Zeitstempel
134
+ - Vorbereitung für zukünftige Wochen-, Monats- und Saisonstatistiken
135
+
136
+ **Verbesserungen**
137
+ - Einheitliche Struktur durch neuen Hauptordner `analytics`
138
+ - Keine dauerhaften Loops oder Timerbelastungen – reine Eventverarbeitung
139
+ - Verbesserte Performance und Speicherstabilität
140
+ - Überarbeitete Initialisierung aller Statistik-States beim Start
141
+
142
+ **Hinweis**
143
+ Diese Version bildet die stabile Basis für alle folgenden Statistik- und Analysefunktionen
144
+ (z. B. Wochen- und Monatsstatistik, Historien- und Effizienz-Auswertungen).
145
+
146
+ ---
147
+
148
+ ### 0.3.1 (2025-10-18)
149
+ - FrostHelper stabilisiert:
150
+ - Feste Hysterese von +2 °C (bisher +1 °C)
151
+ - Ganzzahl-Rundung eingeführt zur Vermeidung von Schaltflattern um 3 °C
152
+ - Keine Änderungen an States oder Konfiguration erforderlich
153
+
154
+ ---
155
+
124
156
  ### 0.3.0 (12.10.2025)
125
157
  **Neu:** Intelligentes Pumpen-Monitoring & Lernsystem
126
158
 
@@ -137,24 +169,6 @@ Funktionen können sich ändern – bitte regelmäßig den Changelog beachten.
137
169
  > Mit dieser Version beginnt die lernfähige Phase des PoolControl-Adapters:
138
170
  > Deine Pumpe weiß jetzt selbst, was für sie „normal“ ist.
139
171
 
140
- ---
141
-
142
- ### 0.2.2 (2025-10-08)
143
- - Einführung einer **automatischen Rückspülerinnerung** (neuer Bereich im ControlHelper2)
144
- - Erinnerung mit Intervall in Tagen und automatischer Rücksetzung nach erfolgter Rückspülung
145
- - Log- und Sprachausgabe bei Fälligkeit oder Überfälligkeit
146
- - Erweiterung des Control-Bereichs um zusätzliche States (backwash_reminder_active, interval_days, last_date, required)
147
- - Anpassung der main.js und controlStates zur Integration
148
- - Vorbereitung weiterer Wartungs- und Steuerungsfunktionen (Beleuchtung, Roboter, Ventile, Gegenstromanlage, Kesseldruck-Überwachung).
149
-
150
- ---
151
-
152
- ### 0.2.0 (2025-10-06)
153
- - Neuer Diagnosebereich **SystemCheck** für interne Debug-Logs und Analysen.
154
- - Möglichkeit, einzelne Adapterbereiche (z. B. Pumpe, Solar, Temperatur) gezielt zu überwachen.
155
- - Fortlaufendes Textprotokoll mit manueller Löschfunktion.
156
- - Alle bisherigen Debug-Funktionen aus `zz_debuglogs` in `SystemCheck.debug_logs` integriert.
157
- - Vorbereitung für zukünftige Diagnose-Erweiterungen (Export, Systemprüfung, Plausibilitäts-Checks).
158
172
 
159
173
  *(ältere Versionen siehe [io-package.json](./io-package.json))*
160
174
 
@@ -64,6 +64,10 @@
64
64
  "lg": 3,
65
65
  "xl": 3,
66
66
  "newLine": true
67
+ },
68
+ "divider_hardware1": {
69
+ "type": "divider",
70
+ "newLine": true
67
71
  }
68
72
  }
69
73
  },
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "poolcontrol",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "news": {
6
+ "0.4.0": {
7
+ "en": "Added daily temperature statistics under analytics.statistics.temperature.today with automatic min/max/average tracking, JSON and HTML summaries, and midnight reset logic.",
8
+ "de": "Tägliche Temperaturstatistik unter analytics.statistics.temperature.today hinzugefügt mit automatischer Erfassung von Min-/Max-/Durchschnittswerten, JSON- und HTML-Zusammenfassungen sowie Mitternachts-Reset.",
9
+ "ru": "Добавлена ежедневная статистика температуры в analytics.statistics.temperature.today с автоматическим отслеживанием мин./макс./средних значений, JSON и HTML сводками и логикой сброса в полночь.",
10
+ "pt": "Adicionadas estatísticas diárias de temperatura em analytics.statistics.temperature.today com rastreamento automático de mínimo/máximo/média, resumos em JSON e HTML e redefinição automática à meia-noite.",
11
+ "nl": "Dagelijkse temperatuurstatistieken toegevoegd onder analytics.statistics.temperature.today met automatische min/max/gemiddelde tracking, JSON- en HTML-samenvattingen en middernachtreset.",
12
+ "fr": "Ajout de statistiques quotidiennes de température sous analytics.statistics.temperature.today avec suivi automatique min/max/moyenne, résumés JSON et HTML et réinitialisation automatique à minuit.",
13
+ "it": "Aggiunte statistiche giornaliere della temperatura in analytics.statistics.temperature.today con monitoraggio automatico di min/max/media, riepiloghi JSON e HTML e reset automatico a mezzanotte.",
14
+ "es": "Se añadieron estadísticas diarias de temperatura en analytics.statistics.temperature.today con seguimiento automático de mínimos/máximos/promedios, resúmenes en JSON y HTML y reinicio automático a medianoche.",
15
+ "pl": "Dodano dzienne statystyki temperatury w analytics.statistics.temperature.today z automatycznym śledzeniem wartości min/max/średnich, podsumowaniami JSON i HTML oraz resetem o północy.",
16
+ "uk": "Додано щоденну статистику температури в analytics.statistics.temperature.today з автоматичним відстеженням мін/макс/середніх значень, JSON і HTML зведеннями та скиданням опівночі.",
17
+ "zh-cn": "在 analytics.statistics.temperature.today 中添加了每日温度统计,具有自动最小/最大/平均跟踪、JSON 和 HTML 摘要以及午夜重置功能。"
18
+ },
19
+ "0.3.1": {
20
+ "en": "Frost protection logic stabilized: fixed hysteresis of +2 °C and rounded temperature values to avoid pump switching fluctuations around 3 °C.",
21
+ "de": "Frostschutz-Logik stabilisiert: feste Hysterese von +2 °C und gerundete Temperaturwerte zur Vermeidung von Pumpenschaltflattern um 3 °C.",
22
+ "ru": "Логика защиты от замерзания стабилизирована: фиксированная гистерезис +2 °C и округленные значения температуры для предотвращения колебаний включения насоса около 3 °C.",
23
+ "pt": "Lógica de proteção contra congelamento estabilizada: histerese fixa de +2 °C e valores de temperatura arredondados para evitar flutuações de comutação da bomba em torno de 3 °C.",
24
+ "nl": "Vorstbeschermingslogica gestabiliseerd: vaste hysterese van +2 °C en afgeronde temperatuurwaarden om pompfluctuaties rond 3 °C te voorkomen.",
25
+ "fr": "Logique de protection antigel stabilisée : hystérésis fixe de +2 °C et valeurs de température arrondies pour éviter les fluctuations de commutation de la pompe autour de 3 °C.",
26
+ "it": "Logica di protezione antigelo stabilizzata: isteresi fissa di +2 °C e valori di temperatura arrotondati per evitare fluttuazioni di commutazione della pompa intorno a 3 °C.",
27
+ "es": "Lógica de protección contra heladas estabilizada: histéresis fija de +2 °C y valores de temperatura redondeados para evitar fluctuaciones de conmutación de la bomba alrededor de 3 °C.",
28
+ "pl": "Ustabilizowano logikę ochrony przed zamarzaniem: stała histereza +2 °C i zaokrąglone wartości temperatury, aby uniknąć wahań przełączania pompy w okolicach 3 °C.",
29
+ "uk": "Стабілізовано логіку захисту від замерзання: фіксована гістерезис +2 °C і округлені значення температури, щоб уникнути коливань увімкнення насоса біля 3 °C.",
30
+ "zh-cn": "防冻逻辑稳定:固定 +2 °C 滞后并四舍五入温度值,以避免泵在 3 °C 附近频繁切换。"
31
+ },
6
32
  "0.3.0": {
7
33
  "en": "Added real pump flow calculation, live monitoring, and self-learning normal range system for pump analysis.",
8
34
  "de": "Reelle Durchflussberechnung, Liveüberwachung und selbstlernendes Normalbereich-System zur Pumpenanalyse hinzugefügt.",
@@ -14,7 +40,7 @@
14
40
  "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
41
  "pl": "Dodano obliczanie rzeczywistego przepływu pompy, monitorowanie na żywo i samouczący się system normalnego zakresu do analizy pompy.",
16
42
  "zh-cn": "添加了实际泵流量计算、实时监控以及用于泵分析的自学习正常范围系统。"
17
- },
43
+ },
18
44
  "0.2.2": {
19
45
  "en": "Added automatic backwash reminder with speech and log notifications.",
20
46
  "de": "Automatische Rückspülerinnerung mit Sprach- und Log-Benachrichtigung hinzugefügt.",
@@ -39,19 +65,6 @@
39
65
  "pl": "Naprawiono problem z niewidzialnymi stanami dla kontroli głosowej i dodano prawidłowe przetwarzanie zmiennych wewnętrznych.",
40
66
  "uk": "Виправлено проблему з невидимими станами для керування голосом та додано правильну обробку внутрішніх змінних.",
41
67
  "zh-cn": "修复了语音控制的隐形状态问题,并添加了正确的内部变量处理。"
42
- },
43
- "0.2.0": {
44
- "en": "New diagnostic area 'SystemCheck' for internal debug logs and future analysis tools.",
45
- "de": "Neuer Diagnosebereich 'SystemCheck' für interne Debug-Logs und künftige Analysefunktionen.",
46
- "ru": "Новая диагностическая область 'SystemCheck' для внутренних журналов отладки и будущих инструментов анализа.",
47
- "pt": "Nova área de diagnóstico 'SystemCheck' para logs de depuração internos e futuras ferramentas de análise.",
48
- "nl": "Nieuw diagnostisch gebied 'SystemCheck' voor interne foutopsporingslogboeken en toekomstige analysetools.",
49
- "fr": "Nouvelle zone de diagnostic 'SystemCheck' pour les journaux de débogage internes et les futurs outils d'analyse.",
50
- "it": "Nuova area diagnostica 'SystemCheck' per i log di debug interni e futuri strumenti di analisi.",
51
- "es": "Nueva área de diagnóstico 'SystemCheck' para registros internos de depuración y futuras herramientas de análisis.",
52
- "pl": "Nowy obszar diagnostyczny 'SystemCheck' dla wewnętrznych dzienników debugowania i przyszłych narzędzi analitycznych.",
53
- "uk": "Нова діагностична область 'SystemCheck' для внутрішніх журналів налагодження та майбутніх інструментів аналізу.",
54
- "zh-cn": "新的诊断区域“SystemCheck”,用于内部调试日志和未来的分析工具。"
55
68
  }
56
69
  },
57
70
  "titleLang": {
@@ -4,9 +4,9 @@
4
4
  * consumptionHelper
5
5
  * - Nutzt externen kWh-Zähler (objectId aus Config)
6
6
  * - Berechnet Periodenwerte (Tag/Woche/Monat/Jahr)
7
- * - Berechnet Kosten anhand Strompreis (€/kWh) mit additiver Logik über Neustarts
7
+ * - Berechnet Kosten anhand Strompreis (€/kWh)
8
8
  * - Offset-Mechanismus: summiert alte Werte bei Zählerwechsel/Reset auf
9
- * - NEU: Erhält Tages-/Wochen-/Monats-/Jahreswerte über Neustarts (keine Datenpunkte neu!)
9
+ * - Erhält Tages-/Wochen-/Monats-/Jahreswerte über Neustarts
10
10
  */
11
11
 
12
12
  const consumptionHelper = {
@@ -16,7 +16,6 @@ const consumptionHelper = {
16
16
  baselines: {},
17
17
  resetTimer: null,
18
18
 
19
- // interne Speicher für additive Berechnung
20
19
  lastKnownPrice: 0,
21
20
  baseTotalKwh: 0,
22
21
  baseTotalEur: 0,
@@ -25,7 +24,6 @@ const consumptionHelper = {
25
24
  this.adapter = adapter;
26
25
  this.energyId = adapter.config.external_energy_total_id || null;
27
26
 
28
- // Komma/Punkt tolerant interpretieren
29
27
  this.price = parseFloat(String(adapter.config.energy_price_eur_kwh).replace(',', '.')) || 0;
30
28
  this.lastKnownPrice = this.price;
31
29
 
@@ -39,18 +37,16 @@ const consumptionHelper = {
39
37
  }
40
38
 
41
39
  this._scheduleDailyReset();
42
- this._loadCostBaselines(); // Basiswerte laden
43
- this._restoreBaselinesFromStates(); // <-- NEU: Baselines aus bestehenden States wiederherstellen
40
+ this._loadCostBaselines();
41
+ this._restoreBaselinesFromStates();
44
42
  },
45
43
 
46
44
  async _loadCostBaselines() {
47
45
  try {
48
46
  const totalKwh = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
49
47
  const totalEur = (await this.adapter.getStateAsync('costs.total_eur'))?.val || 0;
50
-
51
48
  this.baseTotalKwh = totalKwh;
52
49
  this.baseTotalEur = totalEur;
53
-
54
50
  this.adapter.log.debug(
55
51
  `[consumptionHelper] Kosten-Basis geladen → ${this.baseTotalEur.toFixed(
56
52
  2,
@@ -65,12 +61,10 @@ const consumptionHelper = {
65
61
  if (!state || id !== this.energyId) {
66
62
  return;
67
63
  }
68
-
69
64
  const totalNowRaw = Number(state.val);
70
65
  if (!Number.isFinite(totalNowRaw)) {
71
66
  return;
72
67
  }
73
-
74
68
  await this._updateConsumption(totalNowRaw);
75
69
  },
76
70
 
@@ -80,19 +74,25 @@ const consumptionHelper = {
80
74
  const last = (await this.adapter.getStateAsync('consumption.last_total_kwh'))?.val || 0;
81
75
  let totalNow = totalNowRaw;
82
76
 
83
- // Reset-Erkennung
77
+ // FIX: Schutz gegen Überinstallations-Fehler und unplausible Sprünge
84
78
  if (totalNowRaw < last) {
85
- this.adapter.log.warn('[consumptionHelper] Zähler-Reset erkannt Offset wird angepasst');
86
- const newOffset = offset + last;
87
- await this.adapter.setStateAsync('consumption.offset_kwh', { val: newOffset, ack: true });
88
- totalNow = newOffset + totalNowRaw;
79
+ if (offset === 0 && totalNowRaw < 10 && last > 10) {
80
+ this.adapter.log.warn(
81
+ '[consumptionHelper] Überinstallationsschutz aktiv – Zählerstand kleiner, Offset bleibt unverändert.',
82
+ );
83
+ totalNow = last; // kein Offset addieren
84
+ } else {
85
+ this.adapter.log.warn('[consumptionHelper] Zähler-Reset erkannt → Offset wird angepasst');
86
+ const newOffset = offset + last;
87
+ await this.adapter.setStateAsync('consumption.offset_kwh', { val: newOffset, ack: true });
88
+ totalNow = newOffset + totalNowRaw;
89
+ }
89
90
  } else {
90
91
  totalNow = offset + totalNowRaw;
91
92
  }
92
93
 
93
94
  await this.adapter.setStateAsync('consumption.total_kwh', { val: totalNow, ack: true });
94
95
 
95
- // Baselines laden (falls leer)
96
96
  if (Object.keys(this.baselines).length === 0) {
97
97
  await this._loadBaselines(totalNow);
98
98
  }
@@ -112,18 +112,15 @@ const consumptionHelper = {
112
112
  }
113
113
  }
114
114
 
115
- // === Additive Kostenberechnung über Neustarts ===
116
115
  const deltaKwh = Math.max(0, totalNow - this.baseTotalKwh);
117
116
  const deltaEur = deltaKwh * this.price;
118
117
  const totalCost = this.baseTotalEur + deltaEur;
119
118
 
120
- // Tages-/Wochen-/Monats-/Jahreskosten berechnen (nur Momentansicht)
121
119
  const dayCost = values.day * this.price;
122
120
  const weekCost = values.week * this.price;
123
121
  const monthCost = values.month * this.price;
124
122
  const yearCost = values.year * this.price;
125
123
 
126
- // States schreiben
127
124
  await this.adapter.setStateAsync('consumption.day_kwh', { val: Number(values.day.toFixed(3)), ack: true });
128
125
  await this.adapter.setStateAsync('consumption.week_kwh', {
129
126
  val: Number(values.week.toFixed(3)),
@@ -146,17 +143,13 @@ const consumptionHelper = {
146
143
  await this.adapter.setStateAsync('costs.total_eur', { val: Number(totalCost.toFixed(2)), ack: true });
147
144
  }
148
145
 
149
- // Letzten Zählerwert speichern
150
146
  await this.adapter.setStateAsync('consumption.last_total_kwh', { val: totalNowRaw, ack: true });
151
-
152
- // NEU: aktuelle Baselines persistent sichern
153
147
  await this._saveBaselines();
154
148
  } catch (err) {
155
149
  this.adapter.log.warn(`[consumptionHelper] Fehler bei Verbrauchsupdate: ${err.message}`);
156
150
  }
157
151
  },
158
152
 
159
- // Lädt Baselines beim Start, wenn im Speicher leer
160
153
  async _loadBaselines(totalNow) {
161
154
  this.baselines = {};
162
155
  const day = (await this.adapter.getStateAsync('consumption.day_kwh'))?.val;
@@ -172,7 +165,6 @@ const consumptionHelper = {
172
165
  this.adapter.log.debug(`[consumptionHelper] Baselines geladen: ${JSON.stringify(this.baselines)}`);
173
166
  },
174
167
 
175
- // NEU: Baselines aus States rekonstruieren (beim Adapterstart)
176
168
  async _restoreBaselinesFromStates() {
177
169
  try {
178
170
  const totalNow = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
@@ -194,7 +186,6 @@ const consumptionHelper = {
194
186
  }
195
187
  },
196
188
 
197
- // NEU: Aktuelle Baselines speichern (persistente Sicherung)
198
189
  async _saveBaselines() {
199
190
  try {
200
191
  await this.adapter.setStateAsync('consumption.day_kwh', {
@@ -218,13 +209,11 @@ const consumptionHelper = {
218
209
  }
219
210
  },
220
211
 
221
- // === NEU: Manueller Gesamreset für Verbrauch & Kosten ====================
222
212
  async resetAll(adapter) {
223
213
  try {
224
214
  this.adapter = adapter;
225
215
  adapter.log.warn('[consumptionHelper] Manueller Reset aller Verbrauchs- und Kostendaten');
226
216
 
227
- // Verbrauchswerte nullen
228
217
  const consumptionKeys = [
229
218
  'day_kwh',
230
219
  'week_kwh',
@@ -238,13 +227,11 @@ const consumptionHelper = {
238
227
  await adapter.setStateAsync(`consumption.${key}`, { val: 0, ack: true });
239
228
  }
240
229
 
241
- // Kostenwerte nullen
242
230
  const costKeys = ['day_eur', 'week_eur', 'month_eur', 'year_eur', 'total_eur'];
243
231
  for (const key of costKeys) {
244
232
  await adapter.setStateAsync(`costs.${key}`, { val: 0, ack: true });
245
233
  }
246
234
 
247
- // interne Speicher ebenfalls zurücksetzen
248
235
  this.baselines = {};
249
236
  this.baseTotalKwh = 0;
250
237
  this.baseTotalEur = 0;
@@ -255,15 +242,23 @@ const consumptionHelper = {
255
242
  }
256
243
  },
257
244
 
245
+ // FIX: täglicher Reset um Mitternacht für Tagesverbrauch
258
246
  _scheduleDailyReset() {
259
247
  const now = new Date();
260
248
  const nextMidnight = new Date(now);
261
249
  nextMidnight.setHours(24, 0, 0, 0);
262
250
  const msUntilMidnight = nextMidnight - now;
263
251
 
264
- this.resetTimer = setTimeout(() => {
265
- this.baselines = {};
266
- this._scheduleDailyReset();
252
+ this.resetTimer = setTimeout(async () => {
253
+ try {
254
+ this.adapter.log.info('[consumptionHelper] Tageszähler-Reset (Mitternacht)');
255
+ await this.adapter.setStateAsync('consumption.day_kwh', { val: 0, ack: true });
256
+ await this.adapter.setStateAsync('costs.day_eur', { val: 0, ack: true });
257
+ this.baselines.day = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
258
+ } catch (err) {
259
+ this.adapter.log.warn(`[consumptionHelper] Fehler beim Mitternachtsreset: ${err.message}`);
260
+ }
261
+ this._scheduleDailyReset(); // Timer erneut setzen
267
262
  }, msUntilMidnight);
268
263
  },
269
264
 
@@ -3,8 +3,9 @@
3
3
  /**
4
4
  * frostHelper
5
5
  * - Prüft Außentemperatur gegen Frostschutz-Grenze
6
- * - Schaltet Pumpe bei Frost ein (nur im Modus "auto")
7
- * - Kleine Hysterese: +1°C zum Ausschalten
6
+ * - Frostschutz arbeitet unabhängig vom Pumpenmodus
7
+ * -(Sicherheitsfunktion: greift immer, wenn frost_protection_active = true)
8
+ * - Kleine Hysterese: +2°C zum Ausschalten
8
9
  * - Schaltet über den zentralen Bool-State pump.pump_switch
9
10
  */
10
11
 
@@ -39,12 +40,6 @@ const frostHelper = {
39
40
  return;
40
41
  }
41
42
 
42
- // Nur aktiv im AUTO-Modus
43
- const mode = (await this.adapter.getStateAsync('pump.mode'))?.val;
44
- if (mode !== 'auto') {
45
- return;
46
- }
47
-
48
43
  // Frostschutz aktiviert?
49
44
  const frostActive = (await this.adapter.getStateAsync('pump.frost_protection_active'))?.val;
50
45
  if (!frostActive) {
@@ -68,10 +63,15 @@ const frostHelper = {
68
63
  const pumpActive = (await this.adapter.getStateAsync('pump.pump_switch'))?.val;
69
64
  let shouldRun = pumpActive;
70
65
 
71
- // Logik: einschalten bei <= frostTemp, ausschalten bei >= frostTemp+1
72
- if (outside <= frostTemp) {
66
+ // FIX: Stabilere Logik mit fester Hysterese von +2 °C und Ganzzahl-Rundung
67
+ const outsideRounded = Math.round(outside);
68
+ const frostTempRounded = Math.round(frostTemp);
69
+
70
+ // Einschalten bei <= frostTempRounded
71
+ // Ausschalten erst bei >= frostTempRounded + 2 (2 K Hysterese)
72
+ if (outsideRounded <= frostTempRounded) {
73
73
  shouldRun = true;
74
- } else if (outside >= frostTemp + 1) {
74
+ } else if (outsideRounded >= frostTempRounded + 2) {
75
75
  shouldRun = false;
76
76
  }
77
77
 
@@ -255,31 +255,41 @@ const pumpHelper = {
255
255
  let error = false;
256
256
  let errorMsg = '';
257
257
 
258
+ // Pumpe EIN, aber keine Leistung
258
259
  if (active === true && power < 5) {
259
260
  error = true;
260
261
  errorMsg = 'Fehler: Pumpe EIN, aber keine Leistung!';
261
262
  }
262
263
 
264
+ // Pumpe AUS, aber Leistung vorhanden
263
265
  if (active === false && power > 10) {
264
266
  error = true;
265
267
  errorMsg = 'Fehler: Pumpe AUS, aber Leistung vorhanden!';
266
268
  }
267
269
 
268
- if (active === true && maxWatt > 0 && power > maxWatt) {
269
- error = true;
270
- errorMsg = `Überlast: ${power} W > Maximalwert ${maxWatt} W → Pumpe wird abgeschaltet!`;
270
+ // --- Überlastschutz (mit fester 10%-Toleranz) ---
271
+ if (active === true && maxWatt > 0) {
272
+ const overloadTolerance = 1.1; // 10 % Sicherheitsfenster
273
+ const overloadLimit = maxWatt * overloadTolerance;
274
+
275
+ if (power > overloadLimit) {
276
+ error = true;
277
+ errorMsg = `Überlast: ${power.toFixed(1)} W > Sicherheitsgrenze ${overloadLimit.toFixed(
278
+ 1,
279
+ )} W (Maxwert ${maxWatt} W) → Pumpe wird abgeschaltet!`;
280
+
281
+ if (pumpSwitchId) {
282
+ await this.adapter.setForeignStateAsync(pumpSwitchId, {
283
+ val: false,
284
+ ack: false,
285
+ });
286
+ }
271
287
 
272
- if (pumpSwitchId) {
273
- await this.adapter.setForeignStateAsync(pumpSwitchId, {
274
- val: false,
275
- ack: false,
288
+ await this.adapter.setStateAsync('pump.mode', {
289
+ val: 'off',
290
+ ack: true,
276
291
  });
277
292
  }
278
-
279
- await this.adapter.setStateAsync('pump.mode', {
280
- val: 'off',
281
- ack: true,
282
- });
283
293
  }
284
294
 
285
295
  if (error !== errorOld) {
@@ -20,11 +20,12 @@
20
20
  *
21
21
  * Alle Zielstates sind persistent (siehe pumpStates2.js).
22
22
  * ----------------------------------------------------------
23
- * Version: 1.0.0
23
+ * Version: 1.0.3
24
24
  */
25
25
 
26
26
  const pumpHelper2 = {
27
27
  adapter: null,
28
+ lastKnownFlow: 0, // merkt sich den letzten gültigen Durchflusswert
28
29
 
29
30
  /**
30
31
  * Initialisiert den PumpHelper2
@@ -61,11 +62,20 @@ const pumpHelper2 = {
61
62
  await this._updateLiveValues();
62
63
  }
63
64
 
64
- // Pumpenstatus-Änderung → ggf. letzten Durchflusswert speichern
65
+ // Pumpenstatus-Änderung → letzten Durchflusswert sichern
65
66
  if (id.endsWith('pump.pump_switch')) {
66
67
  const pumpOn = state.val === true;
67
68
  if (!pumpOn) {
68
- await this._storeLastFlowValue();
69
+ // FIX: Verwende den zuletzt gemerkten Wert, statt live zu lesen (verhindert 0-Durchfluss)
70
+ const flowBeforeStop = this.lastKnownFlow;
71
+ if (flowBeforeStop > 0) {
72
+ await this._setIfChanged('pump.live.last_flow_lh', flowBeforeStop);
73
+ this.adapter.log.debug(
74
+ `[pumpHelper2] FIX: Letzter Durchflusswert gesichert: ${flowBeforeStop} l/h`,
75
+ );
76
+ } else {
77
+ this.adapter.log.debug('[pumpHelper2] Kein gespeicherter Durchflusswert vorhanden.');
78
+ }
69
79
  }
70
80
  }
71
81
  },
@@ -94,6 +104,11 @@ const pumpHelper2 = {
94
104
  // Reeller Durchfluss
95
105
  const flowCurrentLh = Math.round(nominalFlow * (currentPower / maxPower) * 10) / 10; // 1 Nachkommastelle
96
106
 
107
+ // Letzten gültigen Wert merken
108
+ if (flowCurrentLh > 0) {
109
+ this.lastKnownFlow = flowCurrentLh;
110
+ }
111
+
97
112
  // In States schreiben
98
113
  await this._setIfChanged('pump.live.current_power_w', currentPower);
99
114
  await this._setIfChanged('pump.live.flow_current_lh', flowCurrentLh);
@@ -107,19 +122,6 @@ const pumpHelper2 = {
107
122
  }
108
123
  },
109
124
 
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
125
  /**
124
126
  * Liest einen numerischen Statewert (oder 0 bei Fehler).
125
127
  *
@@ -17,7 +17,7 @@
17
17
  * - pump.live.flow_current_lh
18
18
  * - pump.pump_switch
19
19
  *
20
- * Version: 1.0.0
20
+ * Version: 1.0.2 (zusätzliche Throttle-Logik für Deviation-Werte)
21
21
  */
22
22
 
23
23
  const pumpHelper3 = {
@@ -29,6 +29,14 @@ const pumpHelper3 = {
29
29
  flow: [],
30
30
  },
31
31
 
32
+ // NEU: interne Variablen für Schreibschutz
33
+ _lastLearningWrite: 0,
34
+ _lastLearningStatus: '',
35
+ // NEU: Drosselung für Deviation-Werte
36
+ _lastDevWrite: 0,
37
+ _lastDevPower: 0,
38
+ _lastDevFlow: 0,
39
+
32
40
  /**
33
41
  * Initialisiert den Helper
34
42
  *
@@ -53,7 +61,7 @@ const pumpHelper3 = {
53
61
  * @param {ioBroker.State} state - neuer Statewert
54
62
  */
55
63
  async handleStateChange(id, state) {
56
- if (!state || state.ack === false) {
64
+ if (!state) {
57
65
  return;
58
66
  }
59
67
 
@@ -167,6 +175,7 @@ const pumpHelper3 = {
167
175
 
168
176
  /**
169
177
  * Bewertet aktuelle Abweichungen und schreibt Status.
178
+ * (jetzt mit Throttle- und Change-Detection)
170
179
  */
171
180
  async _updateDeviationAndStatus() {
172
181
  try {
@@ -183,18 +192,40 @@ const pumpHelper3 = {
183
192
  const deviationPower = ((currentPower - avgPower) / avgPower) * 100;
184
193
  const deviationFlow = ((currentFlow - avgFlow) / avgFlow) * 100;
185
194
 
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
- });
195
+ // --- NEU: Throttle + Change Detection für Deviation-Werte ---
196
+ const roundedPower = Math.round(deviationPower * 10) / 10;
197
+ const roundedFlow = Math.round(deviationFlow * 10) / 10;
198
+
199
+ if (
200
+ (this._lastDevPower !== roundedPower || this._lastDevFlow !== roundedFlow) &&
201
+ Date.now() - this._lastDevWrite > 1000
202
+ ) {
203
+ this._lastDevPower = roundedPower;
204
+ this._lastDevFlow = roundedFlow;
205
+ this._lastDevWrite = Date.now();
194
206
 
195
- // Bewertung
196
- const statusText = this._getStatusText(deviationPower, deviationFlow);
197
- await this.adapter.setStateAsync('pump.learning.status_text', { val: statusText, ack: true });
207
+ await this.adapter.setStateAsync('pump.learning.deviation_power_percent', {
208
+ val: roundedPower,
209
+ ack: true,
210
+ });
211
+ await this.adapter.setStateAsync('pump.learning.deviation_flow_percent', {
212
+ val: roundedFlow,
213
+ ack: true,
214
+ });
215
+ }
216
+
217
+ // Bewertungstext ermitteln
218
+ const statusText = await this._getStatusText(deviationPower, deviationFlow);
219
+
220
+ // --- NEU: Schreibsperre + Change-Detection für Status ---
221
+ if (statusText !== this._lastLearningStatus && Date.now() - this._lastLearningWrite > 1000) {
222
+ this._lastLearningStatus = statusText;
223
+ this._lastLearningWrite = Date.now();
224
+ await this.adapter.setStateAsync('pump.learning.status_text', {
225
+ val: statusText,
226
+ ack: true,
227
+ });
228
+ }
198
229
  } catch (err) {
199
230
  this.adapter.log.warn(`[pumpHelper3] Fehler bei _updateDeviationAndStatus: ${err.message}`);
200
231
  }
@@ -206,17 +237,21 @@ const pumpHelper3 = {
206
237
  * @param devPower - Aktuelle Abweichung der Leistung (W)
207
238
  * @param devFlow - Aktuelle Abweichung des Durchflusses (L/H)
208
239
  */
209
- _getStatusText(devPower, devFlow) {
240
+ async _getStatusText(devPower, devFlow) {
210
241
  const absPower = Math.abs(devPower);
211
242
  const absFlow = Math.abs(devFlow);
212
243
 
213
- if (absPower <= 15 && absFlow <= 15) {
244
+ // NEU: Dynamischen Toleranzwert lesen (Standard 15%)
245
+ const toleranceState = await this.adapter.getStateAsync('pump.learning.tolerance_percent');
246
+ const tolerance = Number(toleranceState?.val) || 15;
247
+
248
+ if (absPower <= tolerance && absFlow <= tolerance) {
214
249
  return 'Pumpe läuft im Normalbereich';
215
250
  }
216
- if (devPower < -15 || devFlow < -15) {
251
+ if (devPower < -tolerance || devFlow < -tolerance) {
217
252
  return 'Pumpe läuft unterhalb des Normalbereichs (möglicher Filterdruck)';
218
253
  }
219
- if (devPower > 15 || devFlow > 15) {
254
+ if (devPower > tolerance || devFlow > tolerance) {
220
255
  return 'Pumpe läuft oberhalb des Normalbereichs (möglicher Luftstau)';
221
256
  }
222
257
  return 'Pumpe außerhalb des bekannten Bereichs';