node-red-contrib-dmx-for-ha 0.1.0

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.
@@ -0,0 +1,547 @@
1
+ <!-- ============================================================
2
+ ha-mqtt-dmx — DMX Node Editor
3
+ Package: node-red-contrib-dmx-for-ha
4
+ Author: DeSwaggy — Discord: @deswaggy
5
+ ============================================================ -->
6
+
7
+ <script type="text/javascript">
8
+
9
+ // ── Areas and Sub-areas ────────────────────────────────────────
10
+ const HA_DMX_AREAS = [
11
+ { v: 'Area 52', l: 'TBC' },
12
+ { v: 'Attic Roof', l: 'Attic Roof' },
13
+ { v: 'Backyard', l: 'Backyard' },
14
+ { v: 'Balcony', l: 'Balcony' },
15
+ { v: 'Bathroom', l: 'Bathroom' },
16
+ { v: 'Bedroom 1', l: 'Bedroom 1' },
17
+ { v: 'Bedroom 2', l: 'Bedroom 2' },
18
+ { v: 'Bedroom 3', l: 'Bedroom 3' },
19
+ { v: 'Bedroom 4', l: 'Bedroom 4' },
20
+ { v: 'Carport', l: 'Carport' },
21
+ { v: 'Cellar', l: 'Cellar' },
22
+ { v: 'Dining', l: 'Dining' },
23
+ { v: 'Driveway', l: 'Driveway' },
24
+ { v: 'Entry', l: 'Entry' },
25
+ { v: 'Garage', l: 'Garage' },
26
+ { v: 'Gym', l: 'Gym' },
27
+ { v: 'Hallway', l: 'Hallway' },
28
+ { v: 'Kitchen', l: 'Kitchen' },
29
+ { v: 'Laundry', l: 'Laundry' },
30
+ { v: 'Link Bridge (Upper)', l: 'Link Bridge Upper' },
31
+ { v: 'Link Bridge (Lower)', l: 'Link Bridge Lower' },
32
+ { v: 'Living', l: 'Living' },
33
+ { v: 'Media', l: 'Media' },
34
+ { v: 'Office', l: 'Office' },
35
+ { v: 'Pantry', l: 'Pantry' },
36
+ { v: 'Pass Over', l: 'Pass Over' },
37
+ { v: 'Pass Under', l: 'Pass Under' },
38
+ { v: 'Passageway', l: 'Passageway' },
39
+ { v: 'Pool', l: 'Pool' },
40
+ { v: 'Portico', l: 'Portico' },
41
+ { v: 'PowderRoom', l: 'PowderRoom' },
42
+ { v: 'Reading', l: 'Reading' },
43
+ { v: 'Rumpus', l: 'Rumpus' },
44
+ { v: 'Spa', l: 'Spa' },
45
+ { v: 'Stairs', l: 'Stairs' },
46
+ { v: 'Store', l: 'Store' },
47
+ { v: 'SubFloor', l: 'SubFloor' },
48
+ { v: 'Terrace', l: 'Terrace' },
49
+ ];
50
+
51
+ const HA_DMX_SUB_AREAS = [
52
+ { v: '', l: 'None' },
53
+ { v: 'Balcony', l: 'Balcony' },
54
+ { v: 'Bed (North)', l: 'Bed North' },
55
+ { v: 'Bed (South)', l: 'Bed South' },
56
+ { v: 'Bed (East)', l: 'Bed East' },
57
+ { v: 'Bed (West)', l: 'Bed West' },
58
+ { v: 'Ceiling', l: 'Ceiling' },
59
+ { v: 'Dress', l: 'Dress' },
60
+ { v: 'Ensuite', l: 'Ensuite' },
61
+ { v: 'Exterior', l: 'Exterior' },
62
+ { v: 'Ground Floor', l: 'Ground Floor' },
63
+ { v: '1st Floor', l: '1st Floor' },
64
+ { v: 'Landing', l: 'Landing' },
65
+ { v: 'Office', l: 'Office' },
66
+ { v: 'Stairs', l: 'Stairs' },
67
+ { v: 'WIR', l: 'WIR' },
68
+ { v: '(North)', l: 'North' },
69
+ { v: '(South)', l: 'South' },
70
+ { v: '(East)', l: 'East' },
71
+ { v: '(West)', l: 'West' },
72
+ { v: 'Void', l: 'Void' },
73
+ ];
74
+
75
+ // ── Helper — populate a select from an array of {v, l} ────────
76
+ function populateSelect(selectId, items, currentVal) {
77
+ const sel = $('#' + selectId);
78
+ sel.empty();
79
+ items.forEach(function (item) {
80
+ sel.append($('<option>')
81
+ .val(item.v)
82
+ .text(item.l)
83
+ .prop('selected', item.v === currentVal));
84
+ });
85
+ }
86
+
87
+ // ── Show/hide DMX channel rows based on colour mode ─────────────
88
+ function updateChannelVisibility(mode) {
89
+ const show = function (id) { $('#dmx-ch-row-' + id).show(); };
90
+ const hide = function (id) { $('#dmx-ch-row-' + id).hide(); };
91
+
92
+ // Hide all first
93
+ ['red','green','blue','white','warmWhite'].forEach(hide);
94
+
95
+ switch (mode) {
96
+ case 'rgbw':
97
+ show('red'); show('green'); show('blue'); show('white');
98
+ break;
99
+ case 'rgbww':
100
+ show('red'); show('green'); show('blue');
101
+ show('white'); show('warmWhite');
102
+ break;
103
+ case 'rgb':
104
+ show('red'); show('green'); show('blue');
105
+ break;
106
+ case 'color_temp':
107
+ show('white'); show('warmWhite');
108
+ break;
109
+ case 'brightness':
110
+ show('white'); // single channel — use white as master
111
+ break;
112
+ case 'onoff':
113
+ show('white'); // single channel
114
+ break;
115
+ }
116
+ }
117
+
118
+ // ── Node Registration ──────────────────────────────────────────
119
+ RED.nodes.registerType('ha-mqtt-dmx', {
120
+ category: 'DMX for HA',
121
+ color: '#ae45ff',
122
+ icon: 'font-awesome/fa-lightbulb-o',
123
+ inputs: 1,
124
+ outputs: 0,
125
+ paletteLabel: 'DMX',
126
+
127
+ defaults: {
128
+ name: { value: '' },
129
+ config: { value: '', type: 'ha-mqtt-config', required: true },
130
+ // Fixture identity
131
+ colorMode: { value: 'rgbw', required: true },
132
+ deviceType: { value: 'Downlight', required: true },
133
+ uidPrefix: { value: 'L', required: true },
134
+ uid: { value: '', required: true },
135
+ uidPostfix: { value: '' },
136
+ // Location
137
+ area: { value: 'Area 52' },
138
+ situation: { value: 'in' },
139
+ subLocation: { value: '' },
140
+ // DMX channels
141
+ chRed: { value: '' },
142
+ chGreen: { value: '' },
143
+ chBlue: { value: '' },
144
+ chWhite: { value: '' },
145
+ chWarmWhite: { value: '' },
146
+ // DMX controller
147
+ controllerNum: { value: '1' },
148
+ universe: { value: '1' },
149
+ // Options
150
+ haIcon: { value: 'mdi:lightbulb' },
151
+ showEffects: { value: true },
152
+ transitions: { value: true },
153
+ groupSync: { value: false },
154
+ defaultState: { value: 'OFF' },
155
+ // Advanced
156
+ dmxLimiter: { value: '255' },
157
+ minOutput: { value: '1' },
158
+ brightBump: { value: '50' },
159
+ ticksPerSec: { value: '31' },
160
+ },
161
+
162
+ label: function () {
163
+ if (this.name) return this.name;
164
+ const prefix = this.uidPrefix || 'L';
165
+ const id = this.uid || '?';
166
+ const postfix = this.uidPostfix || '';
167
+ return prefix + '-' + id + postfix;
168
+ },
169
+
170
+ labelStyle: function () {
171
+ return this.name ? 'node_label_italic' : '';
172
+ },
173
+
174
+ outputLabels: [],
175
+ inputLabels: ['Input'],
176
+
177
+ oneditprepare: function () {
178
+ const node = this;
179
+
180
+ // Populate area dropdown
181
+ populateSelect('node-input-area', HA_DMX_AREAS, node.area);
182
+
183
+ // Populate sub-area dropdown
184
+ populateSelect('node-input-subLocation', HA_DMX_SUB_AREAS, node.subLocation);
185
+
186
+ // Set colour mode and update channel visibility
187
+ $('#node-input-colorMode').val(node.colorMode || 'rgbw');
188
+ updateChannelVisibility(node.colorMode || 'rgbw');
189
+
190
+ // Update channel visibility on colour mode change
191
+ $('#node-input-colorMode').on('change', function () {
192
+ updateChannelVisibility($(this).val());
193
+ });
194
+ },
195
+
196
+ oneditsave: function () {
197
+ // Sanitise UID — digits only
198
+ const uid = $('#node-input-uid').val().replace(/[^0-9]/g, '');
199
+ $('#node-input-uid').val(uid);
200
+ },
201
+ });
202
+
203
+ </script>
204
+
205
+ <!-- ============================================================
206
+ Editor Panel Template
207
+ ============================================================ -->
208
+ <script type="text/html" data-template-name="ha-mqtt-dmx">
209
+
210
+ <!-- NAME (optional — defaults to fixture ID on canvas) -->
211
+ <div class="form-row">
212
+ <label for="node-input-name">
213
+ <i class="fa fa-tag"></i> Name
214
+ </label>
215
+ <input type="text" id="node-input-name"
216
+ placeholder="Optional — defaults to fixture ID e.g. L-992-A" />
217
+ </div>
218
+
219
+ <!-- CONFIG NODE PICKER -->
220
+ <div class="form-row">
221
+ <label for="node-input-config">
222
+ <i class="fa fa-cog"></i> Config
223
+ </label>
224
+ <input type="text" id="node-input-config" />
225
+ </div>
226
+
227
+ <hr/>
228
+
229
+ <!-- ── FIXTURE (* Required) ──────────────────────────────── -->
230
+ <div class="form-row">
231
+ <label style="width:100%; font-weight:bold; color:#999; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em;">
232
+ <i class="fa fa-lightbulb-o"></i> Fixture &nbsp;<span style="color:#e74c3c">* Required</span>
233
+ </label>
234
+ </div>
235
+
236
+ <!-- Colour Mode -->
237
+ <div class="form-row">
238
+ <label for="node-input-colorMode">
239
+ <i class="fa fa-sliders"></i> Colour Mode
240
+ </label>
241
+ <select id="node-input-colorMode" style="width:55%">
242
+ <option value="rgbw">RGBW</option>
243
+ <option value="rgbww">RGBWW</option>
244
+ <option value="rgb">RGB</option>
245
+ <option value="color_temp">Colour Temperature (CCT)</option>
246
+ <option value="brightness">Brightness only</option>
247
+ <option value="onoff">On/Off only</option>
248
+ </select>
249
+ </div>
250
+
251
+ <!-- Device Type -->
252
+ <div class="form-row">
253
+ <label for="node-input-deviceType">
254
+ <i class="fa fa-lightbulb-o"></i> Device Type
255
+ </label>
256
+ <input type="text" id="node-input-deviceType"
257
+ placeholder="e.g. Downlight, Strip light, Panel"
258
+ style="width:55%" />
259
+ </div>
260
+
261
+ <!-- Prefix / ID / Postfix on one row -->
262
+ <div class="form-row">
263
+ <label for="node-input-uidPrefix">
264
+ <i class="fa fa-tv"></i> Fixture ID
265
+ </label>
266
+ <select id="node-input-uidPrefix" style="width:55px">
267
+ <option value="L">L</option>
268
+ <option value="P">P</option>
269
+ </select>
270
+ &nbsp;-&nbsp;
271
+ <input type="text" id="node-input-uid"
272
+ placeholder="992" style="width:70px"
273
+ title="Cable/plan ID number from electrical drawing" />
274
+ &nbsp;
275
+ <select id="node-input-uidPostfix" style="width:65px">
276
+ <option value="">(none)</option>
277
+ <option value="-A">-A</option>
278
+ <option value="-B">-B</option>
279
+ <option value="-C">-C</option>
280
+ <option value="-D">-D</option>
281
+ </select>
282
+ <span style="margin-left:8px; color:#999; font-size:0.85em;">
283
+ Prefix — Plan ID — Channel
284
+ </span>
285
+ </div>
286
+
287
+ <!-- Area -->
288
+ <div class="form-row">
289
+ <label for="node-input-area">
290
+ <i class="fa fa-map-marker"></i> Area
291
+ </label>
292
+ <select id="node-input-area" style="width:55%"></select>
293
+ </div>
294
+
295
+ <!-- Situation -->
296
+ <div class="form-row">
297
+ <label for="node-input-situation">
298
+ <i class="fa fa-compass"></i> Situation
299
+ </label>
300
+ <select id="node-input-situation" style="width:55%">
301
+ <option value="in">in</option>
302
+ <option value="above">above</option>
303
+ <option value="below">below</option>
304
+ <option value="outside">outside</option>
305
+ <option value="throughout">throughout</option>
306
+ <option value="under">under</option>
307
+ <option value="over">over</option>
308
+ </select>
309
+ </div>
310
+
311
+ <!-- Sub-Area -->
312
+ <div class="form-row">
313
+ <label for="node-input-subLocation">
314
+ <i class="fa fa-map-marker"></i> Sub-Area
315
+ </label>
316
+ <select id="node-input-subLocation" style="width:55%"></select>
317
+ </div>
318
+
319
+ <hr/>
320
+
321
+ <!-- ── DMX CHANNELS ──────────────────────────────────────── -->
322
+ <div class="form-row">
323
+ <label style="width:100%; font-weight:bold; color:#999; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em;">
324
+ <i class="fa fa-sort-numeric-asc"></i> DMX Channels
325
+ </label>
326
+ </div>
327
+
328
+ <div class="form-row" id="dmx-ch-row-red">
329
+ <label for="node-input-chRed">
330
+ <i class="fa fa-circle" style="color:#e74c3c"></i> Red
331
+ </label>
332
+ <input type="number" id="node-input-chRed"
333
+ min="1" max="512" style="width:80px" placeholder="e.g. 201" />
334
+ </div>
335
+
336
+ <div class="form-row" id="dmx-ch-row-green">
337
+ <label for="node-input-chGreen">
338
+ <i class="fa fa-circle" style="color:#2ecc71"></i> Green
339
+ </label>
340
+ <input type="number" id="node-input-chGreen"
341
+ min="1" max="512" style="width:80px" placeholder="e.g. 202" />
342
+ </div>
343
+
344
+ <div class="form-row" id="dmx-ch-row-blue">
345
+ <label for="node-input-chBlue">
346
+ <i class="fa fa-circle" style="color:#3498db"></i> Blue
347
+ </label>
348
+ <input type="number" id="node-input-chBlue"
349
+ min="1" max="512" style="width:80px" placeholder="e.g. 203" />
350
+ </div>
351
+
352
+ <div class="form-row" id="dmx-ch-row-white">
353
+ <label for="node-input-chWhite">
354
+ <i class="fa fa-circle" style="color:#ecf0f1; text-shadow:0 0 1px #999"></i> White
355
+ </label>
356
+ <input type="number" id="node-input-chWhite"
357
+ min="1" max="512" style="width:80px" placeholder="e.g. 204" />
358
+ </div>
359
+
360
+ <div class="form-row" id="dmx-ch-row-warmWhite">
361
+ <label for="node-input-chWarmWhite">
362
+ <i class="fa fa-circle" style="color:#f39c12"></i> Warm White
363
+ </label>
364
+ <input type="number" id="node-input-chWarmWhite"
365
+ min="1" max="512" style="width:80px" placeholder="e.g. 205" />
366
+ </div>
367
+
368
+ <hr/>
369
+
370
+ <!-- ── DMX CONTROLLER ────────────────────────────────────── -->
371
+ <div class="form-row">
372
+ <label style="width:100%; font-weight:bold; color:#999; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em;">
373
+ <i class="fa fa-sort-numeric-asc"></i> DMX Controller
374
+ </label>
375
+ </div>
376
+
377
+ <div class="form-row">
378
+ <label for="node-input-controllerNum">
379
+ <i class="fa fa-sort-numeric-asc"></i> Controller
380
+ </label>
381
+ <input type="number" id="node-input-controllerNum"
382
+ min="1" style="width:70px" />
383
+ &nbsp;&nbsp;
384
+ <label for="node-input-universe" style="width:auto">
385
+ Universe
386
+ </label>
387
+ <input type="number" id="node-input-universe"
388
+ min="1" style="width:70px" />
389
+ </div>
390
+
391
+ <hr/>
392
+
393
+ <!-- ── OPTIONS ───────────────────────────────────────────── -->
394
+ <div class="form-row">
395
+ <label style="width:100%; font-weight:bold; color:#999; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em;">
396
+ <i class="fa fa-sliders"></i> Options
397
+ </label>
398
+ </div>
399
+
400
+ <div class="form-row">
401
+ <label for="node-input-haIcon">
402
+ <i class="fa fa-image"></i> HA Icon
403
+ </label>
404
+ <input type="text" id="node-input-haIcon"
405
+ placeholder="mdi:lightbulb" style="width:55%" />
406
+ </div>
407
+
408
+ <div class="form-row">
409
+ <label for="node-input-defaultState">
410
+ <i class="fa fa-toggle-off"></i> Default state
411
+ </label>
412
+ <select id="node-input-defaultState" style="width:100px">
413
+ <option value="OFF">OFF</option>
414
+ <option value="ON">ON</option>
415
+ </select>
416
+ </div>
417
+
418
+ <div class="form-row">
419
+ <label>&nbsp;</label>
420
+ <input type="checkbox" id="node-input-showEffects"
421
+ style="width:auto; margin-right:8px" />
422
+ <label for="node-input-showEffects" style="width:auto">
423
+ Show effects in HA
424
+ </label>
425
+ </div>
426
+
427
+ <div class="form-row">
428
+ <label>&nbsp;</label>
429
+ <input type="checkbox" id="node-input-transitions"
430
+ style="width:auto; margin-right:8px" />
431
+ <label for="node-input-transitions" style="width:auto">
432
+ Enable transitions
433
+ </label>
434
+ </div>
435
+
436
+ <div class="form-row">
437
+ <label>&nbsp;</label>
438
+ <input type="checkbox" id="node-input-groupSync"
439
+ style="width:auto; margin-right:8px" />
440
+ <label for="node-input-groupSync" style="width:auto">
441
+ Sync effects via Group Node
442
+ </label>
443
+ </div>
444
+
445
+ <hr/>
446
+
447
+ <!-- ── ADVANCED ──────────────────────────────────────────── -->
448
+ <div class="form-row">
449
+ <label style="width:100%; font-weight:bold; color:#999; font-size:0.85em; text-transform:uppercase; letter-spacing:0.05em;">
450
+ <i class="fa fa-wrench"></i> Advanced
451
+ </label>
452
+ </div>
453
+
454
+ <div class="form-row">
455
+ <label for="node-input-dmxLimiter">
456
+ <i class="fa fa-sort-numeric-asc"></i> DMX limiter
457
+ </label>
458
+ <input type="number" id="node-input-dmxLimiter"
459
+ min="0" max="255" style="width:70px" />
460
+ <span style="margin-left:8px; color:#999; font-size:0.85em;">0–255</span>
461
+ </div>
462
+
463
+ <div class="form-row">
464
+ <label for="node-input-minOutput">
465
+ <i class="fa fa-sort-numeric-asc"></i> Min output when ON
466
+ </label>
467
+ <input type="number" id="node-input-minOutput"
468
+ min="0" max="255" style="width:70px" />
469
+ <span style="margin-left:8px; color:#999; font-size:0.85em;">0 = pure gamma</span>
470
+ </div>
471
+
472
+ <div class="form-row">
473
+ <label for="node-input-brightBump">
474
+ <i class="fa fa-sort-numeric-asc"></i> Brightness bump
475
+ </label>
476
+ <input type="number" id="node-input-brightBump"
477
+ min="0" max="255" style="width:70px" />
478
+ <span style="margin-left:8px; color:#999; font-size:0.85em;">Initial ON value (0–255)</span>
479
+ </div>
480
+
481
+ <div class="form-row">
482
+ <label for="node-input-ticksPerSec">
483
+ <i class="fa fa-clock-o"></i> Transition ticks/sec
484
+ </label>
485
+ <input type="number" id="node-input-ticksPerSec"
486
+ min="1" max="100" style="width:70px" />
487
+ <span style="margin-left:8px; color:#999; font-size:0.85em;">Default 31</span>
488
+ </div>
489
+
490
+ </script>
491
+
492
+ <!-- ============================================================
493
+ Help Panel
494
+ ============================================================ -->
495
+ <script type="text/html" data-help-name="ha-mqtt-dmx">
496
+ <p>
497
+ Controls a single DMX fixture via Home Assistant and MQTT.
498
+ Supports RGBW, RGBWW, RGB, CCT, brightness and on/off colour modes.
499
+ Handles HA MQTT discovery, state reporting, transitions, and effects.
500
+ </p>
501
+
502
+ <h3>Setup</h3>
503
+ <ol>
504
+ <li>Select your <strong>Config</strong> node (site and zone settings)</li>
505
+ <li>Choose <strong>Colour Mode</strong> — channel fields update automatically</li>
506
+ <li>Set <strong>Fixture ID</strong> — Prefix, Plan ID number, and Channel letter if applicable</li>
507
+ <li>Enter <strong>DMX Channels</strong> matching your fixture's DMX start address</li>
508
+ <li>Set <strong>Controller</strong> and <strong>Universe</strong> numbers</li>
509
+ <li>Deploy — the fixture appears in HA automatically</li>
510
+ </ol>
511
+
512
+ <h3>Fixture ID</h3>
513
+ <p>
514
+ Matches your electrical plan cable ID. <code>L-992-A</code> = Light fixture,
515
+ cable 992, channel A. The entity ID in HA is locked to this ID and
516
+ survives friendly name changes in the HA dashboard.
517
+ </p>
518
+
519
+ <h3>Inputs</h3>
520
+ <dl class="message-properties">
521
+ <dt>device:add <span class="property-type">msg.device = "add"</span></dt>
522
+ <dd>Run MQTT discovery. Wire from SYSTEM node inject.</dd>
523
+ <dt>device:remove <span class="property-type">msg.device = "remove"</span></dt>
524
+ <dd>Remove entity from HA and clear memory.</dd>
525
+ <dt>HA command <span class="property-type">msg.payload.state</span></dt>
526
+ <dd>Handled internally via MQTT — no wiring needed.</dd>
527
+ <dt>Group cascade <span class="property-type">msg.dmx_trace</span></dt>
528
+ <dd>Command forwarded from a DMX Group Node via its Link output.</dd>
529
+ </dl>
530
+
531
+ <h3>DMX output format</h3>
532
+ <p>
533
+ Publishes to <code>{siteId}/{zone}/dmx/{universe}</code><br/>
534
+ Payload: <code>"{channel3digits}{value3digits}"</code>
535
+ e.g. <code>"212255"</code> = channel 212, value 255.
536
+ </p>
537
+
538
+ <h3>Colour modes</h3>
539
+ <ul>
540
+ <li><strong>RGBW</strong> — Red, Green, Blue, White channels</li>
541
+ <li><strong>RGBWW</strong> — Red, Green, Blue, White, Warm White</li>
542
+ <li><strong>RGB</strong> — Red, Green, Blue only</li>
543
+ <li><strong>Colour Temperature</strong> — White + Warm White (CCT)</li>
544
+ <li><strong>Brightness</strong> — Single channel dimmer</li>
545
+ <li><strong>On/Off</strong> — Binary switching via DMX</li>
546
+ </ul>
547
+ </script>