iobroker.smartloadmanager 0.0.6 → 0.0.7

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
  }
@@ -610,18 +660,18 @@ class ZeroFeedIn extends utils.Adapter {
610
660
  v.timer = null;
611
661
  v.timerEnd = null;
612
662
  v.pendingAction = null;
613
- const channelId = `consumer.${idx}_${v.name.replace(/\s+/g, '_')}`;
663
+ const channelId = `consumer.${idx}_${safeName}`;
614
664
  await this.setStateAsync(`${this.namespace}.${channelId}.onTimerRemaining`, { val: 0, ack: true });
615
665
  await this.setStateAsync(`${this.namespace}.${channelId}.offTimerRemaining`, { val: 0, ack: true });
616
- this.log.debug(`⏹️ Ausschalt-Timer für "${v.name}" abgebrochen (Bedingung entfallen).`);
666
+ this.log.debug(`Switch-off timer for "${v.name}" canceled (condition no longer valid).`);
617
667
  }
618
668
 
619
669
  if (!v.alwaysOffAtTime && (!withinWindow || gridUsage)) {
620
670
  if (v.timer && v.timerEnd && v.pendingAction === false) {
621
671
  const remaining = Math.max(0, Math.round((v.timerEnd - Date.now()) / 1000));
622
- this.log.debug(`📋 Aktive Timer → ${v.name}: noch ${remaining}s`);
672
+ this.log.debug(`Active timer for ${v.name}: ${remaining}s remaining`);
623
673
  } else {
624
- this.log.debug(`⏳ Ausschalten: "${v.name}" - offDelay ${v.switchOffDelay || 0}s.`);
674
+ this.log.debug(`Switching off: "${v.name}" - offDelay ${v.switchOffDelay || 0}s.`);
625
675
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
626
676
  }
627
677
  }
@@ -641,9 +691,9 @@ class ZeroFeedIn extends utils.Adapter {
641
691
  });
642
692
 
643
693
  if (runningTimers.length > 0) {
644
- this.log.debug(`📋 Aktive Timer → ${runningTimers.join(' | ')}`);
694
+ this.log.debug(`Active timers: ${runningTimers.join(' | ')}`);
645
695
  } else {
646
- this.log.debug('📋 Keine aktiven Timer.');
696
+ this.log.debug('No active timers.');
647
697
  }
