node-red-contrib-velbus-2026 0.9.3 → 0.10.2

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,261 @@ 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.2 — 09/07/2026
33
+
34
+ ### velbus-sensor — VMB4AN generic analogue reading (channels 9-12)
35
+
36
+ - **Prompted by Stuart noticing `isVMB4AN` was defined but never used** —
37
+ confirmed the sensor-input channels (Group 2 of VMB4AN's three functional
38
+ groups) produced literally no output at all beforehand; the packet
39
+ carrying that data wasn't handled anywhere in the file.
40
+ - **Real trap found and avoided:** the obviously-named `0xEA`
41
+ ("sensor status") packet does **not** carry a value at all — it's
42
+ operating-mode/sleep-timer/auto-send configuration only. The actual
43
+ reading lives in a separate, easy-to-miss packet earlier in the protocol
44
+ document: `0xA9` (`COMMAND_SENSOR_RAW_DATA`, "Transmit the sensor raw
45
+ value"), genuinely standalone with no need to cross-reference anything
46
+ else. Also confirmed `0xE8`/`0xE9` ("sensor settings") is preset
47
+ *configuration* storage, not a live value either — three different
48
+ packets that could each plausibly have been mistaken for "the reading."
49
+ - Deliberately generic, per explicit preference: emits `{ type: "analogue",
50
+ channel, mode, raw }` with no engineering-unit conversion attempted —
51
+ `mode` (voltage/current/resistance/period) and the PDF's resolution
52
+ table are exposed in the node's help so conversion can happen in the flow
53
+ instead.
54
+ - New `get_analogue` input command (`0xE5`, request-a-reading-now) — uses
55
+ the protocol's own "auto-send config byte = 0" option to request a value
56
+ without side-effecting the module's existing auto-send schedule.
57
+ **Priority byte corrected during verification:** the protocol PDF
58
+ explicitly states "SID10-SID9 = 11 (lowest priority)" for this specific
59
+ command — `0xFB`, not the `0xF8` most other commands in this file use.
60
+ Checked directly rather than assumed, since this is the one place it
61
+ genuinely differs from the file's established convention.
62
+ - Both confirmed absent from `VMB7IN`'s own protocol PDF before wiring
63
+ in — this file's switch statement handles both module types together,
64
+ so a collision would misfire for `VMB7IN` modules if either byte had
65
+ been reused there.
66
+ - **Full Group 2 configuration (presets, mode-switching, sleep time,
67
+ offset, auto-send interval config) and all of Group 3 (analog outputs)
68
+ remain parked** — see `coverage-roadmap.md` section 6. This is
69
+ deliberately just the minimal reading piece, not the full feature.
70
+
71
+ Verified via the mock-RED harness: voltage/period modes, both period
72
+ special-value cases (`0x000000` short-circuit, `0xFFFFFF` open-circuit),
73
+ `VMB7IN` isolation confirmed (same bytes produce no output on that type),
74
+ `get_analogue` checksum hand-verified, correctly rejected on non-VMB4AN
75
+ types. Not yet sent to a real bus.
76
+
77
+ ---
78
+
79
+ ## v0.10.1 — 09/07/2026
80
+
81
+ ### Two critical bugs found on real hardware — velbus-dimmer and velbus-glass-panel
82
+
83
+ **velbus-dimmer — `on`/`state` always wrong for original-series dimmers.**
84
+ Reported by Stuart: a VMBDMI at 75% dim showing `state:"off"`, `on:false`.
85
+ Root cause was a genuine misunderstanding of the protocol, not a small
86
+ off-by-one: `0xB8`'s `DATABYTE3` packs run-mode, error, load-type, and
87
+ temperature band **all into one status byte** (confirmed identical across
88
+ `protocol_vmbdmi.pdf`, `protocol_vmbdmi_r.pdf`, and `protocol_vmb4dc.pdf`).
89
+ The previous code treated this as if it were a separate mode-byte +
90
+ status-byte pair (the way relay modules genuinely have), and additionally
91
+ read `DATABYTE5` (LED indicator status — real values `0x00`/`0x80`/`0x40`/
92
+ `0x20`/`0x10`) as a second status word, checking its low 2 bits for
93
+ confirmation. Since none of the real LED status values have those bits
94
+ set, a dimmer in ordinary "normal running" mode — the overwhelmingly
95
+ common case — always fell through to `'off'`, regardless of actual dim
96
+ level. Also fixed in the same pass: the 24-bit current-delay timer was
97
+ being read as only 16 bits from the wrong byte offset, and `decodeThermal`
98
+ was being called on the LED byte instead of the real status byte (explains
99
+ why `thermal` always showed all zeros in the field report). `ledState` is
100
+ now correctly decoded and added to the payload. Verified against the exact
101
+ reported scenario plus inhibited/forced_on/disabled/thermal-alarm cases,
102
+ with the bit-field math for the packed status byte hand-checked.
103
+
104
+ **velbus-glass-panel — `0xEA` thermostat status has been silently crashing
105
+ since it was written.** Reported by Stuart via a `"velbus-bridge dispatch
106
+ error: currentTemp is not defined"` message appearing repeatedly while
107
+ testing a VMBGP2. Root cause: `currentTemp` and `targetTemp` were only ever
108
+ assigned as properties of the `payload` object literal, never declared as
109
+ their own variables — but the very next line referenced them as bare
110
+ identifiers in the `setStatus(...)` call. Because JS evaluates a function's
111
+ arguments before calling it, this threw a `ReferenceError` before
112
+ `node.send()` on the following line ever executed. **This means the
113
+ `type:"thermostat"` payload has likely never once been successfully
114
+ delivered for any thermostat-equipped glass panel** — the bridge's
115
+ dispatch-error handling caught the exception each time rather than
116
+ crashing the whole process, which is exactly why this went unnoticed for
117
+ so long rather than being immediately obvious. Fixed by declaring both as
118
+ proper local `const`s before use. Did a full sweep of every other packet
119
+ case in this file (button, module status, temperature, light sensor, memo
120
+ text, counter, name parts) via the mock harness afterward, specifically
121
+ exercising the OLED- and PIR-gated branches too — no other instances
122
+ found. Also swept every other node file for the same bare-identifier
123
+ `.toFixed()` pattern that caused this — one other match (`velbus-meteo`)
124
+ checked and confirmed already correct (properly prefixed with `payload.`).
125
+
126
+ Both verified via the mock-RED harness against the exact reported symptoms
127
+ before and after the fix — not just re-reading the corrected code. Not yet
128
+ re-confirmed on Stuart's real hardware.
129
+
130
+ ---
131
+
132
+ ## v0.10.0 — 09/07/2026
133
+
134
+ ### Coverage roadmap implementation — 9 new module types, velbus-button overhaul, velbus-clock sunrise/sunset
135
+
136
+ Following a full rationalization pass over every unaddressed Velbus module
137
+ type and feature (see `coverage-roadmap.md`), this implements everything
138
+ confirmed in scope. Every single item below was checked against its actual
139
+ protocol document before being added — several real divergences were caught
140
+ in the process that would otherwise have shipped silently wrong.
141
+
142
+ **velbus-button — substantial overhaul, not just new registry entries:**
143
+ - 7 new module types: VMB8IR, VMB4PD, VMB4RF, VMBRFR8S, VMBVP01, VMBKP, VMBIN.
144
+ - **Lock/unlock (0x12/0x13)** — confirmed present on 8 of the 12 total types
145
+ now covered; **NOT universal**, despite being asked for as "a key Velbus
146
+ feature." VMB8PB, VMB8IR, VMB4PD, and VMBVP01 genuinely lack this command
147
+ in their own protocol documents. Gated per-type (`hasLock`) — sending it to
148
+ an unsupported type now warns clearly on output 2 rather than silently
149
+ doing nothing.
150
+ - **Richer 0xED status decode** (locked/enabled/inverted/program-disabled) —
151
+ also NOT universal. VMB8PB's 0xED is a completely different, simpler
152
+ LED-only format; VMB4RF's status uses command byte 0xB4, not 0xED, with a
153
+ different field at DATABYTE4 ("learn transmitter mode"); VMBVP01's 0xED is
154
+ a third, shorter shape again. Gated per-type (`hasRichStatus`) — decoding
155
+ is skipped entirely for types that don't match, rather than risk
156
+ misreading whatever they actually send.
157
+ - **Channel names surfaced in event output** (0xF0/F1/F2) — found the
158
+ selector byte convention itself is inconsistent across types: some use a
159
+ bitmask (one bit per channel), others a literal 1-based number. These
160
+ produce the *same* byte value for channels 1-2, diverging only from
161
+ channel 3 onward — exactly the kind of thing that would pass casual
162
+ testing and then silently corrupt every name from channel 3 up. Verified
163
+ explicitly at channel 3 for both conventions before shipping.
164
+ - **VMBVP01 (DoorBird)** gets fixed semantic channel labels (Motion 1/2,
165
+ Bell 1/2, Door 1/2, Virtual button 1/2) — hardware-fixed functions, not
166
+ VelbusLink-configurable names, so not sourced from 0xF0-F2 at all.
167
+ - Output changed from 1 to 2 (events/status, warnings) — non-breaking for
168
+ existing flows, the new output simply has no wires by default.
169
+ - **Real mistake caught before shipping:** first pass had VMB4RF at 8
170
+ channels; its own status packet says "channel 1 to 4," matching its name.
171
+ Corrected to 4 before release, not after.
172
+ - **A second, more serious pre-existing bug found and fixed, unrelated to
173
+ today's additions:** `VMB4PB` and `VMB6PB-20` — two of the *original* five
174
+ button types, present since v0.5.2 — were registered under wrong type
175
+ bytes (`0x1C` and `0x20`) in this file's own registry. `0x1C` isn't a real
176
+ Velbus type byte at all; `0x20` actually belongs to `VMBGP4`, an unrelated
177
+ glass panel type. `velbus-scan.js` has always had the correct values
178
+ (`0x44`/`0x4C`) — only this file's internal lookup was wrong, meaning a
179
+ real `VMB4PB` or `VMB6PB-20`, correctly identified by a scan, would never
180
+ have matched this file's own type descriptor at all. Every type-specific
181
+ feature (and now, lock/unlock and rich status too) would have silently
182
+ never activated for these two types. Found by cross-referencing the
183
+ official type list while writing this changelog entry, not by design —
184
+ worth remembering that documentation review can surface real bugs too.
185
+
186
+ **velbus-sensor:** VMB6IN added. Confirmed simpler than its VMB7IN sibling,
187
+ not just a smaller version of it — no lock/unlock command exists for it at
188
+ all, and its 0xED module status is 5 bytes vs VMB7IN's 7. The existing
189
+ `body.length < 7` guard already skips it safely; no code change needed
190
+ beyond the registry entry itself.
191
+
192
+ **velbus-glass-panel:** VMBGP4PIR-2 (0x3E) and VMBGPTC (0x25) added.
193
+ - **Real mistake caught:** VMBGP4PIR-2's channels 5-8 have completely
194
+ different semantics from its 0x2D sibling despite the near-identical name
195
+ (Dark/Light output, Motion output, Light-depending-motion, Absence output
196
+ — not virtual/dark/light/motion). Copying the sibling's mapping would have
197
+ silently mislabeled four channels.
198
+ - VMBGPTC confirmed sharing its actual protocol document with VMBGPO
199
+ (0x21) — a thermostat-only variant of the same panel hardware, added to
200
+ the glass-panel registry rather than as thermostat-node-only, so
201
+ `velbus-thermostat` picks up its function automatically the same way it
202
+ already does for every other panel address.
203
+
204
+ **velbus-clock:** sunrise/sunset enable/disable (0xAE) added, same
205
+ global/local address pattern as the existing `set_alarm` — confirmed
206
+ identical packet body for both from the protocol PDF.
207
+
208
+ **velbus-scan:** all of the above added across its three independent tables
209
+ (`ALL_TYPES`/`NODE_SUGGESTION`/`MODULE_CHANNELS`) — the exact lesson from
210
+ the VMBGPOD saga (v0.9.2/v0.9.3), applied proactively this time rather than
211
+ discovered the hard way again. Also adds explicit `"Not supported"` scan
212
+ labels (rather than falling through to a bare name with no node) for
213
+ VMBDALI, VMBDALI-20, VMBLCDWB, VMCM3, VMBSIG, VMBSIG-20, and VMBSIG-21 —
214
+ recognized correctly in a scan, deliberately not built, by design rather
215
+ than oversight.
216
+
217
+ **Explicitly deferred, not built this round:** VMB1DM, VMBDME, and VMB1LED
218
+ (the dimmer-family additions) all turned out to use a genuinely different
219
+ single-channel `0xEE` status layout — distinct from both `velbus-dimmer`'s
220
+ own format and `velbus-dimmer-20`'s multi-channel bitmask format. This needs
221
+ real new decode logic, not a registry entry, and doesn't meet the
222
+ "minimal effort" bar set for this round. Parked rather than forced in.
223
+
224
+ **Verification:** every new packet format checked against its actual
225
+ protocol document (not inferred from a same-named sibling) before being
226
+ implemented; every new command exercised through the mock-RED harness with
227
+ hand-checked checksums; the channel-3 naming-convention divergence
228
+ specifically tested for both conventions, not just one; a full simulated
229
+ scan run across every new and "not supported" type confirmed correct
230
+ `suggestedNode`/`channels` output end to end. Not yet sent to a real bus.
231
+
232
+ ---
233
+
234
+ ## v0.9.4 — 09/07/2026
235
+
236
+ ### velbus-button — critical bug, live since v0.5.2: button events shifted by one byte
237
+
238
+ - **Found while scoping VMBKP's decode logic, not reported directly** — checking
239
+ `velbus-button.js` as a template surfaced that its `0x00` handler read
240
+ `body[0]`/`body[1]`/`body[2]` for pressed/released/long-pressed, when
241
+ `body[0]` is always the command byte itself (always `0x00`, per this
242
+ project's own most-repeated rule — see `HANDOVER.md` section 4.3). Every
243
+ field was reading one byte too early.
244
+ - **Real impact, not theoretical:** `pressed` was always empty (reading the
245
+ constant command byte), `released` was actually reporting what DATABYTE2
246
+ (the real "pressed" bitmask) contained, `longPressed` was reporting what
247
+ DATABYTE3 (the real "released" bitmask) contained, and the real
248
+ DATABYTE4 (long-press bitmask) was never read at all. The `on` field
249
+ followed the same corruption — it could report `true` on a release and
250
+ never correctly on an immediate press.
251
+ - **Confirmed live since `velbus-button`'s introduction in v0.5.2** — this
252
+ is not a new regression, it has been shipping incorrect button-event data
253
+ for the entire time this node has existed. `velbus-glass-panel`'s own
254
+ `0x00` handler was checked immediately afterward and confirmed **not**
255
+ affected — it already used the correct `body[1]`/`body[2]`/`body[3]`
256
+ indexing, so this was isolated to one file, not systemic.
257
+ - **Fixed and verified with a real repro, not just a corrected read of the
258
+ code:** built a mock-harness test that reproduces the exact failure first
259
+ (channel 3 pressed came back reported as "released") before applying the
260
+ fix, then re-ran the same test plus three more (release, long-press,
261
+ simultaneous multi-channel press) to confirm all four now report
262
+ correctly.
263
+ - **If you have flows depending on `velbus-button`'s `pressed`/`released`/
264
+ `longPressed` distinction** (rather than just the `on` boolean, or
265
+ scanning/discovery, which were unaffected), check them after updating —
266
+ behaviour that previously "worked" by only watching `on`, or by
267
+ compensating for the shift some other way in the flow itself, may now
268
+ behave differently now that the underlying data is actually correct.
269
+
270
+ ---
271
+
17
272
  ## v0.9.3 — 09/07/2026
18
273
 
19
274
  ### velbus-scan — VMBGPOD still showed "unknown_0x28" after the v0.9.2 fix
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.3, 19 nodes, published on npm.**
9
+ Current state at time of writing: **v0.10.2, 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
@@ -262,6 +262,22 @@ implementing a new packet handler, always write out the DATABYTE-to-`body[]` ind
262
262
  mapping explicitly as a comment before writing the parsing logic — it is not something
263
263
  to trust from memory or infer quickly.
264
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
+
265
281
  ### 4.4 Address format
266
282
 
267
283
  Module addresses are stored as **hex strings in the editor's dropdown UI** (e.g.
@@ -284,18 +300,18 @@ or a string before assuming the parsing logic itself is wrong.
284
300
  | `velbus-relay-20` | Velbus (outputs) | V2 relays: VMB1RYS-20, VMB4RYLD-20, VMB4RYNO-20 |
285
301
  | `velbus-dimmer` | Velbus (outputs) | Original-series dimmers: VMBDMI, VMBDMI-R, VMB4DC |
286
302
  | `velbus-dimmer-20` | Velbus (outputs) | V2 dimmers: VMB2DC-20, VMB8DC-20, VMB4LEDPWM-20 (incl. RGB/RGBW grouping mode) |
287
- | `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 |
288
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 |
289
- | `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) |
290
306
  | `velbus-pir` | Velbus (inputs) | Original-series PIR: VMBPIRO-10, VMBPIRM, VMBPIRC, VMBPIRO |
291
307
  | `velbus-pir-20` | Velbus (inputs) | V2 PIR: VMBPIR-20, VMBPIRO-20 |
292
308
  | `velbus-meteo` | Velbus (inputs) | Weather station: VMBMETEO |
293
- | `velbus-sensor` | Velbus (inputs) | Original-series input/analogue: VMB7IN, VMB4AN |
309
+ | `velbus-sensor` | Velbus (inputs) | Original-series input/analogue: VMB7IN, VMB4AN (channels 1-8 alarm outputs + generic analogue reading on channels 9-12; full sensor/preset config and analog outputs on channels 13-16 remain parked), VMB6IN |
294
310
  | `velbus-sensor-20` | Velbus (inputs) | V2 input: VMB8IN-20 |
295
311
  | `velbus-blind` | Velbus (outputs) | VMB1BL, VMB2BL |
296
312
  | `velbus-blind-s` | Velbus (outputs) | VMB1BLS, VMB2BLE, VMB2BLE-10 |
297
313
  | `velbus-blind-20` | Velbus (outputs) | VMB2BLE-20 |
298
- | `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) |
299
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 |
300
316
 
301
317
  Palette group colours: **Velbus (inputs)** is teal (`#3A8C8C`), **Velbus (outputs)** is
@@ -331,23 +347,31 @@ blue (`#4A90D9`).
331
347
  | 0x2F | VMBDMI-R | velbus-dimmer | n/a |
332
348
  | 0x4B | VMB8DC-20 | velbus-dimmer-20 | n/a |
333
349
 
334
- ### Push buttons
335
- | Type byte | Module | Node |
336
- |---|---|---|
337
- | 0x01 | VMB8PB | velbus-button |
338
- | 0x16 | VMB8PBU | velbus-button |
339
- | 0x17 | VMB6PBN | velbus-button |
340
- | 0x18 | VMB2PBN | velbus-button |
341
- | 0x44 | VMB4PB | velbus-button |
342
- | 0x4C | VMB6PB-20 | velbus-button |
343
-
344
- ### 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)
345
368
  | Type byte | Module | OLED | PIR | Open collector | Min. map version |
346
369
  |---|---|---|---|---|---|
347
370
  | 0x1E | VMBGP1 | no | no | unconfirmed¹ | — |
348
371
  | 0x1F | VMBGP2 | no | no | unconfirmed¹ | — |
349
372
  | 0x20 | VMBGP4 | no | no | unconfirmed¹ | — |
350
373
  | 0x21 | VMBGPO | yes | no | yes | 2 |
374
+ | 0x25 | VMBGPTC | yes | no | no | — |
351
375
  | 0x28 | VMBGPOD | yes | no | no | — |
352
376
  | 0x2D | VMBGP4PIR | no | yes | unconfirmed¹ | — |
353
377
  | 0x34 | VMBEL1 | no | no | yes | 2 |
@@ -359,6 +383,7 @@ blue (`#4A90D9`).
359
383
  | 0x3B | VMBGP2-2 | no | no | no² | — |
360
384
  | 0x3C | VMBGP4-2 | no | no | no² | — |
361
385
  | 0x3D | VMBGPOD-2 | yes | no | unconfirmed¹ | 2 |
386
+ | 0x3E | VMBGP4PIR-2 | no | yes | unconfirmed¹ | — |
362
387
  | 0x47 | VMBEL2PIR | no | yes | yes | — |
363
388
  | 0x4F | VMBEL1-20 | no | no | yes | — |
364
389
  | 0x50 | VMBEL2-20 | no | no | yes | — |
@@ -372,6 +397,14 @@ blue (`#4A90D9`).
372
397
  | 0x5C | VMBEL2PIR-20³ | no | yes | yes | — |
373
398
  | 0x5F | VMBGP4PIR-20 | no | yes | unconfirmed¹ | — |
374
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
+
375
408
  ¹ Open-collector support unconfirmed against real hardware — see [section 13](#13-known-open-issues).
376
409
  ² Confirmed no open-collector commands in the protocol PDF, but not yet live-verified.
377
410
  ³ The official Velbus type list shows different module names at these two type bytes than
@@ -395,6 +428,7 @@ what this registry currently uses — flagged for verification, see
395
428
  | 0x31 | VMBMETEO | velbus-meteo |
396
429
  | 0x32 | VMB4AN | velbus-sensor |
397
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 |
398
432
 
399
433
  ### Blind / shutter
400
434
  | Type byte | Module | Node |
@@ -494,6 +528,36 @@ body[6] = error bitmask
494
528
  body[7] = alarm/program byte
495
529
  ```
496
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
+
497
561
  ### 7.6 `0xA5` dim level — up to four channels packed per packet
498
562
  ```
499
563
  body[0] = 0xA5, body[1] = channel, body[2] = level (0-254)
@@ -525,6 +589,23 @@ Address assignment: allow 500ms, then rescan to confirm the new address took eff
525
589
  not the right trigger for an automatic write-back, both for the EEPROM-wear reason above
526
590
  and because it risks a feedback loop. Reads have no such constraint and are always safe.
527
591
 
592
+ ### 7.8a `VMB4AN` — three similar-sounding packets, only one has the actual value
593
+
594
+ A real trap, worth remembering for any future work on this module's
595
+ sensor channels (9-12): `0xEA` ("sensor status") sounds like it should be
596
+ the reading, but it's operating-mode/sleep-timer/auto-send configuration
597
+ only — no value field at all. `0xE8`/`0xE9` ("sensor settings") sounds
598
+ promising too, but that's stored preset *configuration*, not a live
599
+ reading. The actual value lives in `0xA9` (`COMMAND_SENSOR_RAW_DATA`,
600
+ labelled "Transmit the sensor raw value" in the PDF) — a completely
601
+ standalone packet with no need to cross-reference either of the other two.
602
+ ```
603
+ body[0] = 0xA9, body[1] = channel (9-12)
604
+ body[2] = operating mode (00=voltage, 01=current, 10=resistance, 11=period)
605
+ body[3-5] = 24-bit raw value, MSB first
606
+ (period mode: 0x000000 = short-circuited, 0xFFFFFF = open-circuit)
607
+ ```
608
+
528
609
  ### 7.9 Thermostat commands — always to the primary address
529
610
  ```
530
611
  0xDB = comfort mode, 0xDC = day mode, 0xDD = night mode, 0xDE = safe mode
@@ -621,7 +702,7 @@ about why in the commit message.
621
702
 
622
703
  - **Generational split.** Nodes generally split at the V2.0 boundary — one node for
623
704
  original-series, a separate node for V2. **Exceptions:** `velbus-glass-panel` (one
624
- node covers all 27 types, both generations, via the type registry's per-type flags
705
+ node covers all 29 types, both generations, via the type registry's per-type flags
625
706
  rather than a code-level split), `velbus-thermostat` (covers the thermostat function
626
707
  on any glass panel type), `velbus-meteo` (only one generation exists), and the blind
627
708
  family (`velbus-blind` / `velbus-blind-s` / `velbus-blind-20`, split by actual protocol
@@ -775,6 +856,25 @@ orientation at the time of writing:
775
856
 
776
857
  - **Field-tested against live hardware:** the core relay, dimmer, glass panel, and
777
858
  thermostat nodes have real-hardware confirmation for their primary functions.
859
+ `VMBGPOD` (0x28) specifically confirmed 09/07/2026 against two real panels on
860
+ Stuart's own home bus, closing out the v0.9.2/v0.9.3 registry-gap saga with an
861
+ actual result rather than just a passing test.
862
+ - **`velbus-dimmer`'s `0xB8` status decode was fundamentally wrong from when it was
863
+ first written until v0.10.1** — see section 7.5a. A dimmer in ordinary "normal
864
+ running" mode always reported `off` regardless of actual dim level. Fixed and
865
+ verified via the mock harness against the exact reported scenario, but not yet
866
+ re-confirmed against Stuart's real VMBDMI.
867
+ - **`velbus-glass-panel`'s `0xEA` thermostat status has been silently crashing
868
+ since it was written, until v0.10.1** — see section 16's code style rules for
869
+ the full story. The `type:"thermostat"` payload has likely never once been
870
+ successfully delivered for any real thermostat-equipped panel. Fixed and swept
871
+ for the same pattern elsewhere in the file (none found), but not yet
872
+ re-confirmed against Stuart's real hardware.
873
+ - **`velbus-button` had a critical, real bug from its first version until v0.9.4** —
874
+ see section 4.3 for the full story. Its press/release/long-press decode is now
875
+ fixed and verified with a real repro, but has not yet been re-confirmed against a
876
+ live button press on real hardware (only via the mock harness) — worth doing
877
+ before trusting it fully, given how long the broken version went unnoticed.
778
878
  - **Mock-harness verified only, not yet confirmed against a real bus:** `velbus-clock`
779
879
  (both the time/date/DST broadcast and the `set_alarm` command), the
780
880
  `velbus-dimmer-20` `get_device_type` read command, and `velbus-energy` in its
@@ -790,9 +890,10 @@ orientation at the time of writing:
790
890
  ## 13. Known open issues
791
891
 
792
892
  - **`VMBKP` (0x42, "Keypad interface module") has no node at all.** Found scanning a
793
- real installation (Stuart's home) alongside the `VMBGPOD` gap below a genuinely new
794
- module type, not yet scoped. Its protocol PDF (`protocol_vmbkp.pdf`, 28 pages) is
795
- substantial: channel status, module status, and a full per-channel LED control layer
893
+ real installation (Stuart's home) confirmed present at address `0xFD`, a real
894
+ module on a real bus, not a hypothetical. A genuinely new module type, not yet
895
+ scoped. Its protocol PDF (`protocol_vmbkp.pdf`, 28 pages) is substantial: channel
896
+ status, module status, and a full per-channel LED control layer
796
897
  (clear/set/slow-blink/fast-blink/very-fast-blink), similar in spirit to `velbus-button`
797
898
  but with LED feedback control `velbus-button` doesn't have. This needs the same
798
899
  "how much work would this involve" scoping pass `velbus-energy` got before it was
@@ -826,6 +927,33 @@ orientation at the time of writing:
826
927
  against the year's January/July offsets to infer whether daylight saving is active)
827
928
  has only been sanity-checked in an environment where daylight saving never applies —
828
929
  a genuine positive "DST is active" case hasn't been observed and confirmed correct.
930
+ - **`VMB1DM`, `VMBDME`, `VMB1LED` (dimmer-family additions) deferred, not built.**
931
+ All three use a genuinely different single-channel `0xEE` status layout
932
+ (mode/dim-value/LED/timer/config in one packet) — distinct from both
933
+ `velbus-dimmer`'s own `0xB8`-based format and `velbus-dimmer-20`'s
934
+ multi-channel bitmask `0xEE` format. Needs real new decode logic, not a
935
+ registry entry — see `coverage-roadmap.md` for the full reasoning.
936
+ - **Bus error counter (`0xDA`) — design settled, not built.** Confirmed
937
+ useful but explicitly framed as a rare edge case. Resolved design: every
938
+ node that registers for its own address already receives an unsolicited
939
+ `0xDA` broadcast if one occurs, so no new request command is needed —
940
+ just passive decoding, emitted only on a secondary output and only when
941
+ at least one counter is non-zero, so it never appears during normal
942
+ operation. Deliberately deferred since it touches most/all existing nodes
943
+ — a session of its own, not a quick addition.
944
+ - **OLED image writing — stretch goal, not built.** Pushing a custom B&W
945
+ 1-bit bitmap to an OLED glass panel (e.g. swapping in a different-language
946
+ greeting for a visitor without opening VelbusLink) — genuine use case,
947
+ explicitly not urgent. `velbus-glass-panel` currently only reads memo text
948
+ (`0xAC`), no write path exists in either direction for display content.
949
+ - **`VMBDALI`/`VMBDALI-20`, `VMBLCDWB`, `VMCM3`, `VMBSIG`/`VMBSIG-20`/`VMBSIG-21`
950
+ — recognized, deliberately not supported.** These show their correct name
951
+ in a scan (not `unknown_0xNN`) with an explicit `"Not supported"` in place
952
+ of a suggested node, rather than silently falling through to `null`. DALI
953
+ is its own protocol layer beyond the gateway; the Signum types are a
954
+ proprietary HomeAssistant-based master clock, not interactable; VMBLCDWB
955
+ and VMCM3 are legacy/custom modules with no planned support. By design,
956
+ not oversight — see `coverage-roadmap.md` for the full per-type reasoning.
829
957
 
830
958
  ---
831
959
 
@@ -916,6 +1044,49 @@ git push --tags
916
1044
  that node's own `.html` dropdown copy, and `velbus-scan.js`'s independent copy) —
917
1045
  see section 3 for the full explanation. Missing one produces a symptom in a
918
1046
  different part of the palette than wherever the type was actually added.
1047
+ - **Never assume a feature is universal across a module family — check each type's
1048
+ own protocol document.** Proven wrong repeatedly (09/07/2026) while adding
1049
+ lock/unlock and richer status decode to `velbus-button`: of 12 button-family
1050
+ types, 4 genuinely lack the lock/unlock command entirely (not "probably most
1051
+ don't" — specific, named exceptions); one type's status uses a completely
1052
+ different command byte (`0xB4` instead of `0xED`); the "which channel" selector
1053
+ byte in the name-request commands uses two incompatible conventions (bitmask vs.
1054
+ literal number) that happen to produce identical values for channels 1-2 and only
1055
+ diverge from channel 3 onward — exactly the kind of divergence that survives
1056
+ casual testing. When a person asks for a capability "because it's a key feature,"
1057
+ that's a reason to verify it broadly, not a reason to skip checking each type.
1058
+ - **Registry type-byte keys need the same verification as everything else** — don't
1059
+ assume an existing entry's key is correct just because it's already shipped.
1060
+ Found 09/07/2026: `VMB4PB` and `VMB6PB-20` had been keyed under wrong type bytes
1061
+ in `velbus-button.js`'s own registry since v0.5.2 (`0x1C`, which isn't a real
1062
+ Velbus type byte at all, and `0x20`, which actually belongs to `VMBGP4`) — while
1063
+ `velbus-scan.js` had always had the correct values. Cross-check against the
1064
+ official type list (section 15) periodically, not just when adding something new.
1065
+ - **Never reference a variable that was only ever assigned as an object property.**
1066
+ Real bug, live since the code was written (fixed v0.10.1): `velbus-glass-panel`'s
1067
+ `0xEA` handler built `currentTemp`/`targetTemp` only inside a `payload = {...}`
1068
+ object literal, then referenced them as bare identifiers on the very next line
1069
+ (`setStatus('...' + currentTemp.toFixed(1) + ...)`). Since JS evaluates a
1070
+ function's arguments before calling it, this threw a `ReferenceError` *before*
1071
+ `node.send()` on the following line — meaning the entire payload silently never
1072
+ went out, for as long as this code existed. The bridge's own dispatch-error
1073
+ handling caught the exception each time rather than crashing the process, which
1074
+ is exactly what let this go unnoticed rather than being immediately obvious.
1075
+ If a value is going into both a status/log string and a payload object, declare
1076
+ it as its own `const` first and use that name in both places — never build it
1077
+ only inside the object literal and assume you can reference it by name outside.
1078
+ - **Don't assume a "two-byte status" pattern applies to every module family.**
1079
+ Real bug, live since the code was written (fixed v0.10.1): the original-series
1080
+ dimmer's `0xB8` status was assumed to work like relay's genuine separate
1081
+ mode-byte + status-byte pair. In reality `VMBDMI`/`VMBDMI-R`/`VMB4DC` pack
1082
+ run-mode, error, load-type, and temperature into a *single* status byte
1083
+ (confirmed identical across all three protocol PDFs — see section 7.5a), and
1084
+ the code was additionally reading the LED indicator byte as if it were a second
1085
+ status word. The result: a dimmer in ordinary "normal running" mode — the most
1086
+ common real-world state — always reported `off` regardless of actual dim level.
1087
+ Same underlying lesson as the button-family capability work: check each
1088
+ module's own protocol document for its actual byte layout, don't port a pattern
1089
+ from a different (even superficially similar) module family.
919
1090
  - **Thermostat commands go to the primary address only,** never a subaddress.
920
1091
  - **Respect the 20ms minimum between `0xFC` writes** on original-series modules — this
921
1092
  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,