node-red-contrib-velbus-2026 0.9.3 → 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,214 @@ 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
+
17
225
  ## v0.9.3 — 09/07/2026
18
226
 
19
227
  ### 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.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
@@ -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, 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)
@@ -621,7 +685,7 @@ about why in the commit message.
621
685
 
622
686
  - **Generational split.** Nodes generally split at the V2.0 boundary — one node for
623
687
  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
688
+ node covers all 29 types, both generations, via the type registry's per-type flags
625
689
  rather than a code-level split), `velbus-thermostat` (covers the thermostat function
626
690
  on any glass panel type), `velbus-meteo` (only one generation exists), and the blind
627
691
  family (`velbus-blind` / `velbus-blind-s` / `velbus-blind-20`, split by actual protocol
@@ -775,6 +839,25 @@ orientation at the time of writing:
775
839
 
776
840
  - **Field-tested against live hardware:** the core relay, dimmer, glass panel, and
777
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.
778
861
  - **Mock-harness verified only, not yet confirmed against a real bus:** `velbus-clock`
779
862
  (both the time/date/DST broadcast and the `set_alarm` command), the
780
863
  `velbus-dimmer-20` `get_device_type` read command, and `velbus-energy` in its
@@ -790,9 +873,10 @@ orientation at the time of writing:
790
873
  ## 13. Known open issues
791
874
 
792
875
  - **`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
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
796
880
  (clear/set/slow-blink/fast-blink/very-fast-blink), similar in spirit to `velbus-button`
797
881
  but with LED feedback control `velbus-button` doesn't have. This needs the same
798
882
  "how much work would this involve" scoping pass `velbus-energy` got before it was
@@ -826,6 +910,33 @@ orientation at the time of writing:
826
910
  against the year's January/July offsets to infer whether daylight saving is active)
827
911
  has only been sanity-checked in an environment where daylight saving never applies —
828
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.
829
940
 
830
941
  ---
831
942
 
@@ -916,6 +1027,49 @@ git push --tags
916
1027
  that node's own `.html` dropdown copy, and `velbus-scan.js`'s independent copy) —
917
1028
  see section 3 for the full explanation. Missing one produces a symptom in a
918
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.
919
1073
  - **Thermostat commands go to the primary address only,** never a subaddress.
920
1074
  - **Respect the 20ms minimum between `0xFC` writes** on original-series modules — this
921
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,
@@ -17,6 +17,14 @@
17
17
  // nameStyle: 'bitmask'
18
18
  // hasCounter: false
19
19
  // hasAnalogue: true
20
+ //
21
+ // VMB6IN (0x05): 6 digital inputs, no pulse counters. Confirmed (09/07/2026)
22
+ // against protocol_vmb6in.pdf: no Lock channel/Unlock channel command at
23
+ // all (simpler than VMB7IN, not just a smaller version of it) — this
24
+ // node doesn't implement lock/unlock regardless, so no gating needed.
25
+ // nameStyle: 'bitmask' (confirmed: "00000001 = Input 1 / 00100000 = Input 6")
26
+ // Its 0xED module status is only 5 bytes (vs VMB7IN's 7) — the existing
27
+ // `body.length < 7` guard already skips it safely, no code change needed.
20
28
  // ─────────────────────────────────────────────────────────────────────────────
21
29
 
22
30
  const SENSOR_TYPES = {
@@ -42,6 +50,17 @@ const SENSOR_TYPES = {
42
50
  minMapVer: null,
43
51
  series: 'original',
44
52
  },
53
+ 0x05: {
54
+ name: 'VMB6IN',
55
+ channels: 6,
56
+ counterCh: [],
57
+ hasCounter: false,
58
+ hasAnalogue: false,
59
+ lockStyle: null, // no lock/unlock command exists for this module at all
60
+ nameStyle: 'bitmask',
61
+ minMapVer: null,
62
+ series: 'original',
63
+ },
45
64
  };
46
65
 
47
66
  const SENSOR_TYPE_IDS = new Set(Object.keys(SENSOR_TYPES).map(k => parseInt(k)));