648
698
  } catch (err) {
649
699
  this.log.error(`Error in checkConsumers: ${err.message}`);
@@ -660,13 +710,13 @@ class ZeroFeedIn extends utils.Adapter {
660
710
  try {
661
711
  const withinWindow = this.timeWithinWindow(v.switchOnTime, v.switchOffTime);
662
712
  if (!withinWindow) {
663
- this.log.debug(`[Percent] ${v.name} außerhalb Zeitfenster – auf 0% setzen`);
713
+ this.log.debug(`[Percent] ${v.name} outside time window, set to 0%`);
664
714
  await this.setForeignStateAsync(v.datapoint, 0);
665
715
  return;
666
716
  }
667
717
 
668
718
  if (!this.feedInDatapoint) {
669
- this.log.error('❌ feedInDatapoint ist nicht gesetzt!');
719
+ this.log.error('feedInDatapoint not set!');
670
720
  await this.sendNotification('smartloadmanager', 'alert', '❌ feedInDatapoint ist nicht gesetzt!');
671
721
  return;
672
722
  }
@@ -696,7 +746,7 @@ class ZeroFeedIn extends utils.Adapter {
696
746
 
697
747
  if (newPercent !== currentPercent) {
698
748
  await this.setForeignStateAsync(v.datapoint, newPercent);
699
- this.log.info(`🔁 ${v.name} wurde auf ${newPercent}% geregelt`);
749
+ this.log.info(`${v.name} set to ${newPercent}%`);
700
750
 
701
751
  if (this.config.notifyPercent) {
702
752
  await this.sendNotification(
@@ -706,10 +756,10 @@ class ZeroFeedIn extends utils.Adapter {
706
756
  );
707
757
  }
708
758
  } else {
709
- this.log.debug(`[Percent] ${v.name} bleibt bei ${currentPercent}%`);
759
+ this.log.debug(`[Percent] ${v.name} remains at ${currentPercent}%`);
710
760
  }
711
761
  } catch (error) {
712
- this.log.error(`❌ Fehler in controlPercentConsumer: ${error.message}`);
762
+ this.log.error(`Error in controlPercentConsumer: ${error.message}`);
713
763
  await this.sendNotification(
714
764
  'smartloadmanager',
715
765
  'alert',
@@ -720,24 +770,33 @@ class ZeroFeedIn extends utils.Adapter {
720
770
 
721
771
  async controlBattery(v, feedIn) {
722
772
  try {
723
- this.log.debug(`[Battery] Prüfung für ${v.name}`);
773
+ this.log.debug(`[Battery] Checking for ${v.name}`);
724
774
 
725
775
  const withinWindow = this.timeWithinWindow(v.switchOnTime, v.switchOffTime);
726
776
  if (!withinWindow) {
727
- this.log.debug(`[Battery] ${v.name} außerhalb Zeitfenster – auf 0`);
777
+ this.log.debug(`[Battery] ${v.name} outside time window, set to 0`);
728
778
  if (v.batterySetpoint) {
729
- await this.setForeignStateAsync(v.batterySetpoint, { val: 0, ack: true });
779
+ await this.setForeignStateAsync(v.batterySetpoint, {
780
+ val: 0,
781
+ ack: true,
782
+ });
730
783
  }
731
- if (v.batteryControlModeDatapoint) {
732
- await this.setForeignStateAsync(v.batteryControlModeDatapoint, { val: 0, ack: true });
784
+ if (this.batteryControlModeDatapoint) {
785
+ await this.setForeignStateAsync(this.batteryControlModeDatapoint, {
786
+ val: 0,
787
+ ack: true,
788
+ });
733
789
  }
734
790
  return;
735
791
  }
736
792
 
737
793
  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 });
794
+ this.log.warn(`[Battery] ${v.name} has no batterySetpoint`);
795
+ if (this.batteryControlModeDatapoint) {
796
+ await this.setForeignStateAsync(this.batteryControlModeDatapoint, {
797
+ val: 0,
798
+ ack: true,
799
+ });
741
800
  }
742
801
  return;
743
802
  }
@@ -750,7 +809,7 @@ class ZeroFeedIn extends utils.Adapter {
750
809
  soc = Number(socState?.val);
751
810
  targetSoc = Number(targetSocState?.val);
752
811
  if (isNaN(soc) || isNaN(targetSoc)) {
753
- this.log.warn(`[Battery] ${v.name}: Ungültiger SOC oder targetSOC`);
812
+ this.log.warn(`[Battery] ${v.name}: invalid SOC or targetSOC`);
754
813
  soc = null;
755
814
  targetSoc = null;
756
815
  }
@@ -777,8 +836,11 @@ class ZeroFeedIn extends utils.Adapter {
777
836
  : -1;
778
837
 
779
838
  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`);
839
+ await this.setForeignStateAsync(v.batterySetpoint, {
840
+ val: powerToSet,
841
+ ack: true,
842
+ });
843
+ this.log.info(`Battery ${v.name}: charging power set to ${powerToSet}W`);
782
844
  if (this.config.notifyBattery) {
783
845
  await this.sendNotification(
784
846
  'smartloadmanager',
@@ -787,12 +849,15 @@ class ZeroFeedIn extends utils.Adapter {
787
849
  );
788
850
  }
789
851
  } else {
790
- this.log.debug(`[Battery] ${v.name} Ladeleistung bleibt bei ${oldPower}W`);
852
+ this.log.debug(`[Battery] ${v.name} charging power remains at ${oldPower}W`);
791
853
  }
792
854
 
793
855
  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}`);
856
+ await this.setForeignStateAsync(this.batteryControlModeDatapoint, {
857
+ val: modeToSet,
858
+ ack: true,
859
+ });
860
+ this.log.debug(`[Battery] ${v.name} mode changed from ${oldMode} to ${modeToSet}`);
796
861
  if (this.config.notifyBattery) {
797
862
  await this.sendNotification(
798
863
  'smartloadmanager',
@@ -802,7 +867,7 @@ class ZeroFeedIn extends utils.Adapter {
802
867
  }
803
868
  }
804
869
  } catch (error) {
805
- this.log.error(`[Battery] Fehler für ${v.name}: ${error.message}`);
870
+ this.log.error(`[Battery] Error for ${v.name}: ${error.message}`);
806
871
  await this.sendNotification(
807
872
  'smartloadmanager',
808
873
  'alert',
@@ -822,16 +887,28 @@ class ZeroFeedIn extends utils.Adapter {
822
887
 
823
888
  if (consumer.datapoint) {
824
889
  // Erst setzen
825
- await this.setForeignStateAsync(consumer.datapoint, { val: targetState, ack: false });
890
+ await this.setForeignStateAsync(consumer.datapoint, {
891
+ val: targetState,
892
+ ack: false,
893
+ });
826
894
  // Danach sofort bestätigen (falls keine Rückmeldung vom Gerät kommt)
827
- await this.setForeignStateAsync(consumer.datapoint, { val: targetState, ack: true });
895
+ await this.setForeignStateAsync(consumer.datapoint, {
896
+ val: targetState,
897
+ ack: true,
898
+ });
828
899
  }
829
900
 
830
901
  if (consumer.numericDatapoint) {
831
902
  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}`);
903
+ await this.setForeignStateAsync(consumer.numericDatapoint, {
904
+ val: numVal,
905
+ ack: false,
906
+ });
907
+ await this.setForeignStateAsync(consumer.numericDatapoint, {
908
+ val: numVal,
909
+ ack: true,
910
+ });
911
+ this.log.debug(`Numeric datapoint for "${consumer.name}" set to ${numVal}`);
835
912
  }
836
913
 
837
914
  const msg = `${consumer.name} wurde ${targetState ? 'eingeschaltet' : 'ausgeschaltet'}`;
@@ -856,7 +933,8 @@ class ZeroFeedIn extends utils.Adapter {
856
933
  : consumer.switchOffDelay || 0;
857
934
 
858
935
  const idx = this.consumerList.indexOf(consumer);
859
- const channelId = `consumer.${idx}_${consumer.name.replace(/\s+/g, '_')}`;
936
+ const safeName = this.name2id(consumer.name);
937
+ const channelId = `consumer.${idx}_${safeName}`;
860
938
  const onTimerState = `${this.namespace}.${channelId}.onTimerRemaining`;
861
939
  const offTimerState = `${this.namespace}.${channelId}.offTimerRemaining`;
862
940
 
@@ -885,7 +963,10 @@ class ZeroFeedIn extends utils.Adapter {
885
963
  await this.setStateAsync(onTimerState, { val: remaining, ack: true });
886
964
  await this.setStateAsync(offTimerState, { val: 0, ack: true });
887
965
  } else {
888
- await this.setStateAsync(offTimerState, { val: remaining, ack: true });
966
+ await this.setStateAsync(offTimerState, {
967
+ val: remaining,
968
+ ack: true,
969
+ });
889
970
  await this.setStateAsync(onTimerState, { val: 0, ack: true });
890
971
  }
891
972
  };
@@ -894,7 +975,7 @@ class ZeroFeedIn extends utils.Adapter {
894
975
  clearInterval(consumer.timerTick);
895
976
  consumer.timerTick = null;
896
977
  }
897
- consumer.timerTick = setInterval(tick, 1000);
978
+ consumer.timerTick = this.safeSetInterval(tick, 1000);
898
979
  };
899
980
 
900
981
  const doSwitch = async () => {
@@ -962,8 +1043,7 @@ class ZeroFeedIn extends utils.Adapter {
962
1043
  await stopTimerTicker();
963
1044
  };
964
1045
 
965
- // 🟢 Hier ist der Fix:
966
- // Falls Timer läuft, aber Bedingung sich geändert hat → Timer abbrechen
1046
+ // Falls Timer läuft, aber Bedingung sich geändert hat → Timer abbrechen / anpassen
967
1047
  if (consumer.timer) {
968
1048
  // wenn Einschalt-Timer läuft, aber turnOn nicht mehr gilt → abbrechen
969
1049
  if (consumer.pendingAction === true && !turnOn) {
@@ -990,7 +1070,7 @@ class ZeroFeedIn extends utils.Adapter {
990
1070
  return;
991
1071
  }
992
1072
 
993
- // ansonsten Timer neu setzen
1073
+ // ansonsten Timer neu setzen (älteren abbrechen)
994
1074
  clearTimeout(consumer.timer);
995
1075
  consumer.timer = null;
996
1076
  consumer.timerEnd = null;
@@ -1001,7 +1081,7 @@ class ZeroFeedIn extends utils.Adapter {
1001
1081
  if (delay > 0) {
1002
1082
  consumer.timerEnd = Date.now() + delay * 1000;
1003
1083
  consumer.pendingAction = turnOn;
1004
- consumer.timer = setTimeout(doSwitch, delay * 1000);
1084
+ consumer.timer = this.safeSetTimeout(doSwitch, delay * 1000);
1005
1085
  await startTimerTicker();
1006
1086
  } else {
1007
1087
  await doSwitch();
@@ -1017,7 +1097,7 @@ class ZeroFeedIn extends utils.Adapter {
1017
1097
  // 1) Controller-Notification (nur wenn Option aktiv ist)
1018
1098
  if (cfg.enableNotificationManager) {
1019
1099
  await this.registerNotification(scope, category, message);
1020
- this.log.debug(`📨 Controller-Notification [${scope}/${category}]: ${message}`);
1100
+ this.log.debug(`Controller-Notification [${scope}/${category}]: ${message}`);
1021
1101
  }
1022
1102
 
1023
1103
  // 2) Messenger-Nachrichten
@@ -1031,11 +1111,11 @@ class ZeroFeedIn extends utils.Adapter {
1031
1111
  if (recipients.length > 0) {
1032
1112
  recipients.forEach(user => {
1033
1113
  this.sendTo(cfg.telegramInstance, { text: message, user });
1034
- this.log.debug(`📨 Telegram → ${cfg.telegramInstance}/${user}: ${message}`);
1114
+ this.log.debug(`Telegram → ${cfg.telegramInstance}/${user}: ${message}`);
1035
1115
  });
1036
1116
  } else {
1037
1117
  this.sendTo(cfg.telegramInstance, message);
1038
- this.log.debug(`📨 Telegram → ${cfg.telegramInstance}: ${message}`);
1118
+ this.log.debug(`Telegram → ${cfg.telegramInstance}: ${message}`);
1039
1119
  }
1040
1120
  }
1041
1121
 
@@ -1045,7 +1125,7 @@ class ZeroFeedIn extends utils.Adapter {
1045
1125
  title: 'SmartLoadManager',
1046
1126
  message: message,
1047
1127
  });
1048
- this.log.debug(`📨 Gotify → ${cfg.gotifyInstance}: ${message}`);
1128
+ this.log.debug(`Gotify → ${cfg.gotifyInstance}: ${message}`);
1049
1129
  }
1050
1130
 
1051
1131
  // E-Mail
@@ -1061,14 +1141,14 @@ class ZeroFeedIn extends utils.Adapter {
1061
1141
  text: message,
1062
1142
  subject: 'SmartLoadManager',
1063
1143
  });
1064
- this.log.debug(`📨 Email → ${cfg.emailInstance}/${to}: ${message}`);
1144
+ this.log.debug(`Email → ${cfg.emailInstance}/${to}: ${message}`);
1065
1145
  });
1066
1146
  } else {
1067
1147
  this.sendTo(cfg.emailInstance, {
1068
1148
  text: message,
1069
1149
  subject: 'SmartLoadManager',
1070
1150
  });
1071
- this.log.debug(`📨 Email → ${cfg.emailInstance}: ${message}`);
1151
+ this.log.debug(`Email → ${cfg.emailInstance}: ${message}`);
1072
1152
  }
1073
1153
  }
1074
1154
 
@@ -1081,11 +1161,11 @@ class ZeroFeedIn extends utils.Adapter {
1081
1161
  if (recipients.length > 0) {
1082
1162
  recipients.forEach(user => {
1083
1163
  this.sendTo(cfg.whatsappInstance, { text: message, user });
1084
- this.log.debug(`📨 WhatsApp → ${cfg.whatsappInstance}/${user}: ${message}`);
1164
+ this.log.debug(`WhatsApp → ${cfg.whatsappInstance}/${user}: ${message}`);
1085
1165
  });
1086
1166
  } else {
1087
1167
  this.sendTo(cfg.whatsappInstance, { text: message });
1088
- this.log.debug(`📨 WhatsApp → ${cfg.whatsappInstance}: ${message}`);
1168
+ this.log.debug(`WhatsApp → ${cfg.whatsappInstance}: ${message}`);
1089
1169
  }
1090
1170
  }
1091
1171
 
@@ -1098,11 +1178,11 @@ class ZeroFeedIn extends utils.Adapter {
1098
1178
  if (recipients.length > 0) {
1099
1179
  recipients.forEach(user => {
1100
1180
  this.sendTo(cfg.signalInstance, { text: message, user });
1101
- this.log.debug(`📨 Signal → ${cfg.signalInstance}/${user}: ${message}`);
1181
+ this.log.debug(`Signal → ${cfg.signalInstance}/${user}: ${message}`);
1102
1182
  });
1103
1183
  } else {
1104
1184
  this.sendTo(cfg.signalInstance, { text: message });
1105
- this.log.debug(`📨 Signal → ${cfg.signalInstance}: ${message}`);
1185
+ this.log.debug(`Signal → ${cfg.signalInstance}: ${message}`);
1106
1186
  }
1107
1187
  }
1108
1188
 
@@ -1119,25 +1199,26 @@ class ZeroFeedIn extends utils.Adapter {
1119
1199
  title: 'SmartLoadManager',
1120
1200
  user,
1121
1201
  });
1122
- this.log.debug(`📨 Pushover → ${cfg.pushoverInstance}/${user}: ${message}`);
1202
+ this.log.debug(`Pushover → ${cfg.pushoverInstance}/${user}: ${message}`);
1123
1203
  });
1124
1204
  } else {
1125
1205
  this.sendTo(cfg.pushoverInstance, {
1126
1206
  message: message,
1127
1207
  title: 'SmartLoadManager',
1128
1208
  });
1129
- this.log.debug(`📨 Pushover → ${cfg.pushoverInstance}: ${message}`);
1209
+ this.log.debug(`Pushover → ${cfg.pushoverInstance}: ${message}`);
1130
1210
  }
1131
1211
  }
1132
1212
  } catch (e) {
1133
- this.log.warn(`❗ Fehler beim Senden der Notification: ${e.message}`);
1213
+ this.log.warn(`Error sending notification: ${e.message}`);
1134
1214
  }
1135
1215
  }
1136
1216
 
1137
1217
  async initializeConsumerStatus() {
1138
1218
  // Initiale Status-Updates
1139
1219
  for (const v of this.consumerList) {
1140
- const channelId = `consumer.${this.consumerList.indexOf(v)}_${v.name.replace(/\s+/g, '_')}`;
1220
+ const safeName = this.name2id(v.name);
1221
+ const channelId = `consumer.${this.consumerList.indexOf(v)}_${safeName}`;
1141
1222
  const modeState = await this.getStateAsync(`${this.namespace}.${channelId}.controlMode`);
1142
1223
  if (!modeState) {
1143
1224
  await this.setStateAsync(`${this.namespace}.${channelId}.controlMode`, {
@@ -1149,7 +1230,7 @@ class ZeroFeedIn extends utils.Adapter {
1149
1230
  }
1150
1231
 
1151
1232
  sleep(ms) {
1152
- return new Promise(resolve => setTimeout(resolve, ms));
1233
+ return new Promise(resolve => this.safeSetTimeout(resolve, ms, 'sleep'));
1153
1234
  }
1154
1235
 
1155
1236
  parseTimeToMinutes(timeStr) {