iobroker.poolcontrol 0.1.2 → 0.2.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
@@ -48,6 +48,18 @@ Er ermöglicht die Automatisierung von Pumpen, Temperatur- und Solarsteuerung so
48
48
  - Ausgabe über Alexa oder Telegram
49
49
  - Ansagen bei Pumpenstart/-stopp, Fehlern oder Temperaturschwellen
50
50
 
51
+ - **Systemcheck (Diagnosebereich)**
52
+ Ab Version **0.2.0** enthält der Adapter einen neuen Diagnosebereich **SystemCheck**.
53
+ Er bietet interne Debug-Logs, mit denen bestimmte Teilbereiche (z. B. Pumpen-, Solar- oder Temperatursteuerung) gezielt überwacht werden können.
54
+
55
+ *Funktionen:*
56
+ - Auswahl des zu überwachenden Bereichs
57
+ - Fortlaufendes Log der letzten Änderungen
58
+ - Manuelles Löschen des Logs möglich
59
+
60
+ Dieser Bereich dient ausschließlich der Analyse und Fehlerdiagnose.
61
+ Im normalen Betrieb sollte die Überwachung deaktiviert bleiben.
62
+
51
63
  ---
52
64
 
53
65
  ## Installation
@@ -94,6 +106,14 @@ Funktionen können sich ändern, bitte regelmäßig den Changelog beachten.
94
106
  ## Changelog
95
107
  Auszug, vollständige Liste siehe `io-package.json`:
96
108
 
109
+ ### 0.2.0 (2025-10-06)
110
+ - Neuer Diagnosebereich **SystemCheck** für interne Debug-Logs und Analysen.
111
+ - Möglichkeit, einzelne Adapterbereiche (z. B. Pumpe, Solar, Temperatur) gezielt zu überwachen.
112
+ - Fortlaufendes Textprotokoll mit manueller Löschfunktion.
113
+ - Alle bisherigen Debug-Funktionen aus `zz_debuglogs` in `SystemCheck.debug_logs` integriert.
114
+ - Vorbereitung für zukünftige Diagnose-Erweiterungen (Export, Systemprüfung, Plausibilitäts-Checks).
115
+
116
+
97
117
  ### 0.1.2
98
118
  - Verbesserung: Beim Adapterstart werden nun die letzten bekannten Temperaturwerte aller aktiven Sensoren (z. B. Oberfläche, Kollektor, Außentemperatur usw.) automatisch übernommen.
99
119
  - Dadurch werden auch Sensoren korrekt angezeigt, die ihren Messwert nur selten aktualisieren (z. B. Homematic oder stromsparende Funk-Sensoren).
@@ -557,7 +557,7 @@
557
557
  "sm": 3,
558
558
  "md": 3,
559
559
  "lg": 3,
560
- "xl": 3,
560
+ "xl": 3
561
561
  },
