iobroker.smartloadmanager 0.0.8 → 0.1.2

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
@@ -18,10 +18,21 @@ class ZeroFeedIn extends utils.Adapter {
18
18
  this.batteryTimer = null;
19
19
  this.checkRunning = false;
20
20
 
21
+ // 🔮 Forecast (PV forecast integration)
22
+ this.forecastEnabled = false;
23
+ this.forecastJsonDatapoint = null;
24
+ this.forecast = {
25
+ power: 0,
26
+ surplus: 0,
27
+ minutes: 0,
28
+ lastUpdate: 0,
29
+ };
30
+
21
31
  this.on('ready', this.onReady.bind(this));
22
32
  this.on('stateChange', this.onStateChange.bind(this));
23
33
  this.on('unload', this.onUnload.bind(this));
24
34
  }
35
+
25
36
  name2id(pName) {
26
37
  return (pName || '')
27
38
  .replace(this.FORBIDDEN_CHARS, '_') // ioBroker verbotene Zeichen
@@ -31,6 +42,7 @@ class ZeroFeedIn extends utils.Adapter {
31
42
  .replace(/_+/g, '_') // mehrere Unterstriche zusammenfassen
32
43
  .replace(/^_+|_+$/g, ''); // führende/trailing _ weg
33
44
  }
45
+
34
46
  // Wrapper für sichere Timer
35
47
  safeSetTimeout(fn, ms, ...args) {
36
48
  const safeMs = Math.min(Math.max(ms, 0), this.MAX_TIMEOUT);
@@ -48,6 +60,9 @@ class ZeroFeedIn extends utils.Adapter {
48
60
  return this.setInterval(fn, safeMs, ...args);
49
61
  }
50
62
 
63
+ // =====================================================================
64
+ // ============================ onReady ================================
65
+ // =====================================================================
51
66
  async onReady() {
52
67
  try {
53
68
  this.log.info(`Adapter started, PID: ${process.pid}`);
@@ -63,21 +78,40 @@ class ZeroFeedIn extends utils.Adapter {
63
78
  this.batteryControlModeDatapoint = this.config.batteryControlModeDatapoint || null;
64
79
  this.log.info(`Configured batteryControlModeDatapoint: ${this.batteryControlModeDatapoint}`);
65
80
 
81
+ // 🔮 Forecast config (PV forecast integration)
82
+ this.forecastEnabled = !!this.config.enableForecast;
83
+ this.forecastJsonDatapoint = this.config.forecastJsonDatapoint || null;
84
+
66
85
  this.consumerList = Array.isArray(this.config.consumer)
67
86
  ? this.config.consumer.filter(
68
87
  v =>
69
- v &&
70
- v.enabled &&
71
- (v.ruletype === 'battery' || ((v.datapoint || v.numericDatapoint) && v.performance > 0)),
88
+ v && (v.ruletype === 'battery' || ((v.datapoint || v.numericDatapoint) && v.performance > 0)),
72
89
  )
73
90
  : [];
74
91
 
75
92
  this.consumerList.forEach(v => (v.processingLockSwitch = false));
76
93
 
94
+ for (let i = 0; i < this.consumerList.length; i++) {
95
+ const v = this.consumerList[i];
96
+ const safeName = this.name2id(v.name);
97
+ const id = `${this.namespace}.consumer.${i}_${safeName}.active`;
98
+ const state = await this.getStateAsync(id);
99
+ if (state && state.val !== null) {
100
+ v.enabled = state.val;
101
+ this.log.debug(`"${v.name}": Active status taken from object tree (${state.val})`);
102
+ }
103
+ }
104
+
77
105
  this.log.info(`Loaded consumers: ${this.consumerList.map(v => v.name).join(', ')}`);
78
106
 
79
107
  await this.checkAndCreateConsumerObjects();
80
108
  await this.createConsumerStates();
109
+
110
+ // 🔮 Forecast states (adapter-internal)
111
+ if (this.forecastEnabled) {
112
+ await this.createForecastStates();
113
+ }
114
+
81
115
  await this.initializeConsumerStatus();
82
116
  await this.subscribeStatesAsync('*');
83
117
  await this.subscribeForeignStatesAsync(this.feedInDatapoint);
@@ -85,6 +119,12 @@ class ZeroFeedIn extends utils.Adapter {
85
119
  await this.subscribeForeignStatesAsync(this.batteryControlModeDatapoint);
86
120
  }
87
121
 
122
+ // 🔮 Forecast subscribe + initial parse
123
+ if (this.forecastEnabled && this.forecastJsonDatapoint) {
124
+ await this.subscribeForeignStatesAsync(this.forecastJsonDatapoint);
125
+ await this.processPvforecastJson();
126
+ }
127
+
88
128
  await this.checkConsumers();
89
129
  } catch (error) {
90
130
  this.log.error(`Error in onReady: ${error.message}`);
@@ -93,6 +133,9 @@ class ZeroFeedIn extends utils.Adapter {
93
133
  }
94
134
  }
95
135
 
136
+ // =====================================================================
137
+ // ===================== Consumer object checks ========================
138
+ // =====================================================================
96
139
  async checkAndCreateConsumerObjects() {
97
140
  for (const v of this.consumerList) {
98
141
  if (v.datapoint) {
@@ -110,6 +153,9 @@ class ZeroFeedIn extends utils.Adapter {
110
153
  }
111
154
  }
112
155
 
156
+ // =====================================================================
157
+ // ========================= createConsumerStates ======================
158
+ // =====================================================================
113
159
  async createConsumerStates() {
114
160
  // Übergeordnetes Device erstellen - löst "intermediate objects missing" Problem
115
161
  await this.setObjectNotExistsAsync('consumer', {
@@ -227,6 +273,61 @@ class ZeroFeedIn extends utils.Adapter {
227
273
  native: {},
228
274
  });
229
275
 
276
+ // === NEUER STATE: Aktiv-Status ============================
277
+ await this.setObjectNotExistsAsync(`${channelId}.active`, {
278
+ type: 'state',
279
+ common: {
280
+ name: `${v.name} aktiv`,
281
+ type: 'boolean',
282
+ role: 'switch',
283
+ read: true,
284
+ write: true,
285
+ def: v.enabled || false,
286
+ },
287
+ native: {},
288
+ });
289
+
290
+ // 🔮 Forecast options per consumer
291
+ await this.setObjectNotExistsAsync(`${channelId}.useForecast`, {
292
+ type: 'state',
293
+ common: {
294
+ name: 'Use forecast',
295
+ type: 'boolean',
296
+ role: 'switch',
297
+ read: true,
298
+ write: true,
299
+ def: v.useForecast || false,
300
+ },
301
+ native: {},
302
+ });
303
+
304
+ await this.setObjectNotExistsAsync(`${channelId}.forecastMinMinutes`, {
305
+ type: 'state',
306
+ common: {
307
+ name: 'Forecast min minutes',
308
+ type: 'number',
309
+ role: 'value.interval',
310
+ read: true,
311
+ write: true,
312
+ def: v.forecastMinMinutes || 0,
313
+ unit: 'min',
314
+ },
315
+ native: {},
316
+ });
317
+
318
+ await this.setObjectNotExistsAsync(`${channelId}.forecastStatus`, {
319
+ type: 'state',
320
+ common: {
321
+ name: 'Forecast status',
322
+ type: 'string',
323
+ role: 'text',
324
+ read: true,
325
+ write: false,
326
+ def: '',
327
+ },
328
+ native: {},
329
+ });
330
+
230
331
  if (v.ruletype === 'battery') {
231
332
  await this.setObjectNotExistsAsync(`${channelId}.batterySetpoint`, {
232
333
  type: 'state',
@@ -303,11 +404,228 @@ class ZeroFeedIn extends utils.Adapter {
303
404
  await this.setStateAsync(`${this.namespace}.${channelId}.onTimerRemaining`, { val: 0, ack: true });
304
405
  await this.setStateAsync(`${this.namespace}.${channelId}.offTimerRemaining`, { val: 0, ack: true });
305
406
 
407
+ await this.setStateAsync(`${channelId}.active`, { val: v.enabled || false, ack: true });
408
+
409
+ // 🔮 Forecast states init per consumer
410
+ await this.setStateAsync(`${channelId}.useForecast`, { val: v.useForecast || false, ack: true });
411
+ await this.setStateAsync(`${channelId}.forecastMinMinutes`, {
412
+ val: v.forecastMinMinutes || 0,
413
+ ack: true,
414
+ });
415
+ await this.setStateAsync(`${channelId}.forecastStatus`, { val: '', ack: true });
416
+
306
417
  v.switchOnTime = finalOnTime;
307
418
  v.switchOffTime = finalOffTime;
308
419
  }
309
420
  }
310
421
 
422
+ // =====================================================================
423
+ // ========================= Forecast states ===========================
424
+ // =====================================================================
425
+ async createForecastStates() {
426
+ await this.setObjectNotExistsAsync('forecast', {
427
+ type: 'channel',
428
+ common: { name: 'Forecast' },
429
+ native: {},
430
+ });
431
+
432
+ await this.setObjectNotExistsAsync('forecast.power', {
433
+ type: 'state',
434
+ common: {
435
+ name: 'Forecast Power',
436
+ type: 'number',
437
+ role: 'value.power',
438
+ read: true,
439
+ write: false,
440
+ unit: 'W',
441
+ def: 0,
442
+ },
443
+ native: {},
444
+ });
445
+
446
+ await this.setObjectNotExistsAsync('forecast.surplus', {
447
+ type: 'state',
448
+ common: {
449
+ name: 'Forecast Surplus',
450
+ type: 'number',
451
+ role: 'value.power',
452
+ read: true,
453
+ write: false,
454
+ unit: 'W',
455
+ def: 0,
456
+ },
457
+ native: {},
458
+ });
459
+
460
+ await this.setObjectNotExistsAsync('forecast.minutes', {
461
+ type: 'state',
462
+ common: {
463
+ name: 'Forecast Minutes',
464
+ type: 'number',
465
+ role: 'value.interval',
466
+ read: true,
467
+ write: false,
468
+ unit: 'min',
469
+ def: 0,
470
+ },
471
+ native: {},
472
+ });
473
+
474
+ await this.setObjectNotExistsAsync('forecast.lastUpdate', {
475
+ type: 'state',
476
+ common: {
477
+ name: 'Forecast Last Update',
478
+ type: 'number',
479
+ role: 'value.time',
480
+ read: true,
481
+ write: false,
482
+ def: 0,
483
+ },
484
+ native: {},
485
+ });
486
+ }
487
+
488
+ // =====================================================================
489
+ // ========================= Forecast JSON parse =======================
490
+ // =====================================================================
491
+ async processPvforecastJson() {
492
+ try {
493
+ if (!this.forecastEnabled || !this.forecastJsonDatapoint) {
494
+ return;
495
+ }
496
+
497
+ const state = await this.getForeignStateAsync(this.forecastJsonDatapoint);
498
+ if (!state || !state.val) {
499
+ return;
500
+ }
501
+
502
+ let data;
503
+ try {
504
+ data = typeof state.val === 'string' ? JSON.parse(state.val) : state.val;
505
+ } catch {
506
+ this.log.warn('[Forecast] Invalid JSON in pvforecast datapoint');
507
+ return;
508
+ }
509
+
510
+ if (!Array.isArray(data) || data.length === 0) {
511
+ return;
512
+ }
513
+
514
+ const now = Date.now();
515
+ let best = null;
516
+
517
+ for (const entry of data) {
518
+ if (!entry || typeof entry.t !== 'number' || typeof entry.y !== 'number') {
519
+ continue;
520
+ }
521
+ if (entry.t < now) {
522
+ continue;
523
+ }
524
+ best = entry;
525
+ break;
526
+ }
527
+
528
+ if (!best) {
529
+ return;
530
+ }
531
+
532
+ // pvforecast liefert kW → wir rechnen auf W
533
+ const power = Math.round(best.y * 1000);
534
+ const minutes = Math.max(0, Math.round((best.t - now) / 60000));
535
+ const surplus = power - (this.config.baseload || 0);
536
+
537
+ this.forecast.power = power;
538
+ this.forecast.surplus = surplus > 0 ? surplus : 0;
539
+ this.forecast.minutes = minutes;
540
+ this.forecast.lastUpdate = Date.now();
541
+
542
+ await this.setStateAsync(`${this.namespace}.forecast.power`, { val: power, ack: true });
543
+ await this.setStateAsync(`${this.namespace}.forecast.surplus`, {
544
+ val: this.forecast.surplus,
545
+ ack: true,
546
+ });
547
+ await this.setStateAsync(`${this.namespace}.forecast.minutes`, { val: minutes, ack: true });
548
+ await this.setStateAsync(`${this.namespace}.forecast.lastUpdate`, {
549
+ val: this.forecast.lastUpdate,
550
+ ack: true,
551
+ });
552
+
553
+ this.log.debug(
554
+ `[Forecast] JSON parsed → power=${power}W, surplus=${this.forecast.surplus}W, minutes=${minutes}`,
555
+ );
556
+ } catch (error) {
557
+ this.log.error(`[Forecast] Error processing pvforecast JSON: ${error.message}`);
558
+ }
559
+ }
560
+
561
+ // =====================================================================
562
+ // ========================= Forecast helpers ==========================
563
+ // =====================================================================
564
+ getForecastPower(_v) {
565
+ return this.forecast?.power || 0;
566
+ }
567
+
568
+ getForecastMinutes(_v) {
569
+ return this.forecast?.minutes || 0;
570
+ }
571
+
572
+ isForecastAllowingSwitchOn(v) {
573
+ if (!this.forecastEnabled || !v.useForecast) {
574
+ return false;
575
+ }
576
+ const minMinutes = v.forecastMinMinutes || 0;
577
+ if (this.forecast.minutes > minMinutes) {
578
+ return false;
579
+ }
580
+ if (this.forecast.surplus >= v.performance) {
581
+ return true;
582
+ }
583
+ return false;
584
+ }
585
+
586
+ isForecastDelayingSwitchOff(v, _currentSurplus) {
587
+ if (!this.forecastEnabled || !v.useForecast) {
588
+ return false;
589
+ }
590
+ const minMinutes = v.forecastMinMinutes || 0;
591
+ if (this.forecast.minutes > minMinutes) {
592
+ return false;
593
+ }
594
+ if (this.forecast.surplus >= v.switchOnPoint) {
595
+ return true;
596
+ }
597
+ return false;
598
+ }
599
+
600
+ async setForecastState(v, idx, active, reason, power, minutes) {
601
+ try {
602
+ const safeName = this.name2id(v.name);
603
+ const channelId = `consumer.${idx}_${safeName}`;
604
+ const text = active
605
+ ? `${reason || 'forecast'} (${power || this.forecast.power || 0}W / ${minutes || this.forecast.minutes || 0}min)`
606
+ : '';
607
+ await this.setStateAsync(`${this.namespace}.${channelId}.forecastStatus`, {
608
+ val: text,
609
+ ack: true,
610
+ });
611
+ } catch (err) {
612
+ this.log.warn(`[Forecast] Failed to set forecastStatus: ${err.message}`);
613
+ }
614
+ }
615
+
616
+ async sendForecastNotification(type, message) {
617
+ try {
618
+ if (this.config.notifyForecast) {
619
+ await this.sendNotification('smartloadmanager', 'notify', message);
620
+ }
621
+ } catch (err) {
622
+ this.log.warn(`[Forecast] Failed to send forecast notification: ${err.message}`);
623
+ }
624
+ }
625
+
626
+ // =====================================================================
627
+ // ========================= Time window helper ========================
628
+ // =====================================================================
311
629
  timeWithinWindow(onTime, offTime, name) {
312
630
  if (!onTime || !offTime) {
313
631
  return true;
@@ -331,11 +649,23 @@ class ZeroFeedIn extends utils.Adapter {
331
649
  return within;
332
650
  }
333
651
 
652
+ // =====================================================================
653
+ // ========================= onStateChange =============================
654
+ // =====================================================================
334
655
  async onStateChange(id, state) {
335
656
  if (!state) {
336
657
  return;
337
658
  }
338
659
 
660
+ // 🔮 Forecast JSON datapoint changed
661
+ if (this.forecastEnabled && this.forecastJsonDatapoint && id === this.forecastJsonDatapoint) {
662
+ if (state.ack === true) {
663
+ await this.processPvforecastJson();
664
+ await this.checkConsumers();
665
+ }
666
+ return;
667
+ }
668
+
339
669
  // FeedIn Datapoint - fremder State, nur bei ack=true reagieren
340
670
  if (id === this.feedInDatapoint) {
341
671
  // Nur verarbeiten wenn der Wert vom anderen Adapter bestätigt wurde
@@ -387,6 +717,19 @@ class ZeroFeedIn extends utils.Adapter {
387
717
  if (state && !state.ack) {
388
718
  this.log.debug(`State changed: ${id} => ${state.val}`);
389
719
 
720
+ // --- Aktiv-Status manuell umschalten ---------------------------------
721
+ if (id.match(/consumer\.(\d+)_.*?\.active/)) {
722
+ const match = id.match(/consumer\.(\d+)_.*?\.active/);
723
+ const index = parseInt(match[1]);
724
+ const v = this.consumerList[index];
725
+ if (v) {
726
+ v.enabled = state.val;
727
+ this.log.info(`Consumer "${v.name}" became ${state.val ? 'activated' : 'deactivated'}.`);
728
+ await this.setStateAsync(id, { val: state.val, ack: true });
729
+ }
730
+ return;
731
+ }
732
+
390
733
  // ControlMode Änderungen
391
734
  const match = id.match(/consumer\.(\d+)_.*?\.controlMode/);
392
735
  if (match) {
@@ -412,6 +755,9 @@ class ZeroFeedIn extends utils.Adapter {
412
755
  }
413
756
  }
414
757
 
758
+ // =====================================================================
759
+ // ============================ checkConsumers =========================
760
+ // =====================================================================
415
761
  async checkConsumers() {
416
762
  if (this.checkRunning) {
417
763
  this.checkQueued = true;
@@ -423,11 +769,7 @@ class ZeroFeedIn extends utils.Adapter {
423
769
  try {
424
770
  if (!this.feedInDatapoint) {
425
771
  this.log.warn('No FeedIn datapoint set – skipping checkConsumers()');
426
- await this.sendNotification(
427
- 'smartloadmanager',
428
- 'alert',
429
- '❗ Kein FeedIn-Datenpunkt gesetzt – Regelung übersprungen.',
430
- );
772
+ await this.sendNotification('smartloadmanager', 'alert', '❗ No FeedIn data point set – rule skipped.');
431
773
  this.checkRunning = false;
432
774
  if (this.checkQueued) {
433
775
  await this.checkConsumers();
@@ -456,6 +798,11 @@ class ZeroFeedIn extends utils.Adapter {
456
798
  this.log.debug(`Switch-on check for ${sortedOn.length} binary/heating-consumers.`);
457
799
 
458
800
  for (const v of sortedOn) {
801
+ // Überspringe deaktivierte Verbraucher
802
+ if (!v.enabled) {
803
+ this.log.debug(`"${v.name}" is deactivated – skipped (power-on check).`);
804
+ continue;
805
+ }
459
806
  const idx = this.consumerList.indexOf(v);
460
807
  const safeName = this.name2id(v.name);
461
808
  const id = `${this.namespace}.consumer.${idx}_${safeName}`;
@@ -510,7 +857,7 @@ class ZeroFeedIn extends utils.Adapter {
510
857
  if (v.enableDatapoint) {
511
858
  const enableState = await this.getForeignStateAsync(v.enableDatapoint);
512
859
  if (!enableState?.val) {
513
- this.log.debug(`"${v.name}" NICHT eingeschaltet – Hardware release missing.`);
860
+ this.log.debug(`"${v.name}" NOT switched on – Hardware release missing.`);
514
861
  continue;
515
862
  }
516
863
  }
@@ -551,6 +898,20 @@ class ZeroFeedIn extends utils.Adapter {
551
898
  await this.switchConsumerWithDelay(v, true, v.switchOnDelay || 0);
552
899
  feedIn -= v.performance;
553
900
  this.log.debug(`Remaining surplus after switch-on: ${feedIn - baseload} W`);
901
+ await this.setForecastState(v, idx, false, '', null, null);
902
+ } else if (!gridUsage && !isOn && this.isForecastAllowingSwitchOn(v)) {
903
+ const forecastPower = this.getForecastPower(v);
904
+ const minutes = this.getForecastMinutes(v);
905
+ this.log.debug(
906
+ `[Forecast] Early switch-on allowed for "${v.name}": ${forecastPower} W in ${minutes} min.`,
907
+ );
908
+ await this.setForecastState(v, idx, true, 'forecastSwitchOn', forecastPower, minutes);
909
+ await this.sendForecastNotification(
910
+ v.ruletype === 'percent' ? 'percent' : 'binary',
911
+ `🔮 Forecast: "${v.name}" will be switched on in ${minutes} minutes (${forecastPower} W expected).`,
912
+ );
913
+ await this.switchConsumerWithDelay(v, true, v.switchOnDelay || 0);
914
+ feedIn -= v.performance;
554
915
  } else if (gridUsage && !isOn) {
555
916
  this.log.debug(`Not switched on: grid usage active.`);
556
917
  } else if (!isOn) {
@@ -577,6 +938,11 @@ class ZeroFeedIn extends utils.Adapter {
577
938
  const safeName = this.name2id(v.name);
578
939
  const id = `${this.namespace}.consumer.${idx}_${safeName}`;
579
940
  const mode = await this.getStateAsync(`${id}.controlMode`);
941
+ // Überspringe deaktivierte Verbraucher
942
+ if (!v.enabled) {
943
+ this.log.debug(`"${v.name}" is deactivated – will be skipped (power-off check).`);
944
+ continue;
945
+ }
580
946
  if (!mode || mode.val !== 2) {
581
947
  continue;
582
948
  }
@@ -689,6 +1055,21 @@ class ZeroFeedIn extends utils.Adapter {
689
1055
  const shouldSwitchOff = !withinWindow || gridUsage || surplus < v.switchOffPoint;
690
1056
 
691
1057
  if (!v.alwaysOffAtTime && shouldSwitchOff) {
1058
+ // 🔮 Forecast delay switch-off (PV forecast integration)
1059
+ if (this.isForecastDelayingSwitchOff(v, surplus)) {
1060
+ const forecastPower = this.getForecastPower(v);
1061
+ const minutes = this.getForecastMinutes(v);
1062
+ this.log.debug(
1063
+ `[Forecast] Switch-off delayed for "${v.name}": ${forecastPower} W in ${minutes} min.`,
1064
+ );
1065
+ await this.setForecastState(v, idx, true, 'forecastDelayOff', forecastPower, minutes);
1066
+ await this.sendForecastNotification(
1067
+ v.ruletype === 'percent' ? 'percent' : 'binary',
1068
+ `🔮 Forecast: "${v.name}" remains active (${forecastPower} W expected in ${minutes} min).`,
1069
+ );
1070
+ continue;
1071
+ }
1072
+
692
1073
  if (v.timer && v.timerEnd && v.pendingAction === false) {
693
1074
  const remaining = Math.max(0, Math.round((v.timerEnd - Date.now()) / 1000));
694
1075
  this.log.debug(`Active timer for ${v.name}: ${remaining}s remaining`);
@@ -697,6 +1078,7 @@ class ZeroFeedIn extends utils.Adapter {
697
1078
  `Switching off: "${v.name}" - surplus ${surplus}W < switchOffPoint ${v.switchOffPoint}W, offDelay ${v.switchOffDelay || 0}s.`,
698
1079
  );
699
1080
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
1081
+ await this.setForecastState(v, idx, false, '', null, null);
700
1082
  }
701
1083
  }
702
1084
  }
@@ -704,6 +1086,10 @@ class ZeroFeedIn extends utils.Adapter {
704
1086
  // --- Batterie unverändert ---
705
1087
  const batteryConsumers = this.consumerList.filter(c => c.ruletype === 'battery');
706
1088
  for (const v of batteryConsumers) {
1089
+ if (!v.enabled) {
1090
+ this.log.debug(`"${v.name}" is deactivated – battery control skipped.`);
1091
+ continue;
1092
+ }
707
1093
  await this.controlBattery(v, feedIn);
708
1094
  }
709
1095
 
@@ -730,6 +1116,9 @@ class ZeroFeedIn extends utils.Adapter {
730
1116
  }
731
1117
  }
732
1118
 
1119
+ // =====================================================================
1120
+ // ========================= controlPercentConsumer ====================
1121
+ // =====================================================================
733
1122
  async controlPercentConsumer(v) {
734
1123
  try {
735
1124
  const withinWindow = this.timeWithinWindow(v.switchOnTime, v.switchOffTime);
@@ -792,6 +1181,9 @@ class ZeroFeedIn extends utils.Adapter {
792
1181
  }
793
1182
  }
794
1183
 
1184
+ // =====================================================================
1185
+ // =============================== controlBattery ======================
1186
+ // =====================================================================
795
1187
  async controlBattery(v, feedIn) {
796
1188
  try {
797
1189
  this.log.debug(`[Battery] Checking for ${v.name}`);
@@ -948,6 +1340,9 @@ class ZeroFeedIn extends utils.Adapter {
948
1340
  }
949
1341
  }
950
1342
 
1343
+ // =====================================================================
1344
+ // ========================= switchConsumerWithDelay ===================
1345
+ // =====================================================================
951
1346
  async switchConsumerWithDelay(consumer, turnOn, delaySeconds) {
952
1347
  const delay =
953
1348
  delaySeconds !== undefined
@@ -1112,6 +1507,9 @@ class ZeroFeedIn extends utils.Adapter {
1112
1507
  }
1113
1508
  }
1114
1509
 
1510
+ // =====================================================================
1511
+ // ========================= sendNotification ==========================
1512
+ // =====================================================================
1115
1513
  // Zentrale, einheitliche Notification-Funktion
1116
1514
  // Aufruf: await this.sendNotification('smartloadmanager', 'info'|'notify'|'alert', 'Nachricht');
1117
1515
  async sendNotification(scope, category, message) {
@@ -1238,6 +1636,9 @@ class ZeroFeedIn extends utils.Adapter {
1238
1636
  }
1239
1637
  }
1240
1638
 
1639
+ // =====================================================================
1640
+ // ========================= initializeConsumerStatus ==================
1641
+ // =====================================================================
1241
1642
  async initializeConsumerStatus() {
1242
1643
  // Initiale Status-Updates
1243
1644
  for (const v of this.consumerList) {
@@ -1268,6 +1669,9 @@ class ZeroFeedIn extends utils.Adapter {
1268
1669
  return h * 60 + m;
1269
1670
  }
1270
1671
 
1672
+ // =====================================================================
1673
+ // =============================== onUnload ============================
1674
+ // =====================================================================
1271
1675
  async onUnload(callback) {
1272
1676
  try {
1273
1677
  if (this.percentTimer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.smartloadmanager",
3
- "version": "0.0.8",
3
+ "version": "0.1.2",
4
4
  "description": "This adapter allows devices to be controlled via PV surplus and thus achieve zero feed-in.",
5
5
  "author": {
6
6
  "name": "quorle",
@@ -28,16 +28,16 @@
28
28
  "translate": "^3.0.1"
29
29
  },
30
30
  "devDependencies": {
31
- "@alcalzone/release-script": "^3.8.0",
32
- "@alcalzone/release-script-plugin-iobroker": "^3.7.2",
33
- "@alcalzone/release-script-plugin-license": "^3.7.0",
34
- "@alcalzone/release-script-plugin-manual-review": "^3.7.0",
35
- "@iobroker/adapter-dev": "^1.4.0",
36
- "@iobroker/eslint-config": "^2.1.0",
37
- "@iobroker/testing": "^5.1.1",
38
- "@tsconfig/node20": "^20.1.6",
39
- "@types/node": "^24.3.1",
40
- "typescript": "~5.0.4"
31
+ "@alcalzone/release-script": "^5.1.1",
32
+ "@alcalzone/release-script-plugin-iobroker": "^5.1.2",
33
+ "@alcalzone/release-script-plugin-license": "^5.1.1",
34
+ "@alcalzone/release-script-plugin-manual-review": "^5.1.1",
35
+ "@iobroker/adapter-dev": "^1.5.0",
36
+ "@iobroker/eslint-config": "^2.2.0",
37
+ "@iobroker/testing": "^5.2.2",
38
+ "@tsconfig/node20": "^20.1.9",
39
+ "@types/node": "^25.6.0",
40
+ "typescript": "~6.0.2"
41
41
  },
42
42
  "main": "main.js",
43
43
  "files": [