Remove obsolete INFORMATION_NEEDED.md and streamline README.md for clarity
- Deleted the INFORMATION_NEEDED.md file as it was no longer necessary for documentation. - Revised README.md to enhance the overview of the home automation system, focusing on how services and CODESYS connectivity work. - Updated project structure in README.md for better organization and clarity, including links to relevant documentation. This update simplifies the documentation and improves the overall user experience for developers and users of the home automation system.
This commit is contained in:
31
docs/integration/ha-to-nvl-function.js
Normal file
31
docs/integration/ha-to-nvl-function.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// Writes to state.rooms.cmd_livingroom (NVL variable cmd_livingroom).
|
||||
const ROOM_NAME = 'cmd_livingroom';
|
||||
const entityId = (msg.topic || '').toString();
|
||||
const parts = entityId.split('_');
|
||||
const last = parts[parts.length - 1] || '';
|
||||
const num = parseInt(last, 10);
|
||||
const lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;
|
||||
|
||||
// Normalize state: HA / trigger-state can send payload as string, object with .state, or data.new_state.state
|
||||
let rawState = msg.payload;
|
||||
if (rawState !== null && typeof rawState === 'object' && rawState.state !== undefined) rawState = rawState.state;
|
||||
if (msg.data && msg.data.new_state && msg.data.new_state.state !== undefined) rawState = msg.data.new_state.state;
|
||||
const isOn = (rawState === 'on' || rawState === true);
|
||||
|
||||
node.warn('[HA to NVL] topic=' + entityId + ' rawState=' + JSON.stringify(rawState) + ' → lightNum=' + lightNum + ' isOn=' + isOn);
|
||||
|
||||
if (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });
|
||||
const state = flow.get('nvlInState');
|
||||
if (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};
|
||||
const r = state.rooms[ROOM_NAME];
|
||||
r['ha_l' + lightNum + '_on'] = false;
|
||||
r['ha_l' + lightNum + '_off'] = false;
|
||||
if (isOn) r['ha_l' + lightNum + '_on'] = true;
|
||||
else r['ha_l' + lightNum + '_off'] = true;
|
||||
flow.set('nvlInState', state);
|
||||
|
||||
node.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend');
|
||||
|
||||
// PLC uses R_TRIG (rising edge). If "off" still doesn’t work, add a flow: after this node, 80ms delay then clear ha_l*_off and buildAndSend again (pulse).
|
||||
msg.payload = { buildAndSend: true };
|
||||
return msg;
|
||||
70
docs/integration/nodered-flows-analysis.md
Normal file
70
docs/integration/nodered-flows-analysis.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Node-RED flows analysis
|
||||
|
||||
Flows pulled from **root@10.20.30.12** (`/root/.node-red/flows.json`).
|
||||
Snapshot: Feb 8, 2026.
|
||||
|
||||
---
|
||||
|
||||
## Tabs (flow sheets)
|
||||
|
||||
| Tab | Purpose (from nodes) |
|
||||
|----------------|-----------------------------|
|
||||
| **Flow 1** | CODESYS NVL (UDP), HA buttons, Room Lights subflow, dashboard links |
|
||||
| Flow 2–7 | (Unnamed / per-room or feature) |
|
||||
| **Living Room**| Living room–specific logic → **[Living Room flow doc](nodered-livingroom-flow.md)** |
|
||||
| **Notifications** | Notifications (Telegram, etc.) |
|
||||
| **Zigbee** | Zigbee2MQTT in/out, MQTT |
|
||||
| **Scheduler** | cronplus, bigtimer, triggers |
|
||||
|
||||
---
|
||||
|
||||
## Node counts (summary)
|
||||
|
||||
| Category | Node types / count |
|
||||
|-----------------|--------------------|
|
||||
| **UI** | 128 ui_button, 17 ui_tab, 17 ui_group, 1 ui_base |
|
||||
| **Logic** | 59 function, 12 change, 10 switch, 17 inject, 2 trigger, 2 delay, 2 bigtimer, 3 cronplus |
|
||||
| **Home Assistant** | 47 ha-entity-config, 29 ha-button, 18 ha-binary-sensor, 15 ha-device-config, 5 api-current-state, 2 api-call-service, 1 server-state-changed, 1 poll-state, 1 trigger-state, 1 ha-sensor |
|
||||
| **Zigbee** | 28 zigbee2mqtt-in, 9 zigbee2mqtt-out, 1 zigbee2mqtt-server, 1 zigbee2mqtt-get |
|
||||
| **CODESYS / NVL** | 1 nvl-send, 1 nvl-receive, 1 nvl-datatypes, 1 udp in, 1 udp out |
|
||||
| **Other** | 1 server (HA WebSocket), 1 mqtt-broker, 2 mqtt in, 9 http request, 2 telegram bot, 1 telegram sender, 1 telegram receiver, 43 debug, 29 link in, 29 link out |
|
||||
| **Subflows** | Room Lights (57bd1b149526fcee), Room filter (4dc13919bcc9d676), Subflow 1 |
|
||||
|
||||
---
|
||||
|
||||
## CODESYS connectivity (Flow 1)
|
||||
|
||||
All NVL and UDP nodes are on tab **Flow 1** (`z": "46892ced77481340"`).
|
||||
|
||||
| Node | Role | Config |
|
||||
|-----------|------|--------|
|
||||
| **udp out** | Send to PLC | `addr`: **10.20.30.5**, `port`: **1202** |
|
||||
| **udp in** | Receive from PLC | `port`: **1202** |
|
||||
| **nvl-send** | Build NVL_In payload | Feeds **udp out**. Uses `node-red-contrib-nvl` with inline definition (struct_switches × 15 rooms + cmd_livingroom struct_room_cmds). |
|
||||
| **nvl-receive** | Parse NVL_Out | Triggered by **udp in**. Definition: 15 × struct_lights + **light_livingRoom** as struct_room_outs. |
|
||||
|
||||
**Notes**
|
||||
|
||||
- PLC IP is **10.20.30.5**; Node-RED uses the same port **1202** for both sending to and receiving from the PLC (CODESYS NVL Sender and Receiver may use the same port or different; confirm in CODESYS).
|
||||
- nvl-receive uses **struct_lights** (6 BOOLs) for 15 rooms and **struct_room_outs** (6 outputs + 6 status) only for `light_livingRoom`. Your `codesys/src/NVL` docs use struct_room_outs for all rooms; the flow is a subset.
|
||||
- Room Lights subflow (**Room Lights**) is used from Flow 1 (and possibly Living Room) to map HA/buttons to room/light commands.
|
||||
|
||||
---
|
||||
|
||||
## Integrations
|
||||
|
||||
- **Home Assistant**: WebSocket **server** config; many **ha-entity-config** / **ha-device-config**; **ha-button**, **ha-binary-sensor**; **api-current-state**, **api-call-service**.
|
||||
- **Zigbee2MQTT**: One **zigbee2mqtt-server**; **zigbee2mqtt-in** (28) and **zigbee2mqtt-out** (9) for device events and commands.
|
||||
- **MQTT**: One **mqtt-broker**; **mqtt in** (2) for other topics.
|
||||
- **Telegram**: **telegram bot** (2), **telegram sender**, **telegram receiver** for notifications.
|
||||
- **Dashboard**: **node-red-dashboard** (ui_base, ui_tab, ui_group, ui_button) for a simple UI.
|
||||
|
||||
---
|
||||
|
||||
## File location
|
||||
|
||||
- **Flows (full export):** [nodered-flows.json](nodered-flows.json)
|
||||
- **This analysis:** [nodered-flows-analysis.md](nodered-flows-analysis.md)
|
||||
|
||||
To refresh: from the project PC run
|
||||
`ssh root@10.20.30.12 "cat /root/.node-red/flows.json" > docs/integration/nodered-flows.json`
|
||||
14504
docs/integration/nodered-flows.json
Normal file
14504
docs/integration/nodered-flows.json
Normal file
File diff suppressed because one or more lines are too long
167
docs/integration/nodered-livingroom-flow.md
Normal file
167
docs/integration/nodered-livingroom-flow.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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.
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
30
docs/integration/nvl-to-ha-http-call.js
Normal file
30
docs/integration/nvl-to-ha-http-call.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Use when you don't use the Action node: prepares one msg for the "http request" node to call HA.
|
||||
// Accepts either: (1) Action format: payload.action + payload.target.entity_id, or (2) payload.entity_id + msg.service.
|
||||
// Set ha_base_url and ha_token in flow or env.
|
||||
|
||||
const HA_BASE_URL = flow.get('ha_base_url') || 'http://homeassistant.local:8123';
|
||||
const HA_TOKEN = flow.get('ha_token') || env.get('HA_TOKEN') || '';
|
||||
|
||||
const p = msg.payload || {};
|
||||
let entityId = p.target && p.target.entity_id && p.target.entity_id[0];
|
||||
let domain = 'input_boolean';
|
||||
let service = 'turn_off';
|
||||
if (p.action) {
|
||||
const parts = p.action.split('.');
|
||||
domain = parts[0];
|
||||
service = parts[1] || 'turn_off';
|
||||
}
|
||||
if (!entityId) entityId = p.entity_id;
|
||||
if (!entityId) return null;
|
||||
if (!p.action) domain = (entityId + '').split('.')[0];
|
||||
|
||||
const url = HA_BASE_URL.replace(/\/$/, '') + '/api/services/' + domain + '/' + service;
|
||||
|
||||
msg.url = url;
|
||||
msg.method = 'POST';
|
||||
msg.headers = {
|
||||
'Authorization': 'Bearer ' + HA_TOKEN,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
msg.payload = { entity_id: entityId };
|
||||
return msg;
|
||||
33
docs/integration/nvl-to-ha-sync-livingroom.js
Normal file
33
docs/integration/nvl-to-ha-sync-livingroom.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// Run this AFTER nvl-receive. Syncs all mapped lights from PLC state to HA entities.
|
||||
// Connect output 1 → Action node (turn_on), output 2 → Action node (turn_off).
|
||||
// Format matches node-red-contrib-home-assistant-websocket "Action" node: payload.action + payload.target.entity_id (array).
|
||||
// Only outputs when state changed (per entity). Add more entries to LIGHT_ENTITY_MAP as you add rooms/lights.
|
||||
|
||||
// Map: NVL room key (in payload) + light index 1..6 → HA entity_id
|
||||
const LIGHT_ENTITY_MAP = [
|
||||
{ room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_new' },
|
||||
// { room: 'light_livingRoom', light: 2, entityId: 'input_boolean.living_room_2' },
|
||||
// { room: 'l_kitchen', light: 1, entityId: 'light.kitchen_ceiling' },
|
||||
];
|
||||
|
||||
const payload = msg.payload || {};
|
||||
const onMsgs = [];
|
||||
const offMsgs = [];
|
||||
|
||||
for (const entry of LIGHT_ENTITY_MAP) {
|
||||
const room = payload[entry.room] || {};
|
||||
const isOn = !!(room['l_' + entry.light] || room['l' + entry.light]);
|
||||
|
||||
const flowKey = 'nvlToHa_' + entry.entityId.replace(/\./g, '_');
|
||||
const last = flow.get(flowKey);
|
||||
if (last === isOn) continue;
|
||||
flow.set(flowKey, isOn);
|
||||
|
||||
const domain = entry.entityId.split('.')[0] || 'input_boolean';
|
||||
const action = domain + '.' + (isOn ? 'turn_on' : 'turn_off');
|
||||
const out = { payload: { action: action, target: { entity_id: [entry.entityId] } } };
|
||||
if (isOn) onMsgs.push(out); else offMsgs.push(out);
|
||||
}
|
||||
|
||||
if (onMsgs.length === 0 && offMsgs.length === 0) return null;
|
||||
return [onMsgs, offMsgs];
|
||||
30
docs/integration/state-to-nvl-send-payload.js
Normal file
30
docs/integration/state-to-nvl-send-payload.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Build the payload for nvl-send from flow.nvlInState.
|
||||
// Redesign: all rooms use struct_room_cmds. Add more names to roomNames when you add rooms.
|
||||
// Wire: HA to NVL / Zigbee to NVL → this function → nvl-send → udp out
|
||||
|
||||
const struct_room_cmds_default = {
|
||||
ha_l1_on: false, ha_l1_off: false, ha_l2_on: false, ha_l2_off: false,
|
||||
ha_l3_on: false, ha_l3_off: false, ha_l4_on: false, ha_l4_off: false,
|
||||
ha_l5_on: false, ha_l5_off: false, ha_l6_on: false, ha_l6_off: false,
|
||||
zigbee_sw1: false, zigbee_sw2: false, zigbee_sw3: false,
|
||||
zigbee_sw4: false, zigbee_sw5: false, zigbee_sw6: false,
|
||||
ha_all_on: false, ha_all_off: false
|
||||
};
|
||||
|
||||
// NVL variable names per room (struct_room_cmds). Add more when you add rooms.
|
||||
const roomNames = ['cmd_livingroom'];
|
||||
|
||||
const state = flow.get('nvlInState') || { rooms: {}, boiler: {} };
|
||||
const rooms = state.rooms || {};
|
||||
const payload = {};
|
||||
|
||||
for (const name of roomNames) {
|
||||
const cmd = rooms[name] || {};
|
||||
payload[name] = { ...struct_room_cmds_default };
|
||||
for (const k of Object.keys(struct_room_cmds_default)) {
|
||||
if (cmd[k] !== undefined) payload[name][k] = !!cmd[k];
|
||||
}
|
||||
}
|
||||
|
||||
msg.payload = payload;
|
||||
return msg;
|
||||
32
docs/integration/zigbee-to-nvl-function.js
Normal file
32
docs/integration/zigbee-to-nvl-function.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Writes to state.rooms.cmd_livingroom (same as HA to NVL). Zigbee action → zigbee_sw1..6.
|
||||
const ROOM_NAME = 'cmd_livingroom';
|
||||
const actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 };
|
||||
const payload = msg.payload || {};
|
||||
const actionRaw = (payload.action || payload.click || '').toLowerCase();
|
||||
// Support "1_single", "2_double" etc. (button_number + action) or plain "single", "double"
|
||||
const parts = actionRaw.split('_');
|
||||
let swIndex = null;
|
||||
if (parts.length >= 2) {
|
||||
const buttonNum = parseInt(parts[0], 10);
|
||||
if (buttonNum >= 1 && buttonNum <= 6) swIndex = buttonNum;
|
||||
}
|
||||
if (swIndex == null) swIndex = actionToSwitch[actionRaw];
|
||||
if (swIndex == null && parts.length >= 2) swIndex = actionToSwitch[parts.slice(1).join('_')];
|
||||
if (swIndex == null) swIndex = actionToSwitch[parts[parts.length - 1]];
|
||||
|
||||
if (swIndex == null) {
|
||||
node.warn('[Zigbee to NVL] unknown action: ' + JSON.stringify(actionRaw) + ' payload=' + JSON.stringify(payload));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });
|
||||
const state = flow.get('nvlInState');
|
||||
if (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};
|
||||
state.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true;
|
||||
flow.set('nvlInState', state);
|
||||
|
||||
node.warn('[Zigbee to NVL] cmd_livingroom zigbee_sw' + swIndex + '=true (action=' + actionRaw + '), buildAndSend + zigbeeClear');
|
||||
|
||||
msg.payload = { buildAndSend: true };
|
||||
msg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex };
|
||||
return msg;
|
||||
Reference in New Issue
Block a user