node-red-contrib-dmx-for-ha 0.4.4 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # node-red-contrib-dmx-for-ha
2
2
 
3
- DMX lighting control for Home Assistant via Node-RED and MQTT.
3
+ **Professional DMX lighting control for Home Assistant via Node-RED and MQTT.**
4
+
5
+ Built by a lighting integrator, for lighting integrators — and anyone else who needs serious DMX control inside Home Assistant.
4
6
 
5
7
  Place a node, fill in the settings, deploy. Your DMX fixture appears in Home Assistant automatically — ready to use in automations, dashboards, and scenes.
6
8
 
@@ -8,6 +10,43 @@ No YAML. No custom MQTT discovery config. No external wiring inside Node-RED.
8
10
 
9
11
  ---
10
12
 
13
+ ## Who this is for
14
+
15
+ This package was designed for **professional AV and lighting integrators** working on large residential and commercial installations. If any of these sound familiar, this was built for you:
16
+
17
+ - You have hundreds of DMX fixtures that each need individual HA entity management
18
+ - You commission remotely — physically pressing every button on a large site is not practical
19
+ - Your client's lights must come back to the correct state after any power cycle, every time
20
+ - Your automation logic lives in HA, not hardcoded in the lighting controller
21
+ - You have multiple DMX universes and controllers in a single zone
22
+ - You've stood in a roof cavity at 11pm wondering why channel 47 isn't responding
23
+
24
+ It also works perfectly for advanced home users who want proper DMX integration rather than a workaround.
25
+
26
+ ---
27
+
28
+ ## The architecture
29
+
30
+ Node-RED acts as the **virtual lighting desk** — it owns the DMX channels, manages transitions, and handles hardware communication. Home Assistant is the **automation and UI layer** — it sees clean light entities and knows nothing about DMX channels, universes, or controllers.
31
+
32
+ ```
33
+ Home Assistant Node-RED DMX Hardware
34
+ ────────────── ──────── ────────────
35
+ Automations ←──→ ha-mqtt-dmx ──→ EtherTen /
36
+ Dashboards ha-mqtt-group DMX decoder /
37
+ Scenes ha-mqtt-relay MQTT bridge
38
+ Voice control ha-mqtt-button ←── Wall buttons
39
+ ha-mqtt-pir ←── PIR sensors
40
+ ```
41
+
42
+ This separation means:
43
+ - **HA stays clean** — light entities behave like any other HA light
44
+ - **NR handles complexity** — DMX addressing, gamma correction, transitions, effects
45
+ - **Hardware is abstracted** — swap controllers without touching HA config
46
+ - **Remote commissioning** — every physical device has a software mirror in HA, testable from anywhere
47
+
48
+ ---
49
+
11
50
  ## What this package does
12
51
 
13
52
  Bridges the gap between DMX lighting hardware and Home Assistant.
@@ -18,9 +57,12 @@ Most DMX implementations require either expensive proprietary hardware or comple
18
57
  - RGBW, RGBWW, RGB, Colour Temperature, Brightness, and On/Off colour modes
19
58
  - Gamma-corrected DMX output
20
59
  - Transitions, effects, and group control
21
- - State persistence across reboots
22
- - Wall button and PIR motion sensor integration
60
+ - State persistence across reboots — survives power cycles and HA updates
61
+ - Wall button and PIR motion sensor integration with remote commissioning support
23
62
  - 230V relay switching
63
+ - DMX channel conflict detection — warns on duplicate channel assignments
64
+ - Configurable DMX floor value — prevents low-value flicker on hardware that needs it
65
+ - Debug mode per node — 12hr auto-disable, safe to leave in production flows
24
66
 
25
67
  > **Note:** This is building automation DMX — fixtures, decoders, dimmers, relay switching. Not entertainment industry DMX (no movers, gobos, or fixture profiles). DMX is an open standard and this package works with any DMX controller that accepts MQTT payloads.
26
68
 
@@ -120,7 +162,20 @@ Fill in the node editor:
120
162
  1. **Fixture ID** — Prefix (`L`), Plan ID, optional channel letter e.g. `L` `992` `-A`
121
163
  2. **Colour Mode** — RGBW, RGB, CCT etc.
122
164
  3. **Device Type** — e.g. Downlight, Strip light
123
- 4. **Situation** — in, above, throughout etc.
165
+ 4. **Situation** — describes where the fixture is relative to its area. Options:
166
+
167
+ | Situation | Example result |
168
+ |---|---|
169
+ | `in` | Downlight **in** Bedroom 1 |
170
+ | `at` | Spotlight **at** Entry Door |
171
+ | `near` | Strip light **near** Kitchen Bench |
172
+ | `above` | Flood **above** Garage Door |
173
+ | `below` | Step light **below** Handrail |
174
+ | `outside` | Rail light **outside** Media Room |
175
+ | `throughout` | Downlight **throughout** Living Area |
176
+
177
+ > **Tip:** Don't default to `in` for everything — `outside`, `near` and `above` are useful for fixtures in corridors, at thresholds, and above architectural features. On a large installation this makes fixture names immediately meaningful without opening a plan.
178
+
124
179
  5. **Config** — select your config node
