Enhance Zigbee to NVL integration with button mapping and configuration updates
- Updated documentation to clarify the mapping of Zigbee buttons to specific (room, light) pairs using `switchBindings`. - Improved the Zigbee to NVL function to support both single-device and multi-device payloads, enhancing flexibility in handling actions. - Revised the room configuration to include detailed switch bindings and fallback mechanisms for device identification, streamlining the integration process. This update improves the usability and functionality of the Zigbee integration within Node-RED, facilitating better control of lighting systems.
This commit is contained in:
51
node-red/reload-room-config-docs.md
Normal file
51
node-red/reload-room-config-docs.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Reload room config without restarting Node-RED
|
||||
|
||||
After you edit `room-config.js` on the server (and save/upload it), you can have Node-RED reload it into `global.roomConfig` so all flows use the new config **without restarting Node-RED**.
|
||||
|
||||
---
|
||||
|
||||
## Option A: Auto-reload when you save the file (recommended)
|
||||
|
||||
When `room-config.js` is saved on the server, it is reloaded automatically. No extra nodes to install—use the built-in **Watch** node.
|
||||
|
||||
### Add the “Config reload on save” flow
|
||||
|
||||
Add these nodes and wire in order:
|
||||
|
||||
| Order | Node type | Name | Config |
|
||||
|-------|-----------|------|--------|
|
||||
| 1 | **watch** (built-in) | Watch room-config | In the node config, enter the **file** to watch. One of: **`/root/.node-red/room-config.js`** (single file) or **`/root/.node-red`** (directory; then add the filter Function below so only `room-config.js` triggers reload). The Watch node puts the changed file path in `msg.payload` and `msg.filename`, and the short name in `msg.file`. |
|
||||
| 2 | **Exec** | Read room-config as JSON | **Command:** `node -e "console.log(JSON.stringify(require('/root/.node-red/room-config.js')))"` · **Append:** payload (so output becomes `msg.payload`). |
|
||||
| 3 | **Function** | Reload room config from payload | Paste the code from **[reload-room-config-function.js](reload-room-config-function.js)**. |
|
||||
|
||||
**Wiring:** Watch → Exec → Reload room config from payload (Function).
|
||||
|
||||
If you watch the **directory** `/root/.node-red` instead of the single file, insert a **Function** between Watch and Exec so only changes to `room-config.js` trigger a reload:
|
||||
|
||||
```javascript
|
||||
// Only pass through when the changed file is room-config.js
|
||||
if (msg.file !== 'room-config.js') return null;
|
||||
return msg;
|
||||
```
|
||||
|
||||
After deploy, saving (or uploading) `/root/.node-red/room-config.js` will trigger the Watch → Exec → reload. Config updates with no restart and no manual reload.
|
||||
|
||||
---
|
||||
|
||||
## Option B: Manual reload (no file watcher)
|
||||
|
||||
Use this if you prefer not to install the watch node.
|
||||
|
||||
1. **Inject** – e.g. “Reload config” (click to trigger).
|
||||
2. **Exec** – same command as in Option A, step 3.
|
||||
3. **Function** – same “Reload room config from payload” code as in Option A, step 4.
|
||||
|
||||
**Wiring:** Inject → Exec → Function.
|
||||
|
||||
Trigger the Inject after you upload a new `room-config.js`.
|
||||
|
||||
---
|
||||
|
||||
## Note
|
||||
|
||||
`room-config.js` must export a plain object (no functions, no non-JSON values). The repo version is JSON-serializable.
|
||||
22
node-red/reload-room-config-function.js
Normal file
22
node-red/reload-room-config-function.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Paste into a Function node named "Reload room config from payload".
|
||||
// Input: msg.payload = JSON string (from Exec node that runs the script below).
|
||||
// Sets global roomConfig so all flows see the new config without restarting Node-RED.
|
||||
|
||||
const raw = msg.payload;
|
||||
if (typeof raw !== 'string') {
|
||||
node.warn('[Reload config] expected string payload, got ' + typeof raw);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const config = JSON.parse(raw.trim());
|
||||
if (!config.roomNames || !Array.isArray(config.roomNames)) {
|
||||
node.error('[Reload config] invalid config: missing roomNames');
|
||||
return null;
|
||||
}
|
||||
global.set('roomConfig', config);
|
||||
node.warn('[Reload config] OK: ' + config.roomNames.length + ' rooms, ' + (config.lightEntityMap ? config.lightEntityMap.length : 0) + ' light mappings');
|
||||
return msg;
|
||||
} catch (e) {
|
||||
node.error('[Reload config] parse error: ' + e.message);
|
||||
return null;
|
||||
}
|
||||
@@ -1,13 +1,24 @@
|
||||
// Paste this into a Function node named "Load room config".
|
||||
// Connect an Inject node (once after 0.5 s) to it so it runs at startup.
|
||||
//
|
||||
// Since functionExternalModules is true in settings.js, you can require files.
|
||||
// room-config.js must be on the Node-RED server at /root/.node-red/room-config.js
|
||||
// Load room config into global (no require - Function nodes can't use it).
|
||||
// 1. Open node-red/room-config.js in the repo.
|
||||
// 2. Copy the whole object: from "const ROOM_CONFIG = {" up to "};" (do NOT copy "module.exports").
|
||||
// 3. Replace the ROOM_CONFIG below with your paste. Keep the two lines at the end.
|
||||
|
||||
// Delete cached version so changes to the file are picked up on redeploy
|
||||
delete require.cache[require.resolve('/root/.node-red/room-config.js')];
|
||||
const ROOM_CONFIG = require('/root/.node-red/room-config.js');
|
||||
const ROOM_CONFIG = {
|
||||
roomNames: ['cmd_livingroom', 'cmd_out'],
|
||||
lightEntityMap: [
|
||||
{ room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_new' },
|
||||
],
|
||||
entityToRoom: { living_room_new: 'cmd_livingroom' },
|
||||
deviceToRoom: { 'Office Switch': 'cmd_livingroom' },
|
||||
deviceIdToName: {},
|
||||
switchBindings: {
|
||||
'Office Switch': {
|
||||
1: { room: 'cmd_livingroom', light: 1 },
|
||||
2: { room: 'cmd_livingroom', light: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
global.set('roomConfig', ROOM_CONFIG);
|
||||
node.warn('[Load room config] loaded: ' + ROOM_CONFIG.roomNames.length + ' rooms, ' + ROOM_CONFIG.lightEntityMap.length + ' light mappings');
|
||||
node.warn('[Load room config] loaded: ' + ROOM_CONFIG.roomNames.length + ' rooms');
|
||||
return null;
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
* Single source of truth for rooms and lights.
|
||||
* Lives on the Node-RED server at /root/.node-red/room-config.js
|
||||
* All function nodes use: global.get('roomConfig')
|
||||
* Loaded at startup by room-config-loader.js (Function node + Inject once).
|
||||
*
|
||||
* When you add a room or light, edit ONLY this file, upload to the server, and restart Node-RED (or redeploy).
|
||||
* Loaded at startup from settings.js (functionGlobalContext). To reload without restart, use the
|
||||
* "Reload room config" flow: see node-red/reload-room-config-docs.md
|
||||
*/
|
||||
|
||||
const ROOM_CONFIG = {
|
||||
// Order must match CODESYS NVL_In. Used by NVL SEND (state-to-nvl-send-payload).
|
||||
roomNames: [
|
||||
'cmd_livingroom', // test slot — replace with 'livingRoom' when moving to production
|
||||
'masterBedroom',
|
||||
'masterBathroom',
|
||||
'bedroom_1',
|
||||
'bedroom_2',
|
||||
'bathroom',
|
||||
'guestWc',
|
||||
'kitchen',
|
||||
'pantry',
|
||||
'livingRoom',
|
||||
'dinningRoom',
|
||||
'entrance',
|
||||
'hallway',
|
||||
'outVeranda',
|
||||
'outFront',
|
||||
'outBack',
|
||||
'outSide',
|
||||
'cmd_livingroom',
|
||||
'cmd_out', // add other NVL room keys as needed
|
||||
// 'masterBedroom',
|
||||
// 'masterBathroom',
|
||||
// 'bedroom_1',
|
||||
// 'bedroom_2',
|
||||
// 'bathroom',
|
||||
// 'guestWc',
|
||||
// 'kitchen',
|
||||
// 'pantry',
|
||||
// 'livingRoom',
|
||||
// 'dinningRoom',
|
||||
// 'entrance',
|
||||
// 'hallway',
|
||||
// 'outVeranda',
|
||||
// 'outFront',
|
||||
// 'outBack',
|
||||
// 'outSide',
|
||||
],
|
||||
|
||||
// PLC → HA sync. room = key in nvl-receive payload, light = 1..6, entityId = HA entity.
|
||||
@@ -38,31 +38,49 @@ const ROOM_CONFIG = {
|
||||
|
||||
// HA entity_id substring (after domain.) without trailing _N → NVL room key. For HA to NVL.
|
||||
entityToRoom: {
|
||||
living_room: 'livingRoom',
|
||||
// living_room: 'livingRoom',
|
||||
living_room_new: 'cmd_livingroom',
|
||||
kitchen: 'kitchen',
|
||||
master_bedroom: 'masterBedroom',
|
||||
bathroom: 'bathroom',
|
||||
bedroom_1: 'bedroom_1',
|
||||
bedroom_2: 'bedroom_2',
|
||||
dinning_room: 'dinningRoom',
|
||||
entrance: 'entrance',
|
||||
hallway: 'hallway',
|
||||
pantry: 'pantry',
|
||||
guest_wc: 'guestWc',
|
||||
out_veranda: 'outVeranda',
|
||||
out_front: 'outFront',
|
||||
out_back: 'outBack',
|
||||
out_side: 'outSide',
|
||||
// kitchen: 'kitchen',
|
||||
// master_bedroom: 'masterBedroom',
|
||||
// bathroom: 'bathroom',
|
||||
// bedroom_1: 'bedroom_1',
|
||||
// bedroom_2: 'bedroom_2',
|
||||
// dinning_room: 'dinningRoom',
|
||||
// entrance: 'entrance',
|
||||
// hallway: 'hallway',
|
||||
// pantry: 'pantry',
|
||||
// guest_wc: 'guestWc',
|
||||
// out_veranda: 'outVeranda',
|
||||
// out_front: 'outFront',
|
||||
// out_back: 'outBack',
|
||||
// out_side: 'outSide',
|
||||
},
|
||||
|
||||
// Zigbee device friendly_name → NVL room key. For Zigbee to NVL.
|
||||
// Zigbee: friendly_name → single room (fallback when switchBindings missing).
|
||||
deviceToRoom: {
|
||||
'Living Room Door Switch': 'livingRoom',
|
||||
'Office Switch': 'cmd_livingroom',
|
||||
'Kitchen Switch': 'kitchen',
|
||||
'Master Bedroom Switch': 'masterBedroom',
|
||||
// Add each Zigbee switch friendly_name and the room key it controls.
|
||||
},
|
||||
|
||||
// When using a multi-device Zigbee node, payload is keyed by IEEE (e.g. 0xa4c138a5b9771b05). Map IEEE → friendly name so switchBindings by name still works.
|
||||
deviceIdToName: {
|
||||
// '0xa4c138a5b9771b05': 'Office Switch', // uncomment and add your device IEEE from the payload
|
||||
},
|
||||
|
||||
// Optional: bind by IEEE instead of friendly name (same shape as switchBindings). Use if the node never sends friendly_name.
|
||||
// switchBindingsByDeviceId: {
|
||||
// '0xa4c138a5b9771b05': { 1: { room: 'cmd_livingroom', light: 1 }, 2: { room: 'cmd_out', light: 2 } },
|
||||
// },
|
||||
|
||||
/**
|
||||
* Which (room, light) each physical button controls. Key = button number (1..6), value = { room, light }.
|
||||
* Button and light are independent: e.g. button 2 can control light 1, or light 2 in another room.
|
||||
* One button can target one { room, light } or an array for multiple lights.
|
||||
*/
|
||||
switchBindings: {
|
||||
'Office Switch': {
|
||||
1: { room: 'cmd_livingroom', light: 1 }, // button 1 → light 1
|
||||
2: { room: 'cmd_livingroom', light: 1 }, // button 2 → light 1 (same light; other switches could do button 2 → light 2)
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
34
node-red/settings-roomsnippet.md
Normal file
34
node-red/settings-roomsnippet.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Load room-config.js at Node-RED startup (fix "require is not defined")
|
||||
|
||||
Function nodes cannot use `require()`. Load the config in **settings.js** instead.
|
||||
|
||||
1. **On the Node-RED server**, edit:
|
||||
```bash
|
||||
nano /root/.node-red/settings.js
|
||||
```
|
||||
|
||||
2. **Find** the block that looks like:
|
||||
```javascript
|
||||
functionGlobalContext: {
|
||||
// os:require('os'),
|
||||
},
|
||||
```
|
||||
|
||||
3. **Add** the room config so it becomes:
|
||||
```javascript
|
||||
functionGlobalContext: {
|
||||
roomConfig: require('/root/.node-red/room-config.js'),
|
||||
// os:require('os'),
|
||||
},
|
||||
```
|
||||
(Use the correct path to your `room-config.js` if different.)
|
||||
|
||||
4. **Save**, then **restart Node-RED**:
|
||||
```bash
|
||||
systemctl restart node-red
|
||||
```
|
||||
or however you run Node-RED.
|
||||
|
||||
5. In your flows, **remove or disconnect** the "Load room config" Function node (or leave it unused). All nodes that use `global.get('roomConfig')` will get the config automatically.
|
||||
|
||||
**When you change room-config.js:** upload the file to the server, then restart Node-RED to reload it.
|
||||
Reference in New Issue
Block a user