node-red-contrib-dmx-for-ha 0.6.14 → 0.6.16

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
@@ -226,6 +226,37 @@ Typical workflow: Remove → update node settings → Deploy → node auto-disco
226
226
 
227
227
  ---
228
228
 
229
+ ## System control topic
230
+
231
+ All nodes subscribe to a system control topic for **bulk add/remove during commissioning**. No wiring needed — publish one MQTT message and all matching nodes respond.
232
+
233
+ **Topic:** `{siteId}/system/control` (e.g. `MW3D/system/control`)
234
+
235
+ **Payload:**
236
+ ```json
237
+ {"cmd": "remove", "zone": "all", "type": "all"}
238
+ {"cmd": "add", "zone": "Master", "type": "dmx"}
239
+ {"cmd": "remove", "zone": "BnB", "type": "button"}
240
+ ```
241
+
242
+ | Field | Values | Description |
243
+ |---|---|---|
244
+ | `cmd` | `add`, `remove` | Add or remove HA discovery |
245
+ | `zone` | `all`, `Master`, `BnB` | Matches config node zone |
246
+ | `type` | `all`, `dmx`, `button`, `pir`, `relay` | Node type filter |
247
+
248
+ **Commissioning workflow:**
249
+ ```
250
+ 1. ADD Master DMX → test all lights
251
+ 2. REMOVE Master DMX → fix wrong names/channels
252
+ 3. ADD Master DMX → verify clean
253
+ 4. Move to next type: Button → PIR → Relay
254
+ ```
255
+
256
+ A **System Control flow** ships with the package (`system_control_flow.json`). Import it into Node-RED, connect the MQTT out node to your broker, and you have one-click bulk control per zone and type.
257
+
258
+ ---
259
+
229
260
  ## DMX Group Node
230
261
 
231
262
  Appears as a single light entity in HA. Commands fan out to all nodes on its **Link** output.
@@ -70,6 +70,23 @@ module.exports = function (RED) {
70
70
  handleDeviceAdd();
71
71
  }
72
72
 
73
+ // ── System control topic ─────────────────────────────────
74
+ const systemTopic = `${cfg.siteId}/system/control`;
75
+ broker.subscribe(systemTopic, 0, function (topic, rawPayload) {
76
+ let msg;
77
+ try { msg = JSON.parse(rawPayload.toString()); } catch(e) { return; }
78
+ if (!msg.cmd) return;
79
+ const zone = (msg.zone || 'all').toLowerCase();
80
+ if (zone !== 'all' && zone !== cfg.zone.toLowerCase()) return;
81
+ const type = (msg.type || 'all').toLowerCase();
82
+ if (type !== 'all' && type !== 'button') return;
83
+ if (msg.cmd === 'remove') {
84
+ handleDeviceRemove();
85
+ } else if (msg.cmd === 'add') {
86
+ _tryDiscover();
87
+ }
88
+ }, node.id + '_sys');
89
+
73
90
  // Auto-discover if broker already connected on deploy
