iobroker.smartloadmanager 0.0.6 → 0.0.8

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/main.js CHANGED
@@ -8,6 +8,9 @@ class ZeroFeedIn extends utils.Adapter {
8
8
  ...options,
9
9
  name: 'smartloadmanager',
10
10
  });
11
+ // Timeout-Limits definieren
12
+ this.MAX_TIMEOUT = 86400000; // 24h in ms
13
+ this.DEFAULT_TIMEOUT = 60000; // 60 Sekunden Fallback
11
14
  this.consumerList = [];
12
15
  this.feedInDatapoint = null;
13
16
  this.batteryControlModeDatapoint = null;
@@ -19,15 +22,40 @@ class ZeroFeedIn extends utils.Adapter {
19
22
  this.on('stateChange', this.onStateChange.bind(this));
20
23
  this.on('unload', this.onUnload.bind(this));
21
24
  }
25
+ name2id(pName) {
26
+ return (pName || '')
27
+ .replace(this.FORBIDDEN_CHARS, '_') // ioBroker verbotene Zeichen
28
+ .replace(/\s+/g, '_') // Leerzeichen → _
29
+ .replace(/\./g, '_') // Punkte → _
30
+ .replace(/[^a-zA-Z0-9\-_]/g, '_') // nur erlaubte Zeichen
31
+ .replace(/_+/g, '_') // mehrere Unterstriche zusammenfassen
32
+ .replace(/^_+|_+$/g, ''); // führende/trailing _ weg
33
+ }
34
+ // Wrapper für sichere Timer
35
+ safeSetTimeout(fn, ms, ...args) {
36
+ const safeMs = Math.min(Math.max(ms, 0), this.MAX_TIMEOUT);
37
+ if (ms > this.MAX_TIMEOUT) {
38
+ this.log.warn(`Timeout of ${ms} ms has been limited to ${this.MAX_TIMEOUT} ms!`);
39
+ }
40
+ return this.setTimeout(fn, safeMs, ...args);
41
+ }
42
+
43
+ safeSetInterval(fn, ms, ...args) {
44
+ const safeMs = Math.min(Math.max(ms, 0), this.MAX_TIMEOUT);
45
+ if (ms > this.MAX_TIMEOUT) {
46
+ this.log.warn(`Interval of ${ms} ms has been limited to ${this.MAX_TIMEOUT} ms!`);
47
+ }
48
+ return this.setInterval(fn, safeMs, ...args);
49
+ }
22
50
 
23
51
  async onReady() {
24
52
  try {
25
- this.log.info(`=== Adapter started - PID: ${process.pid} ===`);
53
+ this.log.info(`Adapter started, PID: ${process.pid}`);
26
54
 
27
55
  if (!this.config.FeedInDataPoint) {
28
56
  this.log.error('No FeedInDataPoint configured!');
29
- // zusätzlich als alert melden
30
- await this.sendNotification('smartloadmanager', 'alarm', '❗ Kein FeedInDataPoint konfiguriert!');
57
+ // additionally send alert
58
+ await this.sendNotification('smartloadmanager', 'alarm', 'No FeedInDataPoint configured!');
31
59
  return;
32
60
  }
33
61
  this.feedInDatapoint = this.config.FeedInDataPoint;
@@ -58,20 +86,10 @@ class ZeroFeedIn extends utils.Adapter {
58
86
  }
59
87
 
60
88
  await this.checkConsumers();
61
-
62
- /* // Info beim erfolgreichen Start
63
- if (!this.adapterStartedNotified) {
64
- await this.sendNotification(
65
- 'smartloadmanager',
66
- 'info',
67
- 'ℹ️ SmartLoadManager Adapter erfolgreich gestartet.',
68
- );
69
- this.adapterStartedNotified = true;
70
- } */
71
89
  } catch (error) {
72
90
  this.log.error(`Error in onReady: ${error.message}`);
73
- // Fehler zusätzlich als alert melden
74
- await this.sendNotification('smartloadmanager', 'alert', `❌ Fehler in onReady: ${error.message}`);
91
+ // additionally send alert
92
+ await this.sendNotification('smartloadmanager', 'alert', `Error in onReady: ${error.message}`);
75
93
  }
76
94
  }
77
95
 
@@ -93,20 +111,30 @@ class ZeroFeedIn extends utils.Adapter {
93
111
  }
94
112
 
