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 +9 -9
- package/admin/jsonConfig.json +0 -17
- package/io-package.json +27 -1
- package/main.js +85 -13
- package/package.json +9 -9
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
|
package/admin/jsonConfig.json
CHANGED
|
@@ -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
|
|
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}"
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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": "^
|
|
32
|
-
"@alcalzone/release-script-plugin-iobroker": "^
|
|
33
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
34
|
-
"@alcalzone/release-script-plugin-manual-review": "^
|
|
35
|
-
"@iobroker/adapter-dev": "^1.
|
|
36
|
-
"@iobroker/eslint-config": "^2.
|
|
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": "^
|
|
40
|
-
"typescript": "~5.
|
|
39
|
+
"@types/node": "^24.9.2",
|
|
40
|
+
"typescript": "~5.9.3"
|
|
41
41
|
},
|
|
42
42
|
"main": "main.js",
|
|
43
43
|
"files": [
|