562
562
  "time2_end": {
563
563
  "type": "text",
@@ -958,6 +958,8 @@
958
958
  "type": "number",
959
959
  "label": "Strompreis (€ / kWh)",
960
960
  "default": 0.35,
961
+ "min": 0,
962
+ "step": 0.01,
961
963
  "xs": 12,
962
964
  "sm": 3,
963
965
  "md": 3,
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "poolcontrol",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "news": {
6
+ "0.2.1": {
7
+ "en": "Fixed the issue with invisible states for speech control and added proper internal variable handling.",
8
+ "de": "Behebung des Problems mit unsichtbaren States für die Sprachsteuerung und hinzugefügte korrekte Handhabung interner Variablen.",
9
+ "ru": "Исправлена проблема с невидимыми состояниями для управления речью и добавлена правильная обработка внутренних переменных.",
10
+ "pt": "Corrigido o problema com estados invisíveis para controle de fala e adicionada a manipulação adequada de variáveis internas.",
11
+ "nl": "Het probleem met onzichtbare toestanden voor spraakbesturing is opgelost en de juiste verwerking van interne variabelen toegevoegd.",
12
+ "fr": "Correction du problème des états invisibles pour le contrôle vocal et ajout d'un traitement correct des variables internes.",
13
+ "it": "Risolto il problema con gli stati invisibili per il controllo vocale e aggiunto un corretto trattamento delle variabili interne.",
14
+ "es": "Se solucionó el problema con los estados invisibles para el control de voz y se añadió el manejo adecuado de las variables internas.",
15
+ "pl": "Naprawiono problem z niewidzialnymi stanami dla kontroli głosowej i dodano prawidłowe przetwarzanie zmiennych wewnętrznych.",
16
+ "uk": "Виправлено проблему з невидимими станами для керування голосом та додано правильну обробку внутрішніх змінних.",
17
+ "zh-cn": "修复了语音控制的隐形状态问题,并添加了正确的内部变量处理。"
18
+ },
19
+ "0.2.0": {
20
+ "en": "New diagnostic area 'SystemCheck' for internal debug logs and future analysis tools.",
21
+ "de": "Neuer Diagnosebereich 'SystemCheck' für interne Debug-Logs und künftige Analysefunktionen.",
22
+ "ru": "Новая диагностическая область 'SystemCheck' для внутренних журналов отладки и будущих инструментов анализа.",
23
+ "pt": "Nova área de diagnóstico 'SystemCheck' para logs de depuração internos e futuras ferramentas de análise.",
24
+ "nl": "Nieuw diagnostisch gebied 'SystemCheck' voor interne foutopsporingslogboeken en toekomstige analysetools.",
25
+ "fr": "Nouvelle zone de diagnostic 'SystemCheck' pour les journaux de débogage internes et les futurs outils d'analyse.",
26
+ "it": "Nuova area diagnostica 'SystemCheck' per i log di debug interni e futuri strumenti di analisi.",
27
+ "es": "Nueva área de diagnóstico 'SystemCheck' para registros internos de depuración y futuras herramientas de análisis.",
28
+ "pl": "Nowy obszar diagnostyczny 'SystemCheck' dla wewnętrznych dzienników debugowania i przyszłych narzędzi analitycznych.",
29
+ "uk": "Нова діагностична область 'SystemCheck' для внутрішніх журналів налагодження та майбутніх інструментів аналізу.",
30
+ "zh-cn": "新的诊断区域“SystemCheck”,用于内部调试日志和未来的分析工具。"
31
+ },
6
32
  "0.1.2": {
7
33
  "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.",
8
34
  "de": "Verbesserung: Beim Adapterstart werden nun die letzten bekannten Temperaturwerte aller aktiven Sensoren übernommen. Sensoren, die nur selten Werte senden, werden jetzt korrekt angezeigt.",
@@ -102,6 +128,11 @@
102
128
  "extIcon": "https://raw.githubusercontent.com/DasBo1975/ioBroker.poolcontrol/main/admin/poolcontrol.png",
103
129
  "readme": "https://github.com/DasBo1975/ioBroker.poolcontrol/blob/main/README.md",
104
130
  "loglevel": "info",
131
+ "ignoreLogs": [
132
+ "State .*speech\\.solar_active.* has no existing object",
133
+ "State .*speech\\.frost_active.* has no existing object",
134
+ "State .*speech\\.time_active.* has no existing object"
135
+ ],
105
136
  "tier": 3,
106
137
  "mode": "daemon",
107
138
  "type": "climate-control",
@@ -4,38 +4,65 @@
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)
7
+ * - Berechnet Kosten anhand Strompreis (€/kWh) mit additiver Logik über Neustarts
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
10
  */
10
11
 
11
12
  const consumptionHelper = {
12
13
  adapter: null,
13
- energyId: null, // Objekt-ID des externen kWh-Zählers
14
- price: 0, // Strompreis €/kWh
15
- baselines: {}, // { day, week, month, year }
14
+ energyId: null,
15
+ price: 0,
16
+ baselines: {},
16
17
  resetTimer: null,
17
18
 
19
+ // interne Speicher für additive Berechnung
20
+ lastKnownPrice: 0,
21
+ baseTotalKwh: 0,
22
+ baseTotalEur: 0,
23
+
18
24
  init(adapter) {
19
25
  this.adapter = adapter;
20
26
  this.energyId = adapter.config.external_energy_total_id || null;
21
- this.price = adapter.config.energy_price_eur_kwh || 0;
27
+
28
+ // Komma/Punkt tolerant interpretieren
29
+ this.price = parseFloat(String(adapter.config.energy_price_eur_kwh).replace(',', '.')) || 0;
30
+ this.lastKnownPrice = this.price;
31
+
32
+ this.adapter.log.debug(`[consumptionHelper] Strompreis: ${this.price} €/kWh`);
22
33
 
23
34
  if (this.energyId) {
24
35
  adapter.subscribeForeignStates(this.energyId);
25
- adapter.log.info(`[consumptionHelper] Überwache externen kWh-Zähler: ${this.energyId}`);
36
+ adapter.log.debug(`[consumptionHelper] Überwache externen kWh-Zähler: ${this.energyId}`);
26
37
  } else {
27
- adapter.log.info('[consumptionHelper] Kein externer kWh-Zähler konfiguriert → Verbrauchslogik inaktiv.');
38
+ adapter.log.debug('[consumptionHelper] Kein externer kWh-Zähler konfiguriert → Verbrauchslogik inaktiv.');
28
39
  }
29
40
 
30
- // Reset-Timer für Mitternacht
31
41
  this._scheduleDailyReset();
42
+ this._loadCostBaselines(); // Basiswerte laden
43
+ this._restoreBaselinesFromStates(); // <-- NEU: Baselines aus bestehenden States wiederherstellen
32
44
  },
33
45
 
34
- async handleStateChange(id, state) {
35
- if (!state) {
36
- return;
46
+ async _loadCostBaselines() {
47
+ try {
48
+ const totalKwh = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
49
+ const totalEur = (await this.adapter.getStateAsync('costs.total_eur'))?.val || 0;
50
+
51
+ this.baseTotalKwh = totalKwh;
52
+ this.baseTotalEur = totalEur;
53
+
54
+ this.adapter.log.debug(
55
+ `[consumptionHelper] Kosten-Basis geladen → ${this.baseTotalEur.toFixed(
56
+ 2,
57
+ )} € bei ${this.baseTotalKwh.toFixed(3)} kWh`,
58
+ );
59
+ } catch (err) {
60
+ this.adapter.log.warn(`[consumptionHelper] Fehler beim Laden der Kosten-Basis: ${err.message}`);
37
61
  }
38
- if (id !== this.energyId) {
62
+ },
63
+
64
+ async handleStateChange(id, state) {
65
+ if (!state || id !== this.energyId) {
39
66
  return;
40
67
  }
41
68
 
@@ -49,37 +76,27 @@ const consumptionHelper = {
49
76
 
50
77
  async _updateConsumption(totalNowRaw) {
51
78
  try {
52
- // Offset laden
53
79
  const offset = (await this.adapter.getStateAsync('consumption.offset_kwh'))?.val || 0;
54
80
  const last = (await this.adapter.getStateAsync('consumption.last_total_kwh'))?.val || 0;
55
-
56
81
  let totalNow = totalNowRaw;
57
82
 
58
- // Prüfen: Reset oder neues Gerät?
83
+ // Reset-Erkennung
59
84
  if (totalNowRaw < last) {
60
85
  this.adapter.log.warn('[consumptionHelper] Zähler-Reset erkannt → Offset wird angepasst');
61
86
  const newOffset = offset + last;
62
- await this.adapter.setStateAsync('consumption.offset_kwh', {
63
- val: newOffset,
64
- ack: true,
65
- });
87
+ await this.adapter.setStateAsync('consumption.offset_kwh', { val: newOffset, ack: true });
66
88
  totalNow = newOffset + totalNowRaw;
67
89
  } else {
68
90
  totalNow = offset + totalNowRaw;
69
91
  }
70
92
 
71
- // Aktuellen Gesamtwert setzen
72
- await this.adapter.setStateAsync('consumption.total_kwh', {
73
- val: totalNow,
74
- ack: true,
75
- });
93
+ await this.adapter.setStateAsync('consumption.total_kwh', { val: totalNow, ack: true });
76
94
 
77
95
  // Baselines laden (falls leer)
78
96
  if (Object.keys(this.baselines).length === 0) {
79
97
  await this._loadBaselines(totalNow);
80
98
  }
81
99
 
82
- // Differenzen berechnen
83
100
  const values = {
84
101
  day: totalNow - (this.baselines.day ?? totalNow),
85
102
  week: totalNow - (this.baselines.week ?? totalNow),
@@ -87,7 +104,7 @@ const consumptionHelper = {
87
104
  year: totalNow - (this.baselines.year ?? totalNow),
88
105
  };
89
106
 
90
- // Negative Werte abfangen (bei Reset)
107
+ // Negative Werte vermeiden
91
108
  for (const key of Object.keys(values)) {
92
109
  if (values[key] < 0) {
93
110
  this.baselines[key] = totalNow;
@@ -95,11 +112,19 @@ const consumptionHelper = {
95
112
  }
96
113
  }
97
114
 
98
- // In States schreiben
99
- await this.adapter.setStateAsync('consumption.day_kwh', {
100
- val: Number(values.day.toFixed(3)),
101
- ack: true,
102
- });
115
+ // === Additive Kostenberechnung über Neustarts ===
116
+ const deltaKwh = Math.max(0, totalNow - this.baseTotalKwh);
117
+ const deltaEur = deltaKwh * this.price;
118
+ const totalCost = this.baseTotalEur + deltaEur;
119
+
120
+ // Tages-/Wochen-/Monats-/Jahreskosten berechnen (nur Momentansicht)
121
+ const dayCost = values.day * this.price;
122
+ const weekCost = values.week * this.price;
123
+ const monthCost = values.month * this.price;
124
+ const yearCost = values.year * this.price;
125
+
126
+ // States schreiben
127
+ await this.adapter.setStateAsync('consumption.day_kwh', { val: Number(values.day.toFixed(3)), ack: true });
103
128
  await this.adapter.setStateAsync('consumption.week_kwh', {
104
129
  val: Number(values.week.toFixed(3)),
105
130
  ack: true,
@@ -113,40 +138,25 @@ const consumptionHelper = {
113
138
  ack: true,
114
139
  });
115
140
 
116
- // Kosten berechnen
117
141
  if (this.price > 0) {
118
- await this.adapter.setStateAsync('costs.day_eur', {
119
- val: Number((values.day * this.price).toFixed(2)),
120
- ack: true,
121
- });
122
- await this.adapter.setStateAsync('costs.week_eur', {
123
- val: Number((values.week * this.price).toFixed(2)),
124
- ack: true,
125
- });
126
- await this.adapter.setStateAsync('costs.month_eur', {
127
- val: Number((values.month * this.price).toFixed(2)),
128
- ack: true,
129
- });
130
- await this.adapter.setStateAsync('costs.year_eur', {
131
- val: Number((values.year * this.price).toFixed(2)),
132
- ack: true,
133
- });
134
- await this.adapter.setStateAsync('costs.total_eur', {
135
- val: Number((totalNow * this.price).toFixed(2)),
136
- ack: true,
137
- });
142
+ await this.adapter.setStateAsync('costs.day_eur', { val: Number(dayCost.toFixed(2)), ack: true });
143
+ await this.adapter.setStateAsync('costs.week_eur', { val: Number(weekCost.toFixed(2)), ack: true });
144
+ await this.adapter.setStateAsync('costs.month_eur', { val: Number(monthCost.toFixed(2)), ack: true });
145
+ await this.adapter.setStateAsync('costs.year_eur', { val: Number(yearCost.toFixed(2)), ack: true });
146
+ await this.adapter.setStateAsync('costs.total_eur', { val: Number(totalCost.toFixed(2)), ack: true });
138
147
  }
139
148
 
140
- // Letzten Stand des externen Zählers merken
141
- await this.adapter.setStateAsync('consumption.last_total_kwh', {
142
- val: totalNowRaw,
143
- ack: true,
144
- });
149
+ // Letzten Zählerwert speichern
150
+ await this.adapter.setStateAsync('consumption.last_total_kwh', { val: totalNowRaw, ack: true });
151
+
152
+ // NEU: aktuelle Baselines persistent sichern
153
+ await this._saveBaselines();
145
154
  } catch (err) {
146
155
  this.adapter.log.warn(`[consumptionHelper] Fehler bei Verbrauchsupdate: ${err.message}`);
147
156
  }
148
157
  },
149
158
 
159
+ // Lädt Baselines beim Start, wenn im Speicher leer
150
160
  async _loadBaselines(totalNow) {
151
161
  this.baselines = {};
152
162
  const day = (await this.adapter.getStateAsync('consumption.day_kwh'))?.val;
@@ -159,7 +169,90 @@ const consumptionHelper = {
159
169
  this.baselines.month = totalNow - (month || 0);
160
170
  this.baselines.year = totalNow - (year || 0);
161
171
 
162
- this.adapter.log.info(`[consumptionHelper] Baselines geladen: ${JSON.stringify(this.baselines)}`);
172
+ this.adapter.log.debug(`[consumptionHelper] Baselines geladen: ${JSON.stringify(this.baselines)}`);
173
+ },
174
+
175
+ // NEU: Baselines aus States rekonstruieren (beim Adapterstart)
176
+ async _restoreBaselinesFromStates() {
177
+ try {
178
+ const totalNow = (await this.adapter.getStateAsync('consumption.total_kwh'))?.val || 0;
179
+ const day = (await this.adapter.getStateAsync('consumption.day_kwh'))?.val || 0;
180
+ const week = (await this.adapter.getStateAsync('consumption.week_kwh'))?.val || 0;
181
+ const month = (await this.adapter.getStateAsync('consumption.month_kwh'))?.val || 0;
182
+ const year = (await this.adapter.getStateAsync('consumption.year_kwh'))?.val || 0;
183
+
184
+ this.baselines.day = totalNow - day;
185
+ this.baselines.week = totalNow - week;
186
+ this.baselines.month = totalNow - month;
187
+ this.baselines.year = totalNow - year;
188
+
189
+ this.adapter.log.debug(`[consumptionHelper] Bestehende Verbrauchswerte wiederhergestellt.`);
190
+ } catch (err) {
191
+ this.adapter.log.warn(
192
+ `[consumptionHelper] Fehler beim Wiederherstellen der Verbrauchsstände: ${err.message}`,
193
+ );
194
+ }
195
+ },
196
+
197
+ // NEU: Aktuelle Baselines speichern (persistente Sicherung)
198
+ async _saveBaselines() {
199
+ try {
200
+ await this.adapter.setStateAsync('consumption.day_kwh', {
201
+ val: (await this.adapter.getStateAsync('consumption.day_kwh'))?.val,
202
+ ack: true,
203
+ });
204
+ await this.adapter.setStateAsync('consumption.week_kwh', {
205
+ val: (await this.adapter.getStateAsync('consumption.week_kwh'))?.val,
206
+ ack: true,
207
+ });
208
+ await this.adapter.setStateAsync('consumption.month_kwh', {
209
+ val: (await this.adapter.getStateAsync('consumption.month_kwh'))?.val,
210
+ ack: true,
211
+ });
212
+ await this.adapter.setStateAsync('consumption.year_kwh', {
213
+ val: (await this.adapter.getStateAsync('consumption.year_kwh'))?.val,
214
+ ack: true,
215
+ });
216
+ } catch (err) {
217
+ this.adapter.log.warn(`[consumptionHelper] Fehler beim Speichern der Baselines: ${err.message}`);
218
+ }
219
+ },
220
+
221
+ // === NEU: Manueller Gesamreset für Verbrauch & Kosten ====================
222
+ async resetAll(adapter) {
223
+ try {
224
+ this.adapter = adapter;
225
+ adapter.log.warn('[consumptionHelper] Manueller Reset aller Verbrauchs- und Kostendaten');
226
+
227
+ // Verbrauchswerte nullen
228
+ const consumptionKeys = [
229
+ 'day_kwh',
230
+ 'week_kwh',
231
+ 'month_kwh',
232
+ 'year_kwh',
233
+ 'total_kwh',
234
+ 'offset_kwh',
235
+ 'last_total_kwh',
236
+ ];
237
+ for (const key of consumptionKeys) {
238
+ await adapter.setStateAsync(`consumption.${key}`, { val: 0, ack: true });
239
+ }
240
+
241
+ // Kostenwerte nullen
242
+ const costKeys = ['day_eur', 'week_eur', 'month_eur', 'year_eur', 'total_eur'];
243
+ for (const key of costKeys) {
244
+ await adapter.setStateAsync(`costs.${key}`, { val: 0, ack: true });
245
+ }
246
+
247
+ // interne Speicher ebenfalls zurücksetzen
248
+ this.baselines = {};
249
+ this.baseTotalKwh = 0;
250
+ this.baseTotalEur = 0;
251
+
252
+ adapter.log.info('[consumptionHelper] Verbrauch und Kosten erfolgreich auf 0 gesetzt');
253
+ } catch (err) {
254
+ this.adapter.log.error(`[consumptionHelper] Fehler beim manuellen Reset: ${err.message}`);
255
+ }
163
256
  },
164
257
 
165
258
  _scheduleDailyReset() {