iobroker.poolcontrol 0.2.2 → 0.3.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.
@@ -21,9 +21,24 @@ const runtimeHelper = {
21
21
  resetTimer: null,
22
22
  liveTimer: null, // Timer für Live-Updates
23
23
 
24
- init(adapter) {
24
+ /**
25
+ * Initialisiert den Runtime-Helper.
26
+ * Führt eine kurze Startverzögerung ein, um sicherzustellen,
27
+ * dass persistente States nach einer Überinstallation korrekt geladen werden.
28
+ *
29
+ * @param {ioBroker.Adapter} adapter - Aktive ioBroker-Adapterinstanz.
30
+ * @returns {Promise<void>}
31
+ */
32
+ async init(adapter) {
25
33
  this.adapter = adapter;
26
34
 
35
+ // ------------------------------------------------------
36
+ // NEU: Kurze Startverzögerung, damit ioBroker persistente States
37
+ // vollständig aus der Datenbank laden kann (Überinstallationsschutz)
38
+ // ------------------------------------------------------
39
+ this.adapter.log.debug('[runtimeHelper] Warte 3 Sekunden, um persistente States zu laden ...');
40
+ await new Promise(resolve => setTimeout(resolve, 3000));
41
+
27
42
  // Pumpenschalter überwachen
28
43
  this.adapter.subscribeStates('pump.pump_switch');
29
44
 
@@ -52,6 +67,13 @@ const runtimeHelper = {
52
67
  const seasonRaw = (await this.adapter.getStateAsync('runtime.season_total'))?.val;
53
68
  const countRaw = (await this.adapter.getStateAsync('runtime.start_count_today'))?.val;
54
69
 
70
+ // FIX: Falls States leer oder neu angelegt sind, Warnhinweis ausgeben und Werte nicht überschreiben
71
+ if (!totalRaw && !seasonRaw) {
72
+ this.adapter.log.info(
73
+ '[runtimeHelper] Keine gespeicherten Laufzeiten gefunden – möglicherweise neue oder überinstallierte Instanz. Laufzeiten starten bei 0.',
74
+ );
75
+ }
76
+
55
77
  // >>> NEU: Formatierten Text (z. B. "3h 12m 5s") in Sekunden umwandeln
56
78
  this.runtimeTotal = this._parseFormattedTimeToSeconds(totalRaw);
57
79
  this.runtimeToday = this._parseFormattedTimeToSeconds(todayRaw);
@@ -134,18 +156,45 @@ const runtimeHelper = {
134
156
  await this.adapter.setStateAsync('runtime.season_total', { val: formattedSeason, ack: true });
135
157
  await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
136
158
 
137
- // Umwälzmenge berechnen
138
- const pumpLph = (await this.adapter.getStateAsync('pump.pump_power_lph'))?.val || 0;
159
+ // Poolparameter laden (vor Durchflussprüfung!)
139
160
  const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
140
161
  const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
141
162
 
142
- const dailyTotal = Math.round((effectiveToday / 3600) * pumpLph);
163
+ // daily_required immer direkt setzen auch ohne Durchfluss
143
164
  const dailyRequired = Math.round(poolSize * minCirc);
165
+ if (dailyRequired > 0) {
166
+ await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
167
+ }
168
+
169
+ // Umwälzmenge berechnen
170
+ // Reeller Durchflusswert aus pump.live.flow_current_lh
171
+ const liveFlowLh = (await this.adapter.getStateAsync('pump.live.flow_current_lh'))?.val || 0;
172
+
173
+ if (liveFlowLh <= 0) {
174
+ this.adapter.log.debug('[runtimeHelper] Kein Live-Durchflusswert vorhanden, Berechnung übersprungen');
175
+ return;
176
+ }
177
+
178
+ // Berechnung der realen Tagesumwälzung (Liter)
179
+ const dailyTotal = Math.round((effectiveToday / 3600) * liveFlowLh);
144
180
  const dailyRemaining = Math.max(dailyRequired - dailyTotal, 0);
145
181
 
146
- await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
147
- await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
148
- await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });
182
+ // Bestehende Werte für Total/Remaining laden
183
+ const oldTotal = (await this.adapter.getStateAsync('circulation.daily_total'))?.val || 0;
184
+ const oldRemaining = (await this.adapter.getStateAsync('circulation.daily_remaining'))?.val || 0;
185
+
186
+ // Nur schreiben, wenn tatsächlich sinnvolle Livewerte vorliegen
187
+ if (liveFlowLh > 0 && dailyTotal > 0) {
188
+ await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
189
+ await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });
190
+ this.adapter.log.debug(
191
+ `[runtimeHelper] Circulation-Werte aktualisiert (Total=${dailyTotal}, Required=${dailyRequired}, Remaining=${dailyRemaining})`,
192
+ );
193
+ } else {
194
+ this.adapter.log.debug(
195
+ `[runtimeHelper] Keine gültigen Live-Daten – bestehende Werte bleiben erhalten (Total=${oldTotal}, Required=${dailyRequired}, Remaining=${oldRemaining})`,
196
+ );
197
+ }
149
198
  } catch (err) {
150
199
  this.adapter.log.warn(`[runtimeHelper] Fehler beim Update der States: ${err.message}`);
151
200
  }