74
91
  if (broker.connected) {
75
92
  _tryDiscover();
@@ -268,6 +285,7 @@ module.exports = function (RED) {
268
285
 
269
286
  // ── Device remove ─────────────────────────────────────────
270
287
  function handleDeviceRemove() {
288
+ _discovered = false; // Allow re-discovery after remove
271
289
  unregisterFixtureId();
272
290
  pub(cfgTopic, '', true);
273
291
  pub(uiBtnCfgTopic, '', true);
@@ -70,6 +70,24 @@ module.exports = function (RED) {
70
70
  handleDeviceAdd();
71
71
  }
72
72
 
73
+ // ── System control topic ─────────────────────────────────
74
+ const systemTopic = `${cfg.siteId}/system/control`;
75
+ broker.subscribe(systemTopic, 0, function (topic, rawPayload) {
76
+ let msg;
77
+ try { msg = JSON.parse(rawPayload.toString()); } catch(e) { return; }
78
+ if (!msg.cmd) return;
79
+ const zone = (msg.zone || 'all').toLowerCase();
80
+ if (zone !== 'all' && zone !== cfg.zone.toLowerCase()) return;
81
+ const type = (msg.type || 'all').toLowerCase();
82
+ if (type !== 'all' && type !== 'dmx') return;
83
+ if (msg.cmd === 'remove') {
84
+ _discovered = false;
85
+ handleDeviceRemove(null);
86
+ } else if (msg.cmd === 'add') {
87
+ _tryDiscover();
88
+ }
89
+ }, node.id + '_sys');
90
+
73
91
  // Auto-discover if broker already connected on deploy
74
92
  if (broker.connected) {
75
93
  _tryDiscover();
@@ -347,6 +365,7 @@ module.exports = function (RED) {
347
365
 
348
366
  // ── Device remove ─────────────────────────────────────────
349
367
  function handleDeviceRemove(incomingTrace) {
368
+ _discovered = false; // Allow re-discovery after remove
350
369
  if (diskTimer) { clearTimeout(diskTimer); diskTimer = null; }
351
370
  // No flush needed on remove — state is cleared below
352
371
  // Keep disk state on remove — state survives remove/add cycles
@@ -78,6 +78,28 @@ module.exports = function (RED) {
78
78
  handleDeviceAdd();
79
79
  }
80
80
 
81
+ // ── System control topic ─────────────────────────────────
82
+ // Subscribes to: {siteId}/system/control
83
+ // Payload: {"cmd":"remove","zone":"all"} or {"cmd":"add","zone":"Master"}
84
+ const systemTopic = `${cfg.siteId}/system/control`;
85
+ broker.subscribe(systemTopic, 0, function (topic, rawPayload) {
86
+ let msg;
87
+ try { msg = JSON.parse(rawPayload.toString()); } catch(e) { return; }
88
+ if (!msg.cmd) return;
89
+ // Zone filter — "all" or match config zone
90
+ const zone = (msg.zone || 'all').toLowerCase();
91
+ if (zone !== 'all' && zone !== cfg.zone.toLowerCase()) return;
92
+ // Type filter — "all" or match node type
93
+ const type = (msg.type || 'all').toLowerCase();
94
+ if (type !== 'all' && type !== 'dmx') return;
95
+ // Execute command
96
+ if (msg.cmd === 'remove') {
97
+ handleDeviceRemove();
98
+ } else if (msg.cmd === 'add') {
99
+ _tryDiscover();
100
+ }
101
+ }, node.id + '_sys');
102
+
81
103
  // Auto-discover if broker already connected on deploy
82
104
  if (broker.connected) {
83
105
  _tryDiscover();
@@ -775,6 +797,7 @@ module.exports = function (RED) {
775
797
 
776
798
  // ── Device remove ─────────────────────────────────────────
777
799
  function handleDeviceRemove() {
800
+ _discovered = false; // Allow re-discovery after remove
778
801
  unregisterFixtureId();
779
802
  clearChannelRegistry();
780
803
  stopEffect();
@@ -67,6 +67,23 @@ module.exports = function (RED) {
67
67
  handleDeviceAdd();
68
68
  }
69
69
 
70
+ // ── System control topic ─────────────────────────────────
71
+ const systemTopic = `${cfg.siteId}/system/control`;
72
+ broker.subscribe(systemTopic, 0, function (topic, rawPayload) {
73
+ let msg;
74
+ try { msg = JSON.parse(rawPayload.toString()); } catch(e) { return; }
75
+ if (!msg.cmd) return;
76
+ const zone = (msg.zone || 'all').toLowerCase();
77
+ if (zone !== 'all' && zone !== cfg.zone.toLowerCase()) return;
78
+ const type = (msg.type || 'all').toLowerCase();
79
+ if (type !== 'all' && type !== 'pir') return;
80
+ if (msg.cmd === 'remove') {
81
+ handleDeviceRemove();
82
+ } else if (msg.cmd === 'add') {
83
+ _tryDiscover();
84
+ }
85
+ }, node.id + '_sys');
86
+
70
87
  // Auto-discover if broker already connected on deploy
71
88
  if (broker.connected) {
72
89
  _tryDiscover();
@@ -301,6 +318,7 @@ module.exports = function (RED) {
301
318
 
302
319
  // ── Device remove ─────────────────────────────────────────
303
320
  function handleDeviceRemove() {
321
+ _discovered = false; // Allow re-discovery after remove
304
322
  unregisterFixtureId();
305
323
  cancelWarmup();
306
324
  pub(avtyTopic, 'offline', true);
@@ -70,6 +70,23 @@ module.exports = function (RED) {
70
70
  handleDeviceAdd();
71
71
  }
72
72
 
73
+ // ── System control topic ─────────────────────────────────
74
+ const systemTopic = `${cfg.siteId}/system/control`;
75
+ broker.subscribe(systemTopic, 0, function (topic, rawPayload) {
76
+ let msg;
77
+ try { msg = JSON.parse(rawPayload.toString()); } catch(e) { return; }
78
+ if (!msg.cmd) return;
79
+ const zone = (msg.zone || 'all').toLowerCase();
80
+ if (zone !== 'all' && zone !== cfg.zone.toLowerCase()) return;
81
+ const type = (msg.type || 'all').toLowerCase();
82
+ if (type !== 'all' && type !== 'relay') return;
83
+ if (msg.cmd === 'remove') {
84
+ handleDeviceRemove();
85
+ } else if (msg.cmd === 'add') {
86
+ _tryDiscover();
87
+ }
88
+ }, node.id + '_sys');
89
+
73
90
  // Auto-discover if broker already connected on deploy
74
91
  if (broker.connected) {
75
92
  _tryDiscover();
@@ -388,6 +405,7 @@ module.exports = function (RED) {
388
405
 
389
406
  // ── Device remove ─────────────────────────────────────────
390
407
  function handleDeviceRemove() {
408
+ _discovered = false; // Allow re-discovery after remove
391
409
  unregisterFixtureId();
392
410
  stopEffect();
393
411
  if (diskTimer) { clearTimeout(diskTimer); diskTimer = null; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-dmx-for-ha",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
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",