Files
kkelomatic_home/docs/integration/nodered-livingroom-flow.md
nearxos d64d0f8923 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.
2026-02-08 22:30:19 +02:00

12 KiB
Raw Blame History

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_newHA 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 delayClear 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 1s to UDP link in → udp out (10.20.30.5:1202).
debug 3641 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 16: 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 16 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 (16) 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 (uses cmd_livingroom). Copy that files contents into the Node-RED function node.


Zigbee to NVL (function code)

  • Uses room-config.js switchBindings (and optional deviceIdToName / switchBindingsByDeviceId) so each button maps to (room, light). See zigbee-to-nvl-function.js.
  • Payload formats supported: (1) single device { action: "1_single", friendly_name: "Office Switch" }; (2) multi-device object keyed by IEEE { "0xa4c...": { action, ... } }; (3) plain action string "1_single" with device name in msg.topic (e.g. zigbee2mqtt/Office Switch/action).
  • If your multi-device node never sends action: the node may only output device list/state. Use an MQTT In node subscribed to zigbee2mqtt/+/action and feed that into Zigbee to NVL; then msg.payload will be the action string (e.g. 1_single) and msg.topic will be e.g. zigbee2mqtt/Office Switch/action (the function parses the friendly name from the topic).
  • Edge behaviour: Passes msg.zigbeeClear (single object or array) so Clear zigbee edge (after 80 ms) clears the flags; use clear-zigbee-edge.js.

Build NVL_In (function code)

  • Buffer: 328 bytes (Buffer.alloc(328)).
  • Rooms: 16 rooms × 20 bytes each: bytes 011 = ha_l1_on, ha_l1_off, … ha_l6_off; bytes 1217 = zigbee_sw1…6; bytes 1819 = 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-sendudp out.

Code for that function (copy into a function node): 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 NVLstate-to-nvl-send-payloadnvl-sendudp 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 doesnt go through HA), the PLC updates but HA doesnt, 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.
  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). 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 + 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 lights 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 syncs 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-payloadnvl-sendudp 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