95
113
  async createConsumerStates() {
114
+ // Übergeordnetes Device erstellen - löst "intermediate objects missing" Problem
115
+ await this.setObjectNotExistsAsync('consumer', {
116
+ type: 'device',
117
+ common: {
118
+ name: 'Consumer Management',
119
+ },
120
+ native: {},
121
+ });
122
+
96
123
  for (let i = 0; i < this.consumerList.length; i++) {
97
124
  const v = this.consumerList[i];
98
- const channelId = `consumer.${i}_${v.name.replace(/\s+/g, '_')}`;
125
+ const safeName = this.name2id(v.name);
126
+ const channelId = `consumer.${i}_${safeName}`;
99
127
 
100
128
  await this.setObjectNotExistsAsync(channelId, {
101
129
  type: 'channel',
102
- common: { name: v.name },
130
+ common: { name: v.name }, // Channel selbst darf Klartext-Nutzername behalten
103
131
  native: {},
104
132
  });
105
133
 
106
134
  await this.setObjectNotExistsAsync(`${channelId}.controlMode`, {
107
135
  type: 'state',
108
136
  common: {
109
- name: `${v.name} Control Mode`,
137
+ name: 'controlMode',
110
138
  type: 'number',
111
139
  role: 'level.mode',
112
140
  read: true,
@@ -117,12 +145,13 @@ class ZeroFeedIn extends utils.Adapter {
117
145
  native: {},
118
146
  });
119
147
 
148
+ // KORRIGIERT: switchOnTime und switchOffTime als text statt value.time
120
149
  await this.setObjectNotExistsAsync(`${channelId}.switchOnTime`, {
121
150
  type: 'state',
122
151
  common: {
123
- name: `${v.name} Switch On Time (HH:MM)`,
152
+ name: 'switchOnTime',
124
153
  type: 'string',
125
- role: 'value.time',
154
+ role: 'text', // GEÄNDERT von 'value.time' zu 'text'
126
155
  read: true,
127
156
  write: true,
128
157
  def: '',
@@ -133,9 +162,9 @@ class ZeroFeedIn extends utils.Adapter {
133
162
  await this.setObjectNotExistsAsync(`${channelId}.switchOffTime`, {
134
163
  type: 'state',
135
164
  common: {
136
- name: `${v.name} Switch Off Time (HH:MM)`,
165
+ name: 'switchOffTime',
137
166
  type: 'string',
138
- role: 'value.time',
167
+ role: 'text', // GEÄNDERT von 'value.time' zu 'text'
139
168
  read: true,
140
169
  write: true,
141
170
  def: '',
@@ -146,7 +175,7 @@ class ZeroFeedIn extends utils.Adapter {
146
175
  await this.setObjectNotExistsAsync(`${channelId}.alwaysOffAtTime`, {
147
176
  type: 'state',
148
177
  common: {
149
- name: `${v.name} Ausschalten nur zur Ausschaltzeit`,
178
+ name: 'alwaysOffAtTime',
150
179
  type: 'boolean',
151
180
  role: 'switch',
152
181
  read: true,
@@ -159,9 +188,9 @@ class ZeroFeedIn extends utils.Adapter {
159
188
  await this.setObjectNotExistsAsync(`${channelId}.performance`, {
160
189
  type: 'state',
161
190
  common: {
162
- name: `${v.name} Gesamtleistung (Watt)`,
191
+ name: 'performance',
163
192
  type: 'number',
164
- role: 'value.power.consumption',
193
+ role: 'value.power', // GEÄNDERT von 'value.power.consumption' zu 'value.power'
165
194
  read: true,
166
195
  write: false,
167
196
  def: v.performance || 0,
@@ -173,7 +202,7 @@ class ZeroFeedIn extends utils.Adapter {
173
202
  await this.setObjectNotExistsAsync(`${channelId}.switchOnPoint`, {
174
203
  type: 'state',
175
204
  common: {
176
- name: `${v.name} Einschaltpunkt (Watt)`,
205
+ name: 'switchOnPoint',
177
206
  type: 'number',
178
207
  role: 'value.power',
179
208
  read: true,
@@ -187,7 +216,7 @@ class ZeroFeedIn extends utils.Adapter {
187
216
  await this.setObjectNotExistsAsync(`${channelId}.switchOffPoint`, {
188
217
  type: 'state',
189
218
  common: {
190
- name: `${v.name} Abschaltpunkt (Watt)`,
219
+ name: 'switchOffPoint',
191
220
  type: 'number',
192
221
  role: 'value.power',
193
222
  read: true,
@@ -202,9 +231,9 @@ class ZeroFeedIn extends utils.Adapter {
202
231
  await this.setObjectNotExistsAsync(`${channelId}.batterySetpoint`, {
203
232
  type: 'state',
204
233
  common: {
205
- name: `${v.name} Batterie Ladeleistungs-Datenpunkt`,
234
+ name: 'batterySetpoint',
206
235
  type: 'string',
207
- role: 'value',
236
+ role: 'text', // GEÄNDERT von 'value' zu 'text' (spezifischer)
208
237
  read: true,
209
238
  write: true,
210
239
  def: v.batterySetpoint || '',
@@ -213,12 +242,13 @@ class ZeroFeedIn extends utils.Adapter {
213
242
  });
214
243
  }
215
244
 
245
+ // KORRIGIERT: value.duration ist korrekt für Timer
216
246
  await this.setObjectNotExistsAsync(`${channelId}.onTimerRemaining`, {
217
247
  type: 'state',
218
248
  common: {
219
- name: `${v.name} Einschalt-Timer Restzeit (s)`,
249
+ name: 'onTimerRemaining',
220
250
  type: 'number',
221
- role: 'value.duration',
251
+ role: 'value.interval', // GEÄNDERT von 'value.duration' zu 'value.interval'
222
252
  read: true,
223
253
  write: false,
224
254
  def: 0,
@@ -230,9 +260,9 @@ class ZeroFeedIn extends utils.Adapter {
230
260
  await this.setObjectNotExistsAsync(`${channelId}.offTimerRemaining`, {
231
261
  type: 'state',
232
262
  common: {
233
- name: `${v.name} Ausschalt-Timer Restzeit (s)`,
263
+ name: 'offTimerRemaining',
234
264
  type: 'number',
235
- role: 'value.duration',
265
+ role: 'value.interval', // GEÄNDERT von 'value.duration' zu 'value.interval'
236
266
  read: true,
237
267
  write: false,
238
268
  def: 0,
@@ -241,11 +271,18 @@ class ZeroFeedIn extends utils.Adapter {
241
271
  native: {},
242
272
  });
243
273
 
274
+ // States initialisieren
244
275
  const finalOnTime = v.switchOnTime || '';
245
276
  const finalOffTime = v.switchOffTime || '';
246
277
 
247
- await this.setStateAsync(`${this.namespace}.${channelId}.switchOnTime`, { val: finalOnTime, ack: true });
248
- await this.setStateAsync(`${this.namespace}.${channelId}.switchOffTime`, { val: finalOffTime, ack: true });
278
+ await this.setStateAsync(`${this.namespace}.${channelId}.switchOnTime`, {
279
+ val: finalOnTime,
280
+ ack: true,
281
+ });
282
+ await this.setStateAsync(`${this.namespace}.${channelId}.switchOffTime`, {
283
+ val: finalOffTime,
284
+ ack: true,
285
+ });
249
286
  await this.setStateAsync(`${this.namespace}.${channelId}.alwaysOffAtTime`, {
250
287
  val: v.alwaysOffAtTime || false,
251
288
  ack: true,
@@ -285,7 +322,9 @@ class ZeroFeedIn extends utils.Adapter {
285
322
 
286
323
  if (name) {
287
324
  this.log.debug(
288
- `🕒 ${name}: Zeitfenster ${onTime}–${offTime}, aktuell ${now.toTimeString().slice(0, 5)} (${currentMinutes} min) → innerhalb? ${within}`,
325
+ `${name}: Time window ${onTime}–${offTime}, current ${now
326
+ .toTimeString()
327
+ .slice(0, 5)} (${currentMinutes} min), within? ${within}`,
289
328
  );
290
329
  }
291
330
 
@@ -297,52 +336,58 @@ class ZeroFeedIn extends utils.Adapter {
297
336
  return;
298
337
  }
299
338
 
300
- // FeedIn soll immer reagieren, egal ob ack true/false
339
+ // FeedIn Datapoint - fremder State, nur bei ack=true reagieren
301
340
  if (id === this.feedInDatapoint) {
302
- this.log.debug(`📊 FeedIn-Wert aktualisiert: ${state.val} (ack=${state.ack})`);
303
- await this.checkConsumers();
341
+ // Nur verarbeiten wenn der Wert vom anderen Adapter bestätigt wurde
342
+ if (state.ack === true) {
343
+ this.log.debug(`Feed-in value updated: ${state.val} (ack=${state.ack})`);
344
+ await this.checkConsumers();
304
345
 
305
- if (this.percentTimer) {
306
- clearTimeout(this.percentTimer);
307
- }
308
- this.percentTimer = setTimeout(
309
- async () => {
310
- for (const v of this.consumerList.filter(v => v.ruletype === 'percent')) {
311
- await this.controlPercentConsumer(v);
312
- }
313
- this.percentTimer = null;
314
- },
315
- (this.config.delaySecondsProzent || 60) * 1000,
316
- );
346
+ if (this.percentTimer) {
347
+ clearTimeout(this.percentTimer);
348
+ }
349
+ this.percentTimer = this.safeSetTimeout(
350
+ async () => {
351
+ for (const v of this.consumerList.filter(v => v.ruletype === 'percent')) {
352
+ await this.controlPercentConsumer(v);
353
+ }
354
+ this.percentTimer = null;
355
+ },
356
+ (this.config.delaySecondsProzent || this.DEFAULT_TIMEOUT / 1000) * 1000,
357
+ );
317
358
 
318
- if (this.batteryTimer) {
319
- clearTimeout(this.batteryTimer);
320
- }
321
- this.batteryTimer = setTimeout(
322
- async () => {
323
- if (!this.feedInDatapoint) {
324
- return;
325
- }
326
- const feedInState = await this.getForeignStateAsync(this.feedInDatapoint);
327
- let feedIn = Number(feedInState?.val) || 0;
328
- if (this.config.feedinNegativ) {
329
- feedIn = -feedIn;
330
- }
359
+ if (this.batteryTimer) {
360
+ clearTimeout(this.batteryTimer);
361
+ }
362
+ this.batteryTimer = this.safeSetTimeout(
363
+ async () => {
364
+ if (!this.feedInDatapoint) {
365
+ return;
366
+ }
367
+ const feedInState = await this.getForeignStateAsync(this.feedInDatapoint);
368
+ let feedIn = Number(feedInState?.val) || 0;
369
+ if (this.config.feedinNegativ) {
370
+ feedIn = -feedIn;
371
+ }
331
372
 
332
- for (const v of this.consumerList.filter(v => v.ruletype === 'battery')) {
333
- await this.controlBattery(v, feedIn);
334
- }
335
- this.batteryTimer = null;
336
- },
337
- (this.config.batteryDelaySeconds || 60) * 1000,
338
- );
339
- return; // WICHTIG: damit nicht auch der !state.ack-Teil ausgeführt wird
373
+ for (const v of this.consumerList.filter(v => v.ruletype === 'battery')) {
374
+ await this.controlBattery(v, feedIn);
375
+ }
376
+ this.batteryTimer = null;
377
+ },
378
+ (this.config.batteryDelaySeconds || this.DEFAULT_TIMEOUT / 1000) * 1000,
379
+ );
380
+ } else {
381
+ this.log.debug(`Feed-in value change ignored (ack=${state.ack}) - waiting for confirmed value`);
382
+ }
383
+ return;
340
384
  }
341
385
 
342
- // Rest bleibt wie gehabt (nur bei ack=false)
386
+ // Eigene States - nur bei ack=false verarbeiten (Commands/Befehle)
343
387
  if (state && !state.ack) {
344
388
  this.log.debug(`State changed: ${id} => ${state.val}`);
345
389
 
390
+ // ControlMode Änderungen
346
391
  const match = id.match(/consumer\.(\d+)_.*?\.controlMode/);
347
392
  if (match) {
348
393
  const index = parseInt(match[1]);
@@ -356,6 +401,7 @@ class ZeroFeedIn extends utils.Adapter {
356
401
  }
357
402
  }
358
403
 
404
+ // Time Änderungen
359
405
  const timeMatch = id.match(/consumer\.(\d+)_.*?\.(switchOnTime|switchOffTime)/);
360
406
  if (timeMatch) {
361
407
  const index = parseInt(timeMatch[1]);
@@ -376,7 +422,7 @@ class ZeroFeedIn extends utils.Adapter {
376
422
 
377
423
  try {
378
424
  if (!this.feedInDatapoint) {
379
- this.log.warn('❗ Kein FeedIn-Datenpunkt gesetzt – checkConsumers() wird übersprungen.');
425
+ this.log.warn('No FeedIn datapoint set – skipping checkConsumers()');
380
426
  await this.sendNotification(
381
427
  'smartloadmanager',
382
428
  'alert',
@@ -398,7 +444,7 @@ class ZeroFeedIn extends utils.Adapter {
398
444
  const baseload = this.config.baseload || 0;
399
445
  const gridUsage = feedIn <= baseload;
400
446
 
401
- this.log.debug(`📊 FeedIn: ${feedIn} W, Baseload: ${baseload} W, GridUsage: ${gridUsage}`);
447
+ this.log.debug(`FeedIn: ${feedIn} W, Baseload: ${baseload} W, GridUsage: ${gridUsage}`);
402
448
 
403
449
  // ----------------------------------
404
450
  // Einschalten binary + heating
@@ -407,17 +453,18 @@ class ZeroFeedIn extends utils.Adapter {
407
453
  .filter(v => v.ruletype === 'binary' || v.ruletype === 'heating')
408
454
  .sort((a, b) => (a.priority || 1) - (b.priority || 1));
409
455
 
410
- this.log.debug(`🔍 Einschalt-Prüfung für ${sortedOn.length} binary/heating-Verbraucher.`);
456
+ this.log.debug(`Switch-on check for ${sortedOn.length} binary/heating-consumers.`);
411
457
 
412
458
  for (const v of sortedOn) {
413
459
  const idx = this.consumerList.indexOf(v);
414
- const id = `${this.namespace}.consumer.${idx}_${v.name.replace(/\s+/g, '_')}`;
460
+ const safeName = this.name2id(v.name);
461
+ const id = `${this.namespace}.consumer.${idx}_${safeName}`;
415
462
  const mode = await this.getStateAsync(`${id}.controlMode`);
416
463
 
417
- this.log.debug(`➡️ Prüfe "${v.name}" (Prio ${v.priority}, ${v.performance} W, Typ=${v.ruletype})`);
464
+ this.log.debug(`Checking "${v.name}" (priority ${v.priority}, ${v.performance} W, Type=${v.ruletype})`);
418
465
 
419
466
  if (!mode || mode.val !== 2) {
420
- this.log.debug(`⏭️ Manueller Modus aktiv, Überspringe.`);
467
+ this.log.debug(`Manual mode active, skipping.`);
421
468
  continue;
422
469
  }
423
470
 
@@ -435,10 +482,10 @@ class ZeroFeedIn extends utils.Adapter {
435
482
  if (numVal === 1) {
436
483
  isOn = true;
437
484
  }
438
- this.log.debug(`🔢 NumericDatapoint für "${v.name}": ${v.numericDatapoint} = ${numVal}`);
485
+ this.log.debug(`NumericDatapoint für "${v.name}": ${v.numericDatapoint} = ${numVal}`);
439
486
  }
440
487
 
441
- this.log.debug(`💡 "${v.name}" Status: isOn=${isOn}`);
488
+ this.log.debug(`"${v.name}" Status: isOn=${isOn}`);
442
489
 
443
490
  // Zusatz-Checks heating
444
491
  if (v.ruletype === 'heating' && withinWindow && !isOn) {
@@ -449,13 +496,13 @@ class ZeroFeedIn extends utils.Adapter {
449
496
 
450
497
  if (currentTemp !== null && v.maxTemperature !== undefined && currentTemp >= v.maxTemperature) {
451
498
  this.log.warn(
452
- `🔥 "${v.name}" NICHT eingeschaltet – Temperatur ${currentTemp}°C >= MaxTemp ${v.maxTemperature}°C`,
499
+ `"${v.name}" not switched on, temperature ${currentTemp}°C >= MaxTemp ${v.maxTemperature}°C`,
453
500
  );
454
501
  continue;
455
502
  }
456
503
  if (currentTemp !== null && v.minTemperature !== undefined && currentTemp < v.minTemperature) {
457
504
  this.log.debug(
458
- `🌡️ "${v.name}" noch nicht einschalten – Temperatur ${currentTemp}°C < MinTemp ${v.minTemperature}°C`,
505
+ `"${v.name}" not switched on yet, temperature ${currentTemp}°C < MinTemp ${v.minTemperature}°C`,
459
506
  );
460
507
  continue;
461
508
  }
@@ -463,7 +510,7 @@ class ZeroFeedIn extends utils.Adapter {
463
510
  if (v.enableDatapoint) {
464
511
  const enableState = await this.getForeignStateAsync(v.enableDatapoint);
465
512
  if (!enableState?.val) {
466
- this.log.debug(`🔒 "${v.name}" NICHT eingeschaltet – Hardwarefreigabe fehlt.`);
513
+ this.log.debug(`"${v.name}" NICHT eingeschaltet – Hardware release missing.`);
467
514
  continue;
468
515
  }
469
516
  }
@@ -471,7 +518,7 @@ class ZeroFeedIn extends utils.Adapter {
471
518
  if (v.heartbeatDatapoint) {
472
519
  const hbState = await this.getForeignStateAsync(v.heartbeatDatapoint);
473
520
  if (!hbState?.val) {
474
- this.log.warn(`💔 "${v.name}" NICHT eingeschaltet – Heartbeat fehlt.`);
521
+ this.log.warn(`"${v.name}" not switched on – hardware approval missing.`);
475
522
  continue;
476
523
  }
477
524
  }
@@ -488,28 +535,30 @@ class ZeroFeedIn extends utils.Adapter {
488
535
  v.timer = null;
489
536
  v.timerEnd = null;
490
537
  v.pendingAction = null;
491
- const channelId = `consumer.${idx}_${v.name.replace(/\s+/g, '_')}`;
538
+ const channelId = `consumer.${idx}_${safeName}`;
492
539
  await this.setStateAsync(`${this.namespace}.${channelId}.onTimerRemaining`, { val: 0, ack: true });
493
540
  await this.setStateAsync(`${this.namespace}.${channelId}.offTimerRemaining`, { val: 0, ack: true });
494
- this.log.debug(`⏹️ Einschalt-Timer für "${v.name}" abgebrochen (Bedingung entfallen).`);
541
+ this.log.debug(`Switch-on timer for "${v.name}" canceled (condition no longer valid).`);
495
542
  }
496
543
 
497
544
  if (withinWindow) {
498
545
  if (!gridUsage && v.performance <= feedIn - baseload && !isOn) {
499
546
  this.log.debug(
500
- `✅ Einschalten möglich: Überschuss ${feedIn - baseload} W, innerhalb Zeitfenster (onDelay ${v.switchOnDelay || 0}s).`,
547
+ `Switch-on possible: surplus ${
548
+ feedIn - baseload
549
+ } W, within time window (onDelay ${v.switchOnDelay || 0}s).`,
501
550
  );
502
551
  await this.switchConsumerWithDelay(v, true, v.switchOnDelay || 0);
503
552
  feedIn -= v.performance;
504
- this.log.debug(`🔄 Verbleibender Überschuss nach Zuschaltung: ${feedIn - baseload} W`);
553
+ this.log.debug(`Remaining surplus after switch-on: ${feedIn - baseload} W`);
505
554
  } else if (gridUsage && !isOn) {
506
- this.log.debug(`🚫 Nicht eingeschaltet: Netzbezug aktiv.`);
555
+ this.log.debug(`Not switched on: grid usage active.`);
507
556
  } else if (!isOn) {
508
557
  this.log.debug(
509
- `⚠️ Nicht eingeschaltet: Überschuss zu gering (${feedIn - baseload} W < ${v.performance} W).`,
558
+ `Not switched on: surplus too low (${feedIn - baseload} W < ${v.performance} W).`,
510
559
  );
511
560
  } else {
512
- this.log.debug(`ℹ️ "${v.name}" ist bereits eingeschaltet – keine Aktion nötig.`);
561
+ this.log.debug(`"${v.name}" is already switched on – no action needed.`);
513
562
  }
514
563
  }
515
564
  }
@@ -521,11 +570,12 @@ class ZeroFeedIn extends utils.Adapter {
521
570
  .filter(v => v.ruletype === 'binary' || v.ruletype === 'heating')
522
571
  .sort((a, b) => (b.priority || 1) - (a.priority || 1));
523
572
 
524
- this.log.debug(`🔍 Ausschalt-Prüfung für ${sortedOff.length} binary/heating-Verbraucher.`);
573
+ this.log.debug(`Switch-off check for ${sortedOff.length} binary/heating-consumers.`);
525
574
 
526
575
  for (const v of sortedOff) {
527
576
  const idx = this.consumerList.indexOf(v);
528
- const id = `${this.namespace}.consumer.${idx}_${v.name.replace(/\s+/g, '_')}`;
577
+ const safeName = this.name2id(v.name);
578
+ const id = `${this.namespace}.consumer.${idx}_${safeName}`;
529
579
  const mode = await this.getStateAsync(`${id}.controlMode`);
530
580
  if (!mode || mode.val !== 2) {
531
581
  continue;
@@ -552,13 +602,13 @@ class ZeroFeedIn extends utils.Adapter {
552
602
  if (v.alwaysOffAtTime && !withinWindow && isOn) {
553
603
  if (v.timer && v.timerEnd && v.pendingAction === false) {
554
604
  const remaining = Math.max(0, Math.round((v.timerEnd - Date.now()) / 1000));
555
- this.log.debug(`📋 Aktive Timer → ${v.name}: noch ${remaining}s`);
605
+ this.log.debug(`Active timer for ${v.name}: ${remaining}s remaining`);
556
606
  } else if (v.timer && v.timerEnd && v.pendingAction === true) {
557
607
  clearTimeout(v.timer);
558
608
  v.timer = null;
559
609
  v.timerEnd = null;
560
610
  v.pendingAction = null;
561
- const channelId = `consumer.${idx}_${v.name.replace(/\s+/g, '_')}`;
611
+ const channelId = `consumer.${idx}_${safeName}`;
562
612
  await this.setStateAsync(`${this.namespace}.${channelId}.onTimerRemaining`, {
563
613
  val: 0,
564
614
  ack: true,
@@ -567,14 +617,14 @@ class ZeroFeedIn extends utils.Adapter {
567
617
  val: 0,
568
618
  ack: true,
569
619
  });
570
- this.log.debug(`⏹️ Einschalt-Timer für "${v.name}" abgebrochen (AlwaysOff aktiv).`);
620
+ this.log.debug(`Switch-on timer for "${v.name}" canceled (AlwaysOff active).`);
571
621
  this.log.debug(
572
- `⏳ AlwaysOff aktiv: "${v.name}" - Zeitfenster vorbei, offDelay ${v.switchOffDelay || 0}s.`,
622
+ `AlwaysOff active: "${v.name}" - time window expired, offDelay ${v.switchOffDelay || 0}s.`,
573
623
  );
574
624
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
575
625
  } else {
576
626
  this.log.debug(
577
- `⏳ AlwaysOff aktiv: "${v.name}" - Zeitfenster vorbei, offDelay ${v.switchOffDelay || 0}s.`,
627
+ `AlwaysOff activ: "${v.name}" - time window expired, offDelay ${v.switchOffDelay || 0}s.`,
578
628
  );
579
629
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
580
630
  }
@@ -585,7 +635,7 @@ class ZeroFeedIn extends utils.Adapter {
585
635
  if (v.enableDatapoint) {
586
636
  const enableState = await this.getForeignStateAsync(v.enableDatapoint);
587
637
  if (!enableState?.val) {
588
- this.log.warn(`🔒 "${v.name}" wird abgeschaltet – Hardwarefreigabe fehlt.`);
638
+ this.log.warn(`"${v.name}" is being switched off - hardware approval missing.`);
589
639
  await this.switchConsumerWithDelay(v, false, 0);
590
640
  continue;
591
641
  }
@@ -593,7 +643,7 @@ class ZeroFeedIn extends utils.Adapter {
593
643
  if (v.heartbeatDatapoint) {
594
644
  const hbState = await this.getForeignStateAsync(v.heartbeatDatapoint);
595
645
  if (!hbState?.val) {
596
- this.log.warn(`💔 "${v.name}" wird abgeschaltet – Heartbeat fehlt.`);
646
+ this.log.warn(`"${v.name}" is being switched off - heartbeat missing.`);
597
647
  await this.switchConsumerWithDelay(v, false, 0);
598
648
  continue;
599
649
  }
@@ -604,30 +654,54 @@ class ZeroFeedIn extends utils.Adapter {
604
654
  continue;
605
655
  }
606
656
 
657
+ // Temperatur-basierte Ausschaltung für heating
658
+ if (v.ruletype === 'heating' && v.maxTemperature !== undefined && isOn) {
659
+ const tempState = v.temperatureDatapoint
660
+ ? await this.getForeignStateAsync(v.temperatureDatapoint)
661
+ : null;
662
+ const currentTemp = tempState?.val !== undefined ? Number(tempState.val) : null;
663
+ if (currentTemp !== null && currentTemp >= v.maxTemperature) {
664
+ this.log.warn(
665
+ `"${v.name}" switched off due to maxTemperature reached: ${currentTemp}°C >= ${v.maxTemperature}°C`,
666
+ );
667
+ await this.switchConsumerWithDelay(v, false, 0);
668
+ continue; // weiter zum nächsten Verbraucher
669
+ }
670
+ }
671
+
607
672
  // Timer-Abbruch bei wegfallender Bedingung (Ausschalten)
608
- if (v.timer && v.timerEnd && v.pendingAction === false && withinWindow && !gridUsage) {
673
+ const surplus = feedIn - baseload;
674
+ if (v.timer && v.timerEnd && v.pendingAction === false && withinWindow && surplus >= v.switchOffPoint) {
609
675
  clearTimeout(v.timer);
610
676
  v.timer = null;
611
677
  v.timerEnd = null;
612
678
  v.pendingAction = null;
613
- const channelId = `consumer.${idx}_${v.name.replace(/\s+/g, '_')}`;
679
+ const channelId = `consumer.${idx}_${safeName}`;
614
680
  await this.setStateAsync(`${this.namespace}.${channelId}.onTimerRemaining`, { val: 0, ack: true });
615
681
  await this.setStateAsync(`${this.namespace}.${channelId}.offTimerRemaining`, { val: 0, ack: true });
616
- this.log.debug(`⏹️ Ausschalt-Timer für "${v.name}" abgebrochen (Bedingung entfallen).`);
682
+ this.log.debug(`Switch-off timer for "${v.name}" canceled (condition no longer valid).`);
617
683
  }
618
684
 
619
- if (!v.alwaysOffAtTime && (!withinWindow || gridUsage)) {
685
+ // Ausschalten, wenn:
686
+ // - Zeitfenster verlassen ODER
687
+ // - Netzbezug aktiv ODER
688
+ // - Überschuss < switchOffPoint
689
+ const shouldSwitchOff = !withinWindow || gridUsage || surplus < v.switchOffPoint;
690
+
691
+ if (!v.alwaysOffAtTime && shouldSwitchOff) {
620
692
  if (v.timer && v.timerEnd && v.pendingAction === false) {
621
693
  const remaining = Math.max(0, Math.round((v.timerEnd - Date.now()) / 1000));
622
- this.log.debug(`📋 Aktive Timer → ${v.name}: noch ${remaining}s`);
694
+ this.log.debug(`Active timer for ${v.name}: ${remaining}s remaining`);
623
695
  } else {
624
- this.log.debug(`⏳ Ausschalten: "${v.name}" - offDelay ${v.switchOffDelay || 0}s.`);
696
+ this.log.debug(
697
+ `Switching off: "${v.name}" - surplus ${surplus}W < switchOffPoint ${v.switchOffPoint}W, offDelay ${v.switchOffDelay || 0}s.`,
698
+ );
625
699
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
626
700
  }
627
701
  }
628
702
  }
629
703
 
630
- // --- Batterie bleibt unverändert ---
704
+ // --- Batterie unverändert ---
631
705
  const batteryConsumers = this.consumerList.filter(c => c.ruletype === 'battery');
632
706
  for (const v of batteryConsumers) {
633
707
  await this.controlBattery(v, feedIn);
@@ -641,9 +715,9 @@ class ZeroFeedIn extends utils.Adapter {
641
715
  });
642
716
 
643
717
  if (runningTimers.length > 0) {
644
- this.log.debug(`📋 Aktive Timer → ${runningTimers.join(' | ')}`);
718
+ this.log.debug(`Active timers: ${runningTimers.join(' | ')}`);
645
719
  } else {
646
- this.log.debug('📋 Keine aktiven Timer.');
720
+ this.log.debug('No active timers.');
647
721
  }
648
722
  } catch (err) {
649
723
  this.log.error(`Error in checkConsumers: ${err.message}`);
@@ -660,13 +734,13 @@ class ZeroFeedIn extends utils.Adapter {
660
734
  try {
661
735
  const withinWindow = this.timeWithinWindow(v.switchOnTime, v.switchOffTime);
662
736
  if (!withinWindow) {
663
- this.log.debug(`[Percent] ${v.name} außerhalb Zeitfenster – auf 0% setzen`);
737
+ this.log.debug(`[Percent] ${v.name} outside time window, set to 0%`);
664
738
  await this.setForeignStateAsync(v.datapoint, 0);
665
739
  return;
666
740
  }
667
741
 
668
742
  if (!this.feedInDatapoint) {
669
- this.log.error('❌ feedInDatapoint ist nicht gesetzt!');
743
+ this.log.error('feedInDatapoint not set!');
670
744
  await this.sendNotification('smartloadmanager', 'alert', '❌ feedInDatapoint ist nicht gesetzt!');
671
745
  return;
672
746
  }
@@ -696,7 +770,7 @@ class ZeroFeedIn extends utils.Adapter {
696
770
 
697
771
  if (newPercent !== currentPercent) {
698
772
  await this.setForeignStateAsync(v.datapoint, newPercent);
699
- this.log.info(`🔁 ${v.name} wurde auf ${newPercent}% geregelt`);
773
+ this.log.info(`${v.name} set to ${newPercent}%`);
700
774
 
701
775
  if (this.config.notifyPercent) {
702
776
  await this.sendNotification(
@@ -706,10 +780,10 @@ class ZeroFeedIn extends utils.Adapter {
706
780
  );
707
781
  }
708
782
  } else {
709
- this.log.debug(`[Percent] ${v.name} bleibt bei ${currentPercent}%`);
783
+ this.log.debug(`[Percent] ${v.name} remains at ${currentPercent}%`);
710
784
  }
711
785
  } catch (error) {
712
- this.log.error(`❌ Fehler in controlPercentConsumer: ${error.message}`);
786
+ this.log.error(`Error in controlPercentConsumer: ${error.message}`);
713
787
  await this.sendNotification(
714
788
  'smartloadmanager',
715
789
  'alert',
@@ -720,24 +794,33 @@ class ZeroFeedIn extends utils.Adapter {
720
794
 
721
795
  async controlBattery(v, feedIn) {
722
796
  try {
723
- this.log.debug(`[Battery] Prüfung für ${v.name}`);
797
+ this.log.debug(`[Battery] Checking for ${v.name}`);
724
798
 
725
799
  const withinWindow = this.timeWithinWindow(v.switchOnTime, v.switchOffTime);
726
800
  if (!withinWindow) {
727
- this.log.debug(`[Battery] ${v.name} außerhalb Zeitfenster – auf 0`);
801
+ this.log.debug(`[Battery] ${v.name} outside time window, set to 0`);
728
802
  if (v.batterySetpoint) {
729
- await this.setForeignStateAsync(v.batterySetpoint, { val: 0, ack: true });
803
+ await this.setForeignStateAsync(v.batterySetpoint, {
804
+ val: 0,
805
+ ack: true,
806
+ });
730
807
  }
731
- if (v.batteryControlModeDatapoint) {
732
- await this.setForeignStateAsync(v.batteryControlModeDatapoint, { val: 0, ack: true });
808
+ if (this.batteryControlModeDatapoint) {
809
+ await this.setForeignStateAsync(this.batteryControlModeDatapoint, {
810
+ val: 0,
811
+ ack: true,
812
+ });
733
813
  }
734
814
  return;
735
815
  }
736
816
 
737
817
  if (!v.batterySetpoint) {
738
- this.log.warn(`[Battery] ${v.name} hat keinen batterySetpoint`);
739
- if (v.batteryControlModeDatapoint) {
740
- await this.setForeignStateAsync(v.batteryControlModeDatapoint, { val: 0, ack: true });
818
+ this.log.warn(`[Battery] ${v.name} has no batterySetpoint`);
819
+ if (this.batteryControlModeDatapoint) {
820
+ await this.setForeignStateAsync(this.batteryControlModeDatapoint, {
821
+ val: 0,
822
+ ack: true,
823
+ });
741
824
  }
742
825
  return;
743
826
  }
@@ -750,7 +833,7 @@ class ZeroFeedIn extends utils.Adapter {
750
833
  soc = Number(socState?.val);
751
834
  targetSoc = Number(targetSocState?.val);
752
835
  if (isNaN(soc) || isNaN(targetSoc)) {
753
- this.log.warn(`[Battery] ${v.name}: Ungültiger SOC oder targetSOC`);
836
+ this.log.warn(`[Battery] ${v.name}: invalid SOC or targetSOC`);
754
837
  soc = null;
755
838
  targetSoc = null;
756
839
  }
@@ -777,8 +860,11 @@ class ZeroFeedIn extends utils.Adapter {
777
860
  : -1;
778
861
 
779
862
  if (powerToSet !== oldPower) {
780
- await this.setForeignStateAsync(v.batterySetpoint, { val: powerToSet, ack: true });
781
- this.log.info(`🔁 Batterie ${v.name}: Ladeleistung auf ${powerToSet}W gesetzt`);
863
+ await this.setForeignStateAsync(v.batterySetpoint, {
864
+ val: powerToSet,
865
+ ack: true,
866
+ });
867
+ this.log.info(`Battery ${v.name}: charging power set to ${powerToSet}W`);
782
868
  if (this.config.notifyBattery) {
783
869
  await this.sendNotification(
784
870
  'smartloadmanager',
@@ -787,12 +873,15 @@ class ZeroFeedIn extends utils.Adapter {
787
873
  );
788
874
  }
789
875
  } else {
790
- this.log.debug(`[Battery] ${v.name} Ladeleistung bleibt bei ${oldPower}W`);
876
+ this.log.debug(`[Battery] ${v.name} charging power remains at ${oldPower}W`);
791
877
  }
792
878
 
793
879
  if (this.batteryControlModeDatapoint && modeToSet !== oldMode) {
794
- await this.setForeignStateAsync(this.batteryControlModeDatapoint, { val: modeToSet, ack: true });
795
- this.log.debug(`[Battery] ${v.name} Modus geändert: ${oldMode} → ${modeToSet}`);
880
+ await this.setForeignStateAsync(this.batteryControlModeDatapoint, {
881
+ val: modeToSet,
882
+ ack: true,
883
+ });
884
+ this.log.debug(`[Battery] ${v.name} mode changed from ${oldMode} to ${modeToSet}`);
796
885
  if (this.config.notifyBattery) {
797
886
  await this.sendNotification(
798
887
  'smartloadmanager',
@@ -802,7 +891,7 @@ class ZeroFeedIn extends utils.Adapter {
802
891
  }
803
892
  }
804
893
  } catch (error) {
805
- this.log.error(`[Battery] Fehler für ${v.name}: ${error.message}`);
894
+ this.log.error(`[Battery] Error for ${v.name}: ${error.message}`);
806
895
  await this.sendNotification(
807
896
  'smartloadmanager',
808
897
  'alert',
@@ -822,16 +911,28 @@ class ZeroFeedIn extends utils.Adapter {
822
911
 
823
912
  if (consumer.datapoint) {
824
913
  // Erst setzen
825
- await this.setForeignStateAsync(consumer.datapoint, { val: targetState, ack: false });
914
+ await this.setForeignStateAsync(consumer.datapoint, {
915
+ val: targetState,
916
+ ack: false,
917
+ });
826
918
  // Danach sofort bestätigen (falls keine Rückmeldung vom Gerät kommt)
827
- await this.setForeignStateAsync(consumer.datapoint, { val: targetState, ack: true });
919
+ await this.setForeignStateAsync(consumer.datapoint, {
920
+ val: targetState,
921
+ ack: true,
922
+ });
828
923
  }
829
924
 
830
925
  if (consumer.numericDatapoint) {
831
926
  const numVal = targetState ? 1 : 0;
832
- await this.setForeignStateAsync(consumer.numericDatapoint, { val: numVal, ack: false });
833
- await this.setForeignStateAsync(consumer.numericDatapoint, { val: numVal, ack: true });
834
- this.log.debug(`🔢 NumericDatapoint für "${consumer.name}" gesetzt auf ${numVal}`);
927
+ await this.setForeignStateAsync(consumer.numericDatapoint, {
928
+ val: numVal,
929
+ ack: false,
930
+ });
931
+ await this.setForeignStateAsync(consumer.numericDatapoint, {
932
+ val: numVal,
933
+ ack: true,
934
+ });
935
+ this.log.debug(`Numeric datapoint for "${consumer.name}" set to ${numVal}`);
835
936
  }
836
937
 
837
938
  const msg = `${consumer.name} wurde ${targetState ? 'eingeschaltet' : 'ausgeschaltet'}`;
@@ -856,7 +957,8 @@ class ZeroFeedIn extends utils.Adapter {
856
957
  : consumer.switchOffDelay || 0;
857
958
 
858
959
  const idx = this.consumerList.indexOf(consumer);
859
- const channelId = `consumer.${idx}_${consumer.name.replace(/\s+/g, '_')}`;
960
+ const safeName = this.name2id(consumer.name);
961
+ const channelId = `consumer.${idx}_${safeName}`;
860
962
  const onTimerState = `${this.namespace}.${channelId}.onTimerRemaining`;
861
963
  const offTimerState = `${this.namespace}.${channelId}.offTimerRemaining`;
862
964
 
@@ -885,7 +987,10 @@ class ZeroFeedIn extends utils.Adapter {
885
987
  await this.setStateAsync(onTimerState, { val: remaining, ack: true });
886
988
  await this.setStateAsync(offTimerState, { val: 0, ack: true });
887
989
  } else {
888
- await this.setStateAsync(offTimerState, { val: remaining, ack: true });
990
+ await this.setStateAsync(offTimerState, {
991
+ val: remaining,
992
+ ack: true,
993
+ });
889
994
  await this.setStateAsync(onTimerState, { val: 0, ack: true });
890
995
  }
891
996
  };
@@ -894,7 +999,7 @@ class ZeroFeedIn extends utils.Adapter {
894
999
  clearInterval(consumer.timerTick);
895
1000
  consumer.timerTick = null;
896
1001
  }
897
- consumer.timerTick = setInterval(tick, 1000);
1002
+ consumer.timerTick = this.safeSetInterval(tick, 1000);
898
1003
  };
899
1004
 
900
1005
  const doSwitch = async () => {
@@ -962,8 +1067,7 @@ class ZeroFeedIn extends utils.Adapter {
962
1067
  await stopTimerTicker();
963
1068
  };
964
1069
 
965
- // 🟢 Hier ist der Fix:
966
- // Falls Timer läuft, aber Bedingung sich geändert hat → Timer abbrechen
1070
+ // Falls Timer läuft, aber Bedingung sich geändert hat → Timer abbrechen / anpassen
967
1071
  if (consumer.timer) {
968
1072
  // wenn Einschalt-Timer läuft, aber turnOn nicht mehr gilt → abbrechen
969
1073
  if (consumer.pendingAction === true && !turnOn) {
@@ -990,7 +1094,7 @@ class ZeroFeedIn extends utils.Adapter {
990
1094
  return;
991
1095
  }
992
1096
 
993
- // ansonsten Timer neu setzen
1097
+ // ansonsten Timer neu setzen (älteren abbrechen)
994
1098
  clearTimeout(consumer.timer);
995
1099
  consumer.timer = null;
996
1100
  consumer.timerEnd = null;
@@ -1001,7 +1105,7 @@ class ZeroFeedIn extends utils.Adapter {
1001
1105
  if (delay > 0) {
1002
1106
  consumer.timerEnd = Date.now() + delay * 1000;
1003
1107
  consumer.pendingAction = turnOn;
1004
- consumer.timer = setTimeout(doSwitch, delay * 1000);
1108
+ consumer.timer = this.safeSetTimeout(doSwitch, delay * 1000);
1005
1109
  await startTimerTicker();
1006
1110
  } else {
1007
1111
  await doSwitch();
@@ -1017,7 +1121,7 @@ class ZeroFeedIn extends utils.Adapter {
1017
1121
  // 1) Controller-Notification (nur wenn Option aktiv ist)
1018
1122
  if (cfg.enableNotificationManager) {
1019
1123
  await this.registerNotification(scope, category, message);
1020
- this.log.debug(`📨 Controller-Notification [${scope}/${category}]: ${message}`);
1124
+ this.log.debug(`Controller-Notification [${scope}/${category}]: ${message}`);
1021
1125
  }
1022
1126
 
1023
1127
  // 2) Messenger-Nachrichten
@@ -1031,11 +1135,11 @@ class ZeroFeedIn extends utils.Adapter {
1031
1135
  if (recipients.length > 0) {
1032
1136
  recipients.forEach(user => {
1033
1137
  this.sendTo(cfg.telegramInstance, { text: message, user });
1034
- this.log.debug(`📨 Telegram → ${cfg.telegramInstance}/${user}: ${message}`);
1138
+ this.log.debug(`Telegram → ${cfg.telegramInstance}/${user}: ${message}`);
1035
1139
  });
1036
1140
  } else {
1037
1141
  this.sendTo(cfg.telegramInstance, message);
1038
- this.log.debug(`📨 Telegram → ${cfg.telegramInstance}: ${message}`);
1142
+ this.log.debug(`Telegram → ${cfg.telegramInstance}: ${message}`);
1039
1143
  }
1040
1144
  }
1041
1145
 
@@ -1045,7 +1149,7 @@ class ZeroFeedIn extends utils.Adapter {
1045
1149
  title: 'SmartLoadManager',
1046
1150
  message: message,
1047
1151
  });
1048
- this.log.debug(`📨 Gotify → ${cfg.gotifyInstance}: ${message}`);
1152
+ this.log.debug(`Gotify → ${cfg.gotifyInstance}: ${message}`);
1049
1153
  }
1050
1154
 
1051
1155
  // E-Mail
@@ -1061,14 +1165,14 @@ class ZeroFeedIn extends utils.Adapter {
1061
1165
  text: message,
1062
1166
  subject: 'SmartLoadManager',
1063
1167
  });
1064
- this.log.debug(`📨 Email → ${cfg.emailInstance}/${to}: ${message}`);
1168
+ this.log.debug(`Email → ${cfg.emailInstance}/${to}: ${message}`);
1065
1169
  });
1066
1170
  } else {
1067
1171
  this.sendTo(cfg.emailInstance, {
1068
1172
  text: message,
1069
1173
  subject: 'SmartLoadManager',
1070
1174
  });
1071
- this.log.debug(`📨 Email → ${cfg.emailInstance}: ${message}`);
1175
+ this.log.debug(`Email → ${cfg.emailInstance}: ${message}`);
1072
1176
  }
1073
1177
  }
1074
1178
 
@@ -1081,11 +1185,11 @@ class ZeroFeedIn extends utils.Adapter {
1081
1185
  if (recipients.length > 0) {
1082
1186
  recipients.forEach(user => {
1083
1187
  this.sendTo(cfg.whatsappInstance, { text: message, user });
1084
- this.log.debug(`📨 WhatsApp → ${cfg.whatsappInstance}/${user}: ${message}`);
1188
+ this.log.debug(`WhatsApp → ${cfg.whatsappInstance}/${user}: ${message}`);
1085
1189
  });
1086
1190
  } else {
1087
1191
  this.sendTo(cfg.whatsappInstance, { text: message });
1088
- this.log.debug(`📨 WhatsApp → ${cfg.whatsappInstance}: ${message}`);
1192
+ this.log.debug(`WhatsApp → ${cfg.whatsappInstance}: ${message}`);
1089
1193
  }
1090
1194
  }
1091
1195
 
@@ -1098,11 +1202,11 @@ class ZeroFeedIn extends utils.Adapter {
1098
1202
  if (recipients.length > 0) {
1099
1203
  recipients.forEach(user => {
1100
1204
  this.sendTo(cfg.signalInstance, { text: message, user });
1101
- this.log.debug(`📨 Signal → ${cfg.signalInstance}/${user}: ${message}`);
1205
+ this.log.debug(`Signal → ${cfg.signalInstance}/${user}: ${message}`);
1102
1206
  });
1103
1207
  } else {
1104
1208
  this.sendTo(cfg.signalInstance, { text: message });
1105
- this.log.debug(`📨 Signal → ${cfg.signalInstance}: ${message}`);
1209
+ this.log.debug(`Signal → ${cfg.signalInstance}: ${message}`);
1106
1210
  }
1107
1211
  }
1108
1212
 
@@ -1119,25 +1223,26 @@ class ZeroFeedIn extends utils.Adapter {
1119
1223
  title: 'SmartLoadManager',
1120
1224
  user,
1121
1225
  });
1122
- this.log.debug(`📨 Pushover → ${cfg.pushoverInstance}/${user}: ${message}`);
1226
+ this.log.debug(`Pushover → ${cfg.pushoverInstance}/${user}: ${message}`);
1123
1227
  });
1124
1228
  } else {
1125
1229
  this.sendTo(cfg.pushoverInstance, {
1126
1230
  message: message,
1127
1231
  title: 'SmartLoadManager',
1128
1232
  });
1129
- this.log.debug(`📨 Pushover → ${cfg.pushoverInstance}: ${message}`);
1233
+ this.log.debug(`Pushover → ${cfg.pushoverInstance}: ${message}`);
1130
1234
  }
1131
1235
  }
1132
1236
  } catch (e) {
1133
- this.log.warn(`❗ Fehler beim Senden der Notification: ${e.message}`);
1237
+ this.log.warn(`Error sending notification: ${e.message}`);
1134
1238
  }
1135
1239
  }
1136
1240
 
1137
1241
  async initializeConsumerStatus() {
1138
1242
  // Initiale Status-Updates
1139
1243
  for (const v of this.consumerList) {
1140
- const channelId = `consumer.${this.consumerList.indexOf(v)}_${v.name.replace(/\s+/g, '_')}`;
1244
+ const safeName = this.name2id(v.name);
1245
+ const channelId = `consumer.${this.consumerList.indexOf(v)}_${safeName}`;
1141
1246
  const modeState = await this.getStateAsync(`${this.namespace}.${channelId}.controlMode`);
1142
1247
  if (!modeState) {
1143
1248
  await this.setStateAsync(`${this.namespace}.${channelId}.controlMode`, {
@@ -1149,7 +1254,7 @@ class ZeroFeedIn extends utils.Adapter {
1149
1254
  }
1150
1255
 
1151
1256
  sleep(ms) {
1152
- return new Promise(resolve => setTimeout(resolve, ms));
1257
+ return new Promise(resolve => this.safeSetTimeout(resolve, ms, 'sleep'));
1153
1258
  }
1154
1259
 
1155
1260
  parseTimeToMinutes(timeStr) {