125
180
  6. **Area** and **Sub-Area** — location on the property
126
181
  7. **DMX Channels** — channel numbers matching your fixture's DMX start address
@@ -182,8 +237,35 @@ Naming: `L-992-A`, `L-992-B`, `L-992-C` group naturally under `LG-992`.
182
237
  ## Wall buttons
183
238
 
184
239
  Creates two HA entities per button:
185
- - `binary_sensor.s_10_a` — automation trigger, auto-clears after hold time
186
- - `button.s_10_a_btn` — dashboard UI mirror
240
+
241
+ - **`binary_sensor.s_10_a`**physical state. ON = pressed/held, OFF = released. Use this for automations and dashboard display.
242
+ - **`button.s_10_a_btn`** — momentary trigger. Simulates a physical press from the HA UI.
243
+
244
+ ### Why two entities?
245
+
246
+ This package is designed for **professional installation** as well as home use. On large installations it is not practical to physically press every button every time you make a change — the building may be large, the button may be on the other side of the property, or you may be commissioning remotely.
247
+
248
+ The `button` entity gives you a **virtual commissioning panel** — every physical wall button has a software mirror in HA that you can trigger from a laptop anywhere in the world without being on site.
249
+
250
+ ### Important — HA button entity behaviour
251
+
252
+ The `button` domain in HA is **intentionally stateless** — it will never light up or show an active state. This is a HA platform design decision, not a bug. The `button` entity triggers and immediately resets with no persistent state.
253
+
254
+ For visual feedback always use the `binary_sensor` entity.
255
+
256
+ ### Recommended dashboard setup
257
+
258
+ Hide the `button` entity from the default dashboard to keep things clean — it remains fully accessible when needed:
259
+
260
+ ```
261
+ Settings → Devices → (button device) → button entity → ⋮ → Hide from dashboard
262
+ ```
263
+
264
+ This gives you:
265
+ - **Dashboard** — clean, only `binary_sensor` shown
266
+ - **Device page** — both entities visible, Press button accessible for commissioning
267
+ - **Automations** — both entities fully available, nothing hidden from logic
268
+ - **Remote commissioning** — navigate to device page, simulate press from anywhere
187
269
 
188
270
  Letters `I` and `O` are never used as suffixes — they look too similar to `1` and `0`.
189
271
 
@@ -411,7 +493,15 @@ If `disk_values` shows `[module=memory]` — your config.js change didn't take e
411
493
 
412
494
  | Version | Changes |
413
495
  |---|---|
414
- | 0.3.6 | DMX channel conflict detection, jitter timer cancellable on teardown |
496
+ | 0.4.4 | Recovery status shows actual state on canvas instead of "ready — awaiting HA" |
497
+ | 0.4.3 | device:remove no longer clears disk state — state survives remove/add cycles |
498
+ | 0.4.2 | Group node pubState includes color — fixes color wheel jitter in HA |
499
+ | 0.4.1 | Group node status reflects ON/OFF correctly |
500
+ | 0.4.0 | Group recovery no longer forwards state to children — each node recovers independently |
501
+ | 0.3.9 | Four fixes: group device:add forward removed, cooldown warn suppressed, group status permanent, disk flush on close |
502
+ | 0.3.8 | Restored missing variable declarations wiped by debug block corruption |
503
+ | 0.3.7 | Fix _debugId computed from S (not fixtureId) to avoid ReferenceError on startup |
504
+ | 0.3.6 | DMX channel conflict detection, jitter timer tracked and cancellable |
415
505
  | 0.3.5 | Fix debug mode block initialisation order (S before debug) |
416
506
  | 0.3.4 | Debug hint text updated — notes 12hr auto-disable |
417
507
  | 0.3.3 | Debug mode canvas warning + 12hr auto-disable safety net |
@@ -25,6 +25,7 @@
25
25
  transitionHaUiTime: { value: '1' },
26
26
  flashShort: { value: '1' },
27
27
  flashLong: { value: '10' },
28
+ dmxFloor: { value: '3' },
28
29
  },
