iobroker.smartloadmanager 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,6 +30,15 @@ The **smartloadmanager** adapter is used for dynamic control of loads based on a
30
30
  Placeholder for the next version (at the beginning of the line):
31
31
  ### **WORK IN PROGRESS**
32
32
  -->
33
+ ### 0.1.0 (2025-11-07)
34
+
35
+ - (quorle) Added a checkbox for activating consumers from the settings to the object tree
36
+
37
+ ### 0.0.8 (2025-10-03)
38
+
39
+ - (quorle) Fix for binary consumers that do not turn off
40
+ - (quorle) Fix for heating elements that are not switched off at max temperature
41
+
33
42
  ### 0.0.7 (2025-09-09)
34
43
 
35
44
  - (quorle) modified Readme
@@ -52,15 +61,6 @@ The **smartloadmanager** adapter is used for dynamic control of loads based on a
52
61
  - (quorle) Adjustments package.json
53
62
  - (quorle) Code changed
54
63
 
55
- ### 0.0.2 (2025-09-03)
56
-
57
- - (quorle) Adjustments package.json
58
- - (quorle) Code changed
59
-
60
- ### 0.0.1 (2025-09-02)
61
-
62
- - (quorle) Release
63
-
64
64
  ## License
65
65
 
66
66
  MIT License
@@ -113,23 +113,6 @@
113
113
  "zh-cn": "名称"
114
114
  }
115
115
  },
