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

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
@@ -270,6 +270,127 @@ Set to `0` to make all un-timed commands instant.
270
270
 
271
271
  ---
272
272
 
273
+ ## Context store — finding your files
274
+
275
+ This is one of the most common questions with Node-RED on Home Assistant. The context store files are **not** where you expect them.
276
+
277
+ ### Where are the files?
278
+
279
+ The NR add-on runs in its own Docker container with an isolated config directory. This is **separate** from the main HA config you see via Samba.
280
+
281
+ | What you see | Actual path on host |
282
+ |---|---|
283
+ | Samba `\\{haip}\config\` | HA config — flows, automations etc. |
284
+ | NR add-on config | `/addon_configs/a0d7b954_nodered/` |
285
+ | Context store files | `/addon_configs/a0d7b954_nodered/nodeRED/context_stores/context/` |
286
+
287
+ The NR add-on config directory is **not accessible via Samba** — this trips everyone up.
288
+
289
+ ### How to find your context files
290
+
291
+ Use the **Studio Code Server** add-on (VS Code in HA):
292
+
293
+ 1. Install Studio Code Server from the add-on store if not already installed
294
+ 2. Open it: Settings → Add-ons → Studio Code Server → Open Web UI
295
+ 3. Open a terminal: Terminal → New Terminal
296
+ 4. Run:
297
+
298
+ ```bash
299
+ find / -name "*.json" -path "*context*" 2>/dev/null | grep -v proc
300
+ ```
301
+
302
+ You should see something like:
303
+ ```
304
+ /addon_configs/a0d7b954_nodered/nodeRED/context_stores/context/7e2467f26a0693a0/dd6582b967f0314a.json
305
+ /addon_configs/a0d7b954_nodered/nodeRED/context_stores/context/7e2467f26a0693a0/75b10249fcbfd224.json
306
+ ...
307
+ ```
308
+
309
+ One `.json` file per node, named by node ID.
310
+
311
+ ### Reading a context file
312
+
313
+ ```bash
314
+ cat "/addon_configs/a0d7b954_nodered/nodeRED/context_stores/context/{flowId}/{nodeId}.json"
315
+ ```
316
+
317
+ A healthy file looks like:
318
+ ```json
319
+ {
320
+ "state": "ON",
321
+ "brightness": 255,
322
+ "red": 255,
323
+ "green": 255,
324
+ "blue": 255,
325
+ "white": 255,
326
+ "warmWhite": 0
327
+ }
328
+ ```
329
+
330
+ ### Important: NR adds a `/context/` subfolder
331
+
332
+ When you set `dir` in your context store config, NR automatically appends `/context/` to it. So:
333
+
334
+ ```javascript
335
+ // You set:
336
+ config: { dir: '/config/nodeRED/context_stores' }
337
+
338
+ // NR actually writes to:
339
+ // /config/nodeRED/context_stores/context/
340
+ ```
341
+
342
+ ### Recommended config.js settings
343
+
344
+ ```javascript
345
+ contextStorage: {
346
+ memory: { module: 'memory' },
347
+ disk_values: {
348
+ module: 'localfilesystem',
349
+ config: {
350
+ dir: '/config/nodeRED/context_stores',
351
+ flushInterval: 5 // Write to disk every 5 seconds (default is 30)
352
+ }
353
+ },
354
+ disk_meta: {
355
+ module: 'localfilesystem',
356
+ config: {
357
+ dir: '/config/nodeRED/context_stores',
358
+ flushInterval: 5
359
+ }
360
+ },
361
+ default: { module: 'memory' },
362
+ },
363
+ ```
364
+
365
+ **Important:** Use an **absolute path** starting with `/config/`. A relative path will silently resolve to the wrong location and nothing will be saved.
366
+
367
+ `flushInterval: 5` means NR writes to disk every 5 seconds instead of the default 30. This reduces the window of data loss on unexpected shutdown — after turning a light on, wait 5 seconds before restarting.
368
+
369
+ ### Verifying context store is working
370
+
371
+ After saving a state and waiting 5+ seconds, check your files appeared:
372
+
373
+ ```bash
374
+ ls "/addon_configs/a0d7b954_nodered/nodeRED/context_stores/context/"
375
+ ```
376
+
377
+ If the directory is empty or doesn't exist — check your `config.js` path is absolute and restart NR.
378
+
379
+ ### NR startup log confirmation
380
+
381
+ On every startup NR logs which context stores loaded:
382
+
383
+ ```
384
+ Context store : 'memory' [module=memory]
385
+ Context store : 'disk_values' [module=localfilesystem]
386
+ Context store : 'disk_meta' [module=localfilesystem]
387
+ Context store : 'default' [module=memory]
388
+ ```
389
+
390
+ If `disk_values` shows `[module=memory]` — your config.js change didn't take effect.
391
+
392
+ ---
393
+
273
394
  ## Troubleshooting
274
395
 
275
396
  **Nodes don't appear in palette** — Restart Node-RED. Check log for errors.
@@ -290,7 +290,7 @@ module.exports = function (RED) {
290
290
  catch(e) { node.warn(`${groupId} — failed to parse HA command`); return; }
291
291
 
292
292
  saveGroupState(payload);
293
- pubState({ state: payload.state, color_mode: S.colorMode, brightness: payload.brightness });
293
+ pubState({ state: payload.state, color_mode: S.colorMode, brightness: payload.brightness, color: payload.color });
294
294
  forwardToChildren(payload, null);
295
295
  const _lbl = payload.effect ? `effect:${payload.effect}` : `${payload.state}`;
296
296
  groupStatus(payload.state, `${groupId} → ${_lbl}`);
@@ -318,7 +318,8 @@ module.exports = function (RED) {
318
318
  function handleDeviceRemove(incomingTrace) {
319
319
  if (diskTimer) { clearTimeout(diskTimer); diskTimer = null; }
320
320
  // No flush needed on remove — state is cleared below
321
- ctxSet('state', null); ctxSet('state', null, 'disk_values');
321
+ // Keep disk state on remove — state survives remove/add cycles
322
+ ctxSet('state', null); // RAM only
322
323
  pub(cfgTopic, '', true);
323
324
  broker.unsubscribe(cmdTopic, node.id);
324
325
  forwardToChildren({ device: 'remove' }, incomingTrace);
@@ -337,7 +338,7 @@ module.exports = function (RED) {
337
338
  // Forward payload deeper
338
339
  if (msg.payload) {
339
340
  saveGroupState(msg.payload);
340
- pubState({ state: msg.payload.state, color_mode: S.colorMode, brightness: msg.payload.brightness });
341
+ pubState({ state: msg.payload.state, color_mode: S.colorMode, brightness: msg.payload.brightness, color: msg.payload.color });
341
342
  forwardToChildren(msg.payload, msg.dmx_trace);
342
343
  groupStatus(msg.payload.state, `${groupId} → cascade:${msg.payload.state}`);
343
344
  }
@@ -354,7 +355,7 @@ module.exports = function (RED) {
354
355
  }
355
356
  } else if (msg.payload && msg.payload.state != null) {
356
357
  saveGroupState(msg.payload);
357
- pubState({ state: msg.payload.state, color_mode: S.colorMode, brightness: msg.payload.brightness });
358
+ pubState({ state: msg.payload.state, color_mode: S.colorMode, brightness: msg.payload.brightness, color: msg.payload.color });
358
359
  forwardToChildren(msg.payload, null);
359
360
  groupStatus(msg.payload.state, `${groupId} → ${msg.payload.state}`);
360
361
  } else {
@@ -670,7 +670,11 @@ module.exports = function (RED) {
670
670
  sendDmxChannels(buildColorChannels(0, r, g, b, w, ww));
671
671
  }
672
672
  pubState({ state, color_mode: S.colorMode, brightness, color: { r, g, b, w, ww } });
673
- setStatus('yellow', 'ring', `${fixtureId} ready — awaiting HA`);
673
+ if (state === 'ON') {
674
+ setStatus('green', 'dot', `${fixtureId} ON bright:${brightness}`);
675
+ } else {
676
+ setStatus('grey', 'ring', `${fixtureId} OFF`);
677
+ }
674
678
  node.log(`${fixtureId} recovery — state:${state}`);
675
679
  }, 2000);
676
680
  }
@@ -693,8 +697,10 @@ module.exports = function (RED) {
693
697
  ctxSet('warmWhite',ww,'disk_values');
694
698
  }
695
699
  }
700
+ // Keep disk state on remove — state survives remove/add cycles
701
+ // Only clear RAM so recovery on re-add reads last known disk state
696
702
  ['state','brightness','red','green','blue','white','warmWhite'].forEach(function (k) {
697
- ctxSet(k, null); ctxSet(k, null, 'disk_values');
703
+ ctxSet(k, null);
698
704
  });
699
705
  pub(cfgTopic, '', true);
700
706
  broker.unsubscribe(cmdTopic, node.id);
@@ -323,7 +323,11 @@ module.exports = function (RED) {
323
323
  const state = recall('state', 'state_disk', S.defaultState);
324
324
  pubRelay(state === 'ON' ? 1 : 0);
325
325
  pubState(state);
326
- setStatus('yellow', 'ring', `${fixtureId} ready — awaiting HA`);
326
+ if (state === 'ON') {
327
+ setStatus('green', 'dot', `${fixtureId} ON`);
328
+ } else {
329
+ setStatus('grey', 'ring', `${fixtureId} OFF`);
330
+ }
327
331
  node.log(`${fixtureId} recovery — state:${state}`);
328
332
  }, 2000);
329
333
  }
@@ -332,7 +336,8 @@ module.exports = function (RED) {
332
336
  function handleDeviceRemove() {
333
337
  stopEffect();
334
338
  if (diskTimer) { clearTimeout(diskTimer); diskTimer = null; }
335
- ctxSet('state', null); ctxSet('state', null, 'disk_values');
339
+ // Keep disk state on remove — state survives remove/add cycles
340
+ ctxSet('state', null); // RAM only
336
341
  pub(cfgTopic, '', true);
337
342
  broker.unsubscribe(cmdTopic, node.id);
338
343
  setStatus('red', 'ring', `${fixtureId} removed`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-dmx-for-ha",
3
- "version": "0.4.1",
3
+ "version": "0.4.4",
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",