# Node-RED: Living Room flow

This document describes the **Living Room** tab in Node-RED: how HA and Zigbee drive NVL commands and how the 328-byte buffer is built and sent to the PLC.

---

## Flow diagram

```
[Inject] ──► [trigger-state] ◄── HA entity: input_boolean.living_room_new
                  │
                  ├──► [HA to NVL] ──┬──► [Build NVL_In] ──┬──► [link out 26] ──► (Flow 1: to UDP → PLC)
                  │                  │                     └──► debug 39
                  └──► debug 36      │
                                     │
[Zigbee2MQTT] ──► [Zigbee to NVL] ──┼──► [Build NVL_In]
  Living Room Door Switch            │         ▲
  (TS0044)                           ├──► [80ms delay] ──► [Clear zigbee edge] ──► [Build NVL_In]
                                     │                            │
                                     └──► debug 40                └──► debug 41
```

- **HA path:** `trigger-state` watches `input_boolean.living_room_new` → **HA to NVL** updates `flow.nvlInState.rooms.cmd_livingroom` (ha_lN_on / ha_lN_off) → **Build NVL_In** → link out → Flow 1 → UDP to PLC.
- **Zigbee path:** **zigbee2mqtt-in** (Living Room Door Switch TS0044) → **Zigbee to NVL** sets zigbee_swN in state → **Build NVL_In** (send) → then **80ms delay** → **Clear zigbee edge** (clear zigbee_swN) → **Build NVL_In** again (second packet so PLC sees one pulse).

---

## Nodes (Living Room tab)

| Node | Type | Role |
|------|------|------|
| **trigger-state** | node-red-contrib-home-assistant-websocket | Listens to `input_boolean.living_room_new`; output 1 → HA to NVL, output 2 → blocked. |
| **Inject** | inject | Manual/startup trigger into trigger-state (optional). |
| **HA to NVL** | function | Maps HA entity state to `cmd_livingroom` in `flow.nvlInState`; sets ha_l1_on/ha_l1_off … ha_l6_on/ha_l6_off from entity ID and payload; then `buildAndSend: true`. |
| **Zigbee to NVL** | function | Maps Zigbee action (single/double/hold/release/triple/quad) to zigbee_sw1…6; updates `flow.nvlInState.rooms.livingRoom`; sets `msg.zigbeeClear` for later clear; then `buildAndSend: true`. |
| **80ms** | delay | 80 ms then passes message to Clear zigbee edge. |
| **Clear zigbee edge** | function | Clears the zigbee_swN flag in `nvlInState` and sets `buildAndSend: true` again. |
| **Build NVL_In** | function | Reads `flow.nvlInState`, builds 328-byte buffer (16 rooms × 20 bytes + boiler 8 bytes), sets `msg.payload = buf`; outputs to link out 26 and debug. |
| **link out 26** | link out | Sends built buffer to Flow 1’s **to UDP** link in → **udp out** (10.20.30.5:1202). |
| **debug 36–41** | debug | Optional debug (37 active). |

---

## Shared state: `flow.nvlInState`

All rooms and boiler share one object:

- **Shape:** `{ rooms: { cmd_livingroom: { ha_l1_on, ha_l1_off, … }, … }, boiler: { … } }`
- **Living Room (HA test)** writes to `state.rooms.cmd_livingroom`. **Build NVL_In** reads the full object and serializes all 16 slots + boiler into the 328-byte NVL_In layout.

Room names order in Build NVL_In (must match PLC NVL):

`masterBedroom`, `masterBathroom`, `bedroom_1`, `bedroom_2`, `bathroom`, `guestWc`, `kitchen`, `pantry`, **`cmd_livingroom`**, `dinningRoom`, `entrance`, `hallway`, `outVeranda`, `outFront`, `outBack`, `outSide`.

---

## Test variable: `cmd_livingroom`

The flow uses **`cmd_livingroom`** (the NVL variable you added for testing), not `livingRoom`. HA to NVL writes to `flow.nvlInState.rooms.cmd_livingroom`, and Build NVL_In reads that key for the living-room slot in the 328-byte buffer (same 20-byte layout as struct_room_cmds).

---

