@impact0815/node-red-contrib-alphaess-modbus 0.0.0-stage → 0.4.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/CHANGELOG.md +98 -0
- package/LICENSE +21 -0
- package/README.de.md +422 -0
- package/README.md +427 -2
- package/alphaess-modbus.html +256 -0
- package/alphaess-modbus.js +399 -0
- package/examples/alphaess-modbus-example.json +420 -0
- package/lib/commands.js +145 -0
- package/lib/context-store.js +30 -0
- package/lib/derived.js +163 -0
- package/lib/i18n.js +57 -0
- package/lib/modbus-tcp.js +204 -0
- package/lib/mqtt.js +80 -0
- package/lib/registers.js +434 -0
- package/locales/de/alphaess-modbus.html +317 -0
- package/locales/de/alphaess-modbus.json +106 -0
- package/locales/en-US/alphaess-modbus.html +315 -0
- package/locales/en-US/alphaess-modbus.json +106 -0
- package/package.json +54 -5
package/README.md
CHANGED
|
@@ -1,3 +1,428 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @impact0815/node-red-contrib-alphaess-modbus
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Deutsche Version → README.de.md](https://github.com/impact0815/node-red-contrib-alphaess-modbus/blob/main/README.de.md)
|
|
4
|
+
|
|
5
|
+
Local access to **Alpha ESS** storage systems (SMILE series, Storion) via **Modbus TCP**, without the cloud.
|
|
6
|
+
|
|
7
|
+
- Realtime data of grid meter, PV meter, battery, inverter and system
|
|
8
|
+
- Derived values: house consumption, autarky, self-consumption rate
|
|
9
|
+
- Daily energy since local midnight, plus the values of the previous day
|
|
10
|
+
- Alarms and warnings as plain text, with an output that only fires on change
|
|
11
|
+
- Detection of stale data per register block
|
|
12
|
+
- Direct MQTT publishing through an existing Node-RED MQTT broker config, no extra MQTT node needed
|
|
13
|
+
- Optional control, **disabled by default**: dispatch (charge/discharge), feed-in limit, charge/discharge time periods
|
|
14
|
+
- Careful writing: unchanged values are not written, only changed registers are written, optional minimum interval between writes
|
|
15
|
+
- Works with current and older EMS firmware (automatic fallback to the older register list)
|
|
16
|
+
- Editor and help in **English and German**
|
|
17
|
+
- No runtime dependencies
|
|
18
|
+
|
|
19
|
+
Register addresses and scaling are based on the *AlphaESS Household Modbus Register Parameter List*
|
|
20
|
+
(successor of *Register Parameter List V1.1*).
|
|
21
|
+
|
|
22
|
+
## Disclaimer
|
|
23
|
+
|
|
24
|
+
> **Use at your own risk. No warranty.**
|
|
25
|
+
|
|
26
|
+
- This is an independent community project. It is **not affiliated with, endorsed or supported by Alpha ESS**.
|
|
27
|
+
"Alpha ESS", "SMILE" and "Storion" are used only to describe compatibility; trademarks belong to their respective owners.
|
|
28
|
+
- The software is provided **"as is", without warranty of any kind**, see [LICENSE](LICENSE) (MIT).
|
|
29
|
+
The authors are not liable for any damage resulting from its use, to the extent permitted by law.
|
|
30
|
+
- **Reading** data does not change the system. **Write commands** (dispatch, feed-in limit, time periods) change how the system
|
|
31
|
+
charges, discharges and feeds into the grid. Wrong values can lead to unwanted grid import, a too deep or too shallow discharge,
|
|
32
|
+
a missing backup reserve, or a feed-in that violates the rules of your grid operator, and may affect the manufacturer warranty.
|
|
33
|
+
- Write access is disabled by default. Enable it only if you understand the effect of each command; start with short durations
|
|
34
|
+
and check the result in the manufacturer app.
|
|
35
|
+
- Register information is based on the manufacturer documentation and on tests with individual systems.
|
|
36
|
+
Your model or firmware may behave differently.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Via *Manage palette* in the Node-RED editor (search for `alphaess-modbus`), or in your Node-RED user directory (usually `~/.node-red`):
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
npm install @impact0815/node-red-contrib-alphaess-modbus
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then restart Node-RED.
|
|
47
|
+
|
|
48
|
+
Requirements: Node-RED 3.0 or later, Node.js 18 or later, and Modbus TCP enabled on the Alpha ESS system.
|
|
49
|
+
|
|
50
|
+
### Docker
|
|
51
|
+
|
|
52
|
+
With the official `nodered/node-red` image, install into `/data` so the package survives container updates:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
docker exec -it node-red bash -c "cd /data && npm install @impact0815/node-red-contrib-alphaess-modbus"
|
|
56
|
+
docker restart node-red
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
After installing or updating, reload the editor in the browser (F5); otherwise new outputs and settings are not shown.
|
|
60
|
+
|
|
61
|
+
### Upgrading from the unscoped package (versions before 0.4.0)
|
|
62
|
+
|
|
63
|
+
Up to 0.3.x the package was installed as `node-red-contrib-alphaess-modbus` (from a local file).
|
|
64
|
+
Both packages provide the same node types, so remove the old one first. Your flows and settings are kept:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
cd ~/.node-red # Docker: docker exec -it node-red bash -c "cd /data && ..."
|
|
68
|
+
npm uninstall node-red-contrib-alphaess-modbus
|
|
69
|
+
npm install @impact0815/node-red-contrib-alphaess-modbus
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
1. Add an **AlphaESS Modbus** node and create a connection with the IP address of the system (port 502, unit ID 85).
|
|
75
|
+
2. Keep the default blocks and the interval of 15 s. Connect a debug node to output 1 and deploy.
|
|
76
|
+
3. After a few seconds the status shows `PV … | Grid … | SOC … | Load …`.
|
|
77
|
+
4. Optional: set up a persistent context store for the [daily values](#daily-values) and select an [MQTT](#mqtt) broker.
|
|
78
|
+
|
|
79
|
+
If the status shows `ECONNREFUSED`, see [Troubleshooting](#troubleshooting).
|
|
80
|
+
|
|
81
|
+
## Languages
|
|
82
|
+
|
|
83
|
+
| Part | Language |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Editor (labels, hints) and help in the sidebar | English or German, following the language setting of the editor (*User settings → Language*, default: browser language) |
|
|
86
|
+
| Status texts under the node, log messages, error messages | language of the Node-RED server |
|
|
87
|
+
| Data in `msg.payload` (field names, alarm and warning texts) and MQTT topics | always English, so flows work independently of the language |
|
|
88
|
+
|
|
89
|
+
Other languages can be added under `locales/`, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
### Connection (`alphaess-modbus-config`)
|
|
94
|
+
|
|
95
|
+
| Setting | Default | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Host | – | IP address of the system |
|
|
98
|
+
| Port | 502 | Modbus TCP port |
|
|
99
|
+
| Unit ID | 85 | Modbus slave address (0x55) |
|
|
100
|
+
| Timeout | 2000 ms | per request |
|
|
101
|
+
| Delay | 20 ms | pause between requests |
|
|
102
|
+
|
|
103
|
+
All nodes that use the same connection share one TCP connection, and requests are sent one after another.
|
|
104
|
+
The EMS usually accepts only one Modbus TCP connection at a time. Other Modbus clients polling the same system
|
|
105
|
+
(including unused `modbus-client` config nodes of other packages) lead to `ECONNREFUSED`.
|
|
106
|
+
|
|
107
|
+
### Node (`alphaess-modbus`)
|
|
108
|
+
|
|
109
|
+
| Setting | Default | Description |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| Interval | 15 s | polling interval, `0` = only on input |
|
|
112
|
+
| Slow blocks | 300 s | interval for system config, time periods and dispatch state |
|
|
113
|
+
| Stale after | 180 s | a block counts as stale after this time without a successful read |
|
|
114
|
+
| Read blocks | – | select the register blocks to read |
|
|
115
|
+
| EMS | EMS 3.5/3.6 | selects the bit tables for battery faults and warnings |
|
|
116
|
+
| Add PV meter | off | adds an AC-coupled PV inverter (PV meter) to PV power and daily PV energy, see [PV meter](#pv-meter-ac-coupled-systems) |
|
|
117
|
+
| Daily store | default | context store for daily values, selected from the stores configured in `settings.js`; use a persistent store (e.g. `file`) to keep them across restarts, see [Daily values](#daily-values) |
|
|
118
|
+
| MQTT broker | – | optional, see [MQTT](#mqtt) |
|
|
119
|
+
| Allow write access | off | required for any control command; shows a warning in the editor and logs a notice at start |
|
|
120
|
+
| Max. power | – | optional upper limit for dispatch power in W |
|
|
121
|
+
| Min. interval | 10 s | minimum time between two actual writes of the same command, `0` = off |
|
|
122
|
+
| SOC scale | 0.1 | %/bit for the SOC values of the time period registers, see [Check the SOC scale](#check-the-soc-scale) |
|
|
123
|
+
|
|
124
|
+
### PV meter (AC-coupled systems)
|
|
125
|
+
|
|
126
|
+
The block *PV meter* reads a second meter that only exists if an additional, external PV inverter feeds into the house grid
|
|
127
|
+
(AC-coupled system). On DC and most hybrid systems all modules are connected to the Alpha ESS inverter and are already included
|
|
128
|
+
in the *Inverter* block. The block is therefore off by default: on systems without PV meter it would only return errors
|
|
129
|
+
(and a permanent *stale data* alarm) or zeros.
|
|
130
|
+
|
|
131
|
+
Check `payload.details.systemConfig`:
|
|
132
|
+
|
|
133
|
+
| Field | PV meter likely | No PV meter |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `systemMode.text` | `AC` or `Hybrid` | `DC` |
|
|
136
|
+
| `pvCapacityGridInverter` | > 0 | 0 |
|
|
137
|
+
| `meterCtSelect.text` | contains `PV meter` | contains `PV CT` |
|
|
138
|
+
|
|
139
|
+
If you have one, enable the block *PV meter* and *Add PV meter to PV power / daily PV energy*.
|
|
140
|
+
The daily PV energy of the external inverter comes from the block *System running data*, which must stay enabled.
|
|
141
|
+
|
|
142
|
+
## Outputs
|
|
143
|
+
|
|
144
|
+
### Output 1 – data
|
|
145
|
+
|
|
146
|
+
One message per poll cycle and on `read`. The summary keys are the same as in the cloud node
|
|
147
|
+
[node-red-contrib-alphaess](https://github.com/dehsgr/node-red-contrib-alphaess), so flows can switch between cloud and local access.
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"consumption": 980, "grid": -720, "modules": 4000, "battery": -2300, "soc": 87.3,
|
|
152
|
+
"gridImport": 0, "gridExport": 720, "batteryCharge": 2300, "batteryDischarge": 0,
|
|
153
|
+
"autarky": 100, "selfConsumptionRate": 82,
|
|
154
|
+
"daily": {
|
|
155
|
+
"day": "2026-09-27", "since": "2026-09-27T00:00:05.000Z", "complete": true,
|
|
156
|
+
"pv": 12.4, "gridFeed": 4.1, "gridImport": 1.2, "batteryCharge": 5.0, "batteryDischarge": 3.3,
|
|
157
|
+
"batteryChargeFromGrid": 0, "consumption": 7.8, "selfConsumptionRate": 66.9, "autarky": 84.6
|
|
158
|
+
},
|
|
159
|
+
"yesterday": { "...": "same structure" },
|
|
160
|
+
"alarms": [],
|
|
161
|
+
"warnings": ["Battery: Temperature imbalance"],
|
|
162
|
+
"blocks": { "grid": { "age": 0, "stale": false }, "inverter": { "age": 0, "stale": false } },
|
|
163
|
+
"details": { "grid": {}, "battery": {}, "inverter": {}, "systemRun": {}, "systemConfig": {}, "timePeriod": {}, "dispatch": {} },
|
|
164
|
+
"info": {
|
|
165
|
+
"inverterInfo": { "serialNumber": "...", "armSoftwareVersion": "..." },
|
|
166
|
+
"systemInfo": { "emsSerialNumber": "...", "emsVersion": {}, "wifiSerialNumber": "..." },
|
|
167
|
+
"batteryInfo": { "serialNumbers": ["..."] }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Sign conventions:
|
|
173
|
+
|
|
174
|
+
- `grid`: + = import, - = export
|
|
175
|
+
- `battery`: + = discharge, - = charge
|
|
176
|
+
- `consumption = modules + grid + battery`
|
|
177
|
+
|
|
178
|
+
Power values are in W and energy values in kWh.
|
|
179
|
+
|
|
180
|
+
If a block cannot be read, its last value is used until it becomes stale. After that it is removed from `details`, and the derived values that depend on it are left out.
|
|
181
|
+
The read errors of the current cycle are in `msg.errors`.
|
|
182
|
+
|
|
183
|
+
Values that only newer firmware provides are left out on older systems:
|
|
184
|
+
|
|
185
|
+
| Field | Description |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `details.grid.energyConsumeFromGridPhase`, `energyFeedToGridPhase` | lifetime energy per phase L1–L3 in kWh |
|
|
188
|
+
| `details.inverter.pvPowerTotalRegister` | PV total power as reported by the inverter; `pvPowerTotal` (sum of PV1–PV6) is always present and used for `modules` |
|
|
189
|
+
| `details.dispatch.pvSwitch` | PV switch of the dispatch (Note 29) |
|
|
190
|
+
| `info.inverterInfo.armSoftwareVersion`, `info.systemInfo.wifiSerialNumber`, `info.batteryInfo.serialNumbers` | device information |
|
|
191
|
+
|
|
192
|
+
### Output 2 – alarm
|
|
193
|
+
|
|
194
|
+
This output is only sent when alarms or warnings change, and once after start:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{ "active": true, "alarms": ["System: Grid_Meter_Lost"], "warnings": [], "timestamp": "..." }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Alarms include:
|
|
201
|
+
|
|
202
|
+
- system faults
|
|
203
|
+
- battery faults
|
|
204
|
+
- inverter faults (fault1/2 and fault extend)
|
|
205
|
+
- inverter work mode *Fault*
|
|
206
|
+
- stale blocks
|
|
207
|
+
|
|
208
|
+
Warnings include battery and inverter warnings (as text), and a missing context store for the daily values.
|
|
209
|
+
|
|
210
|
+
### Output 3 – command response
|
|
211
|
+
|
|
212
|
+
The answer to every input command except `read`: `readInfo`, `readRaw` and all write commands.
|
|
213
|
+
Data messages on output 1 therefore never contain command responses.
|
|
214
|
+
|
|
215
|
+
| Property | Description |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `payload` | the affected block, read back after the command (raw registers for `readRaw`) |
|
|
218
|
+
| `written` | written frames `[{ "address": "0x0850", "values": [200] }]`, empty if nothing was written |
|
|
219
|
+
| `unchanged` | `true` if `feedIn` / `timePeriod` already had the requested values |
|
|
220
|
+
|
|
221
|
+
All other properties of the input message are kept, so the response can be matched to the request.
|
|
222
|
+
Errors (invalid values, write protection, minimum interval) are reported as node errors and can be handled with a *catch* node.
|
|
223
|
+
|
|
224
|
+
## Daily values
|
|
225
|
+
|
|
226
|
+
The Alpha ESS system only provides lifetime counters via Modbus. Daily values are therefore calculated as the difference
|
|
227
|
+
to the counter values at local midnight (server time zone). These midnight values are kept in the node context.
|
|
228
|
+
|
|
229
|
+
- `daily.since` shows when counting started.
|
|
230
|
+
- `daily.complete` is `true` if counting started within 15 minutes after midnight, i.e. the values cover the whole day.
|
|
231
|
+
It is `false` if Node-RED was not running at midnight, or after a restart during the day without a persistent store.
|
|
232
|
+
- If a counter is reset during the day, the value reached so far is kept.
|
|
233
|
+
|
|
234
|
+
To keep the daily values across restarts, configure a persistent context store in `settings.js` and select it
|
|
235
|
+
(here `file`) as *Daily store* in the node. After changing `settings.js`, restart Node-RED and reload the editor, then the store appears in the drop-down:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
contextStorage: {
|
|
239
|
+
default: { module: "memory" },
|
|
240
|
+
file: { module: "localfilesystem" }
|
|
241
|
+
},
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
If the selected store does not exist, Node-RED silently uses the default store instead. The node detects this,
|
|
245
|
+
logs a warning at start, shows a yellow status and adds a warning to `payload.warnings`.
|
|
246
|
+
|
|
247
|
+
## MQTT
|
|
248
|
+
|
|
249
|
+
Select an existing MQTT broker configuration in the node. The node then publishes directly, without an MQTT out node:
|
|
250
|
+
|
|
251
|
+
| Topic | Payload |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `<prefix>/consumption`, `/grid`, `/modules`, `/battery`, `/soc` | number |
|
|
254
|
+
| `<prefix>/gridImport`, `/gridExport`, `/batteryCharge`, `/batteryDischarge`, `/autarky`, `/selfConsumptionRate` | number |
|
|
255
|
+
| `<prefix>/daily/<key>` | number (kWh or %) |
|
|
256
|
+
| `<prefix>/daily/complete` | `true` or `false` |
|
|
257
|
+
| `<prefix>/status` | `ok` or `alarm` |
|
|
258
|
+
| `<prefix>/alarm` | JSON, only on change |
|
|
259
|
+
| `<prefix>/info` | JSON, always retained |
|
|
260
|
+
| `<prefix>/details/<block>` | JSON, optional |
|
|
261
|
+
|
|
262
|
+
The default prefix is `alphaess`. QoS and retain can be configured.
|
|
263
|
+
The node uses the connection of the Node-RED core `mqtt-broker` config node, which therefore must not be disabled in `settings.js`.
|
|
264
|
+
|
|
265
|
+
## Input commands (`msg.topic`)
|
|
266
|
+
|
|
267
|
+
| Topic | Payload | Description |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| `read` or empty | – | read all enabled blocks now, including slow blocks |
|
|
270
|
+
| `readInfo` | – | re-read device information |
|
|
271
|
+
| `readRaw` | `{"address":1024,"count":10}` | raw holding registers, read only |
|
|
272
|
+
| `dispatch` | `{"power":-3000,"soc":90,"duration":900,"mode":2}` | start dispatch |
|
|
273
|
+
| `dispatchStop` | – | stop dispatch |
|
|
274
|
+
| `feedIn` | `70` | max. feed-in in % |
|
|
275
|
+
| `timePeriod` | `{"flag":1,"chargeCutSoc":90,"upsReserveSoc":10,"charge1":{"start":"01:00","stop":"05:00"}}` | charge/discharge time periods |
|
|
276
|
+
|
|
277
|
+
The write commands require **Allow write access** – read the [Disclaimer](#disclaimer) first. The response is sent on output 3.
|
|
278
|
+
|
|
279
|
+
`dispatch`:
|
|
280
|
+
|
|
281
|
+
- `power` in W: negative = charge, positive = discharge.
|
|
282
|
+
- `soc`: target in %. Default is 100 when charging and 10 when discharging.
|
|
283
|
+
- `duration` in seconds, default 300. After this time the system returns to normal operation.
|
|
284
|
+
- `mode`: default 2 = *State of Charge control*. Allowed: 1–10 and 19 (*No Battery Charge*).
|
|
285
|
+
Test and off-grid modes of the register list (BurnIn, OSW modes) are rejected.
|
|
286
|
+
|
|
287
|
+
`feedIn`: a feed-in limit may be required by your grid operator – only change it if you are allowed to.
|
|
288
|
+
|
|
289
|
+
`timePeriod` changes only the given fields.
|
|
290
|
+
`flag`: 0 = off, 1 = charge, 2 = discharge, 3 = both.
|
|
291
|
+
Before the first write, compare the SOC values that are read back with the settings in the app. If they do not match, adjust *SOC scale*.
|
|
292
|
+
|
|
293
|
+
Lowering `upsReserveSoc` takes effect immediately: the battery may discharge down to the new value right away.
|
|
294
|
+
If that should only happen at a certain time (e.g. the night before a sunny day), send the command at that time.
|
|
295
|
+
|
|
296
|
+
### Check the SOC scale
|
|
297
|
+
|
|
298
|
+
The documentation specifies 0.1 %/bit for `upsReserveSoc` and `chargeCutSoc`, but systems exist that use 1 %/bit.
|
|
299
|
+
Check before the first `timePeriod` write:
|
|
300
|
+
|
|
301
|
+
1. Send `{"topic": "readRaw", "payload": {"address": 2128, "count": 1}}` (register 0x0850, UPS reserve SOC).
|
|
302
|
+
2. Compare the raw value on output 3 with the reserve set in the app. Reserve 10 % and raw value `10` → set *SOC scale* to `1`.
|
|
303
|
+
Raw value `100` → keep `0.1`.
|
|
304
|
+
3. Afterwards `details.timePeriod.upsReserveSoc` must show the same value as the app.
|
|
305
|
+
|
|
306
|
+
### How writing works
|
|
307
|
+
|
|
308
|
+
- **Unchanged values are not written.** `feedIn` and `timePeriod` read the current registers first.
|
|
309
|
+
If the requested values are already set, nothing is written and `msg.unchanged` is `true`.
|
|
310
|
+
A flow can therefore send the same setting repeatedly without wearing out the EMS memory.
|
|
311
|
+
- **Only changed registers are written.** Each contiguous run of changed registers is written with its own request.
|
|
312
|
+
Settings that were changed in the app between reading and writing are not overwritten.
|
|
313
|
+
Hour and minute of a time slot are separate registers, so changing a time can take two requests.
|
|
314
|
+
- **Minimum interval.** A second actual write of the same command within *Min. interval* fails with an error.
|
|
315
|
+
This protects against loops in a flow. Unchanged requests do not count, and `dispatchStop` is never blocked.
|
|
316
|
+
- `dispatch` and `dispatchStop` are commands, not settings, and are always written.
|
|
317
|
+
|
|
318
|
+
The following registers are intentionally **not** writable: safety test, reset/ATE mode, CT calibration, network and Modbus settings,
|
|
319
|
+
battery MOS control, SOC calibration, and the dispatch test parameters (0x0889/0x088A).
|
|
320
|
+
|
|
321
|
+
## Examples
|
|
322
|
+
|
|
323
|
+
### Send commands from other tabs with feedback
|
|
324
|
+
|
|
325
|
+
Use a *link call* node in the sending flow and a *link in* node in front of the AlphaESS node.
|
|
326
|
+
To return the response, connect **output 3** to a *switch* on `msg._linkSource` with the rule *is not empty*,
|
|
327
|
+
followed by a *link out* in mode *Return to calling link node*:
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
[link in] → [AlphaESS Modbus] ─ output 3 → [switch: msg._linkSource is not empty] → [link out: return]
|
|
331
|
+
└ otherwise → [debug]
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Responses of commands that were not sent via link call (e.g. from an inject node) have no return address and go to the *otherwise* output.
|
|
335
|
+
Enable *Allow write access* in the node.
|
|
336
|
+
|
|
337
|
+
### Reserve SOC depending on the weather forecast
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
// function node in front of the link call
|
|
341
|
+
const sun = Number(global.get("sunhourstomorrow"));
|
|
342
|
+
if (!Number.isFinite(sun) || sun < 0) return null;
|
|
343
|
+
return { topic: "timePeriod", payload: { upsReserveSoc: sun >= 3 ? 10 : 20 } };
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The command can be sent as often as needed: if the value is already set, nothing is written and the response has `unchanged: true`.
|
|
347
|
+
|
|
348
|
+
### Use the values in other flows
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
// function node behind output 1
|
|
352
|
+
global.set("alphaess", msg.payload);
|
|
353
|
+
return { payload: msg.payload.consumption };
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Other tabs can then read e.g. `global.get("alphaess").details.timePeriod.upsReserveSoc`.
|
|
357
|
+
|
|
358
|
+
### Ignore incomplete days in statistics
|
|
359
|
+
|
|
360
|
+
```js
|
|
361
|
+
// function node behind output 1, once per day
|
|
362
|
+
const y = msg.payload.yesterday;
|
|
363
|
+
if (!y || !y.complete) return null;
|
|
364
|
+
return { payload: y };
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Charge from the grid at night for 15 minutes
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{ "topic": "dispatch", "payload": { "power": -3000, "soc": 90, "duration": 900 } }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Keep `duration` short and repeat the command if needed. If the flow stops, the system returns to normal operation by itself.
|
|
374
|
+
|
|
375
|
+
## Troubleshooting
|
|
376
|
+
|
|
377
|
+
| Symptom | Cause and solution |
|
|
378
|
+
|---|---|
|
|
379
|
+
| `ECONNREFUSED` for all blocks | Another Modbus client is connected; the EMS accepts only one connection. Check for old `modbus-getter`/`modbus-write` nodes, an **unused `modbus-client` config node** (delete it under *Configuration nodes* and deploy with *Full*), and other programs (Home Assistant, ioBroker, evcc). Otherwise check that Modbus TCP is enabled or restart the EMS. |
|
|
380
|
+
| `Timeout` / `Connect timeout` | Wrong IP, network or Docker network problem, slow connection. Test with `nc -zv <ip> 502`; increase *Timeout* and *Delay*. |
|
|
381
|
+
| `Modbus exception 2` for one block | The system does not support this block (typically *PV meter*). Disable the block. Extended blocks of the newer register list fall back to the older length automatically; the log shows `extended registers not supported`. |
|
|
382
|
+
| Permanent alarm `Stale data: <block>` | The block cannot be read; see `payload.blocks.<block>.error`. |
|
|
383
|
+
| Daily values restart after every restart, `daily.complete` is `false` | No persistent context store. Configure one in `settings.js` and select it as *Daily store* (yellow status `store "…" missing` if the selected store does not exist). |
|
|
384
|
+
| Time period SOC shows 1 instead of 10 | Wrong *SOC scale*, see [Check the SOC scale](#check-the-soc-scale). |
|
|
385
|
+
| `Writing is disabled` | Enable *Allow write access*. |
|
|
386
|
+
| `write blocked, next write possible in … s` | *Min. interval* protection. Wait, or check the flow for loops. |
|
|
387
|
+
| New outputs/settings not visible after an update | Restart Node-RED and reload the editor (F5). |
|
|
388
|
+
| Node types registered twice / install conflict after upgrade to 0.4.0 | The old unscoped package is still installed, see [Upgrading](#upgrading-from-the-unscoped-package-versions-before-040). |
|
|
389
|
+
| Editor in English although German is expected | Set *User settings → Language* in the editor to *Deutsch* or the browser language to German. |
|
|
390
|
+
| Link call runs into a timeout | Output 3 is not connected to the *link out* in return mode, see [Examples](#send-commands-from-other-tabs-with-feedback). |
|
|
391
|
+
|
|
392
|
+
## Register blocks
|
|
393
|
+
|
|
394
|
+
| Block | Start | Count (older firmware) | Read |
|
|
395
|
+
|---|---|---|---|
|
|
396
|
+
| grid | 0x0010 | 51 (39) | every interval |
|
|
397
|
+
| pvMeter | 0x0090 | 39 | every interval |
|
|
398
|
+
| battery | 0x0100 | 73 | every interval |
|
|
399
|
+
| inverter | 0x0400 | 85 (83) | every interval |
|
|
400
|
+
| systemRun | 0x08D0 | 6 | every interval |
|
|
401
|
+
| systemConfig | 0x0800 | 18 | slow interval |
|
|
402
|
+
| timePeriod | 0x084F | 19 | slow interval |
|
|
403
|
+
| dispatch | 0x0880 | 11 (9) | slow interval |
|
|
404
|
+
| inverterInfo | 0x0640 | 25 (20) | at start, then daily |
|
|
405
|
+
| systemInfo | 0x0740 | 25 (15) | at start, then daily |
|
|
406
|
+
| batteryInfo | 0x0150 | 30 (–) | at start, then daily |
|
|
407
|
+
|
|
408
|
+
Blocks with two counts are read with the longer length first. If the system rejects it (Modbus exception 2 or 3),
|
|
409
|
+
the node reads the older length from then on. `payload.blocks.<block>.registers` shows the reduced length.
|
|
410
|
+
The battery serial numbers are skipped if the system does not provide them.
|
|
411
|
+
|
|
412
|
+
Not supported: the *Byte Watt* inverter block (0x0500), the HHE MEC system block (0x06FA–0x072B), Echonet (Japan),
|
|
413
|
+
frequency dispatch, AUX, generator and PV changer blocks.
|
|
414
|
+
|
|
415
|
+
## Development
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
npm test # unit and integration tests against the built-in simulator
|
|
419
|
+
npm run simulator # Modbus TCP simulator on port 5020
|
|
420
|
+
node test/mock-server.js 5020 --legacy # simulator of an older firmware (register list V1.1)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
For a manual test without a real system, start the simulator and set the connection to `127.0.0.1:5020`.
|
|
424
|
+
Contributions and translations: see [CONTRIBUTING.md](CONTRIBUTING.md). Issues: https://github.com/impact0815/node-red-contrib-alphaess-modbus/issues
|
|
425
|
+
|
|
426
|
+
## License
|
|
427
|
+
|
|
428
|
+
MIT – see [LICENSE](LICENSE). Provided without warranty, see [Disclaimer](#disclaimer).
|