@@ -194,12 +243,30 @@ const runtimeHelper = {
194
243
  nextMidnight.setHours(24, 0, 0, 0);
195
244
  const msUntilMidnight = nextMidnight - now;
196
245
 
197
- this.resetTimer = setTimeout(() => {
246
+ this.resetTimer = setTimeout(async () => {
198
247
  this.runtimeToday = 0;
199
248
  this.startCountToday = 0;
200
249
  this.lastOn = this.isRunning ? Date.now() : null;
250
+
251
+ // Laufzeiten zurücksetzen
201
252
  this._updateStates();
253
+
254
+ // --- NEU: Circulation-Werte um Mitternacht zurücksetzen ---
255
+ await this.adapter.setStateAsync('circulation.daily_total', { val: 0, ack: true });
256
+
257
+ // daily_required neu berechnen (optional, falls sich Poolgröße geändert hat)
258
+ const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
259
+ const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
260
+ const dailyRequired = Math.round(poolSize * minCirc);
261
+ await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
262
+
263
+ // 👉 daily_remaining neue berechnen auf Grundlage von daily_required
264
+ await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRequired, ack: true });
265
+
266
+ // Nächsten Reset planen
202
267
  this._scheduleDailyReset();
268
+
269
+ this.adapter.log.debug('[runtimeHelper] Tagesreset (Runtime + Circulation) ausgeführt.');
203
270
  }, msUntilMidnight);
204
271
  },
205
272
 
