node-red-contrib-velbus-2026 0.9.2 → 0.10.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.
@@ -14,6 +14,249 @@ All feedback via GitHub issues, with examples and debug captures where possible.
14
14
 
15
15
  ---
16
16
 
17
+ ## Field-confirmed — 09/07/2026
18
+
19
+ **v0.9.3 confirmed working against real hardware** (Stuart, his own home
20
+ installation). Both real VMBGPOD panels present on the bus (addresses 0x05
21
+ and 0x18/0x2C) now report correctly — `"module":"VMBGPOD"`, correct
22
+ suggested node, correct channel count — where a rescan previously showed
23
+ `unknown_0x28` twice. The only remaining unknown in the same scan is
24
+ `unknown_0x42` (VMBKP, address 0xFD on Stuart's bus) — expected and correct,
25
+ since that module genuinely has no node built yet. This closes out the
26
+ VMBGPOD saga (v0.9.2 → v0.9.3) with an actual confirmed result, not just
27
+ passing tests — worth the distinction given how much back-and-forth two
28
+ separate duplicate-table bugs took to fully resolve.
29
+
30
+ ---
31
+
32
+ ## v0.10.1 — 09/07/2026
33
+
34
+ ### Two critical bugs found on real hardware — velbus-dimmer and velbus-glass-panel
35
+
36
+ **velbus-dimmer — `on`/`state` always wrong for original-series dimmers.**
37
+ Reported by Stuart: a VMBDMI at 75% dim showing `state:"off"`, `on:false`.
38
+ Root cause was a genuine misunderstanding of the protocol, not a small
39
+ off-by-one: `0xB8`'s `DATABYTE3` packs run-mode, error, load-type, and
40
+ temperature band **all into one status byte** (confirmed identical across
41
+ `protocol_vmbdmi.pdf`, `protocol_vmbdmi_r.pdf`, and `protocol_vmb4dc.pdf`).
42
+ The previous code treated this as if it were a separate mode-byte +
43
+ status-byte pair (the way relay modules genuinely have), and additionally
44
+ read `DATABYTE5` (LED indicator status — real values `0x00`/`0x80`/`0x40`/
45
+ `0x20`/`0x10`) as a second status word, checking its low 2 bits for
46
+ confirmation. Since none of the real LED status values have those bits
47
+ set, a dimmer in ordinary "normal running" mode — the overwhelmingly
48
+ common case — always fell through to `'off'`, regardless of actual dim
49
+ level. Also fixed in the same pass: the 24-bit current-delay timer was
50
+ being read as only 16 bits from the wrong byte offset, and `decodeThermal`
51
+ was being called on the LED byte instead of the real status byte (explains
52
+ why `thermal` always showed all zeros in the field report). `ledState` is
53
+ now correctly decoded and added to the payload. Verified against the exact
54
+ reported scenario plus inhibited/forced_on/disabled/thermal-alarm cases,
55
+ with the bit-field math for the packed status byte hand-checked.
56
+
57
+ **velbus-glass-panel — `0xEA` thermostat status has been silently crashing
58
+ since it was written.** Reported by Stuart via a `"velbus-bridge dispatch
59
+ error: currentTemp is not defined"` message appearing repeatedly while
60
+ testing a VMBGP2. Root cause: `currentTemp` and `targetTemp` were only ever
61
+ assigned as properties of the `payload` object literal, never declared as
62
+ their own variables — but the very next line referenced them as bare
63
+ identifiers in the `setStatus(...)` call. Because JS evaluates a function's
64
+ arguments before calling it, this threw a `ReferenceError` before
65
+ `node.send()` on the following line ever executed. **This means the
66
+ `type:"thermostat"` payload has likely never once been successfully
67
+ delivered for any thermostat-equipped glass panel** — the bridge's
68
+ dispatch-error handling caught the exception each time rather than
69
+ crashing the whole process, which is exactly why this went unnoticed for
70
+ so long rather than being immediately obvious. Fixed by declaring both as
71
+ proper local `const`s before use. Did a full sweep of every other packet
72
+ case in this file (button, module status, temperature, light sensor, memo
73
+ text, counter, name parts) via the mock harness afterward, specifically
74
+ exercising the OLED- and PIR-gated branches too — no other instances
75
+ found. Also swept every other node file for the same bare-identifier
76
+ `.toFixed()` pattern that caused this — one other match (`velbus-meteo`)
77
+ checked and confirmed already correct (properly prefixed with `payload.`).
78
+
79
+ Both verified via the mock-RED harness against the exact reported symptoms
80
+ before and after the fix — not just re-reading the corrected code. Not yet
81
+ re-confirmed on Stuart's real hardware.
82
+
83
+ ---
84
+
85
+ ## v0.10.0 — 09/07/2026
86
+
87
+ ### Coverage roadmap implementation — 9 new module types, velbus-button overhaul, velbus-clock sunrise/sunset
88
+
89
+ Following a full rationalization pass over every unaddressed Velbus module
90
+ type and feature (see `coverage-roadmap.md`), this implements everything
91
+ confirmed in scope. Every single item below was checked against its actual
92
+ protocol document before being added — several real divergences were caught
93
+ in the process that would otherwise have shipped silently wrong.
94
+
95
+ **velbus-button — substantial overhaul, not just new registry entries:**
96
+ - 7 new module types: VMB8IR, VMB4PD, VMB4RF, VMBRFR8S, VMBVP01, VMBKP, VMBIN.
97
+ - **Lock/unlock (0x12/0x13)** — confirmed present on 8 of the 12 total types
98
+ now covered; **NOT universal**, despite being asked for as "a key Velbus
99
+ feature." VMB8PB, VMB8IR, VMB4PD, and VMBVP01 genuinely lack this command
100
+ in their own protocol documents. Gated per-type (`hasLock`) — sending it to
101
+ an unsupported type now warns clearly on output 2 rather than silently
102
+ doing nothing.
103
+ - **Richer 0xED status decode** (locked/enabled/inverted/program-disabled) —
104
+ also NOT universal. VMB8PB's 0xED is a completely different, simpler
105
+ LED-only format; VMB4RF's status uses command byte 0xB4, not 0xED, with a
106
+ different field at DATABYTE4 ("learn transmitter mode"); VMBVP01's 0xED is
107
+ a third, shorter shape again. Gated per-type (`hasRichStatus`) — decoding
108
+ is skipped entirely for types that don't match, rather than risk
109
+ misreading whatever they actually send.
110
+ - **Channel names surfaced in event output** (0xF0/F1/F2) — found the
111
+ selector byte convention itself is inconsistent across types: some use a
112
+ bitmask (one bit per channel), others a literal 1-based number. These
113
+ produce the *same* byte value for channels 1-2, diverging only from
114
+ channel 3 onward — exactly the kind of thing that would pass casual
115
+ testing and then silently corrupt every name from channel 3 up. Verified
116
+ explicitly at channel 3 for both conventions before shipping.
117
+ - **VMBVP01 (DoorBird)** gets fixed semantic channel labels (Motion 1/2,
118
+ Bell 1/2, Door 1/2, Virtual button 1/2) — hardware-fixed functions, not
119
+ VelbusLink-configurable names, so not sourced from 0xF0-F2 at all.
120
+ - Output changed from 1 to 2 (events/status, warnings) — non-breaking for
121
+ existing flows, the new output simply has no wires by default.
122
+ - **Real mistake caught before shipping:** first pass had VMB4RF at 8
123
+ channels; its own status packet says "channel 1 to 4," matching its name.
124
+ Corrected to 4 before release, not after.
125
+ - **A second, more serious pre-existing bug found and fixed, unrelated to
126
+ today's additions:** `VMB4PB` and `VMB6PB-20` — two of the *original* five
127
+ button types, present since v0.5.2 — were registered under wrong type
128
+ bytes (`0x1C` and `0x20`) in this file's own registry. `0x1C` isn't a real
129
+ Velbus type byte at all; `0x20` actually belongs to `VMBGP4`, an unrelated
130
+ glass panel type. `velbus-scan.js` has always had the correct values
131
+ (`0x44`/`0x4C`) — only this file's internal lookup was wrong, meaning a
132
+ real `VMB4PB` or `VMB6PB-20`, correctly identified by a scan, would never
133
+ have matched this file's own type descriptor at all. Every type-specific
134
+ feature (and now, lock/unlock and rich status too) would have silently
135
+ never activated for these two types. Found by cross-referencing the
136
+ official type list while writing this changelog entry, not by design —
137
+ worth remembering that documentation review can surface real bugs too.
138
+
139
+ **velbus-sensor:** VMB6IN added. Confirmed simpler than its VMB7IN sibling,
140
+ not just a smaller version of it — no lock/unlock command exists for it at
141
+ all, and its 0xED module status is 5 bytes vs VMB7IN's 7. The existing
142
+ `body.length < 7` guard already skips it safely; no code change needed
143
+ beyond the registry entry itself.
144
+
145
+ **velbus-glass-panel:** VMBGP4PIR-2 (0x3E) and VMBGPTC (0x25) added.
146
+ - **Real mistake caught:** VMBGP4PIR-2's channels 5-8 have completely
147
+ different semantics from its 0x2D sibling despite the near-identical name
148
+ (Dark/Light output, Motion output, Light-depending-motion, Absence output
149
+ — not virtual/dark/light/motion). Copying the sibling's mapping would have
150
+ silently mislabeled four channels.
151
+ - VMBGPTC confirmed sharing its actual protocol document with VMBGPO
152
+ (0x21) — a thermostat-only variant of the same panel hardware, added to
153
+ the glass-panel registry rather than as thermostat-node-only, so
154
+ `velbus-thermostat` picks up its function automatically the same way it
155
+ already does for every other panel address.
156
+
157
+ **velbus-clock:** sunrise/sunset enable/disable (0xAE) added, same
158
+ global/local address pattern as the existing `set_alarm` — confirmed
159
+ identical packet body for both from the protocol PDF.
160
+
161
+ **velbus-scan:** all of the above added across its three independent tables
162
+ (`ALL_TYPES`/`NODE_SUGGESTION`/`MODULE_CHANNELS`) — the exact lesson from
163
+ the VMBGPOD saga (v0.9.2/v0.9.3), applied proactively this time rather than
164
+ discovered the hard way again. Also adds explicit `"Not supported"` scan
165
+ labels (rather than falling through to a bare name with no node) for
166
+ VMBDALI, VMBDALI-20, VMBLCDWB, VMCM3, VMBSIG, VMBSIG-20, and VMBSIG-21 —
167
+ recognized correctly in a scan, deliberately not built, by design rather
168
+ than oversight.
169
+
170
+ **Explicitly deferred, not built this round:** VMB1DM, VMBDME, and VMB1LED
171
+ (the dimmer-family additions) all turned out to use a genuinely different
172
+ single-channel `0xEE` status layout — distinct from both `velbus-dimmer`'s
173
+ own format and `velbus-dimmer-20`'s multi-channel bitmask format. This needs
174
+ real new decode logic, not a registry entry, and doesn't meet the
175
+ "minimal effort" bar set for this round. Parked rather than forced in.
176
+
177
+ **Verification:** every new packet format checked against its actual
178
+ protocol document (not inferred from a same-named sibling) before being
179
+ implemented; every new command exercised through the mock-RED harness with
180
+ hand-checked checksums; the channel-3 naming-convention divergence
181
+ specifically tested for both conventions, not just one; a full simulated
182
+ scan run across every new and "not supported" type confirmed correct
183
+ `suggestedNode`/`channels` output end to end. Not yet sent to a real bus.
184
+
185
+ ---
186
+
187
+ ## v0.9.4 — 09/07/2026
188
+
189
+ ### velbus-button — critical bug, live since v0.5.2: button events shifted by one byte
190
+
191
+ - **Found while scoping VMBKP's decode logic, not reported directly** — checking
192
+ `velbus-button.js` as a template surfaced that its `0x00` handler read
193
+ `body[0]`/`body[1]`/`body[2]` for pressed/released/long-pressed, when
194
+ `body[0]` is always the command byte itself (always `0x00`, per this
195
+ project's own most-repeated rule — see `HANDOVER.md` section 4.3). Every
196
+ field was reading one byte too early.
197
+ - **Real impact, not theoretical:** `pressed` was always empty (reading the
198
+ constant command byte), `released` was actually reporting what DATABYTE2
199
+ (the real "pressed" bitmask) contained, `longPressed` was reporting what
200
+ DATABYTE3 (the real "released" bitmask) contained, and the real
201
+ DATABYTE4 (long-press bitmask) was never read at all. The `on` field
202
+ followed the same corruption — it could report `true` on a release and
203
+ never correctly on an immediate press.
204
+ - **Confirmed live since `velbus-button`'s introduction in v0.5.2** — this
205
+ is not a new regression, it has been shipping incorrect button-event data
206
+ for the entire time this node has existed. `velbus-glass-panel`'s own
207
+ `0x00` handler was checked immediately afterward and confirmed **not**
208
+ affected — it already used the correct `body[1]`/`body[2]`/`body[3]`
209
+ indexing, so this was isolated to one file, not systemic.
210
+ - **Fixed and verified with a real repro, not just a corrected read of the
211
+ code:** built a mock-harness test that reproduces the exact failure first
212
+ (channel 3 pressed came back reported as "released") before applying the
213
+ fix, then re-ran the same test plus three more (release, long-press,
214
+ simultaneous multi-channel press) to confirm all four now report
215
+ correctly.
216
+ - **If you have flows depending on `velbus-button`'s `pressed`/`released`/
217
+ `longPressed` distinction** (rather than just the `on` boolean, or
218
+ scanning/discovery, which were unaffected), check them after updating —
219
+ behaviour that previously "worked" by only watching `on`, or by
220
+ compensating for the shift some other way in the flow itself, may now
221
+ behave differently now that the underlying data is actually correct.
222
+
223
+ ---
224
+
225
+ ## v0.9.3 — 09/07/2026
226
+
227
+ ### velbus-scan — VMBGPOD still showed "unknown_0x28" after the v0.9.2 fix
228
+
229
+ - **Reported by Stuart:** re-ran a scan against v0.9.2 pulled fresh from npm,
230
+ `VMBGPOD` still showed as `unknown_0x28`.
231
+ - **Root cause:** `velbus-scan.js` has its **own, third, independent copy**
232
+ of the type table — `ALL_TYPES`, `NODE_SUGGESTION`, and `MODULE_CHANNELS`
233
+ — entirely separate from `lib/glass-panel-types.js` (used by
234
+ `velbus-glass-panel` at runtime) and the duplicate list inside
235
+ `velbus-glass-panel.html` (used for the editor dropdown), both already
236
+ fixed in v0.9.2. The v0.9.2 fix genuinely worked for the glass-panel node
237
+ itself; the bus scanner simply had no idea `VMBGPOD` existed, since it
238
+ never reads either of the other two files at all.
239
+ - **This is now the third type table found in this codebase.** Searched
240
+ exhaustively this time (`grep -rln "VMBGPO'"` across the whole `nodes/`
241
+ and `lib/` tree) rather than assume these three are the only ones — they
242
+ are, confirmed by the search coming back with exactly these three files
243
+ and no others.
244
+ - Added `0x28: 'VMBGPOD'` to all three tables in `velbus-scan.js`.
245
+ - **Verified end-to-end this time, not just by inspecting the table.** Built
246
+ a full mock-harness test that triggers a real scan, feeds a simulated
247
+ `0xFF` response for `VMBGPOD` (using the exact build/serial from Stuart's
248
+ own scan output), and confirms the emitted `module_found` payload reports
249
+ `"module":"VMBGPOD"`, the correct suggested node, and the correct channel
250
+ count — not just that the table lookup would theoretically work.
251
+ - **Worth remembering for any future module type addition:** check all
252
+ three of `lib/<family>-types[-20].js`, the corresponding node's own
253
+ `.html` (editor dropdown), and `velbus-scan.js` (bus-wide discovery).
254
+ Missing any one of the three produces a real, user-visible symptom in a
255
+ different part of the palette than wherever the fix was actually made —
256
+ exactly what happened here.
257
+
258
+ ---
259
+
17
260
  ## v0.9.2 — 09/07/2026
18
261
 
19
262
  ### velbus-glass-panel — VMBGPOD (0x28) registry gap fixed
package/HANDOVER.md CHANGED
@@ -6,7 +6,7 @@ you're a new contributor, a new maintainer, or an AI assistant starting a fresh
6
6
  with no memory of previous work — this document should be sufficient on its own, together
7
7
  with the source code in this repository, to continue development competently.
8
8
 
9
- Current state at time of writing: **v0.9.2, 19 nodes, published on npm.**
9
+ Current state at time of writing: **v0.10.1, 19 nodes, published on npm.**
10
10
 
11
11
  ---
12
12
 
@@ -106,7 +106,7 @@ lib/ Shared code — protocol utilities and per-module-fam
106
106
  dimmer-types.js Original-series dimmer module type registry
107
107
  dimmer-types-20.js V2 dimmer module type registry (incl. LED grouping mode / Device
108
108
  Type table for VMB4LEDPWM-20)
109
- glass-panel-types.js All 27 glass panel module types in one registry (hasOled/hasPir/
109
+ glass-panel-types.js All 29 glass panel module types in one registry (hasOled/hasPir/
110
110
  hasOc/minMapVer flags per type)
111
111
  pir-types.js / pir-types-20.js PIR sensor module type registries
112
112
  sensor-types.js / sensor-types-20.js Sensor/meteo module type registries
@@ -144,6 +144,24 @@ boilerplate (bridge lookup, packet registration, status bar handling, firmware c
144
144
  V2 nodes) is consistent across every node and easy to get subtly wrong if rebuilt from
145
145
  scratch.
146
146
 
147
+ **Adding or fixing a module type touches three separate files, not one — confirmed
148
+ the hard way (v0.9.2 → v0.9.3).** A module type used to exist in exactly one place per
149
+ node family; it doesn't. Check all three every time:
150
+ 1. `lib/<family>-types[-20].js` — used by the actual node at runtime (`velbus-glass-panel`,
151
+ `velbus-sensor`, etc.)
152
+ 2. The corresponding node's own `.html` file — a **separate, duplicate** copy used only
153
+ to populate the editor's address dropdown and type hint. Not read by the running node
154
+ at all.
155
+ 3. `nodes/velbus-scan/velbus-scan.js` — has its **own, third, entirely independent** copy
156
+ (`ALL_TYPES`, `NODE_SUGGESTION`, `MODULE_CHANNELS`), used only for bus-discovery
157
+ reporting. Doesn't read either of the other two files.
158
+
159
+ Missing any one of the three produces a real, user-visible symptom in a *different* part
160
+ of the palette than wherever the fix actually needs to happen — a scan reporting
161
+ `unknown_0x28` while the glass-panel node itself handles that type perfectly correctly,
162
+ for instance. When in doubt, `grep -rln "<ModuleName>'" nodes/ lib/` to find every file
163
+ that mentions a given type by name, rather than assume you've found all the copies.
164
+
147
165
  ---
148
166
 
149
167
  ## 4. Architecture overview
@@ -244,6 +262,22 @@ implementing a new packet handler, always write out the DATABYTE-to-`body[]` ind
244
262
  mapping explicitly as a comment before writing the parsing logic — it is not something
245
263
  to trust from memory or infer quickly.
246
264
 
265
+ **The canonical real example: `velbus-button` shipped this exact bug from its very
266
+ first version (v0.5.2) until v0.9.4** — over 30 versions, undetected. Its `0x00`
267
+ handler read `body[0]`/`body[1]`/`body[2]` for pressed/released/long-pressed, when the
268
+ correct indices are `body[1]`/`body[2]`/`body[3]`. The practical effect: `pressed` was
269
+ always empty (silently reading the constant command byte), `released` actually reported
270
+ what was really the pressed bitmask, `longPressed` reported what was really the released
271
+ bitmask, and the real long-press data was never read at all. It went unnoticed for this
272
+ long specifically because `velbus-glass-panel`'s own, separately-written `0x00` handler
273
+ got the indexing right from the start, and nobody had directly exercised
274
+ `velbus-button`'s press/release distinction against real hardware — only its
275
+ scan/discovery path (`0xFF`/`0xB0`, handled entirely separately) had seen real traffic.
276
+ A bug in one packet handler is invisible from the outside if nothing forces that
277
+ specific handler to run against real data. If you're verifying a node, verify the
278
+ specific packet type you care about — passing scan/discovery doesn't imply anything
279
+ about a different command byte's handler in the same file.
280
+
247
281
  ### 4.4 Address format
248
282
 
249
283
  Module addresses are stored as **hex strings in the editor's dropdown UI** (e.g.
@@ -266,18 +300,18 @@ or a string before assuming the parsing logic itself is wrong.
266
300
  | `velbus-relay-20` | Velbus (outputs) | V2 relays: VMB1RYS-20, VMB4RYLD-20, VMB4RYNO-20 |
267
301
  | `velbus-dimmer` | Velbus (outputs) | Original-series dimmers: VMBDMI, VMBDMI-R, VMB4DC |
268
302
  | `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 27 glass panel types (original + V2), buttons/OLED/PIR/open-collector as applicable per type |
303
+ | `velbus-glass-panel` | Velbus (inputs) | All 29 glass panel types (original + V2), buttons/OLED/PIR/open-collector as applicable per type |
270
304
  | `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 |
305
+ | `velbus-button` | Velbus (inputs) | 12 types across original and V2 series (VMB8PB, VMB8PBU, VMB6PBN, VMB2PBN, VMB4PB, VMB6PB-20, VMB8IR, VMB4PD, VMB4RF, VMBRFR8S, VMBVP01, VMBKP, VMBIN) — plain button events for all; lock/unlock and richer status decode for the 8 types confirmed to support them; fixed semantic channel labels for VMBVP01 (DoorBird) |
272
306
  | `velbus-pir` | Velbus (inputs) | Original-series PIR: VMBPIRO-10, VMBPIRM, VMBPIRC, VMBPIRO |
273
307
  | `velbus-pir-20` | Velbus (inputs) | V2 PIR: VMBPIR-20, VMBPIRO-20 |
274
308
  | `velbus-meteo` | Velbus (inputs) | Weather station: VMBMETEO |
275
- | `velbus-sensor` | Velbus (inputs) | Original-series input/analogue: VMB7IN, VMB4AN |
309
+ | `velbus-sensor` | Velbus (inputs) | Original-series input/analogue: VMB7IN, VMB4AN, VMB6IN |
276
310
  | `velbus-sensor-20` | Velbus (inputs) | V2 input: VMB8IN-20 |
277
311
  | `velbus-blind` | Velbus (outputs) | VMB1BL, VMB2BL |
278
312
  | `velbus-blind-s` | Velbus (outputs) | VMB1BLS, VMB2BLE, VMB2BLE-10 |
279
313
  | `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) |
314
+ | `velbus-clock` | Velbus (outputs) | **No fixed module address.** Broadcasts system time/date/DST to the bus broadcast address (`0x00`), sets clock alarms and sunrise/sunset enable state either globally (broadcast) or locally (a specific module, via a per-message address override) |
281
315
  | `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
316
 
283
317
  Palette group colours: **Velbus (inputs)** is teal (`#3A8C8C`), **Velbus (outputs)** is
@@ -313,23 +347,31 @@ blue (`#4A90D9`).
313
347
  | 0x2F | VMBDMI-R | velbus-dimmer | n/a |
314
348
  | 0x4B | VMB8DC-20 | velbus-dimmer-20 | n/a |
315
349
 
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, 27 types)
350
+ ### Push buttons (all → velbus-button, 12 types)
351
+ | Type byte | Module | Lock/unlock | Notes |
352
+ |---|---|---|---|
353
+ | 0x01 | VMB8PB | No | Simpler 0xED (LED status only), no lock command at all |
354
+ | 0x16 | VMB8PBU | Yes | |
355
+ | 0x17 | VMB6PBN | Yes | |
356
+ | 0x18 | VMB2PBN | — | Not yet cross-checked for lock support, treated conservatively |
357
+ | 0x44 | VMB4PB | Yes | **Type byte corrected 09/07/2026** — was wrongly keyed 0x1C in `velbus-button.js`'s own registry (not a real type byte at all); `velbus-scan.js` always had 0x44 right |
358
+ | 0x4C | VMB6PB-20 | Yes | **Type byte corrected 09/07/2026** — was wrongly keyed 0x20 (which is actually VMBGP4, a different module); `velbus-scan.js` always had 0x4C right |
359
+ | 0x0A | VMB8IR | No | IR receiver — presents fixed Velbus IR codes as button events |
360
+ | 0x0B | VMB4PD | No | LCD module — only the 4 button channels covered, not the LCD |
361
+ | 0x1A | VMB4RF | Yes | 4 channels (matches its name) — status uses 0xB4, not 0xED, so rich status is not decoded even though lock/unlock works |
362
+ | 0x30 | VMBRFR8S | Yes | |
363
+ | 0x33 | VMBVP01 | No | DoorBird — fixed semantic labels (Motion 1/2, Bell 1/2, Door 1/2, Virtual button 1/2), different/shorter 0xED not decoded |
364
+ | 0x42 | VMBKP | Yes | |
365
+ | 0x43 | VMBIN | Yes | Single channel |
366
+
367
+ ### Glass panels (all → velbus-glass-panel, 29 types)
327
368
  | Type byte | Module | OLED | PIR | Open collector | Min. map version |
328
369
  |---|---|---|---|---|---|
329
370
  | 0x1E | VMBGP1 | no | no | unconfirmed¹ | — |
330
371
  | 0x1F | VMBGP2 | no | no | unconfirmed¹ | — |
331
372
  | 0x20 | VMBGP4 | no | no | unconfirmed¹ | — |
332
373
  | 0x21 | VMBGPO | yes | no | yes | 2 |
374
+ | 0x25 | VMBGPTC | yes | no | no | — |
333
375
  | 0x28 | VMBGPOD | yes | no | no | — |
334
376
  | 0x2D | VMBGP4PIR | no | yes | unconfirmed¹ | — |
335
377
  | 0x34 | VMBEL1 | no | no | yes | 2 |
@@ -341,6 +383,7 @@ blue (`#4A90D9`).
341
383
  | 0x3B | VMBGP2-2 | no | no | no² | — |
342
384
  | 0x3C | VMBGP4-2 | no | no | no² | — |
343
385
  | 0x3D | VMBGPOD-2 | yes | no | unconfirmed¹ | 2 |
386
+ | 0x3E | VMBGP4PIR-2 | no | yes | unconfirmed¹ | — |
344
387
  | 0x47 | VMBEL2PIR | no | yes | yes | — |
345
388
  | 0x4F | VMBEL1-20 | no | no | yes | — |
346
389
  | 0x50 | VMBEL2-20 | no | no | yes | — |
@@ -354,6 +397,14 @@ blue (`#4A90D9`).
354
397
  | 0x5C | VMBEL2PIR-20³ | no | yes | yes | — |
355
398
  | 0x5F | VMBGP4PIR-20 | no | yes | unconfirmed¹ | — |
356
399
 
400
+ ⁴ VMBGPTC (0x25) shares its actual protocol document with VMBGPO (0x21) — a
401
+ thermostat-only variant of the same touch panel hardware, not a separate
402
+ product. Type byte confirmed from the official type list, not spelled out
403
+ separately in the shared document's body. VMBGP4PIR-2 (0x3E) has genuinely
404
+ different channel 5-8 semantics from its 0x2D sibling despite the similar
405
+ name (Dark/Light output, Motion output, Light-depending-motion, Absence
406
+ output — not virtual/dark/light/motion) — confirmed directly, not assumed.
407
+
357
408
  ¹ Open-collector support unconfirmed against real hardware — see [section 13](#13-known-open-issues).
358
409
  ² Confirmed no open-collector commands in the protocol PDF, but not yet live-verified.
359
410
  ³ The official Velbus type list shows different module names at these two type bytes than
@@ -377,6 +428,7 @@ what this registry currently uses — flagged for verification, see
377
428
  | 0x31 | VMBMETEO | velbus-meteo |
378
429
  | 0x32 | VMB4AN | velbus-sensor |
379
430
  | 0x4E | VMB8IN-20 | velbus-sensor-20 |
431
+ | 0x05 | VMB6IN | velbus-sensor — simpler sibling of VMB7IN, no lock/unlock at all, 5-byte 0xED (vs VMB7IN's 7) safely skipped by the existing length guard |
380
432
 
381
433
  ### Blind / shutter
382
434
  | Type byte | Module | Node |
@@ -476,6 +528,36 @@ body[6] = error bitmask
476
528
  body[7] = alarm/program byte
477
529
  ```
478
530
 
531
+ ### 7.5a `0xB8` dimmer status — original series (VMBDMI, VMBDMI-R, VMB4DC)
532
+
533
+ **Not the same shape as 7.5 above, despite both being "dimmer status."**
534
+ Confirmed identical across all three original-series protocol PDFs — one
535
+ combined status byte, not separate mode/status bytes the way relay modules
536
+ or the V2 dimmer status have:
537
+ ```
538
+ body[0] = 0xB8
539
+ body[1] = channel (VMBDMI/-R: always 0x01; VMB4DC: bitmask)
540
+ body[2] = status byte — MULTIPLE bit-fields packed together:
541
+ bits 0-1: run mode (00=normal, 01=inhibited, 10=forced_on, 11=disabled)
542
+ bits 2-3: error (VMBDMI/-R only — VMB4DC has no thermal section at all)
543
+ bit 4: load type (VMBDMI/-R only: 0=resistive, 1=inductive)
544
+ bits 5-7: temperature band (VMBDMI/-R only, 8 levels)
545
+ body[3] = dim value (0-100%)
546
+ body[4] = LED indicator status ONLY — unrelated to run state (0x00 off,
547
+ 0x80 on, 0x40 slow blink, 0x20 fast blink, 0x10 very fast blink)
548
+ body[5-7] = 24-bit current delay time, MSB first
549
+ ```
550
+ There is no separate `"forced_off"` status — that's a real, distinct
551
+ *command* (0x12) you can send, but the module reports its result through
552
+ the same four-value run-mode set above, same as relay's forced/inhibited
553
+ pattern conceptually, but packed into one byte here rather than two.
554
+ `on` is true for `forced_on`, or for `normal` mode with a nonzero dim
555
+ value — **not** simply "mode equals the literal string on," which was the
556
+ real bug (v0.10.1): a dimmer in ordinary normal-running mode — by far the
557
+ most common real-world state — was being read as `off` regardless of dim
558
+ level, because the code checked the LED status byte's low bits for
559
+ confirmation instead of using the dim value directly.
560
+
479
561
  ### 7.6 `0xA5` dim level — up to four channels packed per packet
480
562
  ```
481
563
  body[0] = 0xA5, body[1] = channel, body[2] = level (0-254)
@@ -603,7 +685,7 @@ about why in the commit message.
603
685
 
604
686
  - **Generational split.** Nodes generally split at the V2.0 boundary — one node for
605
687
  original-series, a separate node for V2. **Exceptions:** `velbus-glass-panel` (one
606
- node covers all 27 types, both generations, via the type registry's per-type flags
688
+ node covers all 29 types, both generations, via the type registry's per-type flags
607
689
  rather than a code-level split), `velbus-thermostat` (covers the thermostat function
608
690
  on any glass panel type), `velbus-meteo` (only one generation exists), and the blind
609
691
  family (`velbus-blind` / `velbus-blind-s` / `velbus-blind-20`, split by actual protocol
@@ -757,6 +839,25 @@ orientation at the time of writing:
757
839
 
758
840
  - **Field-tested against live hardware:** the core relay, dimmer, glass panel, and
759
841
  thermostat nodes have real-hardware confirmation for their primary functions.
842
+ `VMBGPOD` (0x28) specifically confirmed 09/07/2026 against two real panels on
843
+ Stuart's own home bus, closing out the v0.9.2/v0.9.3 registry-gap saga with an
844
+ actual result rather than just a passing test.
845
+ - **`velbus-dimmer`'s `0xB8` status decode was fundamentally wrong from when it was
846
+ first written until v0.10.1** — see section 7.5a. A dimmer in ordinary "normal
847
+ running" mode always reported `off` regardless of actual dim level. Fixed and
848
+ verified via the mock harness against the exact reported scenario, but not yet
849
+ re-confirmed against Stuart's real VMBDMI.
850
+ - **`velbus-glass-panel`'s `0xEA` thermostat status has been silently crashing
851
+ since it was written, until v0.10.1** — see section 16's code style rules for
852
+ the full story. The `type:"thermostat"` payload has likely never once been
853
+ successfully delivered for any real thermostat-equipped panel. Fixed and swept
854
+ for the same pattern elsewhere in the file (none found), but not yet
855
+ re-confirmed against Stuart's real hardware.
856
+ - **`velbus-button` had a critical, real bug from its first version until v0.9.4** —
857
+ see section 4.3 for the full story. Its press/release/long-press decode is now
858
+ fixed and verified with a real repro, but has not yet been re-confirmed against a
859
+ live button press on real hardware (only via the mock harness) — worth doing
860
+ before trusting it fully, given how long the broken version went unnoticed.
760
861
  - **Mock-harness verified only, not yet confirmed against a real bus:** `velbus-clock`
761
862
  (both the time/date/DST broadcast and the `set_alarm` command), the
762
863
  `velbus-dimmer-20` `get_device_type` read command, and `velbus-energy` in its
@@ -772,9 +873,10 @@ orientation at the time of writing:
772
873
  ## 13. Known open issues
773
874
 
774
875
  - **`VMBKP` (0x42, "Keypad interface module") has no node at all.** Found scanning a
775
- real installation (Stuart's home) alongside the `VMBGPOD` gap below a genuinely new
776
- module type, not yet scoped. Its protocol PDF (`protocol_vmbkp.pdf`, 28 pages) is
777
- substantial: channel status, module status, and a full per-channel LED control layer
876
+ real installation (Stuart's home) confirmed present at address `0xFD`, a real
877
+ module on a real bus, not a hypothetical. A genuinely new module type, not yet
878
+ scoped. Its protocol PDF (`protocol_vmbkp.pdf`, 28 pages) is substantial: channel
879
+ status, module status, and a full per-channel LED control layer
778
880
  (clear/set/slow-blink/fast-blink/very-fast-blink), similar in spirit to `velbus-button`
779
881
  but with LED feedback control `velbus-button` doesn't have. This needs the same
780
882
  "how much work would this involve" scoping pass `velbus-energy` got before it was
@@ -808,6 +910,33 @@ orientation at the time of writing:
808
910
  against the year's January/July offsets to infer whether daylight saving is active)
809
911
  has only been sanity-checked in an environment where daylight saving never applies —
810
912
  a genuine positive "DST is active" case hasn't been observed and confirmed correct.
913
+ - **`VMB1DM`, `VMBDME`, `VMB1LED` (dimmer-family additions) deferred, not built.**
914
+ All three use a genuinely different single-channel `0xEE` status layout
915
+ (mode/dim-value/LED/timer/config in one packet) — distinct from both
916
+ `velbus-dimmer`'s own `0xB8`-based format and `velbus-dimmer-20`'s
917
+ multi-channel bitmask `0xEE` format. Needs real new decode logic, not a
918
+ registry entry — see `coverage-roadmap.md` for the full reasoning.
919
+ - **Bus error counter (`0xDA`) — design settled, not built.** Confirmed
920
+ useful but explicitly framed as a rare edge case. Resolved design: every
921
+ node that registers for its own address already receives an unsolicited
922
+ `0xDA` broadcast if one occurs, so no new request command is needed —
923
+ just passive decoding, emitted only on a secondary output and only when
924
+ at least one counter is non-zero, so it never appears during normal
925
+ operation. Deliberately deferred since it touches most/all existing nodes
926
+ — a session of its own, not a quick addition.
927
+ - **OLED image writing — stretch goal, not built.** Pushing a custom B&W
928
+ 1-bit bitmap to an OLED glass panel (e.g. swapping in a different-language
929
+ greeting for a visitor without opening VelbusLink) — genuine use case,
930
+ explicitly not urgent. `velbus-glass-panel` currently only reads memo text
931
+ (`0xAC`), no write path exists in either direction for display content.
932
+ - **`VMBDALI`/`VMBDALI-20`, `VMBLCDWB`, `VMCM3`, `VMBSIG`/`VMBSIG-20`/`VMBSIG-21`
933
+ — recognized, deliberately not supported.** These show their correct name
934
+ in a scan (not `unknown_0xNN`) with an explicit `"Not supported"` in place
935
+ of a suggested node, rather than silently falling through to `null`. DALI
936
+ is its own protocol layer beyond the gateway; the Signum types are a
937
+ proprietary HomeAssistant-based master clock, not interactable; VMBLCDWB
938
+ and VMCM3 are legacy/custom modules with no planned support. By design,
939
+ not oversight — see `coverage-roadmap.md` for the full per-type reasoning.
811
940
 
812
941
  ---
813
942
 
@@ -894,6 +1023,53 @@ git push --tags
894
1023
  exceptions — see section 4.3 if this isn't already second nature.
895
1024
  - **Numbers are always numbers in payloads, never strings.**
896
1025
  - **`results.modules`, not `results`,** when reading the scan endpoint.
1026
+ - **A module type lives in three separate files, not one** (per-node `lib/` registry,
1027
+ that node's own `.html` dropdown copy, and `velbus-scan.js`'s independent copy) —
1028
+ see section 3 for the full explanation. Missing one produces a symptom in a
1029
+ different part of the palette than wherever the type was actually added.
1030
+ - **Never assume a feature is universal across a module family — check each type's
1031
+ own protocol document.** Proven wrong repeatedly (09/07/2026) while adding
1032
+ lock/unlock and richer status decode to `velbus-button`: of 12 button-family
1033
+ types, 4 genuinely lack the lock/unlock command entirely (not "probably most
1034
+ don't" — specific, named exceptions); one type's status uses a completely
1035
+ different command byte (`0xB4` instead of `0xED`); the "which channel" selector
1036
+ byte in the name-request commands uses two incompatible conventions (bitmask vs.
1037
+ literal number) that happen to produce identical values for channels 1-2 and only
1038
+ diverge from channel 3 onward — exactly the kind of divergence that survives
1039
+ casual testing. When a person asks for a capability "because it's a key feature,"
1040
+ that's a reason to verify it broadly, not a reason to skip checking each type.
1041
+ - **Registry type-byte keys need the same verification as everything else** — don't
1042
+ assume an existing entry's key is correct just because it's already shipped.
1043
+ Found 09/07/2026: `VMB4PB` and `VMB6PB-20` had been keyed under wrong type bytes
1044
+ in `velbus-button.js`'s own registry since v0.5.2 (`0x1C`, which isn't a real
1045
+ Velbus type byte at all, and `0x20`, which actually belongs to `VMBGP4`) — while
1046
+ `velbus-scan.js` had always had the correct values. Cross-check against the
1047
+ official type list (section 15) periodically, not just when adding something new.
1048
+ - **Never reference a variable that was only ever assigned as an object property.**
1049
+ Real bug, live since the code was written (fixed v0.10.1): `velbus-glass-panel`'s
1050
+ `0xEA` handler built `currentTemp`/`targetTemp` only inside a `payload = {...}`
1051
+ object literal, then referenced them as bare identifiers on the very next line
1052
+ (`setStatus('...' + currentTemp.toFixed(1) + ...)`). Since JS evaluates a
1053
+ function's arguments before calling it, this threw a `ReferenceError` *before*
1054
+ `node.send()` on the following line — meaning the entire payload silently never
1055
+ went out, for as long as this code existed. The bridge's own dispatch-error
1056
+ handling caught the exception each time rather than crashing the process, which
1057
+ is exactly what let this go unnoticed rather than being immediately obvious.
1058
+ If a value is going into both a status/log string and a payload object, declare
1059
+ it as its own `const` first and use that name in both places — never build it
1060
+ only inside the object literal and assume you can reference it by name outside.
1061
+ - **Don't assume a "two-byte status" pattern applies to every module family.**
1062
+ Real bug, live since the code was written (fixed v0.10.1): the original-series
1063
+ dimmer's `0xB8` status was assumed to work like relay's genuine separate
1064
+ mode-byte + status-byte pair. In reality `VMBDMI`/`VMBDMI-R`/`VMB4DC` pack
1065
+ run-mode, error, load-type, and temperature into a *single* status byte
1066
+ (confirmed identical across all three protocol PDFs — see section 7.5a), and
1067
+ the code was additionally reading the LED indicator byte as if it were a second
1068
+ status word. The result: a dimmer in ordinary "normal running" mode — the most
1069
+ common real-world state — always reported `off` regardless of actual dim level.
1070
+ Same underlying lesson as the button-family capability work: check each
1071
+ module's own protocol document for its actual byte layout, don't port a pattern
1072
+ from a different (even superficially similar) module family.
897
1073
  - **Thermostat commands go to the primary address only,** never a subaddress.
898
1074
  - **Respect the 20ms minimum between `0xFC` writes** on original-series modules — this
899
1075
  is real EEPROM wear, not an arbitrary rate limit.
@@ -61,6 +61,24 @@ const GLASS_PANEL_TYPES = {
61
61
  minMapVer: 2,
62
62
  series: 'original',
63
63
  },
64
+ 0x25: {
65
+ // Added 09/07/2026. Shares its actual protocol document with VMBGPO
66
+ // (protocol_vmbgpo_vmbgptc.pdf) — a thermostat-only variant of the same
67
+ // touch panel hardware, not a separate product. Type byte 0x25 confirmed
68
+ // from the official Velbus type list, not spelled out separately in the
69
+ // body of the shared document (it only gives VMBGPO's H'21' as a worked
70
+ // example). hasOc set conservatively to false — the shared document has
71
+ // no "collector" mention at all for either variant, unlike whatever
72
+ // source justified VMBGPO's own hasOc:true above (a different document,
73
+ // not re-verified here — that entry is untouched).
74
+ name: 'VMBGPTC',
75
+ channels: 32,
76
+ hasOled: true,
77
+ hasPir: false,
78
+ hasOc: false,
79
+ minMapVer: null,
80
+ series: 'original',
81
+ },
64
82
  0x28: {
65
83
  name: 'VMBGPOD',
66
84
  // Added 09/07/2026 — found as "unknown_0x28" scanning a real installation.
@@ -98,6 +116,24 @@ const GLASS_PANEL_TYPES = {
98
116
  minMapVer: null,
99
117
  series: 'original',
100
118
  },
119
+ 0x3E: {
120
+ // Added 09/07/2026. IMPORTANT: this is NOT the same channel 5-8 mapping
121
+ // as its 0x2D sibling above, despite the similar name — confirmed
122
+ // directly from protocol_vmbgp4pir_ed2.pdf, which gives an entirely
123
+ // different set of labels. Copying the 0x2D mapping here would have
124
+ // silently mislabeled four channels.
125
+ name: 'VMBGP4PIR-2',
126
+ channels: 8,
127
+ hasOled: false,
128
+ hasPir: true,
129
+ hasOc: null, // no mention found in this PDF either — same unconfirmed state as 0x2D
130
+ pirChannels: {
131
+ 1: 'button1', 2: 'button2', 3: 'button3', 4: 'button4',
132
+ 5: 'dark_light', 6: 'motion', 7: 'light_motion', 8: 'absence',
133
+ },
134
+ minMapVer: null,
135
+ series: 'original',
136
+ },
101
137
  0x34: {
102
138
  name: 'VMBEL1',
103
139
  channels: 1,