## What the trigger-state must output to turn the light ON (PLC)

The **HA to NVL** node decides ON vs OFF from the message it receives. For the PLC to turn the light **on**:

| Field     | Value |
|----------|--------|
| **payload** | `'on'` (string) **or** `true` (boolean) |
| **topic**   | Entity ID (e.g. `input_boolean.living_room_new`). Optional for which light 1–6: if the last part after `_` is a number (e.g. `living_room_2` → 2), that light is used; otherwise light **1** is used. |

So the **trigger-state** should output to the first (allowed) output:

- **Turn ON:**  `msg.payload === 'on'` or `msg.payload === true`  → PLC gets `ha_lN_on = true` in **cmd_livingroom**.
- **Turn OFF:** any other payload (e.g. `'off'`, `false`)  → PLC gets `ha_lN_off = true` in **cmd_livingroom**.

With the current entity `input_boolean.living_room_new` there is no `_1`…`_6`, so it always controls **light 1** in the living room.

---

## HA to NVL (function code)

- **Room (state key):** `cmd_livingroom` (NVL variable for testing).
- **Entity ID:** from `msg.topic` (e.g. `input_boolean.living_room_new`). Light index 1–6 is parsed from the last `_N` in the entity ID; default 1.
- **ON/OFF:** `msg.payload === 'on'` or `true` → set `ha_lN_on = true`, else `ha_lN_off = true` (the other cleared).
- Then sets `msg.payload = { buildAndSend: true }` and returns.

### Debug logs (in Node-RED Debug sidebar)

The function uses `node.warn()` so messages appear in the **Debug** tab (with the warning icon) without needing a separate debug node:

1. **On input:** `[HA to NVL] topic=... payload=... (type ...) → lightNum=... isOn=...`  
   Use this to confirm what the trigger-state sent: `topic`, `payload`, its type, and the parsed `lightNum` (1–6) and `isOn` (true/false).

2. **After state update:** `[HA to NVL] set cmd_livingroom ha_lN_on=true, buildAndSend` (or `ha_lN_off`).  
   Confirms which command was written and that `buildAndSend` is being passed on.

If `isOn` is false when you expect ON, check that the trigger-state output is exactly `'on'` (string) or `true` (boolean). If `topic` is wrong or missing, the light number may default to 1.

**HA to NVL function with debug:** full code in [ha-to-nvl-function.js](ha-to-nvl-function.js) (uses **cmd_livingroom**). Copy that file’s contents into the Node-RED function node.

---

## Zigbee to NVL (function code)

- **Room:** `livingRoom`.
- **Action map:** `single→1, double→2, hold→3, release→4, triple→5, quad→6` → sets `zigbee_swN`.
- **Edge behaviour:** Passes `msg.zigbeeClear = { room: 'livingRoom', key: 'zigbee_swN' }` so **Clear zigbee edge** (after 80 ms) clears that flag and calls **Build NVL_In** again; PLC sees one pulse.

---

## Build NVL_In (function code)

- **Buffer:** 328 bytes (Buffer.alloc(328)).
- **Rooms:** 16 rooms × 20 bytes each: bytes 0–11 = ha_l1_on, ha_l1_off, … ha_l6_off; bytes 12–17 = zigbee_sw1…6; bytes 18–19 = ha_all_on, ha_all_off.
- **Boiler:** offset 320: ha_on, ha_off, schedule_on, schedule_off, emergency_stop, (reserved), max_on_time_minutes (INT16 LE).
- **Output:** `msg.payload = buf`; wired to **link out 26** (and debug).

---

## Zigbee device

- **Friendly name:** Living Room Door Switch (TS0044)
- **Device ID:** 0xa4c1383d7921827a
- **Server:** zigbee2mqtt-server config (id 4e20fc347c658518)

---

## Do you need Build NVL_In?

**No, if you use the nvl-send node.** The nvl-send node (node-red-contrib-nvl) does the binary packing from a payload object. So you can:

1. **Remove** the **Build NVL_In** node and the **link out 26** (and the link in “to UDP” path that receives it).
2. **Add** a single function that builds the payload object from `flow.nvlInState` and feed that into **nvl-send** → **udp out**.

