- 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.
282 lines
13 KiB
Markdown
282 lines
13 KiB
Markdown
# Using the flow for all lights and rooms
|
||
|
||
This doc explains how to scale the working “test light” setup to **all your lights and rooms**: what to configure in Node-RED, what to do in Home Assistant (including creating many entities efficiently), and how it fits with the PLC.
|
||
|
||
---
|
||
|
||
## 0. Single global room config (recommended)
|
||
|
||
To avoid adding a new room or light in several places, use **one config file** that lives on the Node-RED server and is loaded into **global context** at startup. All function nodes read from `global.get('roomConfig')`.
|
||
|
||
**Config file:** [`node-red/room-config.js`](../../node-red/room-config.js) — exports `ROOM_CONFIG` with: **roomNames** (NVL SEND), **lightEntityMap** (NVL to HA Sync), **entityToRoom** (HA to NVL), **deviceToRoom** (Zigbee to NVL).
|
||
|
||
**Server location:** `/root/.node-red/room-config.js` (uploaded via `scp`).
|
||
|
||
**Loader:** [`node-red/room-config-loader.js`](../../node-red/room-config-loader.js) — paste this code into a **Function node** named "Load room config". Connect an **Inject node** (once after 0.5 s) to it and deploy. It `require()`s the config file, clears the cache (so edits are picked up on redeploy), and sets `global.set('roomConfig', ...)`.
|
||
|
||
**Adding a room or light:** Edit `room-config.js` locally, `scp` it to the server, and redeploy (or restart Node-RED). No function nodes need editing.
|
||
|
||
---
|
||
|
||
## 1. How it works (recap)
|
||
|
||
- **HA / Zigbee** → Node-RED updates **`flow.nvlInState.rooms.<roomKey>`** (e.g. `ha_l1_on`, `zigbee_sw2`).
|
||
- **NVL SEND** builds the payload from **all** room keys and sends to the PLC.
|
||
- **PLC** drives relays; sends back light state in **NVL_Out**.
|
||
- **NVL to HA Sync** reads NVL_Out and calls HA **turn_on** / **turn_off** so the UI matches the real state.
|
||
|
||
To support all lights you need:
|
||
|
||
1. A **room list** that matches the PLC (NVL_In / NVL_Out).
|
||
2. **Node-RED:** one state key per room, and mappings: HA entity → room + light index, Zigbee device → room, PLC payload key → HA entity.
|
||
3. **HA:** one entity per light (or per zone) that you want to show in the UI and control.
|
||
|
||
---
|
||
|
||
## 2. Room list (align with PLC)
|
||
|
||
Use a single list of room keys everywhere. Example (match your CODESYS NVL order):
|
||
|
||
| Room key (Node-RED / NVL) | PLC NVL_Out (receive) | Lights per room |
|
||
|---------------------------|------------------------|-----------------|
|
||
| `livingRoom` | `l_livingRoom` | 1..6 |
|
||
| `masterBedroom` | `l_masterBedroom` | 1..6 |
|
||
| `kitchen` | `l_kitchen` | 1..6 |
|
||
| `bathroom` | `l_bathroom` | 1..6 |
|
||
| … | … | 1..6 |
|
||
|
||
- **NVL send (to PLC):** same keys as NVL_In in CODESYS (e.g. `livingRoom`, `masterBedroom`, … or `cmd_livingroom` for the test slot). Order and names must match the PLC.
|
||
- **NVL receive (from PLC):** keys in the nvl-receive output (e.g. `l_livingRoom`, `light_livingRoom`) — use the same key in **LIGHT_ENTITY_MAP** in the sync function.
|
||
|
||
Keep one list (e.g. in a comment or config file) and use it in:
|
||
- **state-to-nvl-send-payload.js** → `roomNames`
|
||
- **NVL to HA Sync** → `LIGHT_ENTITY_MAP[].room` (must match nvl-receive payload keys)
|
||
- **HA to NVL** / **Zigbee to NVL** → which room key to write to
|
||
|
||
---
|
||
|
||
## 3. Node-RED configuration for all lights
|
||
|
||
### 3.1 NVL SEND (state-to-nvl-send-payload.js)
|
||
|
||
Add **every room** that the PLC expects to the **`roomNames`** array. Each room uses the same `struct_room_cmds` shape.
|
||
|
||
```javascript
|
||
const roomNames = [
|
||
'livingRoom', // or cmd_livingroom if that’s your test slot
|
||
'masterBedroom',
|
||
'kitchen',
|
||
'bathroom',
|
||
'bedroom_1',
|
||
'bedroom_2',
|
||
'dinningRoom',
|
||
'entrance',
|
||
'hallway',
|
||
'pantry',
|
||
'guestWc',
|
||
'outVeranda',
|
||
'outFront',
|
||
'outBack',
|
||
'outSide',
|
||
];
|
||
```
|
||
|
||
Match this list to your **CODESYS NVL_In** layout and order.
|
||
|
||
### 3.2 HA → PLC: which room and light?
|
||
|
||
You have two patterns:
|
||
|
||
**Option A – One trigger-state per room (current pattern)**
|
||
- One tab (or subflow) per room.
|
||
- **trigger-state** watches that room’s entities (e.g. `input_boolean.living_room_1`, `input_boolean.living_room_2`).
|
||
- **HA to NVL** has `ROOM_NAME = 'livingRoom'` (or that room’s key).
|
||
- Replicate for each room (or use a subflow and pass `ROOM_NAME` in `msg`).
|
||
|
||
**Option B – One trigger-state for all entities (recommended for many lights)**
|
||
- Single **trigger-state** (or **events: state**) that watches **all** your light/input_boolean entities (e.g. by substring `input_boolean.` or a list of entity IDs).
|
||
- **HA to NVL** gets `msg.topic` = entity_id (e.g. `input_boolean.kitchen_2`).
|
||
- In the function, **derive room + light** from the entity_id using a small config:
|
||
|
||
```javascript
|
||
// Example: entity_id "input_boolean.kitchen_2" → room "kitchen", light 2
|
||
const ENTITY_TO_ROOM = {
|
||
'living_room': 'livingRoom',
|
||
'kitchen': 'kitchen',
|
||
'master_bedroom': 'masterBedroom',
|
||
'bathroom': 'bathroom',
|
||
// ...
|
||
};
|
||
// Parse: input_boolean.living_room_1 → room livingRoom, light 1
|
||
const parts = (msg.topic || '').split('.');
|
||
const name = (parts[1] || '').replace(/_(\d+)$/, '_$1');
|
||
const roomKey = ENTITY_TO_ROOM[name.split('_').slice(0, -1).join('_')] || name;
|
||
const lightNum = parseInt((name.match(/_(\d+)$/) || [,'1'])[1], 10) || 1;
|
||
```
|
||
|
||
Then set `state.rooms[roomKey]['ha_l' + lightNum + '_on']` or `_off` as you do now. So one HA to NVL function can serve all entities if you pass the right `msg.topic` and use a consistent naming (e.g. `room_light_N`).
|
||
|
||
### 3.3 Zigbee → PLC: which light does each button control?
|
||
|
||
Each **button** (1..6) is mapped to a specific **(room, light)** so e.g. Office switch button 1 → cmd_livingroom light 1, button 2 → cmd_out light 2. Config is in **room-config.js** → **`switchBindings`**:
|
||
|
||
- Key: Zigbee device **friendly_name**.
|
||
- Value: object mapping **button index** (1..6) to **`{ room, light }`** or to an array **`[ { room, light }, ... ]`** (one button can control several lights).
|
||
- The PLC receives `zigbee_sw<light>` set in the given **room** for each target.
|
||
|
||
**Example in `room-config.js`:**
|
||
|
||
```javascript
|
||
switchBindings: {
|
||
'Office Switch': {
|
||
1: { room: 'cmd_livingroom', light: 1 }, // button 1 → light 1 in cmd_livingroom
|
||
2: { room: 'cmd_out', light: 2 }, // button 2 → light 2 in cmd_out
|
||
// One button, multiple lights:
|
||
// 3: [ { room: 'kitchen', light: 1 }, { room: 'hallway', light: 1 } ],
|
||
},
|
||
},
|
||
```
|
||
|
||
Add every **room** you use (e.g. `cmd_out`) to **`roomNames`** so NVL SEND includes it. If a device has no `switchBindings` entry, Zigbee to NVL falls back to **`deviceToRoom`** and uses the button index as the light number in that single room. Use **[clear-zigbee-edge.js](clear-zigbee-edge.js)** in the Clear zigbee edge node so it supports an array of `{ room, key }` (multi-target).
|
||
|
||
### 3.4 PLC → HA: NVL to HA Sync (LIGHT_ENTITY_MAP)
|
||
|
||
Add **one entry per light** you want to sync from the PLC back to HA. The **room** key must match what **nvl-receive** puts in `msg.payload` (e.g. `l_livingRoom` or `light_livingRoom`).
|
||
|
||
```javascript
|
||
const LIGHT_ENTITY_MAP = [
|
||
{ room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_1' },
|
||
{ room: 'light_livingRoom', light: 2, entityId: 'input_boolean.living_room_2' },
|
||
{ room: 'l_kitchen', light: 1, entityId: 'input_boolean.kitchen_1' },
|
||
{ room: 'l_kitchen', light: 2, entityId: 'input_boolean.kitchen_2' },
|
||
{ room: 'l_masterBedroom', light: 1, entityId: 'input_boolean.master_bedroom_1' },
|
||
// ... one line per light
|
||
];
|
||
```
|
||
|
||
Generate this list from your room list + lights per room (e.g. 16 rooms × up to 6 lights = up to 96 entries; only add those you actually use).
|
||
|
||
---
|
||
|
||
## 4. Home Assistant: creating entities for all lights
|
||
|
||
You need one HA entity per light (or per zone) that Node-RED and the PLC treat as one target. Two common choices:
|
||
|
||
- **`input_boolean`** – simple on/off, good for “virtual” switches that only drive the PLC.
|
||
- **`light` (template or generic)** – if you want them to appear as lights in the HA UI and in dashboards.
|
||
|
||
You can create them **one by one** or **in bulk**.
|
||
|
||
### 4.1 Efficient way: YAML (many at once)
|
||
|
||
Define all helpers in YAML so HA creates them at startup. No need to click 50 times in the UI.
|
||
|
||
**Option 1 – `configuration.yaml` (or a package)**
|
||
|
||
```yaml
|
||
input_boolean:
|
||
living_room_1:
|
||
name: Living Room Light 1
|
||
living_room_2:
|
||
name: Living Room Light 2
|
||
kitchen_1:
|
||
name: Kitchen Light 1
|
||
kitchen_2:
|
||
name: Kitchen Light 2
|
||
master_bedroom_1:
|
||
name: Master Bedroom Light 1
|
||
# ... add one block per entity
|
||
```
|
||
|
||
- Entity IDs will be **`input_boolean.living_room_1`**, **`input_boolean.kitchen_1`**, etc.
|
||
- Match these **exactly** in Node-RED (**trigger-state** and **LIGHT_ENTITY_MAP**).
|
||
|
||
**Option 2 – Package file (recommended)**
|
||
|
||
Create a file under your config, e.g. `packages/lights_nodered.yaml`:
|
||
|
||
```yaml
|
||
input_boolean:
|
||
living_room_1: { name: Living Room 1 }
|
||
living_room_2: { name: Living Room 2 }
|
||
kitchen_1: { name: Kitchen 1 }
|
||
kitchen_2: { name: Kitchen 2 }
|
||
master_bedroom_1: { name: Master Bedroom 1 }
|
||
bathroom_1: { name: Bathroom 1 }
|
||
# Add every light you want to control
|
||
```
|
||
|
||
In `configuration.yaml` ensure packages are loaded:
|
||
|
||
```yaml
|
||
homeassistant:
|
||
packages: !include_dir_named packages
|
||
```
|
||
|
||
Restart HA. All entities are created in one go. Then in Node-RED use the same IDs in your trigger and in **LIGHT_ENTITY_MAP**.
|
||
|
||
**Example – copy and adapt** (save as `packages/lights_nodered.yaml` in your HA config folder):
|
||
|
||
```yaml
|
||
# Input booleans for PLC lights. Entity IDs must match Node-RED LIGHT_ENTITY_MAP and trigger-state.
|
||
input_boolean:
|
||
living_room_1: { name: Living Room 1 }
|
||
living_room_2: { name: Living Room 2 }
|
||
kitchen_1: { name: Kitchen 1 }
|
||
kitchen_2: { name: Kitchen 2 }
|
||
master_bedroom_1: { name: Master Bedroom 1 }
|
||
master_bedroom_2: { name: Master Bedroom 2 }
|
||
bathroom_1: { name: Bathroom 1 }
|
||
bedroom_1_1: { name: Bedroom 1 Light 1 }
|
||
bedroom_2_1: { name: Bedroom 2 Light 1 }
|
||
dinning_room_1: { name: Dining Room 1 }
|
||
entrance_1: { name: Entrance 1 }
|
||
hallway_1: { name: Hallway 1 }
|
||
pantry_1: { name: Pantry 1 }
|
||
guest_wc_1: { name: Guest WC 1 }
|
||
out_veranda_1: { name: Veranda 1 }
|
||
out_front_1: { name: Front 1 }
|
||
out_back_1: { name: Back 1 }
|
||
out_side_1: { name: Side 1 }
|
||
```
|
||
|
||
Add or remove lines to match your rooms and light counts. Entity IDs become `input_boolean.living_room_1`, etc.
|
||
|
||
### 4.2 UI way (one by one)
|
||
|
||
- **Settings** → **Devices & services** → **Helpers** → **Create Helper** → **Toggle**.
|
||
- Create a toggle, set name (e.g. “Living Room 1”). HA will assign an entity_id like **`input_boolean.living_room_1`** (or similar, depending on the name you give).
|
||
- Repeat for each light. Slower, but no YAML.
|
||
|
||
### 4.3 Naming convention (recommended)
|
||
|
||
Use a **consistent pattern** so you can derive room + light in Node-RED and in YAML:
|
||
|
||
- **Entity ID:** `input_boolean.<room>_<light>`
|
||
Examples: `living_room_1`, `kitchen_2`, `master_bedroom_1`.
|
||
- Then in HA to NVL you can parse: room from the first part, light from the trailing number.
|
||
|
||
---
|
||
|
||
## 5. Checklist: rolling out to all lights
|
||
|
||
| Step | Where | Action |
|
||
|------|--------|--------|
|
||
| 1 | **CODESYS** | Ensure NVL_In / NVL_Out include all rooms and lights in the order you use in Node-RED. |
|
||
| 2 | **HA** | Create all entities (YAML package or UI). Use a clear naming scheme (e.g. `room_light_N`). |
|
||
| 3 | **Node-RED – NVL SEND** | Set **`roomNames`** in `state-to-nvl-send-payload.js` to the full list of room keys (same order as PLC). |
|
||
| 4 | **Node-RED – HA to NVL** | Either duplicate the flow per room (Option A) or use one flow and derive **room + light** from `msg.topic` (Option B). Ensure every HA entity is mapped to a room key and light index. |
|
||
| 5 | **Node-RED – Zigbee to NVL** | In **room-config.js** set **switchBindings** (device → button → `{ room, light }` or array) so each button controls the right room+light; add those rooms to **roomNames**. Use **clear-zigbee-edge.js** for the 80 ms clear. |
|
||
| 6 | **Node-RED – NVL to HA Sync** | Fill **LIGHT_ENTITY_MAP** with one entry per light: `room` (payload key from nvl-receive), `light` (1..6), `entityId` (HA entity). |
|
||
| 7 | **Action nodes** | Keep one Action node (or two for on/off) with **Block Input Overrides** off so the sync’s `payload.action` and `payload.target.entity_id` are used for all entities. |
|
||
|
||
---
|
||
|
||
## 6. Summary
|
||
|
||
- **One shared state:** `flow.nvlInState.rooms.<roomKey>` for all rooms; **NVL SEND** builds one payload for the PLC from that state.
|
||
- **One sync map:** **LIGHT_ENTITY_MAP** in NVL to HA Sync defines which PLC light (room + index) updates which HA entity.
|
||
- **HA:** Create all entities in bulk via a **YAML package** (or one-by-one in Helpers). Use a consistent **entity_id** pattern so Node-RED can map HA ↔ room + light without mistakes.
|
||
|
||
If you want, the next step can be a single **example `packages/lights_nodered.yaml`** and an example **LIGHT_ENTITY_MAP** and **roomNames** list tailored to your actual room names and light counts.
|