29
30
  label: function () {
30
31
  return this.name || 'HA MQTT Config';
@@ -190,6 +191,21 @@
190
191
  min="1" style="width:70px" />
191
192
  </div>
192
193
 
194
+ <div class="form-row">
195
+ <label style="width:100%; font-weight:bold; color:#999; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em;">
196
+ <i class="fa fa-sliders"></i> DMX Channel Limits
197
+ </label>
198
+ </div>
199
+ <div class="form-row">
200
+ <label for="node-config-input-dmxFloor">
201
+ <i class="fa fa-arrow-down"></i> DMX Floor
202
+ </label>
203
+ <input type="number" id="node-config-input-dmxFloor"
204
+ min="0" max="20" step="1" style="width:70px" />
205
+ <span style="margin-left:8px; color:#999; font-size:0.85em;">
206
+ Min DMX value sent — below this snaps up (0=OFF always OFF). Default: 3
207
+ </span>
208
+ </div>
193
209
  </script>
194
210
 
195
211
  <!-- ============================================================
@@ -61,6 +61,9 @@ module.exports = function (RED) {
61
61
  this.transitionRateLimit = parseFloat(config.transitionRateLimit) || 1;
62
62
  this.transitionHaUiTime = parseFloat(config.transitionHaUiTime) || 1;
63
63
 
64
+ // DMX channel limits
65
+ this.dmxFloor = parseInt(config.dmxFloor) >= 0 ? parseInt(config.dmxFloor) : 3;
66
+
64
67
  // ── Topic builder — omits empty zone segment ─────────────────────────
65
68
  this.buildTopic = function() {
66
69
  const segments = Array.from(arguments).filter(function(s) {
@@ -86,6 +89,7 @@ module.exports = function (RED) {
86
89
  flashLong: this.flashLong,
87
90
  transitionRateLimit: this.transitionRateLimit,
88
91
  transitionHaUiTime: this.transitionHaUiTime,
92
+ dmxFloor: this.dmxFloor,
89
93
  };
90
94
  }
91
95
 
@@ -112,6 +112,7 @@ module.exports = function (RED) {
112
112
  flashShort: cfg.flashShort,
113
113
  flashLong: cfg.flashLong,
114
114
  diskDelay: cfg.diskDelay,
115
+ debugMode: config.debugMode === true,
115
116
  };
116
117
 
117
118
  // ── Debug mode safeguards ─────────────────────────────────────────
@@ -136,6 +136,7 @@ module.exports = function (RED) {
136
136
  flashShort: cfg.flashShort,
137
137
  flashLong: cfg.flashLong,
138
138
  diskDelay: cfg.diskDelay,
139
+ dmxFloor: cfg.dmxFloor !== undefined ? cfg.dmxFloor : 3,
139
140
  };
140
141
 
141
142
  // ── Debug mode safeguards ─────────────────────────────────────────
@@ -202,6 +203,9 @@ module.exports = function (RED) {
202
203
  if (gamma === 0 && colorValue > 0 && brightness > 0 && S.minOutput > 0) {
203
204
  return S.minOutput;
204
205
  }
206
+ // DMX floor — snap up if above 0 but below hardware stable threshold
207
+ if (gamma > 0 && gamma < S.dmxFloor) return S.dmxFloor;
208
+ // Note: ceiling is handled by S.dmxLimiter in scaleToDmx above
205
209
  return gamma;
206
210
  }
207
211
 
@@ -285,12 +289,25 @@ module.exports = function (RED) {
285
289
  const p = preEffectState;
286
290
  preEffectState = null;
287
291
  const channels = buildColorChannels(
288
- p.brightness || 255, p.red || 255, p.green || 255,
289
- p.blue || 255, p.white || 255, p.warmWhite || 0
292
+ p.brightness !== undefined ? p.brightness : 255,
293
+ p.red !== undefined ? p.red : 255,
294
+ p.green !== undefined ? p.green : 255,
295
+ p.blue !== undefined ? p.blue : 255,
296
+ p.white !== undefined ? p.white : 255,
297
+ p.warmWhite !== undefined ? p.warmWhite : 0
290
298
  );
291
299
  sendDmxChannels(channels);
292
- pubState({ state: p.state || 'OFF', color_mode: S.colorMode });
293
- setStatus('yellow', 'ring', `${fixtureId} ready — awaiting HA`);
300
+ pubState({
301
+ state: p.state || 'OFF',
302
+ color_mode: S.colorMode,
303
+ brightness: p.brightness,
304
+ color: { r: p.red, g: p.green, b: p.blue, w: p.white, ww: p.warmWhite }
305
+ });
306
+ if (p.state === 'ON') {
307
+ setStatus('green', 'dot', `${fixtureId} ON bright:${p.brightness}`);
308
+ } else {
309
+ setStatus('grey', 'ring', `${fixtureId} OFF`);
310
+ }
294
311
  }
295
312
 
296
313
  function startEffect(label, intervalMs, tickFn) {
@@ -112,6 +112,7 @@ module.exports = function (RED) {
112
112
  flashShort: cfg.flashShort,
113
113
  flashLong: cfg.flashLong,
114
114
  diskDelay: cfg.diskDelay,
115
+ debugMode: config.debugMode === true,
115
116
  };
116
117
 
117
118
  // ── Debug mode safeguards ─────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-dmx-for-ha",
3
- "version": "0.4.4",
3
+ "version": "0.4.8",
4
4
  "description": "DMX lighting control for Home Assistant via Node-RED and MQTT. Place a node, fill in the settings, deploy. Full HA device registry integration with RGBW/RGBWW/CCT/brightness colour modes, transitions, effects, and group control.",
5
5
  "keywords": [
6
6
  "node-red",