iobroker.smartloadmanager 0.1.2 → 0.2.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 +19 -0
- package/admin/i18n/de.json +130 -0
- package/admin/i18n/en.json +130 -0
- package/admin/i18n/es.json +130 -0
- package/admin/i18n/fr.json +130 -0
- package/admin/i18n/it.json +130 -0
- package/admin/i18n/nl.json +130 -0
- package/admin/i18n/pl.json +130 -0
- package/admin/i18n/pt.json +130 -0
- package/admin/i18n/ru.json +130 -0
- package/admin/i18n/uk.json +130 -0
- package/admin/i18n/zh-cn.json +130 -0
- package/admin/jsonConfig.json +196 -5
- package/docs/de/README.md +283 -0
- package/docs/de/release-0.2.0-pruefung.md +74 -0
- package/docs/en/README.md +268 -0
- package/io-package.json +94 -25
- package/lib/forecast.js +123 -0
- package/main.js +337 -125
- package/package.json +18 -15
- package/admin/i18n/de/translations.json +0 -124
- package/admin/i18n/en/translations.json +0 -116
- package/admin/i18n/es/translations.json +0 -124
- package/admin/i18n/fr/translations.json +0 -124
- package/admin/i18n/it/translations.json +0 -124
- package/admin/i18n/nl/translations.json +0 -124
- package/admin/i18n/pl/translations.json +0 -124
- package/admin/i18n/pt/translations.json +0 -124
- package/admin/i18n/ru/translations.json +0 -124
- package/admin/i18n/uk/translations.json +0 -124
- package/admin/i18n/zh-cn/translations.json +0 -124
- package/lib/adapter-config.d.ts +0 -20
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# ioBroker.smartloadmanager
|
|
4
|
+
|
|
5
|
+
## Version 0.2.0 examples
|
|
6
|
+
|
|
7
|
+
Requires Node.js >=22.19.0. Device commands use `ack: false`; the device adapter
|
|
8
|
+
provides the acknowledgement. Feed-in must represent grid connection power in watts.
|
|
9
|
+
Enable `feedinNegativ` when export is negative. Baseload is an additional reserve;
|
|
10
|
+
do not subtract household consumption twice if it is already included in the meter reading.
|
|
11
|
+
|
|
12
|
+
### Binary loads and heating
|
|
13
|
+
|
|
14
|
+
Example: `performance: 1000`, `switchOnPoint: 1200`, `switchOffPoint: 0`,
|
|
15
|
+
`switchOnDelay: 20`, `switchOffDelay: 10`. A 1200 W surplus after baseload starts
|
|
16
|
+
the on timer. Import or surplus below the off threshold starts the off timer.
|
|
17
|
+
`minimumOnMinutes: 10` and `minimumOffMinutes: 5` reduce cycling; import above
|
|
18
|
+
`forecastMaxGridImport` overrides minimum runtime. Set minimum times to 0 to disable them.
|
|
19
|
+
|
|
20
|
+
Use `datapoint` for boolean switching and `numericDatapoint` for numeric 0/1.
|
|
21
|
+
Heating consumers additionally support temperature, enable and heartbeat inputs.
|
|
22
|
+
Maximum temperature and missing enable/heartbeat prevent activation.
|
|
23
|
+
Heartbeat checks the input's truth value, not the age of its last update.
|
|
24
|
+
A missing or nonfinite configured temperature sensor prevents activation and
|
|
25
|
+
stops automatic heating. “Switch off only at switch-off time” intentionally
|
|
26
|
+
disables normal surplus shutdown until the time window ends; heating protection remains active.
|
|
27
|
+
|
|
28
|
+
### Percentage and battery control
|
|
29
|
+
|
|
30
|
+
A 2000 W percent consumer at 50% consumes 1000 W. With 400 W additional export,
|
|
31
|
+
the target becomes 70%. With 200 W import it becomes 40%. Below `minPercentStart`,
|
|
32
|
+
the target becomes 0%. Disabled consumers are not regulated. This calculation
|
|
33
|
+
assumes the load is included in the grid meter reading.
|
|
34
|
+
|
|
35
|
+
Configure a battery's `batterySetpoint`, `performance`, and optional `batterySOC`
|
|
36
|
+
and `batteryTargetSOC`. With 800 W surplus and a 1500 W rating, the charging command
|
|
37
|
+
is 800 W. Reaching target SOC sets charging power to 0. The optional global battery
|
|
38
|
+
mode datapoint uses 0 (off), 1 (target reached), and 2 (charging); verify that these
|
|
39
|
+
values match the device adapter.
|
|
40
|
+
|
|
41
|
+
`controlMode`: 0 = off, 1 = manual on, 2 = automatic. Manual on sets a percent load
|
|
42
|
+
to 100% or battery charging power to its configured rating. Automatic regulators
|
|
43
|
+
respect manual modes.
|
|
44
|
+
Battery control includes already requested charging power and corrects it by the
|
|
45
|
+
measured grid surplus. `batteryDelaySeconds` and `delaySecondsProzent` remain
|
|
46
|
+
effective during frequent meter updates; 0 means immediate regulation.
|
|
47
|
+
Missing configured SOC/target SOC values stop charging. Time windows are also
|
|
48
|
+
checked every 15 seconds. Disabling a consumer cancels pending switches but leaves
|
|
49
|
+
its existing device state unchanged; use `controlMode: 0` to switch it off.
|
|
50
|
+
|
|
51
|
+
### PV forecast example
|
|
52
|
+
|
|
53
|
+
Enable `enableForecast`, select e.g. `pvforecast.0.summary.JSONData` as
|
|
54
|
+
`forecastJsonDatapoint`, and enable `useForecast` for the consumer.
|
|
55
|
+
The expected JSON array uses Unix timestamps in milliseconds:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
const forecast = [0, 15, 30, 45].map(minutes => ({
|
|
60
|
+
t: now + minutes * 60000,
|
|
61
|
+
y: 3.5,
|
|
62
|
+
}));
|
|
63
|
+
// JSON.stringify(forecast), with forecastPowerUnit = "kW"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With 500 W baseload this gives 3000 W forecast surplus, reduced to 2400 W at
|
|
67
|
+
`forecastSafetyPercent: 80`. A 2000 W consumer requires a sustained
|
|
68
|
+
`forecastMinMinutes: 30` window starting within `forecastMaxStartMinutes: 30`.
|
|
69
|
+
At `forecastMinCurrentPercent: 50`, at least 1000 W must already be available.
|
|
70
|
+
|
|
71
|
+
`forecastBridgeMinutes: 5` limits dip bridging; `forecastMaxGridImport: 100`
|
|
72
|
+
prevents bridging above 100 W import. Past forecast points are ignored.
|
|
73
|
+
Select `W` explicitly for small watt readings: automatic detection treats values
|
|
74
|
+
up to 100 as kW. Inspect `forecast.*` and each consumer's forecast status states.
|
|
75
|
+
|
|
76
|
+
Time windows may cross midnight (22:00–06:00). Equal start/end times mean all day.
|
|
77
|
+
Automated tests use simulated devices; no physical device installation was available.
|
|
78
|
+
|
|
79
|
+
## 🔧 Description
|
|
80
|
+
|
|
81
|
+
The **smartloadmanager** adapter dynamically controls consumers based on a PV feed-in value.
|
|
82
|
+
The goal is to consume surplus power locally and minimize or completely avoid feeding into the public grid.
|
|
83
|
+
It supports binary consumers, percentage-controllable devices, and battery storage systems.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🚀 Features
|
|
88
|
+
|
|
89
|
+
- ✅ Monitoring of a configurable feed-in datapoint
|
|
90
|
+
- ✅ Dynamic switching on of consumers in case of surplus
|
|
91
|
+
- ✅ Dynamic switching off in case of deficit or grid consumption
|
|
92
|
+
- ✅ Supports **binary**, **percent**, **heating** and **battery** consumers
|
|
93
|
+
- ✅ Percentage control with delay (smooth down-regulation), optionally configurable per consumer
|
|
94
|
+
- ✅ Dynamic charging power for battery storage with target SOC
|
|
95
|
+
- ✅ Time windows for on/off logic per consumer (incl. "only at off-time")
|
|
96
|
+
- ✅ Automatic object creation with extended information per consumer
|
|
97
|
+
- ✅ Control mode: Off / Manual On / Auto per consumer
|
|
98
|
+
- ✅ Hysteresis control via separate on/off thresholds
|
|
99
|
+
- ✅ Consumer-specific switching delay (override of global setting)
|
|
100
|
+
- ✅ Global battery switching delay (`batteryDelaySeconds`)
|
|
101
|
+
- ✅ Write check for battery control mode (debug output in log)
|
|
102
|
+
- ✅ **Delayed switching via timers** for on and off
|
|
103
|
+
- ✅ **Display of active timers in debug log** with remaining time
|
|
104
|
+
- ✅ Optional **notifications** for binary, percent, battery and heating (Telegram / Gotify)
|
|
105
|
+
- ✅ Optimization for 0/1 datapoints (boolean or numeric 0/1 control)
|
|
106
|
+
- ✅ Detailed logging of all timers, surpluses and switching operations
|
|
107
|
+
- ✅ Notifications per consumer type (Binary, Percent, Battery, Heating), optionally enabled
|
|
108
|
+
- ✅ Heating/immersion heater control with temperature, enable and heartbeat checks
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## ⚙️ Configuration
|
|
113
|
+
|
|
114
|
+
### 🔹 Main settings
|
|
115
|
+
|
|
116
|
+
| Setting | Description |
|
|
117
|
+
| ---------------------------------- | ------------------------------------------------------------------------------- |
|
|
118
|
+
| **Feed-in datapoint** | Object ID of the feed-in value (e.g. PV surplus) |
|
|
119
|
+
| **Base load** | Always subtracted from feed-in value (e.g. standby consumption) |
|
|
120
|
+
| **Switch-on threshold** | Threshold in watts at which consumers are switched on |
|
|
121
|
+
| **Switch-off threshold** | Threshold in watts at which consumers are switched off |
|
|
122
|
+
| **Delay (seconds)** | Delay for binary switching (hysteresis smoothing) |
|
|
123
|
+
| **Delay percent (s)** | Global delay for down-regulation of percent consumers (e.g. wallbox) |
|
|
124
|
+
| **Delay battery (s)** | Global delay for battery control |
|
|
125
|
+
| **Feed-in negative** | If active: negative = feed-in / positive = grid consumption |
|
|
126
|
+
| **Battery control mode datapoint** | Optional datapoint for battery mode switching (Auto/Manual/Off) |
|
|
127
|
+
| **Notifications** | Enable notifications for Binary / Percent / Battery / Heating (Telegram/Gotify) |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 🔹 Consumers
|
|
132
|
+
|
|
133
|
+
| Field | Description |
|
|
134
|
+
| ----------------------------------- | --------------------------------------------------------------------- |
|
|
135
|
+
| **Name** | Display name |
|
|
136
|
+
| **Active** | Enables control for this consumer |
|
|
137
|
+
| **Rule type** | `"binary"`, `"percent"`, `"battery"`, `"heating"` |
|
|
138
|
+
| **Control datapoint** | ID to switch or regulate, supports 0/1 datapoints |
|
|
139
|
+
| **Power [W]** | Realistic electrical power of the consumer |
|
|
140
|
+
| **Switch-on threshold [W]** | Required surplus for activation |
|
|
141
|
+
| **Switch-off threshold [W]** | Lower limit for deactivation |
|
|
142
|
+
| **Max power [W]** | Reference value for percentage control |
|
|
143
|
+
| **Delay percent [s]** | Optional delay for down-regulation for this consumer only |
|
|
144
|
+
| **Delay override [s]** | Consumer-specific switching delay (e.g. immediate switching possible) |
|
|
145
|
+
| **Switch-on time (HH:MM)** | Time when control may start |
|
|
146
|
+
| **Switch-off time (HH:MM)** | Time when control must end |
|
|
147
|
+
| **Only off at time** | Checkbox: switch off only at configured time |
|
|
148
|
+
| **batterySetpoint (battery only)** | Datapoint where desired charging power is written |
|
|
149
|
+
| **batterySOC / targetSOC** | Optional: SOC & target SOC to stop charging at target |
|
|
150
|
+
| **Timer / TimerEnd** | Internal timer for delayed switching, remaining time visible in log |
|
|
151
|
+
| **minTemperature / maxTemperature** | Lower/upper threshold for heating operation |
|
|
152
|
+
| **temperatureDatapoint (heating)** | Temperature datapoint for min/max checks |
|
|
153
|
+
| **enableDatapoint (heating)** | Optional datapoint: heating only allowed if true |
|
|
154
|
+
| **heartbeatDatapoint (heating)** | Optional: must cyclically deliver true, otherwise switched off |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 🔋 Battery storage support
|
|
159
|
+
|
|
160
|
+
- Consumers with `"ruletype": "battery"` control the charging setpoint based on current surplus.
|
|
161
|
+
- If `batterySOC` and `batteryTargetSOC` are set, charging stops at target SOC.
|
|
162
|
+
- Optional `batteryControlModeDatapoint`:
|
|
163
|
+
- `0 = Off`, `1 = Manual`, `2 = Auto`
|
|
164
|
+
- Control is only active within configured time windows.
|
|
165
|
+
- Control runs on every feed-in update, after configured delay (`batteryDelaySeconds`).
|
|
166
|
+
- Delays for all consumer types (binary, percent, battery) can be set globally or per consumer.
|
|
167
|
+
- Detailed logging and optional notifications for battery switching.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 🌡️ Heating support
|
|
172
|
+
|
|
173
|
+
- Consumers with `"ruletype": "heating"` are intended for immersion heaters or similar devices.
|
|
174
|
+
- Control only occurs when:
|
|
175
|
+
- Current temperature is below `maxTemperature`
|
|
176
|
+
- Optional: above `minTemperature`
|
|
177
|
+
- Optional: `enableDatapoint` is active
|
|
178
|
+
- Optional: `heartbeatDatapoint` is valid
|
|
179
|
+
- If conditions are not met, switching is stopped immediately and timers are canceled.
|
|
180
|
+
- Supports on/off timers (`onTimerRemaining` / `offTimerRemaining`) with remaining time display.
|
|
181
|
+
- Detailed logging and optional notifications for heating consumers.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🧠 Control logic
|
|
186
|
+
|
|
187
|
+
1. **Feed-in datapoint value is interpreted** (positive = grid consumption or feed-in, depending on config)
|
|
188
|
+
2. **Surplus > base load + switch-on threshold**:
|
|
189
|
+
→ Consumers are switched on (binary) in ascending order of power
|
|
190
|
+
3. **Deficit < base load - switch-off threshold**:
|
|
191
|
+
→ Consumers are switched off in reverse order
|
|
192
|
+
4. **Percent consumers**:
|
|
193
|
+
→ % = surplus / max power
|
|
194
|
+
→ Adjusted after delay (global or per consumer)
|
|
195
|
+
→ Optional overrides possible
|
|
196
|
+
5. **Battery consumers**:
|
|
197
|
+
→ Charging power = min(surplus, max power), if target SOC not reached
|
|
198
|
+
→ Delay via `batteryDelaySeconds` or per consumer
|
|
199
|
+
6. **Time window check for all consumers**
|
|
200
|
+
7. **Control only in mode Auto (2)**
|
|
201
|
+
8. **Delayed switching via timers** (cancel/restart on updates, remaining time visible in log)
|
|
202
|
+
9. **Optional notifications for all switching/regulation types**
|
|
203
|
+
10. **0/1 datapoint optimization** (boolean or numeric 0/1 supported)
|
|
204
|
+
11. **Detailed logging of all switching and surplus values**
|
|
205
|
+
12. **Heating logic**: switch only within temp ranges and with valid enable/heartbeat datapoints
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 💡 Example: Wallbox
|
|
210
|
+
|
|
211
|
+
| Parameter | Value |
|
|
212
|
+
| --------- | ------- |
|
|
213
|
+
| Feed-in | 1000 W |
|
|
214
|
+
| Base load | 100 W |
|
|
215
|
+
| Max power | 11000 W |
|
|
216
|
+
|
|
217
|
+
**Calculation:**
|
|
218
|
+
|
|
219
|
+
- Surplus: 1000 – 100 = 900 W
|
|
220
|
+
- Percent: 900 / 11000 ≈ 8.2 % → Wallbox set to 8 %
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 📋 Object structure
|
|
225
|
+
|
|
226
|
+
Each consumer gets its own channel with the following states:
|
|
227
|
+
|
|
228
|
+
- `.controlMode` → 0 = Off, 1 = Manual, 2 = Auto
|
|
229
|
+
- `.switchOnTime` / `.switchOffTime`
|
|
230
|
+
- `.alwaysOffAtTime` → true/false
|
|
231
|
+
- `.performance`, `.switchOnPoint`, `.switchOffPoint`
|
|
232
|
+
- `.batterySetpoint` (battery only)
|
|
233
|
+
- `.temperatureDatapoint` (heating only)
|
|
234
|
+
- `.onTimerRemaining / .offTimerRemaining` → Remaining time for timers
|
|
235
|
+
- `.timer` → internal timer for delayed switching
|
|
236
|
+
- `.timerEnd` → timestamp when timer expires (for log)
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 🚫 Limitations
|
|
241
|
+
|
|
242
|
+
- No SOC history, no long-term logic
|
|
243
|
+
- No multiple use of identical datapoints
|
|
244
|
+
- Battery control based only on feed-in, not discharge power
|
|
245
|
+
- Heating without direct power measurement – only thresholds and enable checks
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 🛣️ Future features
|
|
250
|
+
|
|
251
|
+
- ~~PV forecast-based control (beta)~~
|
|
252
|
+
- Combined consumer support
|
|
253
|
+
- ~~Prioritization profiles~~
|
|
254
|
+
- Overheating or fault handling per device
|
|
255
|
+
- ~~Min/max percentage limits~~
|
|
256
|
+
- Configurable group or room logic
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## PV forecast control
|
|
261
|
+
|
|
262
|
+
SmartLoadManager can use `pvforecast.<instance>.summary.JSONData`. Enable forecast control, select the JSON data point and enable it for the desired consumers.
|
|
263
|
+
|
|
264
|
+
The adapter evaluates a continuous forecast window instead of a single value. A configurable safety factor reduces optimistic forecasts. Forecast-assisted switch-on additionally requires part of the required power to be present as real surplus. Short drops can be bridged for a limited time, but only up to the configured maximum grid import.
|
|
265
|
+
|
|
266
|
+
Minimum run and off times prevent rapid cycling. Forecast decisions never override time windows, heating temperature limits, hardware enable/heartbeat signals or battery SOC limits.
|
|
267
|
+
|
|
268
|
+
Recommended starting values: 80% safety factor, 30-minute forecast window, 30-minute maximum lead time, 50% current-surplus requirement, 5-minute dip bridging, 100 W maximum grid import and 10/5-minute minimum run/off times.
|
package/io-package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "smartloadmanager",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"news": {
|
|
6
|
-
"0.
|
|
7
|
-
"en": "
|
|
8
|
-
"de": "
|
|
9
|
-
"ru": "
|
|
10
|
-
"pt": "
|
|
11
|
-
"nl": "
|
|
12
|
-
"fr": "
|
|
13
|
-
"it": "
|
|
14
|
-
"es": "
|
|
15
|
-
"pl": "
|
|
16
|
-
"uk": "
|
|
17
|
-
"zh-cn": "
|
|
6
|
+
"0.2.0": {
|
|
7
|
+
"en": "Added sustained PV forecast control and minimum run/off times; fixed consumer commands, percent regulation, battery control, time windows and timers; updated translations, tests and workflows.",
|
|
8
|
+
"de": "PV-Prognosesteuerung und Mindestlaufzeiten ergänzt; Verbrauchersteuerung, Zeitfenster und Timer korrigiert; Übersetzungen, Tests und Workflows aktualisiert.",
|
|
9
|
+
"ru": "Добавлены управление по прогнозу ФЭ и минимальные времена работы; исправлены управление потребителями, временные окна и таймеры; обновлены переводы, тесты и рабочие процессы.",
|
|
10
|
+
"pt": "Adicionado controlo por previsão solar e tempos mínimos de funcionamento; corrigidos consumidores, horários e temporizadores; atualizados traduções, testes e fluxos de trabalho.",
|
|
11
|
+
"nl": "PV-prognosesturing en minimale aan/uit-tijden toegevoegd; schakelopdrachten, procentregeling, batterij, tijdvensters en timers hersteld; vertalingen, tests en workflows bijgewerkt.",
|
|
12
|
+
"fr": "Ajout des prévisions solaires et durées minimales ; correction des commandes, plages horaires et minuteries ; mise à jour des traductions, tests et workflows.",
|
|
13
|
+
"it": "Aggiunti controllo tramite previsioni FV e tempi minimi di accensione/spegnimento; corretti comandi, regolazione percentuale, batteria, fasce orarie e timer; aggiornati traduzioni, test e workflow.",
|
|
14
|
+
"es": "Añadidos control por previsión FV y tiempos mínimos; corregidos consumidores, horarios y temporizadores; actualizados traducciones, pruebas y flujos de trabajo.",
|
|
15
|
+
"pl": "Dodano prognozę PV i minimalne czasy pracy; poprawiono sterowanie odbiornikami, okna czasowe i zegary; zaktualizowano tłumaczenia, testy i przepływy pracy.",
|
|
16
|
+
"uk": "Додано керування за прогнозом ФЕ та мінімальні часи роботи; виправлено керування споживачами, часові вікна та таймери; оновлено переклади, тести та робочі процеси.",
|
|
17
|
+
"zh-cn": "新增光伏预测控制和最短运行时间;修复用电设备控制、时间窗口和定时器;更新翻译、测试和工作流。"
|
|
18
18
|
},
|
|
19
|
-
"0.1.
|
|
19
|
+
"0.1.2": {
|
|
20
20
|
"en": "Update dependencies",
|
|
21
21
|
"de": "Abhängigkeiten aktualisieren",
|
|
22
22
|
"ru": "Обновление зависимостей",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"0.0.1": {
|
|
85
85
|
"en": "Release",
|
|
86
|
-
"de": "
|
|
86
|
+
"de": "Veröffentlichung",
|
|
87
87
|
"ru": "Выпуск",
|
|
88
88
|
"pt": "Lançar",
|
|
89
89
|
"nl": "Loslaten",
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
],
|
|
164
164
|
"globalDependencies": [
|
|
165
165
|
{
|
|
166
|
-
"admin": ">=7.6.
|
|
166
|
+
"admin": ">=7.6.20"
|
|
167
167
|
}
|
|
168
168
|
]
|
|
169
169
|
},
|
|
@@ -185,19 +185,46 @@
|
|
|
185
185
|
},
|
|
186
186
|
"description": {
|
|
187
187
|
"en": "Notifications from SmartLoadManager",
|
|
188
|
-
"de": "Benachrichtigungen vom SmartLoadManager"
|
|
188
|
+
"de": "Benachrichtigungen vom SmartLoadManager",
|
|
189
|
+
"ru": "Уведомления от SmartLoadManager",
|
|
190
|
+
"pt": "Notificações do SmartLoadManager",
|
|
191
|
+
"nl": "Meldingen van SmartLoadManager",
|
|
192
|
+
"fr": "Notifications de SmartLoadManager",
|
|
193
|
+
"it": "Notifiche da SmartLoadManager",
|
|
194
|
+
"es": "Notificaciones de SmartLoadManager",
|
|
195
|
+
"pl": "Powiadomienia z SmartLoadManager",
|
|
196
|
+
"uk": "Сповіщення від SmartLoadManager",
|
|
197
|
+
"zh-cn": "来自 SmartLoadManager 的通知"
|
|
189
198
|
},
|
|
190
199
|
"categories": [
|
|
191
200
|
{
|
|
192
201
|
"category": "info",
|
|
193
202
|
"name": {
|
|
194
203
|
"en": "Info",
|
|
195
|
-
"de": "
|
|
204
|
+
"de": "Information",
|
|
205
|
+
"ru": "Информация",
|
|
206
|
+
"pt": "Informação",
|
|
207
|
+
"nl": "Informatie",
|
|
208
|
+
"fr": "Information",
|
|
209
|
+
"it": "Informazioni",
|
|
210
|
+
"es": "Información",
|
|
211
|
+
"pl": "Informacja",
|
|
212
|
+
"uk": "Інформація",
|
|
213
|
+
"zh-cn": "信息"
|
|
196
214
|
},
|
|
197
215
|
"severity": "notify",
|
|
198
216
|
"description": {
|
|
199
217
|
"en": "Information about consumer switching or battery adjustments",
|
|
200
|
-
"de": "Informationen zu Verbraucherschaltungen oder Batterieanpassungen"
|
|
218
|
+
"de": "Informationen zu Verbraucherschaltungen oder Batterieanpassungen",
|
|
219
|
+
"ru": "Информация о переключении потребителей или настройке батареи",
|
|
220
|
+
"pt": "Informações sobre comutação de consumidores ou ajustes da bateria",
|
|
221
|
+
"nl": "Informatie over het schakelen van verbruikers of batterijaanpassingen",
|
|
222
|
+
"fr": "Informations sur la commutation des consommateurs ou les réglages de batterie",
|
|
223
|
+
"it": "Informazioni sulla commutazione delle utenze o sulle regolazioni della batteria",
|
|
224
|
+
"es": "Información sobre la conmutación de consumidores o los ajustes de batería",
|
|
225
|
+
"pl": "Informacje o przełączaniu odbiorników lub regulacji baterii",
|
|
226
|
+
"uk": "Інформація про перемикання споживачів або налаштування батареї",
|
|
227
|
+
"zh-cn": "有关用电设备切换或电池调整的信息"
|
|
201
228
|
},
|
|
202
229
|
"regex": [],
|
|
203
230
|
"limit": 1
|
|
@@ -206,12 +233,30 @@
|
|
|
206
233
|
"category": "notify",
|
|
207
234
|
"name": {
|
|
208
235
|
"en": "Notify",
|
|
209
|
-
"de": "
|
|
236
|
+
"de": "Benachrichtigung",
|
|
237
|
+
"ru": "Уведомление",
|
|
238
|
+
"pt": "Notificação",
|
|
239
|
+
"nl": "Melding",
|
|
240
|
+
"fr": "Notification",
|
|
241
|
+
"it": "Notifica",
|
|
242
|
+
"es": "Notificación",
|
|
243
|
+
"pl": "Powiadomienie",
|
|
244
|
+
"uk": "Сповіщення",
|
|
245
|
+
"zh-cn": "通知"
|
|
210
246
|
},
|
|
211
247
|
"severity": "notify",
|
|
212
248
|
"description": {
|
|
213
249
|
"en": "Notify about events",
|
|
214
|
-
"de": "
|
|
250
|
+
"de": "Über Ereignisse benachrichtigen",
|
|
251
|
+
"ru": "Уведомлять о событиях",
|
|
252
|
+
"pt": "Notificar sobre eventos",
|
|
253
|
+
"nl": "Melden over gebeurtenissen",
|
|
254
|
+
"fr": "Notifier les événements",
|
|
255
|
+
"it": "Notifica gli eventi",
|
|
256
|
+
"es": "Notificar sobre eventos",
|
|
257
|
+
"pl": "Powiadamiaj o zdarzeniach",
|
|
258
|
+
"uk": "Сповіщати про події",
|
|
259
|
+
"zh-cn": "事件通知"
|
|
215
260
|
},
|
|
216
261
|
"regex": [],
|
|
217
262
|
"limit": 1
|
|
@@ -220,12 +265,30 @@
|
|
|
220
265
|
"category": "alert",
|
|
221
266
|
"name": {
|
|
222
267
|
"en": "Alert",
|
|
223
|
-
"de": "Alarm"
|
|
268
|
+
"de": "Alarm",
|
|
269
|
+
"ru": "Тревога",
|
|
270
|
+
"pt": "Alerta",
|
|
271
|
+
"nl": "Alarm",
|
|
272
|
+
"fr": "Alerte",
|
|
273
|
+
"it": "Allarme",
|
|
274
|
+
"es": "Alerta",
|
|
275
|
+
"pl": "Alarm",
|
|
276
|
+
"uk": "Тривога",
|
|
277
|
+
"zh-cn": "警报"
|
|
224
278
|
},
|
|
225
279
|
"severity": "alert",
|
|
226
280
|
"description": {
|
|
227
281
|
"en": "Alarm or failures in the adapter",
|
|
228
|
-
"de": "Alarm oder Ausfälle im Adapter"
|
|
282
|
+
"de": "Alarm oder Ausfälle im Adapter",
|
|
283
|
+
"ru": "Тревоги или сбои адаптера",
|
|
284
|
+
"pt": "Alarmes ou falhas no adaptador",
|
|
285
|
+
"nl": "Alarmen of storingen in de adapter",
|
|
286
|
+
"fr": "Alarmes ou défaillances de l’adaptateur",
|
|
287
|
+
"it": "Allarmi o guasti nell'adattatore",
|
|
288
|
+
"es": "Alarmas o fallos en el adaptador",
|
|
289
|
+
"pl": "Alarmy lub awarie adaptera",
|
|
290
|
+
"uk": "Тривоги або збої адаптера",
|
|
291
|
+
"zh-cn": "适配器中的警报或故障"
|
|
229
292
|
},
|
|
230
293
|
"regex": [],
|
|
231
294
|
"limit": 1
|
|
@@ -263,13 +326,19 @@
|
|
|
263
326
|
"pushoverRecipients": "",
|
|
264
327
|
"enableNotificationManager": false,
|
|
265
328
|
"enableForecast": false,
|
|
266
|
-
"forecastDefaultMinMinutes":
|
|
329
|
+
"forecastDefaultMinMinutes": 30,
|
|
330
|
+
"forecastPowerUnit": "auto",
|
|
331
|
+
"forecastSafetyPercent": 80,
|
|
332
|
+
"forecastMaxStartMinutes": 30,
|
|
333
|
+
"forecastMinCurrentPercent": 50,
|
|
334
|
+
"forecastBridgeMinutes": 5,
|
|
335
|
+
"forecastMaxGridImport": 100,
|
|
267
336
|
"enableNotifications": false,
|
|
268
337
|
"notifyForecastBinary": false,
|
|
269
338
|
"notifyForecastPercent": false,
|
|
270
339
|
"notifyForecastBattery": false,
|
|
271
340
|
"forecastJsonDatapoint": "",
|
|
272
|
-
"notifyForecast":
|
|
341
|
+
"notifyForecast": false
|
|
273
342
|
},
|
|
274
343
|
"objects": [],
|
|
275
344
|
"instanceObjects": []
|
package/lib/forecast.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determine the conversion factor for forecast power values.
|
|
5
|
+
*
|
|
6
|
+
* @param {'W'|'kW'|null|undefined} unit Configured or detected power unit.
|
|
7
|
+
* @param {number[]} values Raw forecast values used for automatic detection.
|
|
8
|
+
* @returns {number} Multiplier to convert forecast values to watts.
|
|
9
|
+
*/
|
|
10
|
+
function powerFactor(unit, values = []) {
|
|
11
|
+
if (unit === 'W') {
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
if (unit === 'kW') {
|
|
15
|
+
return 1000;
|
|
16
|
+
}
|
|
17
|
+
const finite = values.filter(Number.isFinite).map(Math.abs);
|
|
18
|
+
return finite.length && Math.max(...finite) <= 100 ? 1000 : 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse pvforecast JSONData into normalized future data points.
|
|
23
|
+
*
|
|
24
|
+
* @param {string|Array<{t: number, y: number}>} value JSON string or decoded pvforecast array.
|
|
25
|
+
* @param {{now?: number, unit?: 'W'|'kW'|null, baseload?: number}} options Parser options.
|
|
26
|
+
* @returns {Array<{timestamp: number, power: number, surplus: number}>} Normalized forecast points.
|
|
27
|
+
*/
|
|
28
|
+
function parseForecast(value, options = {}) {
|
|
29
|
+
const now = options.now || Date.now();
|
|
30
|
+
let data = value;
|
|
31
|
+
if (typeof data === 'string') {
|
|
32
|
+
data = JSON.parse(data);
|
|
33
|
+
}
|
|
34
|
+
if (!Array.isArray(data)) {
|
|
35
|
+
throw new Error('Forecast must be an array');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const raw = data
|
|
39
|
+
.map(entry => ({ timestamp: Number(entry?.t), value: Number(entry?.y) }))
|
|
40
|
+
.filter(entry => Number.isFinite(entry.timestamp) && Number.isFinite(entry.value) && entry.timestamp >= now)
|
|
41
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
42
|
+
const factor = powerFactor(
|
|
43
|
+
options.unit,
|
|
44
|
+
raw.map(entry => entry.value),
|
|
45
|
+
);
|
|
46
|
+
const baseload = Math.max(0, Number(options.baseload) || 0);
|
|
47
|
+
return raw.map(entry => ({
|
|
48
|
+
timestamp: entry.timestamp,
|
|
49
|
+
power: Math.max(0, Math.round(entry.value * factor)),
|
|
50
|
+
surplus: Math.max(0, Math.round(entry.value * factor - baseload)),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find a sustained forecast window with enough conservative surplus.
|
|
56
|
+
*
|
|
57
|
+
* @param {Array<{timestamp: number, surplus: number}>} points Normalized forecast points.
|
|
58
|
+
* @param {{now?: number, requiredPower?: number, safetyFactor?: number, minDurationMinutes?: number, maxStartMinutes?: number}} options Evaluation limits.
|
|
59
|
+
* @returns {{available: boolean, startMinutes: number|null, durationMinutes: number, minimumSurplus: number, averageSurplus: number}} Forecast decision.
|
|
60
|
+
*/
|
|
61
|
+
function evaluateForecast(points, options = {}) {
|
|
62
|
+
const now = options.now || Date.now();
|
|
63
|
+
points = points
|
|
64
|
+
.filter(point => Number.isFinite(point.timestamp) && Number.isFinite(point.surplus) && point.timestamp >= now)
|
|
65
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
66
|
+
const requiredPower = Math.max(0, Number(options.requiredPower) || 0);
|
|
67
|
+
const safetyFactor = Math.min(1, Math.max(0.1, Number(options.safetyFactor) || 0.8));
|
|
68
|
+
const minDurationMinutes = Math.max(1, Number(options.minDurationMinutes) || 30);
|
|
69
|
+
const maxStartMinutes = Math.max(
|
|
70
|
+
0,
|
|
71
|
+
options.maxStartMinutes === undefined ? 30 : Number(options.maxStartMinutes) || 0,
|
|
72
|
+
);
|
|
73
|
+
const threshold = requiredPower / safetyFactor;
|
|
74
|
+
const maxStart = now + maxStartMinutes * 60000;
|
|
75
|
+
const fallbackStep = 15 * 60000;
|
|
76
|
+
|
|
77
|
+
for (let start = 0; start < points.length; start++) {
|
|
78
|
+
if (points[start].timestamp > maxStart || points[start].surplus < threshold) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
let end = start;
|
|
82
|
+
let minimum = points[start].surplus;
|
|
83
|
+
let sum = 0;
|
|
84
|
+
while (end < points.length && points[end].surplus >= threshold) {
|
|
85
|
+
if (end > start) {
|
|
86
|
+
const gap = points[end].timestamp - points[end - 1].timestamp;
|
|
87
|
+
if (gap > 90 * 60000) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
minimum = Math.min(minimum, points[end].surplus);
|
|
92
|
+
sum += points[end].surplus;
|
|
93
|
+
end++;
|
|
94
|
+
}
|
|
95
|
+
const step = end - start > 1 ? points[start + 1].timestamp - points[start].timestamp : fallbackStep;
|
|
96
|
+
const durationMinutes = Math.round((points[end - 1].timestamp - points[start].timestamp + step) / 60000);
|
|
97
|
+
if (durationMinutes >= minDurationMinutes) {
|
|
98
|
+
return {
|
|
99
|
+
available: true,
|
|
100
|
+
startMinutes: Math.max(0, Math.round((points[start].timestamp - now) / 60000)),
|
|
101
|
+
durationMinutes,
|
|
102
|
+
minimumSurplus: minimum,
|
|
103
|
+
averageSurplus: Math.round(sum / (end - start)),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { available: false, startMinutes: null, durationMinutes: 0, minimumSurplus: 0, averageSurplus: 0 };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check whether a minimum on/off period has elapsed.
|
|
112
|
+
*
|
|
113
|
+
* @param {number} lastChange Timestamp of the last state change.
|
|
114
|
+
* @param {number} minimumMinutes Required state duration in minutes.
|
|
115
|
+
* @param {number} now Current timestamp.
|
|
116
|
+
* @returns {boolean} Whether a new state change is allowed.
|
|
117
|
+
*/
|
|
118
|
+
function minimumStateTimeSatisfied(lastChange, minimumMinutes, now = Date.now()) {
|
|
119
|
+
const minimumMs = Math.max(0, Number(minimumMinutes) || 0) * 60000;
|
|
120
|
+
return !lastChange || now - lastChange >= minimumMs;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { evaluateForecast, minimumStateTimeSatisfied, parseForecast, powerFactor };
|