@@ -12,6 +12,7 @@ const statusHelper = {
12
12
  adapter: null,
13
13
  midnightTimer: null,
14
14
  pumpOn: null, // interner Merker für Pumpenstatus
15
+ _lastSummaryUpdate: 0, // FIX: Zeitstempel für Throttle-Schutz
15
16
 
16
17
  init(adapter) {
17
18
  this.adapter = adapter;
@@ -95,8 +96,23 @@ const statusHelper = {
95
96
  await this.updateSummary();
96
97
  },
97
98
 
99
+ // FIX: Hilfsfunktion zur sicheren Formatierung
100
+ safeValue(v, digits = 1) {
101
+ if (v == null || isNaN(v)) {
102
+ return '–';
103
+ }
104
+ return Number(v).toFixed(digits);
105
+ },
106
+
98
107
  async updateSummary() {
99
108
  try {
109
+ // FIX: Throttle - Mehrfachupdates innerhalb 1 Sekunde vermeiden
110
+ if (Date.now() - this._lastSummaryUpdate < 1000) {
111
+ this.adapter.log.debug('[statusHelper] UpdateSummary übersprungen (Throttle)');
112
+ return;
113
+ }
114
+ this._lastSummaryUpdate = Date.now();
115
+
100
116
  // Werte laden
101
117
  const pumpStatus = (await this.adapter.getStateAsync('pump.status'))?.val || 'unbekannt';
102
118
  const pumpMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'unknown';
@@ -112,32 +128,41 @@ const statusHelper = {
112
128
  // Laufzeit formatieren
113
129
  const h = Math.floor(runtimeToday / 3600);
114
130
  const m = Math.floor((runtimeToday % 3600) / 60);
115
- const runtimeFormatted = `${h}h ${m}m`;
131
+ const runtimeFormatted = isNaN(h) || isNaN(m) ? '0h 00m' : `${h}h ${m}m`; // FIX: Schutz gegen NaN
116
132
 
117
133
  // Umwälzungsquote
118
134
  let circulationPct = 0;
119
135
  if (dailyRequired > 0) {
120
136
  circulationPct = Math.round((dailyTotal / dailyRequired) * 100);
121
137
  }
138
+ if (isNaN(circulationPct)) {
139
+ circulationPct = 0;
140
+ } // FIX: NaN-Absicherung
122
141
 
123
142
  // Text bauen
124
143
  let text = `Pumpe: ${pumpStatus}`;
125
144
  if (pumpMode && pumpMode !== 'unknown') {
126
145
  text += ` (Modus: ${pumpMode})`;
127
146
  }
147
+
148
+ const safe = this.safeValue.bind(this); // FIX: Kurzreferenz
128
149
  if (poolTemp != null) {
129
- text += `. Pool: ${poolTemp.toFixed(1)} °C`;
150
+ text += `. Pool: ${safe(poolTemp)} °C`;
130
151
  }
131
152
  if (collectorTemp != null) {
132
- text += `, Kollektor: ${collectorTemp.toFixed(1)} °C`;
153
+ text += `, Kollektor: ${safe(collectorTemp)} °C`;
133
154
  }
134
155
  if (outsideTemp != null) {
135
- text += `, Außentemperatur: ${outsideTemp.toFixed(1)} °C`;
156
+ text += `, Außentemperatur: ${safe(outsideTemp)} °C`;
136
157
  }
137
158
  text += `. Tageslaufzeit: ${runtimeFormatted} (${circulationPct}% der Soll-Umwälzung).`;
138
159
 
139
- // In States schreiben
140
- await this.adapter.setStateAsync('status.summary', { val: text, ack: true });
160
+ // In States schreiben (nur bei Änderung)
161
+ const current = (await this.adapter.getStateAsync('status.summary'))?.val;
162
+ if (current !== text) {
163
+ await this.adapter.setStateAsync('status.summary', { val: text, ack: true });
164
+ }
165
+
141
166
  await this.adapter.setStateAsync('status.last_summary_update', {
142
167
  val: new Date().toISOString(),
143
168
  ack: true,
@@ -147,10 +172,10 @@ const statusHelper = {
147
172
  const json = {
148
173
  pump: pumpStatus,
149
174
  mode: pumpMode,
150
- pool: poolTemp,
151
- collector: collectorTemp,
152
- outside: outsideTemp,
153
- runtime_today: runtimeToday,
175
+ pool: poolTemp ?? null,
176
+ collector: collectorTemp ?? null,
177
+ outside: outsideTemp ?? null,
178
+ runtime_today: runtimeToday ?? 0,
154
179
  runtime_formatted: runtimeFormatted,
155
180
  circulation_pct: circulationPct,
156
181
  };
@@ -36,7 +36,7 @@ async function createControlStates(adapter) {
36
36
  native: {},
37
37
  });
38
38
 
39
- // State: control.season.active
39
+ // State: control.season.active (mit Persist-Schutz)
40
40
  await adapter.setObjectNotExistsAsync('control.season.active', {
41
41
  type: 'state',
42
42
  common: {
@@ -47,15 +47,21 @@ async function createControlStates(adapter) {
47
47
  read: true,
48
48
  write: true,
49
49
  def: false,
50
+ persist: true, // dauerhaft speichern
50
51
  },
51
52
  native: {},
52
53
  });
53
54
 
54
- // Initialwert aus der Instanzkonfiguration übernehmen
55
- const cfgValue = !!adapter.config.season_active;
56
- await adapter.setStateAsync('control.season.active', { val: cfgValue, ack: true });
57
-
58
- adapter.log.debug(`[controlStates] State control.season.active initialisiert mit Wert: ${cfgValue}`);
55
+ const existingSeasonActive = await adapter.getStateAsync('control.season.active');
56
+ if (
57
+ existingSeasonActive === null ||
58
+ existingSeasonActive.val === null ||
59
+ existingSeasonActive.val === undefined
60
+ ) {
61
+ const cfgValue = !!adapter.config.season_active;
62
+ await adapter.setStateAsync('control.season.active', { val: cfgValue, ack: true });
63
+ adapter.log.debug(`[controlStates] State control.season.active initialisiert mit Wert: ${cfgValue}`);
64
+ }
59
65
 
60
66
  // ---------------------------------------------------------------------
61
67
  // Channel: control.pump
@@ -100,7 +106,7 @@ async function createControlStates(adapter) {
100
106
  });
101
107
  await adapter.setStateAsync('control.pump.backwash_active', { val: false, ack: true });
102
108
 
103
- // Rückspülungsdauer
109
+ // Rückspülungsdauer (mit Persist-Schutz)
104
110
  await adapter.setObjectNotExistsAsync('control.pump.backwash_duration', {
105
111
  type: 'state',
106
112
  common: {
@@ -113,12 +119,20 @@ async function createControlStates(adapter) {
113
119
  def: 1,
114
120
  min: 1,
115
121
  max: 60,
122
+ persist: true, // dauerhaft speichern
116
123
  },
117
124
  native: {},
118
125
  });
119
- await adapter.setStateAsync('control.pump.backwash_duration', { val: 1, ack: true });
126
+ const existingBackwashDuration = await adapter.getStateAsync('control.pump.backwash_duration');
127
+ if (
128
+ existingBackwashDuration === null ||
129
+ existingBackwashDuration.val === null ||
130
+ existingBackwashDuration.val === undefined
131
+ ) {
132
+ await adapter.setStateAsync('control.pump.backwash_duration', { val: 1, ack: true });
133
+ }
120
134
 
121
- // >>> NEU: Rückspülerinnerung
135
+ // Rückspülerinnerung aktiv (mit Persist-Schutz)
122
136
  await adapter.setObjectNotExistsAsync('control.pump.backwash_reminder_active', {
123
137
  type: 'state',
124
138
  common: {
@@ -129,11 +143,16 @@ async function createControlStates(adapter) {
129
143
  read: true,
130
144
  write: true,
131
145
  def: false,
146
+ persist: true, // dauerhaft speichern
132
147
  },
133
148
  native: {},
134
149
  });
135
- await adapter.setStateAsync('control.pump.backwash_reminder_active', { val: false, ack: true });
150
+ const existingBackwashRem = await adapter.getStateAsync('control.pump.backwash_reminder_active');
151
+ if (existingBackwashRem === null || existingBackwashRem.val === null || existingBackwashRem.val === undefined) {
152
+ await adapter.setStateAsync('control.pump.backwash_reminder_active', { val: false, ack: true });
153
+ }
136
154
 
155
+ // Rückspülintervall (mit Persist-Schutz)
137
156
  await adapter.setObjectNotExistsAsync('control.pump.backwash_interval_days', {
138
157
  type: 'state',
139
158
  common: {
@@ -147,10 +166,18 @@ async function createControlStates(adapter) {
147
166
  def: 7,
148
167
  min: 1,
149
168
  max: 60,
169
+ persist: true, // dauerhaft speichern
150
170
  },
151
171
  native: {},
152
172
  });
153
- await adapter.setStateAsync('control.pump.backwash_interval_days', { val: 7, ack: true });
173
+ const existingBackwashInterval = await adapter.getStateAsync('control.pump.backwash_interval_days');
174
+ if (
175
+ existingBackwashInterval === null ||
176
+ existingBackwashInterval.val === null ||
177
+ existingBackwashInterval.val === undefined
178
+ ) {
179
+ await adapter.setStateAsync('control.pump.backwash_interval_days', { val: 7, ack: true });
180
+ }
154
181
 
155
182
  await adapter.setObjectNotExistsAsync('control.pump.backwash_last_date', {
156
183
  type: 'state',
@@ -181,7 +208,6 @@ async function createControlStates(adapter) {
181
208
  });
182
209
  await adapter.setStateAsync('control.pump.backwash_required', { val: false, ack: true });
183
210
 
184
- // ---------------------------------------------------------------------
185
211
  // Wartungsmodus aktiv
186
212
  await adapter.setObjectNotExistsAsync('control.pump.maintenance_active', {
187
213
  type: 'state',
@@ -198,7 +224,7 @@ async function createControlStates(adapter) {
198
224
  });
199
225
  await adapter.setStateAsync('control.pump.maintenance_active', { val: false, ack: true });
200
226
 
201
- // Benachrichtigungen aktivieren
227
+ // Benachrichtigungen aktivieren (mit Persist-Schutz)
202
228
  await adapter.setObjectNotExistsAsync('control.pump.notifications_enabled', {
203
229
  type: 'state',
204
230
  common: {
@@ -209,10 +235,14 @@ async function createControlStates(adapter) {
209
235
  read: true,
210
236
  write: true,
211
237
  def: true,
238
+ persist: true, // dauerhaft speichern
212
239
  },
213
240
  native: {},
214
241
  });
215
- await adapter.setStateAsync('control.pump.notifications_enabled', { val: true, ack: true });
242
+ const existingNoti = await adapter.getStateAsync('control.pump.notifications_enabled');
243
+ if (existingNoti === null || existingNoti.val === null || existingNoti.val === undefined) {
244
+ await adapter.setStateAsync('control.pump.notifications_enabled', { val: true, ack: true });
245
+ }
216
246
 
217
247
  // ---------------------------------------------------------------------
218
248
  // Channel: control.energy
@@ -252,7 +282,7 @@ async function createControlStates(adapter) {
252
282
  native: {},
253
283
  });
254
284
 
255
- // State: Modus (auto/manual/notify/off)
285
+ // Modus der Umwälzungsprüfung
256
286
  await adapter.setObjectNotExistsAsync('control.circulation.mode', {
257
287
  type: 'state',
258
288
  common: {
@@ -263,6 +293,7 @@ async function createControlStates(adapter) {
263
293
  read: true,
264
294
  write: true,
265
295
  def: 'notify',
296
+ persist: true,
266
297
  states: {
267
298
  auto: 'Automatik',
268
299
  manual: 'Manuell',
@@ -272,9 +303,14 @@ async function createControlStates(adapter) {
272
303
  },
273
304
  native: {},
274
305
  });
306
+ try {
307
+ await adapter.extendObjectAsync('control.circulation.mode', { common: { persist: true } });
308
+ } catch (err) {
309
+ adapter.log.warn(`[controlStates] persist-Flag für control.circulation.mode nicht gesetzt: ${err.message}`);
310
+ }
275
311
  await adapter.setStateAsync('control.circulation.mode', { val: 'notify', ack: true });
276
312
 
277
- // State: Prüfzeitpunkt
313
+ // Prüfzeitpunkt (mit Persist-Schutz)
278
314
  await adapter.setObjectNotExistsAsync('control.circulation.check_time', {
279
315
  type: 'state',
280
316
  common: {
@@ -285,12 +321,16 @@ async function createControlStates(adapter) {
285
321
  read: true,
286
322
  write: true,
287
323
  def: '18:00',
324
+ persist: true, // dauerhaft speichern
288
325
  },
289
326
  native: {},
290
327
  });
291
- await adapter.setStateAsync('control.circulation.check_time', { val: '18:00', ack: true });
328
+ const existingCheckTime = await adapter.getStateAsync('control.circulation.check_time');
329
+ if (existingCheckTime === null || existingCheckTime.val === null || existingCheckTime.val === undefined) {
330
+ await adapter.setStateAsync('control.circulation.check_time', { val: '18:00', ack: true });
331
+ }
292
332
 
293
- // State: letzter Bericht
333
+ // letzter Bericht
294
334
  await adapter.setObjectNotExistsAsync('control.circulation.last_report', {
295
335
  type: 'state',
296
336
  common: {
@@ -307,6 +347,30 @@ async function createControlStates(adapter) {
307
347
  } catch (err) {
308
348
  adapter.log.error(`[controlStates] Fehler beim Erstellen der Control-States: ${err.message}`);
309
349
  }
350
+ // ------------------------------------------------------
351
+ // Control – Hardware
352
+ // ------------------------------------------------------
353
+ await adapter.setObjectNotExistsAsync('control.hardware', {
354
+ type: 'channel',
355
+ common: { name: 'Hardware-Steuerung (externe Boxen)' },
356
+ native: {},
357
+ });
358
+
359
+ // TempBox aktivieren/deaktivieren
360
+ await adapter.setObjectNotExistsAsync('control.hardware.use_tempbox', {
361
+ type: 'state',
362
+ common: {
363
+ name: 'Externe Temperaturbox verwenden',
364
+ desc: 'Wenn aktiviert, nutzt PoolControl die Werte der externen TempBox anstelle der manuellen Temperatursensoren.',
365
+ type: 'boolean',
366
+ role: 'switch',
367
+ def: false,
368
+ read: true,
369
+ write: true,
370
+ persist: true,
371
+ },
372
+ native: {},
373
+ });
310
374
  }
311
375
 
312
376
  module.exports = { createControlStates };
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * hardwareStates.js – Bereich für externe Boxen (TempBox, TasterBox usw.)
5
+ * Überarbeitete Version: Checkboxen & settings_enabled entfernt
6
+ */
7
+
8
+ /**
9
+ * Erstellt die Objektstruktur für alle Hardware-Boxen (z. B. TempBox, TasterBox).
10
+ * Diese Version enthält keine Instanz-Checkboxen oder settings_enabled States mehr.
11
+ *
12
+ * @param {object} adapter - ioBroker Adapterinstanz
13
+ */
14
+ async function createHardwareStates(adapter) {
15
+ try {
16
+ // ------------------------------------------------------
17
+ // Oberordner: Hardware
18
+ // ------------------------------------------------------
19
+ await adapter.setObjectNotExistsAsync('hardware', {
20
+ type: 'channel',
21
+ common: { name: 'Hardware-Boxen (Sensoren & externe Geräte)' },
22
+ native: {},
23
+ });
24
+
25
+ // ======================================================
26
+ // TEMPERATUR-BOX (ESP32)
27
+ // ======================================================
28
+ await adapter.setObjectNotExistsAsync('hardware.tempbox', {
29
+ type: 'channel',
30
+ common: { name: 'Temperatur-Box (ESP32)' },
31
+ native: {},
32
+ });
33
+
34
+ // Statusmeldung: Erkennung/Verbindung
35
+ await adapter.setObjectNotExistsAsync('hardware.tempbox.status_detected', {
36
+ type: 'state',
37
+ common: {
38
+ name: 'Box-Status',
39
+ desc: 'Status der automatischen Erkennung der Temperatur-Box (z. B. verbunden/nicht gefunden)',
40
+ type: 'string',
41
+ role: 'text',
42
+ read: true,
43
+ write: false,
44
+ def: 'nicht gefunden',
45
+ },
46
+ native: {},
47
+ });
48
+
49
+ // LED-Status
50
+ await adapter.setObjectNotExistsAsync('hardware.tempbox.status_led', {
51
+ type: 'state',
52
+ common: {
53
+ name: 'LED-Status (TempBox)',
54
+ desc: 'Status der blauen LED auf der Temperatur-Box',
55
+ type: 'string',
56
+ role: 'text',
57
+ read: true,
58
+ write: false,
59
+ def: 'unbekannt',
60
+ },
61
+ native: {},
62
+ });
63
+
64
+ // Firmware-Version
65
+ await adapter.setObjectNotExistsAsync('hardware.tempbox.fw_version', {
66
+ type: 'state',
67
+ common: {
68
+ name: 'Box-Firmware-Version',
69
+ desc: 'Firmware-Version der Temperatur-Box (ESP32)',
70
+ type: 'string',
71
+ role: 'text',
72
+ read: true,
73
+ write: false,
74
+ def: '',
75
+ },
76
+ native: {},
77
+ });
78
+
79
+ // Box-ID
80
+ await adapter.setObjectNotExistsAsync('hardware.tempbox.box_id', {
81
+ type: 'state',
82
+ common: {
83
+ name: 'Box-ID (TempBox)',
84
+ desc: 'Eindeutige Kennung der Temperatur-Box (z. B. PC-TB-01)',
85
+ type: 'string',
86
+ role: 'text',
87
+ read: true,
88
+ write: false,
89
+ def: '',
90
+ },
91
+ native: {},
92
+ });
93
+
94
+ // Sensorsystem-Status
95
+ await adapter.setObjectNotExistsAsync('hardware.tempbox.status_sensors', {
96
+ type: 'state',
97
+ common: {
98
+ name: 'Sensorsystem-Status',
99
+ desc: 'Meldung zum Zustand der Temperatursensoren in der TempBox',
100
+ type: 'string',
101
+ role: 'text',
102
+ read: true,
103
+ write: false,
104
+ def: 'keine Daten',
105
+ },
106
+ native: {},
107
+ });
108
+
109
+ // ------------------------------------------------------
110
+ // TASTER-BOX (ESP32)
111
+ // ------------------------------------------------------
112
+ await adapter.setObjectNotExistsAsync('hardware.tasterbox', {
113
+ type: 'channel',
114
+ common: { name: 'Taster-Box (ESP32)' },
115
+ native: {},
116
+ });
117
+
118
+ // Statusmeldung
119
+ await adapter.setObjectNotExistsAsync('hardware.tasterbox.status_detected', {
120
+ type: 'state',
121
+ common: {
122
+ name: 'Box-Status',
123
+ desc: 'Status der automatischen Erkennung der Taster-Box (z. B. verbunden/nicht gefunden)',
124
+ type: 'string',
125
+ role: 'text',
126
+ read: true,
127
+ write: false,
128
+ def: 'nicht gefunden',
129
+ },
130
+ native: {},
131
+ });
132
+
133
+ // Firmware-Version
134
+ await adapter.setObjectNotExistsAsync('hardware.tasterbox.fw_version', {
135
+ type: 'state',
136
+ common: {
137
+ name: 'Box-Firmware-Version',
138
+ desc: 'Firmware-Version der Taster-Box (ESP32)',
139
+ type: 'string',
140
+ role: 'text',
141
+ read: true,
142
+ write: false,
143
+ def: '',
144
+ },
145
+ native: {},
146
+ });
147
+
148
+ // Box-ID
149
+ await adapter.setObjectNotExistsAsync('hardware.tasterbox.box_id', {
150
+ type: 'state',
151
+ common: {
152
+ name: 'Box-ID (TasterBox)',
153
+ desc: 'Eindeutige Kennung der Taster-Box (z. B. PC-TB-02)',
154
+ type: 'string',
155
+ role: 'text',
156
+ read: true,
157
+ write: false,
158
+ def: '',
159
+ },
160
+ native: {},
161
+ });
162
+
163
+ // Status der Taster
164
+ await adapter.setObjectNotExistsAsync('hardware.tasterbox.status_buttons', {
165
+ type: 'state',
166
+ common: {
167
+ name: 'Status der Taster',
168
+ desc: 'Textmeldung zum Zustand der Taster (z. B. gedrückt, losgelassen, keine Verbindung)',
169
+ type: 'string',
170
+ role: 'text',
171
+ read: true,
172
+ write: false,
173
+ def: 'unbekannt',
174
+ },
175
+ native: {},
176
+ });
177
+
178
+ adapter.log.debug('[hardwareStates] Hardware-State-Struktur erfolgreich angelegt (bereinigt ohne Checkboxen).');
179
+ } catch (err) {
180
+ adapter.log.error(`[hardwareStates] Fehler beim Anlegen der Hardware-States: ${err.message}`);
181
+ }
182
+ }
183
+
184
+ module.exports = { createHardwareStates };