Code for that function (copy into a function node): **[state-to-nvl-send-payload.js](state-to-nvl-send-payload.js)**. It builds the object with the 15 `struct_switches` rooms (default false) and **cmd_livingroom** from `state.rooms.cmd_livingroom`. Wire: **HA to NVL / Zigbee to NVL** → **state-to-nvl-send-payload** → **nvl-send** → **udp out**.

---

## Syncing PLC state back to Home Assistant (Zigbee / any source → HA UI)

When you change a light with the **Zigbee switch** (or any source that doesn’t go through HA), the PLC updates but HA doesn’t, so entities can stay out of sync. To keep HA in sync for **all** mapped lights:

1. **After the nvl-receive node** (Flow 1), add a **function** node and paste the code from **[nvl-to-ha-sync-livingroom.js](nvl-to-ha-sync-livingroom.js)**.
2. In that function, **`LIGHT_ENTITY_MAP`** defines which PLC room/light → HA entity to sync. Each entry is `{ room: 'payloadKey', light: 1..6, entityId: 'domain.entity_id' }`. Add one row per light (any room/zone). Example:
   - `{ room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_new' }`
   - `{ room: 'l_kitchen', light: 1, entityId: 'light.kitchen_ceiling' }`
3. Give the function **2 outputs**. Then either:
   **Option A – Action node:** In the palette look under **Home Assistant** for **Action** ([docs](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/node/action.html)). The sync already sends `payload.action` (e.g. `input_boolean.turn_on`) and `payload.target.entity_id` (array). Output 1 → **Action** (turn_on), Output 2 → **Action** (turn_off). You can leave Action config empty and use “Block Input Overrides” off so `msg.payload` sets the action and target.
   **Option B – HTTP:** Use **[nvl-to-ha-http-call.js](nvl-to-ha-http-call.js)** + **http request** node; set `flow.set('ha_base_url', 'http://HA_IP:8123')` and `flow.set('ha_token', 'YOUR_TOKEN')`.

The function only sends a message when a mapped light’s state **changed**, so multiple entities can be updated in one NVL cycle. When you use the Zigbee switch (or the PLC changes state any other way), the matching HA entities are updated.

### How to configure the Action node

You can use **one** Action node for both turn_on and turn_off (the sync sends the right `action` and `target` in each message). Connect **both** outputs of the sync function to the **same** Action node.

1. Add an **Action** node (Palette → Home Assistant → Action).
2. **Server:** Select your Home Assistant server config.
3. **Block Input Overrides:** Set to **off** (unchecked). This allows `msg.payload` from the sync to override the node config, so `payload.action` and `payload.target.entity_id` are used.
4. **Action:** The node may require a non-empty value. Enter a placeholder such as **`input_boolean.turn_on`**. With **Block Input Overrides** off, the sync’s `msg.payload.action` overrides this on each message. Leave **Entity**, **Data** etc. empty.
5. **Queue:** Optional — e.g. `none` or `last` if you want to send the last message when HA reconnects.
6. Connect **Output 1** and **Output 2** of the NVL→HA sync function to this Action node.

Alternatively use **two** Action nodes: connect Output 1 to one (optional: set action to `input_boolean.turn_on`), Output 2 to the other (optional: `input_boolean.turn_off`). Still set **Block Input Overrides** to **off** so `payload.target.entity_id` is used.

---

## Connection to Flow 1 (CODESYS)

- **Option A (with Build NVL_In):** Living Room builds the buffer in a function and sends it via **link out 26** → Flow 1 **link in "to UDP"** → **udp out**.
- **Option B (with nvl-send only):** HA to NVL / Zigbee to NVL → **state-to-nvl-send-payload** → **nvl-send** → **udp out**. No Build NVL_In, no link nodes needed for this path.
- The PLC (CODESYS) receives NVL_In on port 1202 and drives lights/boiler from the packed struct.

---

## File reference

- **Flows export:** [nodered-flows.json](nodered-flows.json) — Living Room tab id: `7de41d810b04d992`
- **Payload layout (PLC/Node-RED):** [codesys/src/NVL/nodered-payload.md](../../codesys/src/NVL/nodered-payload.md)
