# 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/config_files/room-config.js`](../../node-red/config_files/room-config.js) — exports `ROOM_CONFIG` with: **roomNames** (NVL SEND), **lightEntityMap** (NVL to HA Sync), **entityToRoom** (HA to NVL), **deviceToRoom** (Zigbee to NVL).

Canonical source-of-truth for this migration:

- Electrical mapping: [`../Electrical/lights_zones_canonical.csv`](../Electrical/lights_zones_canonical.csv)
- Naming rules: [`../Electrical/naming_rules.md`](../Electrical/naming_rules.md)

**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 |
|---------------------------|------------------------|-----------------|
| `open_plan_living_room`   | `l_open_plan_living_room` | 1..6         |
| `open_plan_guest_wc`      | `l_open_plan_guest_wc`    | 1..6         |
| `kitchen_kitchen`         | `l_kitchen_kitchen`       | 1..6         |
| `master_bedroom_suite`    | `l_master_bedroom_suite`  | 1..6         |
| …                         | …                      | 1..6            |

- **NVL send (to PLC):** same keys as NVL_In in CODESYS (e.g. `open_plan_living_room`, `master_bedroom_suite`). Order and names must match the PLC.
- **NVL receive (from PLC):** keys in the nvl-receive output (e.g. `l_open_plan_living_room`) — 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

For a focused guide on **creating entities and assigning them to rooms (Areas)** in HA, see **[ha-lights-and-rooms.md](ha-lights-and-rooms.md)**.

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.<area>_<room>_<light_type>_<index>`  
  Examples: `input_boolean.open_plan_living_room_main_1`, `input_boolean.kitchen_kitchen_island_1`, `input_boolean.exterior_yard_driveway_1`.  
- In HA to NVL, parse room mapping from `entityToRoom` using the base without the trailing `_<index>`.

---

## 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.
