node-red-contrib-velbus-2026 0.9.0 → 0.9.1
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_FORUM.md +39 -0
- package/HANDOVER.md +906 -0
- package/README.md +5 -2
- package/nodes/velbus-blind/velbus-blind.html +1 -1
- package/nodes/velbus-blind-20/velbus-blind-20.html +1 -1
- package/nodes/velbus-blind-s/velbus-blind-s.html +1 -1
- package/nodes/velbus-button/velbus-button.html +1 -1
- package/nodes/velbus-energy/velbus-energy.html +1 -1
- package/nodes/velbus-glass-panel/velbus-glass-panel.html +1 -1
- package/nodes/velbus-meteo/velbus-meteo.html +1 -1
- package/nodes/velbus-pir/velbus-pir.html +1 -1
- package/nodes/velbus-pir-20/velbus-pir-20.html +1 -1
- package/nodes/velbus-sensor/velbus-sensor.html +1 -1
- package/nodes/velbus-sensor-20/velbus-sensor-20.html +1 -1
- package/nodes/velbus-thermostat/velbus-thermostat.html +1 -1
- package/package.json +1 -1
- package/velbus-2026-repo.bundle +0 -0
package/CHANGELOG_FORUM.md
CHANGED
|
@@ -14,6 +14,45 @@ All feedback via GitHub issues, with examples and debug captures where possible.
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
## v0.9.1 — 08/07/2026
|
|
18
|
+
|
|
19
|
+
### Address validation bug — 12 of 19 node types, any address ≥100 decimal
|
|
20
|
+
|
|
21
|
+
- **Reported by Stuart:** adding a velbus-sensor node and selecting VMB4AN
|
|
22
|
+
from the scan dropdown showed a red "not configured correctly" triangle
|
|
23
|
+
no matter what was selected.
|
|
24
|
+
- **Root cause:** every node using the `address` field (as opposed to
|
|
25
|
+
`moduleAddr`, used by relay/dimmer nodes, which were unaffected) validated
|
|
26
|
+
it against `/^(0x)?[0-9a-fA-F]{1,2}$/` — a pattern that only ever made
|
|
27
|
+
sense if `address` were stored as a 1-2 character hex string. But
|
|
28
|
+
`oneditsave` on every one of these nodes stores it as a plain decimal
|
|
29
|
+
**number** instead (`parseInt(...)`). Since regex `.test()` coerces its
|
|
30
|
+
argument to a string first, any address whose decimal representation is
|
|
31
|
+
1-2 digits (1-99) happened to accidentally still match — every digit
|
|
32
|
+
0-9 is also a valid hex character — masking the bug for low addresses.
|
|
33
|
+
Any address **100 or higher** (`0x64`+) produces a 3-digit decimal string,
|
|
34
|
+
which cannot match a `{1,2}`-length pattern, so validation always failed.
|
|
35
|
+
100-254 is a very ordinary real-world address range — this was a live,
|
|
36
|
+
fully user-facing bug, not an edge case.
|
|
37
|
+
- **Affected nodes (12):** velbus-blind, velbus-blind-s, velbus-blind-20,
|
|
38
|
+
velbus-button, velbus-energy, velbus-glass-panel, velbus-meteo,
|
|
39
|
+
velbus-pir, velbus-pir-20, velbus-sensor, velbus-sensor-20,
|
|
40
|
+
velbus-thermostat. Confirmed relay/dimmer/relay-20/dimmer-20 (which use
|
|
41
|
+
`moduleAddr`, no custom validate function) and velbus-clock (no fixed
|
|
42
|
+
address at all) were never affected.
|
|
43
|
+
- **Fix:** replaced the regex with a direct numeric range check
|
|
44
|
+
(`parseInt(v)` between 1 and 254 inclusive) — correct regardless of
|
|
45
|
+
whether the stored value is a number, a decimal string, or a legacy hex
|
|
46
|
+
string (`parseInt` auto-detects a `0x` prefix), so old saved flows keep
|
|
47
|
+
working unchanged.
|
|
48
|
+
- Verified: syntax-checked the extracted JS from all 12 files, and
|
|
49
|
+
confirmed the corrected logic against the actual failing range (50/80/99
|
|
50
|
+
→ true both before and after; 100/150/254 → **false before, true after**;
|
|
51
|
+
0/255/empty/non-numeric → false, correctly rejected as invalid addresses
|
|
52
|
+
both before and after).
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
17
56
|
## v0.9.0 — 08/07/2026
|
|
18
57
|
|
|
19
58
|
### velbus-energy — new node (19th node), VMBPSUMNGR-20
|
package/HANDOVER.md
ADDED
|
@@ -0,0 +1,906 @@
|
|
|
1
|
+
# HANDOVER.md — node-red-contrib-velbus-2026
|
|
2
|
+
|
|
3
|
+
**A complete technical reference for developing this Node-RED palette, written to assume
|
|
4
|
+
no prior context.** If you are picking this project up for the first time — whether
|
|
5
|
+
you're a new contributor, a new maintainer, or an AI assistant starting a fresh session
|
|
6
|
+
with no memory of previous work — this document should be sufficient on its own, together
|
|
7
|
+
with the source code in this repository, to continue development competently.
|
|
8
|
+
|
|
9
|
+
Current state at time of writing: **v0.9.0, 19 nodes, published on npm.**
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of contents
|
|
14
|
+
|
|
15
|
+
1. [What this project is](#1-what-this-project-is)
|
|
16
|
+
2. [Quick start](#2-quick-start)
|
|
17
|
+
3. [Repository structure](#3-repository-structure)
|
|
18
|
+
4. [Architecture overview](#4-architecture-overview)
|
|
19
|
+
5. [Complete node reference](#5-complete-node-reference)
|
|
20
|
+
6. [Complete module type registry](#6-complete-module-type-registry)
|
|
21
|
+
7. [Critical protocol knowledge](#7-critical-protocol-knowledge)
|
|
22
|
+
8. [Payload conventions](#8-payload-conventions)
|
|
23
|
+
9. [Architecture decisions (settled — do not reopen casually)](#9-architecture-decisions-settled--do-not-reopen-casually)
|
|
24
|
+
10. [Development environment setup](#10-development-environment-setup)
|
|
25
|
+
11. [Testing without real hardware — the mock-harness pattern](#11-testing-without-real-hardware--the-mock-harness-pattern)
|
|
26
|
+
12. [Testing status — what's actually verified](#12-testing-status--whats-actually-verified)
|
|
27
|
+
13. [Known open issues](#13-known-open-issues)
|
|
28
|
+
14. [Contribution and release workflow](#14-contribution-and-release-workflow)
|
|
29
|
+
15. [Where to find protocol references](#15-where-to-find-protocol-references)
|
|
30
|
+
16. [Code style rules](#16-code-style-rules)
|
|
31
|
+
17. [License and attribution](#17-license-and-attribution)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 1. What this project is
|
|
36
|
+
|
|
37
|
+
**Velbus** is a CAN-bus-based building automation system, manufactured by Velbus Belgium.
|
|
38
|
+
Installations consist of physical modules on a shared bus — relay outputs, dimmers,
|
|
39
|
+
touch-sensitive glass control panels, PIR motion sensors, blind/shutter controllers,
|
|
40
|
+
weather stations, and more — each with a unique bus address. Modules are commissioned
|
|
41
|
+
using Velbus's own **VelbusLink** software, and communicate over the bus using a
|
|
42
|
+
well-documented binary packet protocol (official reference repositories linked in
|
|
43
|
+
[section 15](#15-where-to-find-protocol-references)).
|
|
44
|
+
|
|
45
|
+
**This project** is a Node-RED palette: a set of custom nodes that let a Node-RED flow
|
|
46
|
+
read and write to a live Velbus bus. It connects to the bus via a TCP gateway (either
|
|
47
|
+
Velbus's own `velbus-tcp` snap package, or the open-source `python-velbustcp` project —
|
|
48
|
+
both simply expose the same underlying serial/CAN bus protocol over a TCP socket). Once
|
|
49
|
+
connected, each node in this palette represents one physical module (or, for a couple of
|
|
50
|
+
nodes, a bus-wide function) and translates between Velbus's binary packets and normal
|
|
51
|
+
Node-RED JSON messages.
|
|
52
|
+
|
|
53
|
+
**Two module generations exist** and are handled throughout this codebase as a first-class
|
|
54
|
+
distinction:
|
|
55
|
+
- **Original series** — the older module generation. Simpler protocol, no firmware/CAN FD
|
|
56
|
+
concept.
|
|
57
|
+
- **V2 / "-20" series** — the current module generation (module type names ending `-20`,
|
|
58
|
+
e.g. `VMB4RYLD-20`). Richer protocol, CAN FD capable, firmware version checking.
|
|
59
|
+
|
|
60
|
+
Where a module type exists in both generations, this palette generally provides two
|
|
61
|
+
separate nodes (e.g. `velbus-relay` for original-series relays, `velbus-relay-20` for
|
|
62
|
+
V2 relays) rather than one node trying to handle both — see
|
|
63
|
+
[section 9](#9-architecture-decisions-settled--do-not-reopen-casually) for the reasoning
|
|
64
|
+
and the specific exceptions to this rule.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2. Quick start
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Install into an existing Node-RED instance
|
|
72
|
+
cd ~/.node-red
|
|
73
|
+
npm install node-red-contrib-velbus-2026
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then, in the Node-RED editor:
|
|
77
|
+
1. Drag a **velbus-bridge** config node onto a flow (or create one via any other Velbus
|
|
78
|
+
node's config panel) — set the host/port of your Velbus TCP gateway.
|
|
79
|
+
2. Drag a **velbus-scan** node, wire an inject node into it, deploy, and fire it — this
|
|
80
|
+
performs a full bus scan and populates every other node's address dropdown with the
|
|
81
|
+
modules it finds.
|
|
82
|
+
3. Add nodes for the specific modules on your bus (see [section 5](#5-complete-node-reference)
|
|
83
|
+
for the full list), select their address from the now-populated dropdown, and deploy.
|
|
84
|
+
|
|
85
|
+
No Velbus hardware to hand? See [section 11](#11-testing-without-real-hardware--the-mock-harness-pattern)
|
|
86
|
+
for how development and testing have been done without a live bus.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 3. Repository structure
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
package.json npm package definition — the node-red.nodes map here is how
|
|
94
|
+
Node-RED discovers every node; a new node must be registered here
|
|
95
|
+
index.js Intentionally minimal — Node-RED finds nodes via package.json, not this file
|
|
96
|
+
README.md User-facing installation and usage documentation
|
|
97
|
+
CHANGELOG_FORUM.md Full version-by-version development history — read this before
|
|
98
|
+
assuming something hasn't been tried or fixed already
|
|
99
|
+
LICENSE MIT
|
|
100
|
+
|
|
101
|
+
lib/ Shared code — protocol utilities and per-module-family type registries
|
|
102
|
+
velbus-utils.js chk() checksum, pkt()/rtrPkt() packet builders, parsePkt(),
|
|
103
|
+
splitPackets() — the packet framing primitives every node uses
|
|
104
|
+
relay-types.js Original-series relay module type registry
|
|
105
|
+
relay-types-20.js V2 relay module type registry
|
|
106
|
+
dimmer-types.js Original-series dimmer module type registry
|
|
107
|
+
dimmer-types-20.js V2 dimmer module type registry (incl. LED grouping mode / Device
|
|
108
|
+
Type table for VMB4LEDPWM-20)
|
|
109
|
+
glass-panel-types.js All 26 glass panel module types in one registry (hasOled/hasPir/
|
|
110
|
+
hasOc/minMapVer flags per type)
|
|
111
|
+
pir-types.js / pir-types-20.js PIR sensor module type registries
|
|
112
|
+
sensor-types.js / sensor-types-20.js Sensor/meteo module type registries
|
|
113
|
+
energy-types-20.js Power-supply-manager module type registry (single V2 type,
|
|
114
|
+
VMBPSUMNGR-20)
|
|
115
|
+
blind-types.js / blind-types-s.js / blind-types-20.js Blind/shutter module type registries
|
|
116
|
+
(three files because the blind family splits by protocol
|
|
117
|
+
capability, not simply by generation — see section 9)
|
|
118
|
+
|
|
119
|
+
nodes/ One directory per node, each containing a .js (logic) and
|
|
120
|
+
.html (editor UI + in-editor help) file of the same name
|
|
121
|
+
velbus-bridge/ Config node — the only node that holds the actual TCP connection
|
|
122
|
+
velbus-scan/ Full bus scanner
|
|
123
|
+
velbus-relay/ , velbus-relay-20/
|
|
124
|
+
velbus-dimmer/ , velbus-dimmer-20/
|
|
125
|
+
velbus-glass-panel/
|
|
126
|
+
velbus-thermostat/
|
|
127
|
+
velbus-button/
|
|
128
|
+
velbus-pir/ , velbus-pir-20/
|
|
129
|
+
velbus-meteo/
|
|
130
|
+
velbus-sensor/ , velbus-sensor-20/
|
|
131
|
+
velbus-blind/ , velbus-blind-s/ , velbus-blind-20/
|
|
132
|
+
velbus-clock/ Broadcasts system time/date/DST and clock alarms — the one
|
|
133
|
+
node with no fixed module address, see section 5
|
|
134
|
+
velbus-energy/ VMBPSUMNGR-20 power supply manager — PSU load, alarms,
|
|
135
|
+
warranty counter, per-rail wattage/voltage/amperage
|
|
136
|
+
|
|
137
|
+
examples/
|
|
138
|
+
velbus-basic-relay-dimmer.json A minimal example flow, importable directly in Node-RED
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Convention for adding a new node:** copy the structure of the most similar existing node
|
|
142
|
+
(same module family or same generation) rather than starting from a blank file — the
|
|
143
|
+
boilerplate (bridge lookup, packet registration, status bar handling, firmware check for
|
|
144
|
+
V2 nodes) is consistent across every node and easy to get subtly wrong if rebuilt from
|
|
145
|
+
scratch.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 4. Architecture overview
|
|
150
|
+
|
|
151
|
+
### 4.1 The bridge is the only thing that touches the network
|
|
152
|
+
|
|
153
|
+
`velbus-bridge` is a Node-RED **config node** (shared, not visible as a flow node itself)
|
|
154
|
+
that owns the actual TCP socket to the Velbus gateway. It handles:
|
|
155
|
+
- Connecting (plain TCP or TLS, with optional auth-key handshake for `python-velbustcp`)
|
|
156
|
+
- Auto-reconnect on disconnect (5 second backoff)
|
|
157
|
+
- Splitting the raw TCP byte stream into individual packets (`splitPackets()` in
|
|
158
|
+
`lib/velbus-utils.js` — a stream doesn't respect packet boundaries, so this has to
|
|
159
|
+
track a remainder buffer across multiple `data` events)
|
|
160
|
+
- Dispatching each parsed packet to whichever node(s) have registered interest in that
|
|
161
|
+
module address (`bridge.register(address, callback)` / `bridge.deregister(...)`)
|
|
162
|
+
- A **scan lock** mechanism: while a bus scan is in progress, other nodes' startup RTR
|
|
163
|
+
(request-to-respond) packets are queued rather than sent immediately, and flushed
|
|
164
|
+
one-per-second once the scan completes — prevents startup traffic from colliding with
|
|
165
|
+
an active scan
|
|
166
|
+
- Serving persisted scan results to the editor's config dialogs, via an HTTP endpoint:
|
|
167
|
+
`GET /velbus/scan-results?bridge=<bridge-node-id>` → `{ modules: [...], count: N }`.
|
|
168
|
+
**The endpoint returns an object, not an array — always use `results.modules`, not
|
|
169
|
+
`results` directly.** This has been a real, repeated source of bugs.
|
|
170
|
+
- Rebuilding its subaddress-to-primary-address map from persisted scan results on
|
|
171
|
+
startup, so subaddress routing survives a Node-RED restart without requiring a fresh
|
|
172
|
+
scan every time.
|
|
173
|
+
- A public `isConnected()` method other nodes can check before sending, to give a clear
|
|
174
|
+
"not connected" status rather than a generic dropped-packet warning.
|
|
175
|
+
|
|
176
|
+
Every other node looks up its configured bridge via `RED.nodes.getNode(config.bridge)`,
|
|
177
|
+
registers for its own module address's packets, and sends outgoing packets via
|
|
178
|
+
`node.bridge.send(buffer)`.
|
|
179
|
+
|
|
180
|
+
### 4.2 The packet protocol
|
|
181
|
+
|
|
182
|
+
Every Velbus packet, at the byte level, has this shape:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
0F [pri] [addr] [dlc] [body...] [chk] 04
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
| Field | Meaning |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `0x0F` | Start-of-frame marker (fixed) |
|
|
191
|
+
| `pri` | Priority byte — see below |
|
|
192
|
+
| `addr` | Target/source module address (`0x00` = broadcast, `0x01`–`0xFE` = a real module) |
|
|
193
|
+
| `dlc` | Data length (low 4 bits = body byte count), or `0x40` for an RTR (request) packet |
|
|
194
|
+
| `body...` | The actual command/data bytes — see [section 4.3](#43-the-body-indexing-rule) |
|
|
195
|
+
| `chk` | Checksum — two's complement of the sum of all preceding bytes, mod 256 |
|
|
196
|
+
| `0x04` | End-of-frame marker (fixed) |
|
|
197
|
+
|
|
198
|
+
**Priority byte values** (confirmed against the official `packetprotocol` repository —
|
|
199
|
+
see [section 15](#15-where-to-find-protocol-references)):
|
|
200
|
+
|
|
201
|
+
| Value | Meaning |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `0xF8` | High priority |
|
|
204
|
+
| `0xF9` | Firmware-related |
|
|
205
|
+
| `0xFA` | Third-party |
|
|
206
|
+
| `0xFB` | Low priority |
|
|
207
|
+
|
|
208
|
+
In practice, this codebase's convention (established after a real, painful bug — see
|
|
209
|
+
`CHANGELOG_FORUM.md` v0.7.0) is: **`0xFB` for status requests (`0xFA` command) and RTR
|
|
210
|
+
scan packets; `0xF8` for essentially everything else**, including outgoing commands and
|
|
211
|
+
most spontaneous module broadcasts. This is a convention observed to match real hardware
|
|
212
|
+
behaviour, not a rule stated explicitly and completely in the official docs — when in
|
|
213
|
+
doubt for a new packet type, check the specific module's protocol PDF for its actual
|
|
214
|
+
priority bits rather than assuming.
|
|
215
|
+
|
|
216
|
+
An RTR (bus scan request) packet has no body at all: `0F FB [addr] 40 [chk] 04`.
|
|
217
|
+
|
|
218
|
+
**Checksum** — sum every byte from the start marker through the last body byte
|
|
219
|
+
(mod 256), then take the two's complement:
|
|
220
|
+
```javascript
|
|
221
|
+
function chk(bytes) {
|
|
222
|
+
let s = 0;
|
|
223
|
+
for (const x of bytes) s = (s + x) & 0xFF;
|
|
224
|
+
return ((~s + 1) & 0xFF);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
This exact function lives in `lib/velbus-utils.js` as `chk()`, used by `pkt()` (builds an
|
|
228
|
+
outgoing packet) and implicitly verified by `parsePkt()` when reading incoming ones.
|
|
229
|
+
|
|
230
|
+
### 4.3 The body indexing rule — the single most common source of bugs
|
|
231
|
+
|
|
232
|
+
`parsePkt()` returns a `body` array where **`body[0]` is always the command byte itself**
|
|
233
|
+
(what the official protocol PDFs call DATABYTE1). Actual data starts at `body[1]`
|
|
234
|
+
(DATABYTE2).
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
PDF says "DATABYTE2 = channel number" → code should read body[1] ✓
|
|
238
|
+
→ code reading body[2] is WRONG (that's DATABYTE3)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This off-by-one is the single most common mistake made throughout this project's
|
|
242
|
+
development history (see `CHANGELOG_FORUM.md` for several real instances). When
|
|
243
|
+
implementing a new packet handler, always write out the DATABYTE-to-`body[]` index
|
|
244
|
+
mapping explicitly as a comment before writing the parsing logic — it is not something
|
|
245
|
+
to trust from memory or infer quickly.
|
|
246
|
+
|
|
247
|
+
### 4.4 Address format
|
|
248
|
+
|
|
249
|
+
Module addresses are stored as **hex strings in the editor's dropdown UI** (e.g.
|
|
250
|
+
`"0x0D — VMBELO"`) but **stored in the actual node config as a decimal integer**.
|
|
251
|
+
Numbers appearing in an old saved flow (from before hex-string dropdowns existed) should
|
|
252
|
+
still be treated as decimal for backward compatibility; new saves from the current editor
|
|
253
|
+
use hex strings. Both are handled by the same address-parsing logic in each node — if
|
|
254
|
+
you're seeing an address parse incorrectly, check whether the value arrived as a number
|
|
255
|
+
or a string before assuming the parsing logic itself is wrong.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 5. Complete node reference
|
|
260
|
+
|
|
261
|
+
| Node | Category | Covers |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| `velbus-bridge` | config | The TCP connection itself — see section 4.1 |
|
|
264
|
+
| `velbus-scan` | Velbus (inputs) | Full bus scan, populates every other node's address dropdown |
|
|
265
|
+
| `velbus-relay` | Velbus (outputs) | Original-series relays: VMB1RY, VMB4RY, VMB4RYLD, VMB4RYNO, VMB1RYNO, VMB1RYNOS, VMB1RYS, VMB4RYLD-10, VMB4RYNO-10 |
|
|
266
|
+
| `velbus-relay-20` | Velbus (outputs) | V2 relays: VMB1RYS-20, VMB4RYLD-20, VMB4RYNO-20 |
|
|
267
|
+
| `velbus-dimmer` | Velbus (outputs) | Original-series dimmers: VMBDMI, VMBDMI-R, VMB4DC |
|
|
268
|
+
| `velbus-dimmer-20` | Velbus (outputs) | V2 dimmers: VMB2DC-20, VMB8DC-20, VMB4LEDPWM-20 (incl. RGB/RGBW grouping mode) |
|
|
269
|
+
| `velbus-glass-panel` | Velbus (inputs) | All 26 glass panel types (original + V2), buttons/OLED/PIR/open-collector as applicable per type |
|
|
270
|
+
| `velbus-thermostat` | Velbus (inputs) | Thermostat function on any glass panel module that has one — same address as the corresponding glass-panel node, coexists without conflict |
|
|
271
|
+
| `velbus-button` | Velbus (inputs) | Dedicated push-button modules: VMB8PB, VMB8PBU, VMB6PBN, VMB2PBN, VMB4PB, VMB6PB-20 |
|
|
272
|
+
| `velbus-pir` | Velbus (inputs) | Original-series PIR: VMBPIRO-10, VMBPIRM, VMBPIRC, VMBPIRO |
|
|
273
|
+
| `velbus-pir-20` | Velbus (inputs) | V2 PIR: VMBPIR-20, VMBPIRO-20 |
|
|
274
|
+
| `velbus-meteo` | Velbus (inputs) | Weather station: VMBMETEO |
|
|
275
|
+
| `velbus-sensor` | Velbus (inputs) | Original-series input/analogue: VMB7IN, VMB4AN |
|
|
276
|
+
| `velbus-sensor-20` | Velbus (inputs) | V2 input: VMB8IN-20 |
|
|
277
|
+
| `velbus-blind` | Velbus (outputs) | VMB1BL, VMB2BL |
|
|
278
|
+
| `velbus-blind-s` | Velbus (outputs) | VMB1BLS, VMB2BLE, VMB2BLE-10 |
|
|
279
|
+
| `velbus-blind-20` | Velbus (outputs) | VMB2BLE-20 |
|
|
280
|
+
| `velbus-clock` | Velbus (outputs) | **No fixed module address.** Broadcasts system time/date/DST to the bus broadcast address (`0x00`), and sets clock alarms either globally (broadcast) or locally (a specific module, via a per-message address override) |
|
|
281
|
+
| `velbus-energy` | Velbus (inputs) | VMBPSUMNGR-20 — power supply manager: PSU load percentages, live wattage/voltage/amperage per rail, a warranty (hours-in-operation) counter, and PSU/warranty alarm status |
|
|
282
|
+
|
|
283
|
+
Palette group colours: **Velbus (inputs)** is teal (`#3A8C8C`), **Velbus (outputs)** is
|
|
284
|
+
blue (`#4A90D9`).
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 6. Complete module type registry
|
|
289
|
+
|
|
290
|
+
### Relays
|
|
291
|
+
| Type byte | Module | Node |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| 0x02 | VMB1RY | velbus-relay |
|
|
294
|
+
| 0x08 | VMB4RY | velbus-relay |
|
|
295
|
+
| 0x0D | VMB1RYS-20 | velbus-relay-20 |
|
|
296
|
+
| 0x10 | VMB4RYLD | velbus-relay |
|
|
297
|
+
| 0x11 | VMB4RYNO | velbus-relay |
|
|
298
|
+
| 0x1B | VMB1RYNO | velbus-relay |
|
|
299
|
+
| 0x26 | VMB4RYLD-20 | velbus-relay-20 |
|
|
300
|
+
| 0x27 | VMB4RYNO-20 | velbus-relay-20 |
|
|
301
|
+
| 0x29 | VMB1RYNOS | velbus-relay |
|
|
302
|
+
| 0x41 | VMB1RYS | velbus-relay |
|
|
303
|
+
| 0x48 | VMB4RYLD-10 | velbus-relay |
|
|
304
|
+
| 0x49 | VMB4RYNO-10 | velbus-relay |
|
|
305
|
+
|
|
306
|
+
### Dimmers
|
|
307
|
+
| Type byte | Module | Node | LED grouping mode |
|
|
308
|
+
|---|---|---|---|
|
|
309
|
+
| 0x06 | VMB4LEDPWM-20 | velbus-dimmer-20 | single / rgb / rgbw (configurable) |
|
|
310
|
+
| 0x12 | VMB4DC | velbus-dimmer | n/a |
|
|
311
|
+
| 0x15 | VMBDMI | velbus-dimmer | n/a |
|
|
312
|
+
| 0x24 | VMB2DC-20 | velbus-dimmer-20 | n/a |
|
|
313
|
+
| 0x2F | VMBDMI-R | velbus-dimmer | n/a |
|
|
314
|
+
| 0x4B | VMB8DC-20 | velbus-dimmer-20 | n/a |
|
|
315
|
+
|
|
316
|
+
### Push buttons
|
|
317
|
+
| Type byte | Module | Node |
|
|
318
|
+
|---|---|---|
|
|
319
|
+
| 0x01 | VMB8PB | velbus-button |
|
|
320
|
+
| 0x16 | VMB8PBU | velbus-button |
|
|
321
|
+
| 0x17 | VMB6PBN | velbus-button |
|
|
322
|
+
| 0x18 | VMB2PBN | velbus-button |
|
|
323
|
+
| 0x44 | VMB4PB | velbus-button |
|
|
324
|
+
| 0x4C | VMB6PB-20 | velbus-button |
|
|
325
|
+
|
|
326
|
+
### Glass panels (all → velbus-glass-panel, 26 types)
|
|
327
|
+
| Type byte | Module | OLED | PIR | Open collector | Min. map version |
|
|
328
|
+
|---|---|---|---|---|---|
|
|
329
|
+
| 0x1E | VMBGP1 | no | no | unconfirmed¹ | — |
|
|
330
|
+
| 0x1F | VMBGP2 | no | no | unconfirmed¹ | — |
|
|
331
|
+
| 0x20 | VMBGP4 | no | no | unconfirmed¹ | — |
|
|
332
|
+
| 0x21 | VMBGPO | yes | no | yes | 2 |
|
|
333
|
+
| 0x2D | VMBGP4PIR | no | yes | unconfirmed¹ | — |
|
|
334
|
+
| 0x34 | VMBEL1 | no | no | yes | 2 |
|
|
335
|
+
| 0x35 | VMBEL2 | no | no | yes | 2 |
|
|
336
|
+
| 0x36 | VMBEL4 | no | no | yes | 2 |
|
|
337
|
+
| 0x37 | VMBELO | yes | no | yes | 4 |
|
|
338
|
+
| 0x38 | VMBELPIR | no | yes | yes | 2 |
|
|
339
|
+
| 0x3A | VMBGP1-2 | no | no | no² | — |
|
|
340
|
+
| 0x3B | VMBGP2-2 | no | no | no² | — |
|
|
341
|
+
| 0x3C | VMBGP4-2 | no | no | no² | — |
|
|
342
|
+
| 0x3D | VMBGPOD-2 | yes | no | unconfirmed¹ | 2 |
|
|
343
|
+
| 0x47 | VMBEL2PIR | no | yes | yes | — |
|
|
344
|
+
| 0x4F | VMBEL1-20 | no | no | yes | — |
|
|
345
|
+
| 0x50 | VMBEL2-20 | no | no | yes | — |
|
|
346
|
+
| 0x51 | VMBEL4-20 | no | no | yes | — |
|
|
347
|
+
| 0x52 | VMBELO-20 | yes | no | yes | — |
|
|
348
|
+
| 0x53 | VMBELPIR-20³ | no | yes | yes | — |
|
|
349
|
+
| 0x54 | VMBGP1-20 | no | no | unconfirmed¹ | — |
|
|
350
|
+
| 0x55 | VMBGP2-20 | no | no | unconfirmed¹ | — |
|
|
351
|
+
| 0x56 | VMBGP4-20 | no | no | unconfirmed¹ | — |
|
|
352
|
+
| 0x57 | VMBGPO-20 | yes | no | yes | — |
|
|
353
|
+
| 0x5C | VMBEL2PIR-20³ | no | yes | yes | — |
|
|
354
|
+
| 0x5F | VMBGP4PIR-20 | no | yes | unconfirmed¹ | — |
|
|
355
|
+
|
|
356
|
+
¹ Open-collector support unconfirmed against real hardware — see [section 13](#13-known-open-issues).
|
|
357
|
+
² Confirmed no open-collector commands in the protocol PDF, but not yet live-verified.
|
|
358
|
+
³ The official Velbus type list shows different module names at these two type bytes than
|
|
359
|
+
what this registry currently uses — flagged for verification, see
|
|
360
|
+
[section 13](#13-known-open-issues).
|
|
361
|
+
|
|
362
|
+
### PIR sensors
|
|
363
|
+
| Type byte | Module | Node | Has temp. sensor |
|
|
364
|
+
|---|---|---|---|
|
|
365
|
+
| 0x23 | VMBPIRO-10 | velbus-pir | yes |
|
|
366
|
+
| 0x2A | VMBPIRM | velbus-pir | no |
|
|
367
|
+
| 0x2B | VMBPIRC | velbus-pir | no |
|
|
368
|
+
| 0x2C | VMBPIRO | velbus-pir | yes |
|
|
369
|
+
| 0x4D | VMBPIR-20 | velbus-pir-20 | no |
|
|
370
|
+
| 0x59 | VMBPIRO-20 | velbus-pir-20 | yes |
|
|
371
|
+
|
|
372
|
+
### Sensors / meteo
|
|
373
|
+
| Type byte | Module | Node |
|
|
374
|
+
|---|---|---|
|
|
375
|
+
| 0x22 | VMB7IN | velbus-sensor |
|
|
376
|
+
| 0x31 | VMBMETEO | velbus-meteo |
|
|
377
|
+
| 0x32 | VMB4AN | velbus-sensor |
|
|
378
|
+
| 0x4E | VMB8IN-20 | velbus-sensor-20 |
|
|
379
|
+
|
|
380
|
+
### Blind / shutter
|
|
381
|
+
| Type byte | Module | Node |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| 0x03 | VMB1BL | velbus-blind |
|
|
384
|
+
| 0x09 | VMB2BL | velbus-blind |
|
|
385
|
+
| 0x1D | VMB2BLE | velbus-blind-s |
|
|
386
|
+
| 0x2E | VMB1BLS | velbus-blind-s |
|
|
387
|
+
| 0x4A | VMB2BLE-10 | velbus-blind-s |
|
|
388
|
+
| 0x61 | VMB2BLE-20 | velbus-blind-20 |
|
|
389
|
+
|
|
390
|
+
### Energy / infrastructure
|
|
391
|
+
| Type byte | Module | Node |
|
|
392
|
+
|---|---|---|
|
|
393
|
+
| 0x04 | VMBPSUMNGR-20 | velbus-energy |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 7. Critical protocol knowledge
|
|
398
|
+
|
|
399
|
+
### 7.1 Firmware build number — the official docs mislabel this field
|
|
400
|
+
|
|
401
|
+
In the `0xFF` module-identification response, bytes labelled "Build Year" and
|
|
402
|
+
"Build Week" in the official protocol PDFs are, in every module tested against real
|
|
403
|
+
hardware, actually the **high and low bytes of a single build number**, not a
|
|
404
|
+
year/week pair:
|
|
405
|
+
|
|
406
|
+
```
|
|
407
|
+
build = (body[5] × 100) + body[6]
|
|
408
|
+
Example: body[5]=0x24 (36 decimal), body[6]=0x36 (54 decimal) → (36×100)+54 = 3654
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 7.2 `0xFF` module type/identification response
|
|
412
|
+
```
|
|
413
|
+
body[0] = 0xFF
|
|
414
|
+
body[1] = module type byte
|
|
415
|
+
body[2] = serial number, high byte
|
|
416
|
+
body[3] = serial number, low byte
|
|
417
|
+
body[4] = memory map version
|
|
418
|
+
body[5] = build number high byte (decimal, see 7.1)
|
|
419
|
+
body[6] = build number low byte (decimal, see 7.1)
|
|
420
|
+
body[7] = properties byte — V2 modules only (bit 5 = CAN FD capable, bit 0 = terminator fitted)
|
|
421
|
+
```
|
|
422
|
+
Original-series modules: 7 bytes total (no `body[7]`).
|
|
423
|
+
Exceptions confirmed against real hardware: **VMB1BL/VMB2BL** send only 5 bytes (no
|
|
424
|
+
serial number; `body[2]` is instead a DIP-switch timeout setting). **VMBELO**, despite
|
|
425
|
+
being an original-series module, sends the full 8-byte V2-style response.
|
|
426
|
+
**VMB2BLE-10** also sends 8 bytes, but `body[7]` there is only a terminator flag, not
|
|
427
|
+
the full V2 properties byte.
|
|
428
|
+
|
|
429
|
+
### 7.3 `0xB0` subaddress response
|
|
430
|
+
```
|
|
431
|
+
body[0] = 0xB0
|
|
432
|
+
body[1] = module type
|
|
433
|
+
body[2-3] = serial number
|
|
434
|
+
body[4-7] = up to four subaddresses (0xFF = subaddress slot disabled/unused)
|
|
435
|
+
```
|
|
436
|
+
Subaddresses are **source addresses for incoming events only** — outgoing commands
|
|
437
|
+
always go to the module's primary address, never to a subaddress. The bridge handles
|
|
438
|
+
`0xB0` responses passively, building a subaddress→primary-address map so that other
|
|
439
|
+
nodes don't need any subaddress-specific logic themselves. A `0xB0` response can arrive
|
|
440
|
+
up to ~500ms after the initial `0xFF` — always allow for this delay before treating a
|
|
441
|
+
module's discovery as complete.
|
|
442
|
+
|
|
443
|
+
### 7.4 `0xFB` relay status — two completely different formats depending on generation
|
|
444
|
+
|
|
445
|
+
**Original series** — one packet per active channel:
|
|
446
|
+
```
|
|
447
|
+
body[0] = 0xFB
|
|
448
|
+
body[1] = channel number
|
|
449
|
+
body[2] = state (0 = off, 1 = on)
|
|
450
|
+
body[3-5] = 24-bit timer remaining, in seconds
|
|
451
|
+
body[6] = LED state
|
|
452
|
+
```
|
|
453
|
+
Since one packet is sent per channel, allow up to ~200ms to collect all of them for a
|
|
454
|
+
multi-channel module.
|
|
455
|
+
|
|
456
|
+
**V2 series** — a single bitmask packet covering every channel at once:
|
|
457
|
+
```
|
|
458
|
+
body[0] = 0xFB
|
|
459
|
+
body[1] = active (on) bitmask
|
|
460
|
+
body[2] = timer-running bitmask
|
|
461
|
+
body[3] = forced bitmask
|
|
462
|
+
body[4] = inhibited bitmask
|
|
463
|
+
body[5] = locked bitmask
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### 7.5 `0xEE` dimmer status — V2 series (all three -20 dimmer types)
|
|
467
|
+
```
|
|
468
|
+
body[0] = 0xEE
|
|
469
|
+
body[1] = on/off bitmask
|
|
470
|
+
body[2] = inhibited bitmask
|
|
471
|
+
body[3] = forced-on bitmask
|
|
472
|
+
body[4] = forced-off bitmask
|
|
473
|
+
body[5] = program-disabled bitmask
|
|
474
|
+
body[6] = error bitmask
|
|
475
|
+
body[7] = alarm/program byte
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### 7.6 `0xA5` dim level — up to four channels packed per packet
|
|
479
|
+
```
|
|
480
|
+
body[0] = 0xA5, body[1] = channel, body[2] = level (0-254)
|
|
481
|
+
[optionally repeated: body[3] = channel, body[4] = level, ...]
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 7.7 `0x1E` RGBW command — VMB4LEDPWM-20 in rgb/rgbw grouping mode only
|
|
485
|
+
```
|
|
486
|
+
body[0] = 0x1E
|
|
487
|
+
body[1] = group (0xFF = all)
|
|
488
|
+
body[2] = R, body[3] = G, body[4] = B, body[5] = W
|
|
489
|
+
body[6] = fade mode (0 = direct, 1 = rate, 2 = time)
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Watch this one carefully:** command bytes `0x12`/`0x14` mean **forced OFF / forced ON**
|
|
493
|
+
on relay nodes, but mean **forced UP / forced DOWN** on blind nodes. The same byte value
|
|
494
|
+
means something entirely different depending on module type — always check module type
|
|
495
|
+
before reusing a command byte pattern from a different node family.
|
|
496
|
+
|
|
497
|
+
### 7.8 Memory read/write timing constraints
|
|
498
|
+
```
|
|
499
|
+
Original series: minimum 20ms between 0xFC writes — this is real EEPROM with a
|
|
500
|
+
finite write-cycle lifespan, do not write faster than this
|
|
501
|
+
V2 series: wait for the module's 0xFE acknowledgement before the next write
|
|
502
|
+
OLED framebuffer: 10ms minimum per 4-byte block (V2 modules with a display)
|
|
503
|
+
Address assignment: allow 500ms, then rescan to confirm the new address took effect
|
|
504
|
+
```
|
|
505
|
+
**Never write in response to a real-time bus event** — an incoming status broadcast is
|
|
506
|
+
not the right trigger for an automatic write-back, both for the EEPROM-wear reason above
|
|
507
|
+
and because it risks a feedback loop. Reads have no such constraint and are always safe.
|
|
508
|
+
|
|
509
|
+
### 7.9 Thermostat commands — always to the primary address
|
|
510
|
+
```
|
|
511
|
+
0xDB = comfort mode, 0xDC = day mode, 0xDD = night mode, 0xDE = safe mode
|
|
512
|
+
0xE0 = heat mode, 0xDF = cool mode
|
|
513
|
+
0xE4 = set target temperature, 0xE5 = request temperature settings, 0xE7 = request status
|
|
514
|
+
0xEF = request module name (append a channel byte)
|
|
515
|
+
0xFA = request module status
|
|
516
|
+
```
|
|
517
|
+
Thermostat commands go to the module's **primary address only**, never to a thermostat
|
|
518
|
+
subaddress, even though the thermostat function itself may be logically associated with
|
|
519
|
+
a subaddress in the module's own internal architecture.
|
|
520
|
+
|
|
521
|
+
### 7.10 Temperature encoding — two different formats in use
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
// Signed byte × 0.5°C — used in the 0xE8 thermostat settings packet
|
|
525
|
+
function signed05(b) { return (b > 127 ? b - 256 : b) * 0.5; }
|
|
526
|
+
|
|
527
|
+
// Signed 16-bit, divided by 512 — used in the 0xE6 temperature packet
|
|
528
|
+
// (0.0625°C resolution). NOTE: official protocol documentation shows a
|
|
529
|
+
// divisor of 16 for this field — that is wrong. It has been directly
|
|
530
|
+
// verified against real hardware that the correct divisor is 512.
|
|
531
|
+
function tempFrom16(hi, lo) {
|
|
532
|
+
const raw = (hi << 8) | lo;
|
|
533
|
+
return (raw > 32767 ? raw - 65536 : raw) / 512;
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
This divisor discrepancy (16 vs. 512) is exactly the kind of thing worth re-verifying
|
|
537
|
+
against a real module if you're implementing a new node that touches `0xE6` — don't
|
|
538
|
+
trust the official PDF's stated divisor for this specific field without checking.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## 8. Payload conventions
|
|
543
|
+
|
|
544
|
+
Every node's Node-RED output message follows the same conventions, regardless of module
|
|
545
|
+
type:
|
|
546
|
+
|
|
547
|
+
- **Numbers are always numbers, never strings.** A temperature is `21.5`, not `"21.5"`.
|
|
548
|
+
- **An `on` boolean is present only where a meaningful binary on/off state actually
|
|
549
|
+
exists.** Temperature, counter, and settings-only payloads have no `on` field at all —
|
|
550
|
+
don't add one just for consistency's sake if the underlying value isn't truly binary.
|
|
551
|
+
- **`topic`** identifies the payload shape/purpose (e.g. `"relay_status"`,
|
|
552
|
+
`"dimmer_status"`, `"button"`, `"thermostat_status"`) so a single debug/switch node
|
|
553
|
+
downstream can distinguish message types without inspecting every field.
|
|
554
|
+
|
|
555
|
+
Representative examples:
|
|
556
|
+
|
|
557
|
+
```json
|
|
558
|
+
{ "topic": "relay_status", "address": "0x10", "module": "Hall Relay",
|
|
559
|
+
"channel": 1, "state": "on", "on": true, "timerRemaining": 0 }
|
|
560
|
+
|
|
561
|
+
{ "topic": "dimmer_status", "address": "0xA5", "module": "GF Lights",
|
|
562
|
+
"channel": 1, "state": "on", "on": true, "level": 187, "percent": 73.6,
|
|
563
|
+
"outputType": "PWM", "dimCurve": "exponential", "ledMode": "single" }
|
|
564
|
+
|
|
565
|
+
{ "topic": "button", "address": "0x03", "module": "Hall Panel",
|
|
566
|
+
"type": "button", "on": true,
|
|
567
|
+
"pressed": [1, 3], "released": [], "longPressed": [] }
|
|
568
|
+
|
|
569
|
+
{ "topic": "thermostat_status", "type": "thermostat",
|
|
570
|
+
"currentTemp": 21.5, "targetTemp": 22.0, "mode": "comfort",
|
|
571
|
+
"heaterMode": true, "heating": false, "thermostatOn": true }
|
|
572
|
+
|
|
573
|
+
{ "topic": "temperature", "type": "temperature",
|
|
574
|
+
"current": 21.5, "min": 15.0, "max": 30.0 }
|
|
575
|
+
|
|
576
|
+
{ "topic": "meteo", "type": "meteo",
|
|
577
|
+
"rain": 0.5, "light": 12500, "wind": 14.3 }
|
|
578
|
+
|
|
579
|
+
{ "topic": "blind_status", "type": "blind_status",
|
|
580
|
+
"channel": 1, "on": true, "status": "up", "position": 25,
|
|
581
|
+
"lockState": "normal", "autoMode": 0 }
|
|
582
|
+
|
|
583
|
+
{ "topic": "module_online", "address": "0x06", "module": "VMB4LEDPWM-20",
|
|
584
|
+
"typeId": "0x06", "outputType": "PWM", "dimCurve": "exponential",
|
|
585
|
+
"ledMode": "single", "channels": 4, "build": 2436, "canFD": false }
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Node status bar format** (shown under each node in the Node-RED editor): `"{module
|
|
589
|
+
name} (0x{address}) {state}"`. Name priority, highest first: an explicit user override
|
|
590
|
+
in node config, then the module's own name as reported by VelbusLink/read from the
|
|
591
|
+
module itself, then falling back to the generic module type string if neither is
|
|
592
|
+
available.
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## 9. Architecture decisions (settled — do not reopen casually)
|
|
597
|
+
|
|
598
|
+
These are decisions made deliberately, generally after real back-and-forth about the
|
|
599
|
+
alternatives. Reopening one isn't forbidden, but should be a considered choice, not an
|
|
600
|
+
accidental drift — if you find yourself changing one of these, it's worth being explicit
|
|
601
|
+
about why in the commit message.
|
|
602
|
+
|
|
603
|
+
- **Generational split.** Nodes generally split at the V2.0 boundary — one node for
|
|
604
|
+
original-series, a separate node for V2. **Exceptions:** `velbus-glass-panel` (one
|
|
605
|
+
node covers all 26 types, both generations, via the type registry's per-type flags
|
|
606
|
+
rather than a code-level split), `velbus-thermostat` (covers the thermostat function
|
|
607
|
+
on any glass panel type), `velbus-meteo` (only one generation exists), and the blind
|
|
608
|
+
family (`velbus-blind` / `velbus-blind-s` / `velbus-blind-20`, split by actual protocol
|
|
609
|
+
capability rather than strictly by generation, since some non-V2 blind modules share
|
|
610
|
+
more protocol in common with certain V2 ones than with other original-series ones).
|
|
611
|
+
- **Firmware check (V2 nodes only).** A three-stage check on receiving the module's
|
|
612
|
+
`0xFF` identification: type byte matches → memory map version meets the node's stated
|
|
613
|
+
minimum → pass. Failure is a hard block with a red status, not a soft warning.
|
|
614
|
+
Original-series nodes skip this check entirely (no firmware/map-version concept
|
|
615
|
+
exists for them).
|
|
616
|
+
- **Name auto-retrieval.** V2 nodes, and the glass-panel/thermostat nodes, request the
|
|
617
|
+
module's name (`0xEF`) automatically on startup, with a 2-second timeout — whatever
|
|
618
|
+
has been received by then is used, even if the name is incomplete.
|
|
619
|
+
- **Scan lock.** `velbus-scan` calls the bridge's scan-lock before scanning; every other
|
|
620
|
+
node's own startup RTR requests are queued during an active scan and flushed at a rate
|
|
621
|
+
of one per second once scanning completes, rather than being sent immediately and
|
|
622
|
+
risking a collision with in-progress scan traffic.
|
|
623
|
+
- **Address dropdown data source.** Always read `results.modules`, never `results`
|
|
624
|
+
directly, from the bridge's `/velbus/scan-results` endpoint — it returns an object
|
|
625
|
+
wrapping the array, not the array itself. Real, repeated source of bugs — worth
|
|
626
|
+
restating even though it's also in section 4.1.
|
|
627
|
+
- **Thermostat commands always target the primary address**, never a subaddress, even
|
|
628
|
+
though the thermostat function may be logically tied to one internally.
|
|
629
|
+
- **`0xFF` response body length** is 7 bytes on original-series modules, 8 bytes on V2
|
|
630
|
+
— except VMBELO (original series, but sends 8 bytes) and VMB2BLE-10 (sends 8 bytes,
|
|
631
|
+
but the 8th is only a terminator flag, not full V2 properties). See section 7.2.
|
|
632
|
+
- **VMB4LEDPWM-20 LED grouping mode** is a node config property that documents which
|
|
633
|
+
mode (single/rgb/rgbw) the physical module is wired for — the node does **not** write
|
|
634
|
+
this setting to the module automatically. Changing a module's grouping mode is a
|
|
635
|
+
deliberate commissioning-time decision made by a human (or a dedicated commissioning
|
|
636
|
+
tool), not something a live command node should do silently. A read-only
|
|
637
|
+
`get_device_type` command exists on `velbus-dimmer-20` to verify the module's actual
|
|
638
|
+
setting matches the node's configured value.
|
|
639
|
+
- **`velbus-clock`'s `set_alarm` command accepts a per-message address override**,
|
|
640
|
+
breaking from the "one node = one fixed module address" pattern every other node
|
|
641
|
+
follows. This was a deliberate exception, not an oversight: the underlying protocol
|
|
642
|
+
command (`0xC3`, clock alarm) is identical whether sent globally (broadcast) or
|
|
643
|
+
locally (one specific module) — differing only in destination address — and is a
|
|
644
|
+
shared system-level command across V2 module types generally, not a feature tied to
|
|
645
|
+
one specific module type. Putting per-module alarm handling on every relevant node
|
|
646
|
+
type instead would mean duplicating an identical handler several times over. This is
|
|
647
|
+
flagged as a considered judgement call rather than a permanently settled decision —
|
|
648
|
+
if it later seems better as a proper per-module config field (matching every other
|
|
649
|
+
node's pattern), that's a reasonable direction to switch to.
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
## 10. Development environment setup
|
|
654
|
+
|
|
655
|
+
**Prerequisites:**
|
|
656
|
+
- Node.js ≥ 14
|
|
657
|
+
- Node-RED ≥ 2.0.0
|
|
658
|
+
- A Velbus TCP gateway of some kind — either the official `velbus-tcp` package, or the
|
|
659
|
+
open-source `python-velbustcp` project (see [section 15](#15-where-to-find-protocol-references)
|
|
660
|
+
for links). Both simply expose the same bus protocol over a TCP socket; this palette
|
|
661
|
+
doesn't care which one it's talking to.
|
|
662
|
+
- Ideally, physical Velbus hardware to test against — though a great deal of useful
|
|
663
|
+
development and verification is possible without it; see
|
|
664
|
+
[section 11](#11-testing-without-real-hardware--the-mock-harness-pattern).
|
|
665
|
+
|
|
666
|
+
**For live development** (editing node code and seeing changes without repackaging a
|
|
667
|
+
tarball every time), symlink this repository directly into Node-RED's node_modules
|
|
668
|
+
rather than repeatedly installing from a tarball:
|
|
669
|
+
```bash
|
|
670
|
+
cd ~/.node-red/node_modules
|
|
671
|
+
ln -s /path/to/your/local/checkout/node-red-contrib-velbus-2026 node-red-contrib-velbus-2026
|
|
672
|
+
```
|
|
673
|
+
Restart Node-RED after each code change to pick it up (Node-RED does not hot-reload
|
|
674
|
+
node `.js` files). For `.html` (editor UI/help) changes specifically, a hard browser
|
|
675
|
+
refresh (`Ctrl+Shift+R` or equivalent) is usually enough, since the editor caches node
|
|
676
|
+
HTML client-side — a full Node-RED restart isn't always necessary for HTML-only changes,
|
|
677
|
+
but doesn't hurt if something isn't picking up.
|
|
678
|
+
|
|
679
|
+
**No physical Velbus hardware and no gateway software set up yet?** You can still make
|
|
680
|
+
substantial progress — see the next section.
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## 11. Testing without real hardware — the mock-harness pattern
|
|
685
|
+
|
|
686
|
+
A useful, repeatedly-proven technique for verifying a node's packet-building logic
|
|
687
|
+
*before* ever touching real hardware: write a small standalone script that mocks just
|
|
688
|
+
enough of the Node-RED `RED` object to load the node file directly, wire a fake bridge
|
|
689
|
+
that captures whatever bytes get sent, and hand-verify the resulting packet against the
|
|
690
|
+
protocol specification (checksum, DLC, byte-for-byte field layout).
|
|
691
|
+
|
|
692
|
+
This does **not** replace testing against real hardware — it catches packet-construction
|
|
693
|
+
bugs (wrong byte count, wrong checksum, off-by-one field indexing) before they ever reach
|
|
694
|
+
a bus, but it cannot confirm that a real module actually does what the protocol PDF says
|
|
695
|
+
it should do in response. Both kinds of verification matter; this one is simply always
|
|
696
|
+
available, with zero hardware dependency.
|
|
697
|
+
|
|
698
|
+
**Minimal example** (adapt the node path, config, and input payload to whatever you're
|
|
699
|
+
testing):
|
|
700
|
+
|
|
701
|
+
```javascript
|
|
702
|
+
const handlers = {};
|
|
703
|
+
|
|
704
|
+
function MockNode() {
|
|
705
|
+
this.on = (evt, fn) => { handlers[evt] = fn; };
|
|
706
|
+
this.status = (s) => console.log('STATUS:', JSON.stringify(s));
|
|
707
|
+
this.warn = (m) => console.log('WARN:', m);
|
|
708
|
+
this.error = (m) => console.log('ERROR:', m);
|
|
709
|
+
this.send = (msg) => console.log('OUTPUT:', JSON.stringify(msg));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const sentPackets = [];
|
|
713
|
+
const mockBridge = {
|
|
714
|
+
isConnected: () => true,
|
|
715
|
+
send: (buf) => sentPackets.push(buf)
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
const RED = {
|
|
719
|
+
nodes: {
|
|
720
|
+
createNode: (self) => { Object.assign(self, new MockNode()); },
|
|
721
|
+
getNode: () => mockBridge,
|
|
722
|
+
registerType: (name, ctor) => { RED._ctor = ctor; }
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
require('/path/to/nodes/velbus-example/velbus-example.js')(RED);
|
|
727
|
+
|
|
728
|
+
const config = { bridge: 'x' /* , ...whatever config fields this node expects */ };
|
|
729
|
+
new RED._ctor(config);
|
|
730
|
+
|
|
731
|
+
// Fire whatever input message you want to test
|
|
732
|
+
handlers['input']({ payload: { cmd: 'some_command', /* ...fields */ } });
|
|
733
|
+
|
|
734
|
+
// Inspect the resulting bytes
|
|
735
|
+
for (const buf of sentPackets) {
|
|
736
|
+
console.log(buf.toString('hex').toUpperCase().match(/.{1,2}/g).join(' '));
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
Hand-verify the printed hex bytes against the protocol PDF: confirm the DLC nibble
|
|
741
|
+
matches the actual body length, confirm each field lands at the expected offset, and
|
|
742
|
+
compute the checksum by hand (sum all bytes before it, mod 256, two's complement) to
|
|
743
|
+
confirm it matches what the code produced. This exact pattern has caught real bugs
|
|
744
|
+
before code ever reached a physical bus — worth running for any new packet-building
|
|
745
|
+
logic, not just as an afterthought.
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## 12. Testing status — what's actually verified
|
|
750
|
+
|
|
751
|
+
Testing maturity **varies significantly by node** — do not assume something works
|
|
752
|
+
against real hardware just because it's present in the published package. The
|
|
753
|
+
authoritative, version-by-version record is `CHANGELOG_FORUM.md`; check the entries for
|
|
754
|
+
the specific node/command you're relying on before treating it as proven. As a general
|
|
755
|
+
orientation at the time of writing:
|
|
756
|
+
|
|
757
|
+
- **Field-tested against live hardware:** the core relay, dimmer, glass panel, and
|
|
758
|
+
thermostat nodes have real-hardware confirmation for their primary functions.
|
|
759
|
+
- **Mock-harness verified only, not yet confirmed against a real bus:** `velbus-clock`
|
|
760
|
+
(both the time/date/DST broadcast and the `set_alarm` command), the
|
|
761
|
+
`velbus-dimmer-20` `get_device_type` read command, and `velbus-energy` in its
|
|
762
|
+
entirety (no VMBPSUMNGR-20 has been confirmed present on a scanned bus yet).
|
|
763
|
+
- **Fixed but pending re-confirmation on hardware different from what it was originally
|
|
764
|
+
tested on:** several nodes had packet-construction bugs fixed in bulk during one
|
|
765
|
+
intensive debugging pass (see `CHANGELOG_FORUM.md`, the entries covering versions
|
|
766
|
+
0.6.6 through 0.7.0) — confirm against whatever specific module you're working with
|
|
767
|
+
if you haven't personally seen it work.
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
## 13. Known open issues
|
|
772
|
+
|
|
773
|
+
- **Open-collector support** on several glass panel types (marked "unconfirmed" in the
|
|
774
|
+
registry in section 6) has not been verified against real hardware — the protocol PDFs
|
|
775
|
+
are ambiguous or silent on some of these. Sending `0x01`/`0x02`/`0x03` open-collector
|
|
776
|
+
commands to one of these modules and observing whether it responds would resolve this.
|
|
777
|
+
- **Module names at type bytes `0x53` and `0x5C`** — the official Velbus type list shows
|
|
778
|
+
different module names than what this registry currently uses for these two type
|
|
779
|
+
bytes. Needs verification against a real module or a VelbusLink bus scan.
|
|
780
|
+
- **`velbus-energy` verified only against a mock test harness** — no
|
|
781
|
+
VMBPSUMNGR-20 has actually been confirmed present on a scanned bus yet, only
|
|
782
|
+
assumed likely present at a known installation. Packet checksums and field
|
|
783
|
+
layout are hand-verified against the protocol PDF; real-hardware behaviour
|
|
784
|
+
is unconfirmed. The `0xA3` (PSU values) byte layout in particular was
|
|
785
|
+
reconstructed from an internally-inconsistent section of the official PDF
|
|
786
|
+
(see `CHANGELOG_FORUM.md`, v0.9.0) rather than read directly — the single
|
|
787
|
+
most likely spot for a real decode error to surface once tested live.
|
|
788
|
+
- **V2 relay interval/blink timer** — confirmed there is no live bus command for this on
|
|
789
|
+
V2-series relays at all (unlike the original series, which has one). A real interval
|
|
790
|
+
timer on a V2 relay requires writing a Program Step to the module's memory (protocol
|
|
791
|
+
command `0xC2`, Action code 22), which is a fundamentally different, more involved
|
|
792
|
+
kind of operation than any other command node in this palette currently performs.
|
|
793
|
+
Whether this belongs in this palette at all (as a new, more involved node) or is out
|
|
794
|
+
of scope is an open question, not a settled one.
|
|
795
|
+
- **`velbus-clock`'s multi-channel behaviour** during simultaneous multi-channel relay
|
|
796
|
+
blinking (interval timer) hasn't been explicitly tested for whether `channel`/
|
|
797
|
+
`channelBit` fields report correctly when more than one channel blinks at once.
|
|
798
|
+
- **DST auto-detection heuristic** in `velbus-clock` (comparing the current UTC offset
|
|
799
|
+
against the year's January/July offsets to infer whether daylight saving is active)
|
|
800
|
+
has only been sanity-checked in an environment where daylight saving never applies —
|
|
801
|
+
a genuine positive "DST is active" case hasn't been observed and confirmed correct.
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
## 14. Contribution and release workflow
|
|
806
|
+
|
|
807
|
+
**Every release, regardless of size:**
|
|
808
|
+
|
|
809
|
+
1. **Bump the version** in `package.json` — npm refuses to publish an already-used
|
|
810
|
+
version number, even for a trivial fix. Add a matching entry to
|
|
811
|
+
`CHANGELOG_FORUM.md` describing what changed and, where relevant, *why* — the reasoning
|
|
812
|
+
behind a fix is often more valuable to a future reader than the diff itself.
|
|
813
|
+
2. **Commit and push:**
|
|
814
|
+
```bash
|
|
815
|
+
git add -A
|
|
816
|
+
git commit -m "Describe the change"
|
|
817
|
+
git push
|
|
818
|
+
```
|
|
819
|
+
3. **Publish to npm:**
|
|
820
|
+
```bash
|
|
821
|
+
npm publish
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
**Publishing requires either 2FA enabled on the npm account used, or a Granular Access
|
|
825
|
+
Token with "Bypass two-factor authentication" explicitly ticked** — npm's current
|
|
826
|
+
policy rejects publish attempts otherwise with a `403` error. Whoever is publishing
|
|
827
|
+
needs one or the other set up; this has been a real stumbling block in practice.
|
|
828
|
+
|
|
829
|
+
**Recommended, not required:** tag the release to match, so it's easy to see which
|
|
830
|
+
commit corresponds to which published version:
|
|
831
|
+
```bash
|
|
832
|
+
git tag v<version>
|
|
833
|
+
git push --tags
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
**When adding a new node:**
|
|
837
|
+
- Copy the closest existing equivalent as a starting structure rather than writing from
|
|
838
|
+
scratch — the bridge lookup, packet registration, and status-bar boilerplate is
|
|
839
|
+
consistent throughout and easy to get subtly wrong if reinvented.
|
|
840
|
+
- Register it in `package.json`'s `node-red.nodes` map — Node-RED will not discover a
|
|
841
|
+
node that exists as a file but isn't listed there.
|
|
842
|
+
- Verify the packet construction using the mock-harness pattern
|
|
843
|
+
([section 11](#11-testing-without-real-hardware--the-mock-harness-pattern)) before
|
|
844
|
+
ever sending it to a real bus.
|
|
845
|
+
- Add its testing status honestly to `CHANGELOG_FORUM.md` — "verified via mock harness
|
|
846
|
+
only, not yet confirmed on real hardware" is a completely acceptable and expected
|
|
847
|
+
status for a new addition; the goal is accuracy, not appearing more finished than it is.
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## 15. Where to find protocol references
|
|
852
|
+
|
|
853
|
+
- **`https://github.com/velbus/packetprotocol`** — the low-level packet framing
|
|
854
|
+
specification (start/end markers, priority byte values, checksum algorithm).
|
|
855
|
+
- **`https://github.com/velbus/moduleprotocol`** — per-module protocol PDFs, one (or a
|
|
856
|
+
few, where several similar modules share one document) per module type. This is the
|
|
857
|
+
authoritative source for any module-specific command/status byte layout — always
|
|
858
|
+
check the actual PDF for the specific module type you're working on rather than
|
|
859
|
+
assuming it matches a similar-looking one, given how many small but real differences
|
|
860
|
+
exist between superficially similar modules (see sections 7.2 and 7.10 for two
|
|
861
|
+
concrete examples of official documentation being subtly wrong or inconsistent).
|
|
862
|
+
- **`https://github.com/velbus/python-velbustcp`** — an open-source TCP gateway
|
|
863
|
+
implementation; one of two practical ways (alongside Velbus's own `velbus-tcp`
|
|
864
|
+
package) to get a real bus accessible over TCP for this palette to connect to.
|
|
865
|
+
- The Velbus community forum (search for "building custom velbus devices") has at least
|
|
866
|
+
one publicly documented from-scratch virtual-module implementation, which has proven a
|
|
867
|
+
useful cross-reference for less-obvious bit-level encoding questions (particularly
|
|
868
|
+
around relay status packet formats) beyond what the official PDFs spell out clearly.
|
|
869
|
+
- Other open-source Velbus integrations worth cross-referencing when a protocol question
|
|
870
|
+
isn't fully resolved by the official docs: the openHAB Velbus binding, and the
|
|
871
|
+
HomeAssistant `velbusaio` integration — both are mature, independently-implemented
|
|
872
|
+
interpretations of the same protocol, and occasionally clarify an ambiguity the
|
|
873
|
+
official PDFs leave unclear.
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
## 16. Code style rules
|
|
878
|
+
|
|
879
|
+
- **No pseudocode, ever.** Every function, file, and HTML block committed should be a
|
|
880
|
+
complete, working, drop-in replacement — never a partial sketch or a "fill this in"
|
|
881
|
+
skeleton.
|
|
882
|
+
- **Prove the logic before restructuring it.** Get a single packet working and verified
|
|
883
|
+
(even just via the mock harness) before refactoring for elegance or reuse.
|
|
884
|
+
- **`body[0]` is always the command byte.** Real data starts at `body[1]`. No
|
|
885
|
+
exceptions — see section 4.3 if this isn't already second nature.
|
|
886
|
+
- **Numbers are always numbers in payloads, never strings.**
|
|
887
|
+
- **`results.modules`, not `results`,** when reading the scan endpoint.
|
|
888
|
+
- **Thermostat commands go to the primary address only,** never a subaddress.
|
|
889
|
+
- **Respect the 20ms minimum between `0xFC` writes** on original-series modules — this
|
|
890
|
+
is real EEPROM wear, not an arbitrary rate limit.
|
|
891
|
+
- **British English** throughout documentation and in-editor help text (this is a
|
|
892
|
+
British-developed project for what is predominantly a European/UK installer base —
|
|
893
|
+
purely a style convention, not a functional requirement).
|
|
894
|
+
|
|
895
|
+
---
|
|
896
|
+
|
|
897
|
+
## 17. License and attribution
|
|
898
|
+
|
|
899
|
+
MIT licensed — see `LICENSE`.
|
|
900
|
+
|
|
901
|
+
Originally developed by Stuart Hanlon / MDAR Limited, the UK Velbus distributor
|
|
902
|
+
(`mdar.co.uk`), as a modern replacement for an earlier, unmaintained community Velbus
|
|
903
|
+
Node-RED palette. Protocol reverse-engineering and verification work throughout this
|
|
904
|
+
project has drawn on the official Velbus protocol repositories, the Velbus community
|
|
905
|
+
forum, and cross-referencing against other independent open-source Velbus integrations
|
|
906
|
+
— see [section 15](#15-where-to-find-protocol-references) for specific links.
|
package/README.md
CHANGED
|
@@ -30,8 +30,11 @@ This is a ground-up rewrite replacing the abandoned
|
|
|
30
30
|
|
|
31
31
|
- Node-RED v3.0 or later (tested on v5.0)
|
|
32
32
|
- A Velbus TCP gateway:
|
|
33
|
-
- [velbus-tcp snap](https://snapcraft.io/velbus-tcp)
|
|
34
|
-
- [python-velbustcp](https://github.com/velbus/python-velbustcp)
|
|
33
|
+
- [velbus-tcp snap](https://snapcraft.io/velbus-tcp)
|
|
34
|
+
- [python-velbustcp](https://github.com/velbus/python-velbustcp)
|
|
35
|
+
- Velserv
|
|
36
|
+
- Velbus_PBserver
|
|
37
|
+
- Signum
|
|
35
38
|
- Node.js 18+
|
|
36
39
|
|
|
37
40
|
---
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
defaults: {
|
|
13
13
|
name: { value: '' },
|
|
14
14
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
15
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
15
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
16
16
|
channel: { value: 1, required: true },
|
|
17
17
|
moduleName: { value: '' },
|
|
18
18
|
typeId: { value: '' },
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
defaults: {
|
|
12
12
|
name: { value: '' },
|
|
13
13
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
14
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
14
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
15
15
|
channel: { value: 1, required: true },
|
|
16
16
|
moduleName: { value: '' },
|
|
17
17
|
typeId: { value: '' },
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
defaults: {
|
|
14
14
|
name: { value: '' },
|
|
15
15
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
16
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
16
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
17
17
|
channel: { value: 1, required: true },
|
|
18
18
|
moduleName: { value: '' },
|
|
19
19
|
typeId: { value: '' },
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
defaults: {
|
|
16
16
|
name: { value: '' },
|
|
17
17
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
18
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
18
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
19
19
|
channelCount: { value: 8 },
|
|
20
20
|
moduleName: { value: '' },
|
|
21
21
|
typeId: { value: '' },
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
defaults: {
|
|
12
12
|
name: { value: '' },
|
|
13
13
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
14
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
14
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
15
15
|
moduleName: { value: '' },
|
|
16
16
|
typeId: { value: '' },
|
|
17
17
|
},
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
defaults: {
|
|
136
136
|
name: { value: '' },
|
|
137
137
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
138
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
138
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
139
139
|
channelCount: { value: 4, required: true, validate: RED.validators.number() },
|
|
140
140
|
moduleName: { value: '' },
|
|
141
141
|
typeId: { value: '' },
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
defaults: {
|
|
8
8
|
name: { value: '' },
|
|
9
9
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
10
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
10
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
11
11
|
},
|
|
12
12
|
inputs: 1,
|
|
13
13
|
outputs: 2,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
defaults: {
|
|
15
15
|
name: { value: '' },
|
|
16
16
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
17
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
17
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
18
18
|
moduleName: { value: '' },
|
|
19
19
|
typeId: { value: '' },
|
|
20
20
|
},
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
defaults: {
|
|
13
13
|
name: { value: '' },
|
|
14
14
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
15
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
15
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
16
16
|
moduleName: { value: '' },
|
|
17
17
|
typeId: { value: '' },
|
|
18
18
|
},
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
defaults: {
|
|
13
13
|
name: { value: '' },
|
|
14
14
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
15
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
15
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
16
16
|
moduleName: { value: '' },
|
|
17
17
|
typeId: { value: '' },
|
|
18
18
|
},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
defaults: {
|
|
12
12
|
name: { value: '' },
|
|
13
13
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
14
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
14
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
15
15
|
moduleName: { value: '' },
|
|
16
16
|
typeId: { value: '' },
|
|
17
17
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
defaults: {
|
|
8
8
|
name: { value: '' },
|
|
9
9
|
bridge: { value: '', type: 'velbus-bridge', required: true },
|
|
10
|
-
address: { value: '', required: true, validate: function(v) { return
|
|
10
|
+
address: { value: '', required: true, validate: function(v) { const n = parseInt(v); return !isNaN(n) && n >= 1 && n <= 254; } },
|
|
11
11
|
moduleName: { value: '' },
|
|
12
12
|
},
|
|
13
13
|
inputs: 1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-velbus-2026",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Node-RED palette for Velbus building automation \u2014 relay, dimmer, glass panel, PIR, blind, sensor, meteo, clock nodes. Original and V2 (-20) series.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
package/velbus-2026-repo.bundle
CHANGED
|
Binary file
|