116
- {
117
- "type": "checkbox",
118
- "attr": "enabled",
119
- "title": {
120
- "en": "Active",
121
- "de": "Aktiv",
122
- "ru": "Активный",
123
- "pt": "Activo",
124
- "nl": "Actief",
125
- "fr": "Activité",
126
- "it": "Attivo",
127
- "es": "Activo",
128
- "pl": "Aktywne",
129
- "uk": "Активний",
130
- "zh-cn": "活动"
131
- }
132
- },
133
116
  {
134
117
  "type": "objectId",
135
118
  "attr": "datapoint",
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "smartloadmanager",
4
- "version": "0.0.7",
4
+ "version": "0.1.0",
5
5
  "news": {
6
+ "0.1.0": {
7
+ "en": "Added a checkbox for activating consumers from the settings to the object tree",
8
+ "de": "Hinzufügen eines Kontrollkästchens zur Aktivierung von Verbrauchern von den Einstellungen zum Objektbaum",
9
+ "ru": "Добавлен чекбокс для активации потребителей от настроек до дерева объектов",
10
+ "pt": "Adicionada uma caixa de seleção para ativar os consumidores das configurações para a árvore de objetos",
11
+ "nl": "Een selectievakje toegevoegd voor het activeren van consumenten van de instellingen naar de objectboom",
12
+ "fr": "Ajout d'une case à cocher pour activer les consommateurs depuis les paramètres vers l'arborescence des objets",
13
+ "it": "Aggiunto una casella di controllo per attivare i consumatori dalle impostazioni all'albero dell'oggetto",
14
+ "es": "Se agregó una casilla de verificación para activar a los consumidores desde la configuración hasta el árbol objeto",
15
+ "pl": "Dodano opcję aktywacji konsumentów od ustawień do drzewa obiektów",
16
+ "uk": "Додано прапорець для активації споживачів з налаштувань до дерева об'єкта",
17
+ "zh-cn": "从设置中添加一个激活消费者的复选框到对象树"
18
+ },
19
+ "0.0.8": {
20
+ "en": "Fix for binary consumers that do not turn off\nFix for heating elements that are not switched off at max temperature",
21
+ "de": "Fix für binäre Verbraucher, die nicht ausschalten\nBefestigung für Heizelemente, die bei max Temperatur nicht abgeschaltet werden",
22
+ "ru": "Исправьте для бинарных потребителей, которые не отключаются\nИсправьте нагревательные элементы, которые не выключены при максимальной температуре",
23
+ "pt": "Corrigir para consumidores binários que não se desligam\nFixação para elementos de aquecimento que não são desligados à temperatura máxima",
24
+ "nl": "Fix voor binaire consumenten die niet uitschakelen\nFix voor verwarmingselementen die niet zijn uitgeschakeld bij maximale temperatuur",
25
+ "fr": "Correction pour les consommateurs binaires qui ne s'arrêtent pas\nFixation pour éléments chauffants qui ne sont pas éteints à la température maximale",
26
+ "it": "Fissare per i consumatori binari che non si spegne\nFissaggio per elementi di riscaldamento non spenti a temperatura massima",
27
+ "es": "Fijación para los consumidores binarios que no apagan\nFijación para elementos de calefacción que no se apagan a temperatura máxima",
28
+ "pl": "Napraw dla konsumentów binarnych, którzy nie wyłączają\nNaprawa elementów grzewczych, które nie są wyłączone w maksymalnej temperaturze",
29
+ "uk": "Виправлення для бінарних споживачів, які не вимкнено\nФіксація нагрівальних елементів, які не відключені при максимальній температурі",
30
+ "zh-cn": "为不关闭的二进制消费者修复\n用于未在最大温度下关闭的加热元素的修复"
31
+ },
6
32
  "0.0.7": {
7
33
  "en": "modified Readme\nGerman and English description created\nchore: cleanup devDependencies\nFixed bugs in timeouts in functions\nsetTimeout/setInterval allowed maximum values ​​implemented\n",
8
34
  "de": "geänderte Readme\nDeutsche und englische Beschreibung erstellt\nchore: devDependencies aufräumen\nFehler bei Timeouts in Funktionen behoben\nsetTimeout/setInterval erlaubte Höchstwerte implementiert\n",
package/main.js CHANGED
@@ -66,14 +66,23 @@ class ZeroFeedIn extends utils.Adapter {
66
66
  this.consumerList = Array.isArray(this.config.consumer)
67
67
  ? this.config.consumer.filter(
68
68
  v =>
69
- v &&
70
- v.enabled &&
71
- (v.ruletype === 'battery' || ((v.datapoint || v.numericDatapoint) && v.performance > 0)),
69
+ v && (v.ruletype === 'battery' || ((v.datapoint || v.numericDatapoint) && v.performance > 0)),
72
70
  )
73
71
  : [];
74
72
 
75
73
  this.consumerList.forEach(v => (v.processingLockSwitch = false));
76
74
 
75
+ for (let i = 0; i < this.consumerList.length; i++) {
76
+ const v = this.consumerList[i];
77
+ const safeName = this.name2id(v.name);
78
+ const id = `${this.namespace}.consumer.${i}_${safeName}.active`;
79
+ const state = await this.getStateAsync(id);
80
+ if (state && state.val !== null) {
81
+ v.enabled = state.val;
82
+ this.log.debug(`"${v.name}": Active status taken from object tree (${state.val})`);
83
+ }
84
+ }
85
+
77
86
  this.log.info(`Loaded consumers: ${this.consumerList.map(v => v.name).join(', ')}`);
78
87
 
79
88
  await this.checkAndCreateConsumerObjects();
@@ -227,6 +236,20 @@ class ZeroFeedIn extends utils.Adapter {
227
236
  native: {},
228
237
  });
229
238
 
239
+ // === NEUER STATE: Aktiv-Status ============================
240
+ await this.setObjectNotExistsAsync(`${channelId}.active`, {
241
+ type: 'state',
242
+ common: {
243
+ name: `${v.name} aktiv`,
244
+ type: 'boolean',
245
+ role: 'switch',
246
+ read: true,
247
+ write: true,
248
+ def: v.enabled || false,
249
+ },
250
+ native: {},
251
+ });
252
+
230
253
  if (v.ruletype === 'battery') {
231
254
  await this.setObjectNotExistsAsync(`${channelId}.batterySetpoint`, {
232
255
  type: 'state',
@@ -303,6 +326,8 @@ class ZeroFeedIn extends utils.Adapter {
303
326
  await this.setStateAsync(`${this.namespace}.${channelId}.onTimerRemaining`, { val: 0, ack: true });
304
327
  await this.setStateAsync(`${this.namespace}.${channelId}.offTimerRemaining`, { val: 0, ack: true });
305
328
 
329
+ await this.setStateAsync(`${channelId}.active`, { val: v.enabled || false, ack: true });
330
+
306
331
  v.switchOnTime = finalOnTime;
307
332
  v.switchOffTime = finalOffTime;
308
333
  }
@@ -387,6 +412,19 @@ class ZeroFeedIn extends utils.Adapter {
387
412
  if (state && !state.ack) {
388
413
  this.log.debug(`State changed: ${id} => ${state.val}`);
389
414
 
415
+ // --- Aktiv-Status manuell umschalten ---------------------------------
416
+ if (id.match(/consumer\.(\d+)_.*?\.active/)) {
417
+ const match = id.match(/consumer\.(\d+)_.*?\.active/);
418
+ const index = parseInt(match[1]);
419
+ const v = this.consumerList[index];
420
+ if (v) {
421
+ v.enabled = state.val;
422
+ this.log.info(`Consumer "${v.name}" became ${state.val ? 'activated' : 'deactivated'}.`);
423
+ await this.setStateAsync(id, { val: state.val, ack: true });
424
+ }
425
+ return;
426
+ }
427
+
390
428
  // ControlMode Änderungen
391
429
  const match = id.match(/consumer\.(\d+)_.*?\.controlMode/);
392
430
  if (match) {
@@ -423,11 +461,7 @@ class ZeroFeedIn extends utils.Adapter {
423
461
  try {
424
462
  if (!this.feedInDatapoint) {
425
463
  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
- );
464
+ await this.sendNotification('smartloadmanager', 'alert', '❗ No FeedIn data point set – rule skipped.');
431
465
  this.checkRunning = false;
432
466
  if (this.checkQueued) {
433
467
  await this.checkConsumers();
@@ -456,6 +490,11 @@ class ZeroFeedIn extends utils.Adapter {
456
490
  this.log.debug(`Switch-on check for ${sortedOn.length} binary/heating-consumers.`);
457
491
 
458
492
  for (const v of sortedOn) {
493
+ // Überspringe deaktivierte Verbraucher
494
+ if (!v.enabled) {
495
+ this.log.debug(`"${v.name}" is deactivated – skipped (power-on check).`);
496
+ continue;
497
+ }
459
498
  const idx = this.consumerList.indexOf(v);
460
499
  const safeName = this.name2id(v.name);
461
500
  const id = `${this.namespace}.consumer.${idx}_${safeName}`;
@@ -510,7 +549,7 @@ class ZeroFeedIn extends utils.Adapter {
510
549
  if (v.enableDatapoint) {
511
550
  const enableState = await this.getForeignStateAsync(v.enableDatapoint);
512
551
  if (!enableState?.val) {
513
- this.log.debug(`"${v.name}" NICHT eingeschaltet – Hardware release missing.`);
552
+ this.log.debug(`"${v.name}" NOT switched on – Hardware release missing.`);
514
553
  continue;
515
554
  }
516
555
  }
@@ -577,6 +616,11 @@ class ZeroFeedIn extends utils.Adapter {
577
616
  const safeName = this.name2id(v.name);
578
617
  const id = `${this.namespace}.consumer.${idx}_${safeName}`;
579
618
  const mode = await this.getStateAsync(`${id}.controlMode`);
619
+ // Überspringe deaktivierte Verbraucher
620
+ if (!v.enabled) {
621
+ this.log.debug(`"${v.name}" is deactivated – will be skipped (power-off check).`);
622
+ continue;
623
+ }
580
624
  if (!mode || mode.val !== 2) {
581
625
  continue;
582
626
  }
@@ -654,8 +698,24 @@ class ZeroFeedIn extends utils.Adapter {
654
698
  continue;
655
699
  }
656
700
 
701
+ // Temperatur-basierte Ausschaltung für heating
702
+ if (v.ruletype === 'heating' && v.maxTemperature !== undefined && isOn) {
703
+ const tempState = v.temperatureDatapoint
704
+ ? await this.getForeignStateAsync(v.temperatureDatapoint)
705
+ : null;
706
+ const currentTemp = tempState?.val !== undefined ? Number(tempState.val) : null;
707
+ if (currentTemp !== null && currentTemp >= v.maxTemperature) {
708
+ this.log.warn(
709
+ `"${v.name}" switched off due to maxTemperature reached: ${currentTemp}°C >= ${v.maxTemperature}°C`,
710
+ );
711
+ await this.switchConsumerWithDelay(v, false, 0);
712
+ continue; // weiter zum nächsten Verbraucher
713
+ }
714
+ }
715
+
657
716
  // Timer-Abbruch bei wegfallender Bedingung (Ausschalten)
658
- if (v.timer && v.timerEnd && v.pendingAction === false && withinWindow && !gridUsage) {
717
+ const surplus = feedIn - baseload;
718
+ if (v.timer && v.timerEnd && v.pendingAction === false && withinWindow && surplus >= v.switchOffPoint) {
659
719
  clearTimeout(v.timer);
660
720
  v.timer = null;
661
721
  v.timerEnd = null;
@@ -666,20 +726,32 @@ class ZeroFeedIn extends utils.Adapter {
666
726
  this.log.debug(`Switch-off timer for "${v.name}" canceled (condition no longer valid).`);
667
727
  }
668
728
 
669
- if (!v.alwaysOffAtTime && (!withinWindow || gridUsage)) {
729
+ // Ausschalten, wenn:
730
+ // - Zeitfenster verlassen ODER
731
+ // - Netzbezug aktiv ODER
732
+ // - Überschuss < switchOffPoint
733
+ const shouldSwitchOff = !withinWindow || gridUsage || surplus < v.switchOffPoint;
734
+
735
+ if (!v.alwaysOffAtTime && shouldSwitchOff) {
670
736
  if (v.timer && v.timerEnd && v.pendingAction === false) {
671
737
  const remaining = Math.max(0, Math.round((v.timerEnd - Date.now()) / 1000));
672
738
  this.log.debug(`Active timer for ${v.name}: ${remaining}s remaining`);
673
739
  } else {
674
- this.log.debug(`Switching off: "${v.name}" - offDelay ${v.switchOffDelay || 0}s.`);
740
+ this.log.debug(
741
+ `Switching off: "${v.name}" - surplus ${surplus}W < switchOffPoint ${v.switchOffPoint}W, offDelay ${v.switchOffDelay || 0}s.`,
742
+ );
675
743
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
676
744
  }
677
745
  }
678
746
  }
679
747
 
680
- // --- Batterie bleibt unverändert ---
748
+ // --- Batterie unverändert ---
681
749
  const batteryConsumers = this.consumerList.filter(c => c.ruletype === 'battery');
682
750
  for (const v of batteryConsumers) {
751
+ if (!v.enabled) {
752
+ this.log.debug(`"${v.name}" is deactivated – battery control skipped.`);
753
+ continue;
754
+ }
683
755
  await this.controlBattery(v, feedIn);
684
756
  }
685
757
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.smartloadmanager",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
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",
31
+ "@alcalzone/release-script": "^5.0.0",
32
+ "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
33
+ "@alcalzone/release-script-plugin-license": "^4.0.0",
34
+ "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
35
+ "@iobroker/adapter-dev": "^1.5.0",
36
+ "@iobroker/eslint-config": "^2.2.0",
37
37
  "@iobroker/testing": "^5.1.1",
38
38
  "@tsconfig/node20": "^20.1.6",
39
- "@types/node": "^20.19.1",
40
- "typescript": "~5.0.4"
39
+ "@types/node": "^24.9.2",
40
+ "typescript": "~5.9.3"
41
41
  },
42
42
  "main": "main.js",
43
43
  "files": [