Enhance Node-RED living room flow with new functionality and documentation updates
- Added a new "Living Room" tab to the Node-RED flow for better organization. - Introduced functions for handling Home Assistant and Zigbee interactions, allowing for more dynamic control of living room lights. - Updated the living room flow documentation to include configuration instructions for the Action node, improving usability for users integrating with Home Assistant. This update enhances the functionality and clarity of the Node-RED integration for the living room setup.
This commit is contained in:
@@ -7,6 +7,14 @@
|
||||
"info": "",
|
||||
"env": []
|
||||
},
|
||||
{
|
||||
"id": "7de41d810b04d992",
|
||||
"type": "tab",
|
||||
"label": "Living Room",
|
||||
"disabled": false,
|
||||
"info": "",
|
||||
"env": []
|
||||
},
|
||||
{
|
||||
"id": "480553bb6f5b2b51",
|
||||
"type": "tab",
|
||||
@@ -79,14 +87,6 @@
|
||||
"info": "",
|
||||
"env": []
|
||||
},
|
||||
{
|
||||
"id": "7de41d810b04d992",
|
||||
"type": "tab",
|
||||
"label": "Living Room",
|
||||
"disabled": false,
|
||||
"info": "",
|
||||
"env": []
|
||||
},
|
||||
{
|
||||
"id": "57bd1b149526fcee",
|
||||
"type": "subflow",
|
||||
@@ -4045,7 +4045,7 @@
|
||||
"globaltypes": "",
|
||||
"pack": "true",
|
||||
"port": "",
|
||||
"x": 360,
|
||||
"x": 400,
|
||||
"y": 40,
|
||||
"wires": [
|
||||
[
|
||||
@@ -4084,7 +4084,7 @@
|
||||
"outport": "",
|
||||
"base64": false,
|
||||
"multicast": "false",
|
||||
"x": 580,
|
||||
"x": 620,
|
||||
"y": 40,
|
||||
"wires": []
|
||||
},
|
||||
@@ -4225,7 +4225,8 @@
|
||||
"name": "from zigbee",
|
||||
"links": [
|
||||
"629cbd7ce15bb9e3",
|
||||
"6fb9cc41c7b14fac"
|
||||
"6fb9cc41c7b14fac",
|
||||
"74c2640db1c8bd3e"
|
||||
],
|
||||
"x": 245,
|
||||
"y": 100,
|
||||
@@ -5034,10 +5035,11 @@
|
||||
"3b5e1f4f9ccd9c74",
|
||||
"2b29c71c180a50df",
|
||||
"860779317fad91b5",
|
||||
"8dd17125d6bb49f7"
|
||||
"8dd17125d6bb49f7",
|
||||
"7c4268195123e319"
|
||||
],
|
||||
"x": 1175,
|
||||
"y": 80,
|
||||
"x": 1205,
|
||||
"y": 100,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@@ -6101,7 +6103,7 @@
|
||||
"type": "debug",
|
||||
"z": "46892ced77481340",
|
||||
"name": "debug 27",
|
||||
"active": true,
|
||||
"active": false,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
@@ -6280,7 +6282,7 @@
|
||||
"z": "46892ced77481340",
|
||||
"name": "to UDP",
|
||||
"links": [],
|
||||
"x": 375,
|
||||
"x": 285,
|
||||
"y": 20,
|
||||
"wires": [
|
||||
[
|
||||
@@ -6288,6 +6290,273 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4045239f675b77d2",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "HA to NVL",
|
||||
"func": "// Writes to state.rooms.cmd_livingroom (NVL variable cmd_livingroom).\nconst ROOM_NAME = 'cmd_livingroom';\nconst entityId = (msg.topic || '').toString();\nconst parts = entityId.split('_');\nconst last = parts[parts.length - 1] || '';\nconst num = parseInt(last, 10);\nconst lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;\n\n// Normalize state: HA / trigger-state can send payload as string, object with .state, or data.new_state.state\nlet rawState = msg.payload;\nif (rawState !== null && typeof rawState === 'object' && rawState.state !== undefined) rawState = rawState.state;\nif (msg.data && msg.data.new_state && msg.data.new_state.state !== undefined) rawState = msg.data.new_state.state;\nconst isOn = (rawState === 'on' || rawState === true);\n\nnode.warn('[HA to NVL] topic=' + entityId + ' rawState=' + JSON.stringify(rawState) + ' → lightNum=' + lightNum + ' isOn=' + isOn);\n\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nconst r = state.rooms[ROOM_NAME];\nr['ha_l' + lightNum + '_on'] = false;\nr['ha_l' + lightNum + '_off'] = false;\nif (isOn) r['ha_l' + lightNum + '_on'] = true;\nelse r['ha_l' + lightNum + '_off'] = true;\nflow.set('nvlInState', state);\n\nnode.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend');\n\n// 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).\nmsg.payload = { buildAndSend: true };\nreturn msg;\n",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 1170,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"75212a4bc3d085db"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "718ba68ae647874f",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "Zigbee to NVL",
|
||||
"func": "// Writes to state.rooms.cmd_livingroom (same as HA to NVL). Zigbee action → zigbee_sw1..6.\nconst ROOM_NAME = 'cmd_livingroom';\nconst actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 };\nconst payload = msg.payload || {};\nconst actionRaw = (payload.action || payload.click || '').toLowerCase();\n// Support \"1_single\", \"2_double\" etc. (button_number + action) or plain \"single\", \"double\"\nconst parts = actionRaw.split('_');\nlet swIndex = null;\nif (parts.length >= 2) {\n const buttonNum = parseInt(parts[0], 10);\n if (buttonNum >= 1 && buttonNum <= 6) swIndex = buttonNum;\n}\nif (swIndex == null) swIndex = actionToSwitch[actionRaw];\nif (swIndex == null && parts.length >= 2) swIndex = actionToSwitch[parts.slice(1).join('_')];\nif (swIndex == null) swIndex = actionToSwitch[parts[parts.length - 1]];\n\nif (swIndex == null) {\n node.warn('[Zigbee to NVL] unknown action: ' + JSON.stringify(actionRaw) + ' payload=' + JSON.stringify(payload));\n return null;\n}\n\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nstate.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true;\nflow.set('nvlInState', state);\n\nnode.warn('[Zigbee to NVL] cmd_livingroom zigbee_sw' + swIndex + '=true (action=' + actionRaw + '), buildAndSend + zigbeeClear');\n\nmsg.payload = { buildAndSend: true };\nmsg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex };\nreturn msg;\n",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 1160,
|
||||
"y": 380,
|
||||
"wires": [
|
||||
[
|
||||
"886c1334e909e2cd",
|
||||
"75212a4bc3d085db"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "886c1334e909e2cd",
|
||||
"type": "delay",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "80ms",
|
||||
"pauseType": "delay",
|
||||
"timeout": "80",
|
||||
"timeoutUnits": "milliseconds",
|
||||
"rate": "1",
|
||||
"nbRateUnits": "1",
|
||||
"rateUnits": "seconds",
|
||||
"randomFirst": "1",
|
||||
"randomLast": "5",
|
||||
"randomUnits": "seconds",
|
||||
"drop": true,
|
||||
"outputs": 1,
|
||||
"x": 1350,
|
||||
"y": 380,
|
||||
"wires": [
|
||||
[
|
||||
"44822e048dbaee31"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "44822e048dbaee31",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "Clear zigbee edge",
|
||||
"func": "if (msg.zigbeeClear) {\n const state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\n const r = state.rooms[msg.zigbeeClear.room];\n if (r) r[msg.zigbeeClear.key] = false;\n flow.set('nvlInState', state);\n}\nmsg.payload = { buildAndSend: true };\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"x": 1550,
|
||||
"y": 380,
|
||||
"wires": [
|
||||
[
|
||||
"75212a4bc3d085db"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "17814d6d1beda546",
|
||||
"type": "zigbee2mqtt-in",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"server": "4e20fc347c658518",
|
||||
"friendly_name": "Office Switch (TS0042)",
|
||||
"device_id": "0xa4c138a5b9771b05",
|
||||
"state": "0",
|
||||
"outputAtStartup": true,
|
||||
"filterChanges": false,
|
||||
"enableMultiple": false,
|
||||
"x": 940,
|
||||
"y": 380,
|
||||
"wires": [
|
||||
[
|
||||
"718ba68ae647874f"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "74c2640db1c8bd3e",
|
||||
"type": "link out",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "link out 26",
|
||||
"mode": "link",
|
||||
"links": [
|
||||
"c6162cb59e21491f"
|
||||
],
|
||||
"x": 1570,
|
||||
"y": 320,
|
||||
"wires": [],
|
||||
"l": true
|
||||
},
|
||||
{
|
||||
"id": "95b626ea6980446e",
|
||||
"type": "trigger-state",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"server": "29eb84a2708d96e0",
|
||||
"version": 5,
|
||||
"inputs": 1,
|
||||
"outputs": 2,
|
||||
"exposeAsEntityConfig": "",
|
||||
"entities": {
|
||||
"entity": [
|
||||
"input_boolean.living_room_new"
|
||||
],
|
||||
"substring": [],
|
||||
"regex": []
|
||||
},
|
||||
"debugEnabled": true,
|
||||
"constraints": [],
|
||||
"customOutputs": [],
|
||||
"outputInitially": false,
|
||||
"stateType": "str",
|
||||
"enableInput": true,
|
||||
"x": 890,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"4045239f675b77d2"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "75212a4bc3d085db",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "NVL SEND",
|
||||
"func": "// Build the payload for nvl-send from flow.nvlInState.\n// Redesign: all rooms use struct_room_cmds. Add more names to roomNames when you add rooms.\n// Wire: HA to NVL / Zigbee to NVL → this function → nvl-send → udp out\n\nconst struct_room_cmds_default = {\n ha_l1_on: false, ha_l1_off: false, ha_l2_on: false, ha_l2_off: false,\n ha_l3_on: false, ha_l3_off: false, ha_l4_on: false, ha_l4_off: false,\n ha_l5_on: false, ha_l5_off: false, ha_l6_on: false, ha_l6_off: false,\n zigbee_sw1: false, zigbee_sw2: false, zigbee_sw3: false,\n zigbee_sw4: false, zigbee_sw5: false, zigbee_sw6: false,\n ha_all_on: false, ha_all_off: false\n};\n\n// NVL variable names per room (struct_room_cmds). Add more when you add rooms.\nconst roomNames = ['cmd_livingroom'];\n\nconst state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\nconst rooms = state.rooms || {};\nconst payload = {};\n\nfor (const name of roomNames) {\n const cmd = rooms[name] || {};\n payload[name] = { ...struct_room_cmds_default };\n for (const k of Object.keys(struct_room_cmds_default)) {\n if (cmd[k] !== undefined) payload[name][k] = !!cmd[k];\n }\n}\n\nmsg.payload = payload;\nreturn msg;\n",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 1370,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"74c2640db1c8bd3e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d5cdc38171230744",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "NVL to HA Sync",
|
||||
"func": "// Run this AFTER nvl-receive. Syncs all mapped lights from PLC state to HA entities.\n// Connect output 1 → api-call-service (input_boolean.turn_on / light.turn_on), output 2 → api-call-service (turn_off).\n// Only outputs when state changed (per entity). Add more entries to LIGHT_ENTITY_MAP as you add rooms/lights.\n\n// Map: NVL room key (in payload) + light index 1..6 → HA entity_id\nconst LIGHT_ENTITY_MAP = [\n { room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_new' },\n // { room: 'light_livingRoom', light: 2, entityId: 'input_boolean.living_room_2' },\n // { room: 'l_kitchen', light: 1, entityId: 'light.kitchen_ceiling' },\n];\n\nconst payload = msg.payload || {};\nconst onMsgs = [];\nconst offMsgs = [];\n\nfor (const entry of LIGHT_ENTITY_MAP) {\n const room = payload[entry.room] || {};\n const isOn = !!(room['l_' + entry.light] || room['l' + entry.light]);\n\n const flowKey = 'nvlToHa_' + entry.entityId.replace(/\\./g, '_');\n const last = flow.get(flowKey);\n if (last === isOn) continue;\n flow.set(flowKey, isOn);\n\n const out = { payload: { entity_id: entry.entityId } };\n if (isOn) onMsgs.push(out); else offMsgs.push(out);\n}\n\nif (onMsgs.length === 0 && offMsgs.length === 0) return null;\nreturn [onMsgs, offMsgs];\n",
|
||||
"outputs": 2,
|
||||
"timeout": 0,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 1240,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"566f142b2e6e841f"
|
||||
],
|
||||
[
|
||||
"5789f9ce23be8782"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7c4268195123e319",
|
||||
"type": "link in",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "link in 14",
|
||||
"links": [
|
||||
"e059a9fd1da3c3fd"
|
||||
],
|
||||
"x": 1015,
|
||||
"y": 220,
|
||||
"wires": [
|
||||
[
|
||||
"d5cdc38171230744"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "566f142b2e6e841f",
|
||||
"type": "api-call-service",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"server": "29eb84a2708d96e0",
|
||||
"version": 7,
|
||||
"debugenabled": false,
|
||||
"action": "input_boolean.turn_on",
|
||||
"floorId": [],
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [
|
||||
"input_boolean.living_room_new"
|
||||
],
|
||||
"labelId": [],
|
||||
"data": "",
|
||||
"dataType": "jsonata",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"blockInputOverrides": false,
|
||||
"domain": "input_boolean",
|
||||
"service": "turn_on",
|
||||
"x": 1540,
|
||||
"y": 200,
|
||||
"wires": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5789f9ce23be8782",
|
||||
"type": "api-call-service",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"server": "29eb84a2708d96e0",
|
||||
"version": 7,
|
||||
"debugenabled": false,
|
||||
"action": "input_boolean.turn_off",
|
||||
"floorId": [],
|
||||
"areaId": [],
|
||||
"deviceId": [],
|
||||
"entityId": [
|
||||
"input_boolean.living_room_new"
|
||||
],
|
||||
"labelId": [],
|
||||
"data": "",
|
||||
"dataType": "jsonata",
|
||||
"mergeContext": "",
|
||||
"mustacheAltTags": false,
|
||||
"outputProperties": [],
|
||||
"queue": "none",
|
||||
"blockInputOverrides": false,
|
||||
"domain": "input_boolean",
|
||||
"service": "turn_off",
|
||||
"x": 1540,
|
||||
"y": 260,
|
||||
"wires": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d592161c8e50638a",
|
||||
"type": "debug",
|
||||
@@ -14067,7 +14336,7 @@
|
||||
"type": "function",
|
||||
"z": "24e9a56732cfa6f1",
|
||||
"name": "HA to NVL",
|
||||
"func": "const ROOM_NAME = 'cmd_livingroom';\nconst entityId = (msg.topic || '').toString();\nconst parts = entityId.split('_');\nconst last = parts[parts.length - 1] || '';\nconst num = parseInt(last, 10);\nconst lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;\nconst isOn = (msg.payload === 'on' || msg.payload === true);\nnode.warn('[HA to NVL] topic=' + entityId + ' payload=' + JSON.stringify(msg.payload) + ' (type ' + typeof msg.payload + ') → lightNum=' + lightNum + ' isOn=' + isOn);\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nconst r = state.rooms[ROOM_NAME];\nr['ha_l' + lightNum + '_on'] = false;\nr['ha_l' + lightNum + '_off'] = false;\nif (isOn) r['ha_l' + lightNum + '_on'] = true;\nelse r['ha_l' + lightNum + '_off'] = true;\nflow.set('nvlInState', state);\nnode.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend');\nmsg.payload = { buildAndSend: true };\nreturn msg;",
|
||||
"func": "const ROOM_NAME = 'livingRoom';\nconst entityId = (msg.topic || '').toString();\nconst parts = entityId.split('_');\nconst last = parts[parts.length - 1] || '';\nconst num = parseInt(last, 10);\nconst lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;\nconst isOn = (msg.payload === 'on' || msg.payload === true);\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nconst r = state.rooms[ROOM_NAME];\nr['ha_l' + lightNum + '_on'] = false;\nr['ha_l' + lightNum + '_off'] = false;\nif (isOn) r['ha_l' + lightNum + '_on'] = true;\nelse r['ha_l' + lightNum + '_off'] = true;\nflow.set('nvlInState', state);\nmsg.payload = { buildAndSend: true };\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"x": 540,
|
||||
"y": 220,
|
||||
@@ -14148,7 +14417,7 @@
|
||||
"type": "function",
|
||||
"z": "24e9a56732cfa6f1",
|
||||
"name": "Build NVL_In",
|
||||
"func": "const roomSize = 20;\nconst roomNames = ['masterBedroom','masterBathroom','bedroom_1','bedroom_2','bathroom','guestWc','kitchen','pantry','cmd_livingroom','dinningRoom','entrance','hallway','outVeranda','outFront','outBack','outSide'];\nconst state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\nconst rooms = state.rooms || {};\nconst boiler = state.boiler || {};\nconst buf = Buffer.alloc(328);\nfor (let r = 0; r < 16; r++) {\n const c = rooms[roomNames[r]] || {};\n const base = r * roomSize;\n buf[base+0]=c.ha_l1_on?1:0;buf[base+1]=c.ha_l1_off?1:0;buf[base+2]=c.ha_l2_on?1:0;buf[base+3]=c.ha_l2_off?1:0;buf[base+4]=c.ha_l3_on?1:0;buf[base+5]=c.ha_l3_off?1:0;buf[base+6]=c.ha_l4_on?1:0;buf[base+7]=c.ha_l4_off?1:0;buf[base+8]=c.ha_l5_on?1:0;buf[base+9]=c.ha_l5_off?1:0;buf[base+10]=c.ha_l6_on?1:0;buf[base+11]=c.ha_l6_off?1:0;\n for (let i = 0; i < 6; i++) buf[base+12+i] = c['zigbee_sw'+(i+1)] ? 1 : 0;\n buf[base+18]=c.ha_all_on?1:0;buf[base+19]=c.ha_all_off?1:0;\n}\nconst bo = 320;\nbuf[bo+0]=boiler.ha_on?1:0;buf[bo+1]=boiler.ha_off?1:0;buf[bo+2]=boiler.schedule_on?1:0;buf[bo+3]=boiler.schedule_off?1:0;buf[bo+4]=boiler.emergency_stop?1:0;buf.writeInt16LE(boiler.max_on_time_minutes??480,bo+6);\nmsg.payload = buf;\nreturn msg;",
|
||||
"func": "const roomSize = 20;\nconst roomNames = ['masterBedroom','masterBathroom','bedroom_1','bedroom_2','bathroom','guestWc','kitchen','pantry','livingRoom','dinningRoom','entrance','hallway','outVeranda','outFront','outBack','outSide'];\nconst state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\nconst rooms = state.rooms || {};\nconst boiler = state.boiler || {};\nconst buf = Buffer.alloc(328);\nfor (let r = 0; r < 16; r++) {\n const c = rooms[roomNames[r]] || {};\n const base = r * roomSize;\n buf[base+0]=c.ha_l1_on?1:0;buf[base+1]=c.ha_l1_off?1:0;buf[base+2]=c.ha_l2_on?1:0;buf[base+3]=c.ha_l2_off?1:0;buf[base+4]=c.ha_l3_on?1:0;buf[base+5]=c.ha_l3_off?1:0;buf[base+6]=c.ha_l4_on?1:0;buf[base+7]=c.ha_l4_off?1:0;buf[base+8]=c.ha_l5_on?1:0;buf[base+9]=c.ha_l5_off?1:0;buf[base+10]=c.ha_l6_on?1:0;buf[base+11]=c.ha_l6_off?1:0;\n for (let i = 0; i < 6; i++) buf[base+12+i] = c['zigbee_sw'+(i+1)] ? 1 : 0;\n buf[base+18]=c.ha_all_on?1:0;buf[base+19]=c.ha_all_off?1:0;\n}\nconst bo = 320;\nbuf[bo+0]=boiler.ha_on?1:0;buf[bo+1]=boiler.ha_off?1:0;buf[bo+2]=boiler.schedule_on?1:0;buf[bo+3]=boiler.schedule_off?1:0;buf[bo+4]=boiler.emergency_stop?1:0;buf.writeInt16LE(boiler.max_on_time_minutes??480,bo+6);\nmsg.payload = buf;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"timeout": "",
|
||||
"noerr": 0,
|
||||
@@ -14237,268 +14506,5 @@
|
||||
"wires": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4045239f675b77d2",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "HA to NVL",
|
||||
"func": "const ROOM_NAME = 'cmd_livingroom';\nconst entityId = (msg.topic || '').toString();\nconst parts = entityId.split('_');\nconst last = parts[parts.length - 1] || '';\nconst num = parseInt(last, 10);\nconst lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;\nconst isOn = (msg.payload === 'on' || msg.payload === true);\nnode.warn('[HA to NVL] topic=' + entityId + ' payload=' + JSON.stringify(msg.payload) + ' (type ' + typeof msg.payload + ') → lightNum=' + lightNum + ' isOn=' + isOn);\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nconst r = state.rooms[ROOM_NAME];\nr['ha_l' + lightNum + '_on'] = false;\nr['ha_l' + lightNum + '_off'] = false;\nif (isOn) r['ha_l' + lightNum + '_on'] = true;\nelse r['ha_l' + lightNum + '_off'] = true;\nflow.set('nvlInState', state);\nnode.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend');\nmsg.payload = { buildAndSend: true };\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"x": 1170,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"75212a4bc3d085db",
|
||||
"f891d88454556409"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "718ba68ae647874f",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "Zigbee to NVL",
|
||||
"func": "const ROOM_NAME = 'livingRoom';\nconst actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 };\nconst payload = msg.payload || {};\nconst action = (payload.action || payload.click || '').toLowerCase();\nconst swIndex = actionToSwitch[action];\nif (swIndex == null) return null;\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nstate.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true;\nflow.set('nvlInState', state);\nmsg.payload = { buildAndSend: true };\nmsg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex };\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"x": 1120,
|
||||
"y": 420,
|
||||
"wires": [
|
||||
[
|
||||
"75212a4bc3d085db",
|
||||
"886c1334e909e2cd",
|
||||
"c0ba289781c06018"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "886c1334e909e2cd",
|
||||
"type": "delay",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "80ms",
|
||||
"pauseType": "delay",
|
||||
"timeout": "80",
|
||||
"timeoutUnits": "milliseconds",
|
||||
"rate": "1",
|
||||
"nbRateUnits": "1",
|
||||
"rateUnits": "seconds",
|
||||
"randomFirst": "1",
|
||||
"randomLast": "5",
|
||||
"randomUnits": "seconds",
|
||||
"drop": true,
|
||||
"outputs": 1,
|
||||
"x": 1340,
|
||||
"y": 420,
|
||||
"wires": [
|
||||
[
|
||||
"44822e048dbaee31"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "44822e048dbaee31",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "Clear zigbee edge",
|
||||
"func": "if (msg.zigbeeClear) {\n const state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\n const r = state.rooms[msg.zigbeeClear.room];\n if (r) r[msg.zigbeeClear.key] = false;\n flow.set('nvlInState', state);\n}\nmsg.payload = { buildAndSend: true };\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"x": 1550,
|
||||
"y": 420,
|
||||
"wires": [
|
||||
[
|
||||
"75212a4bc3d085db",
|
||||
"b9b1a23de2bb693b"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "75212a4bc3d085db",
|
||||
"type": "function",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "Build NVL_In",
|
||||
"func": "const roomSize = 20;\nconst roomNames = ['masterBedroom','masterBathroom','bedroom_1','bedroom_2','bathroom','guestWc','kitchen','pantry','cmd_livingroom','dinningRoom','entrance','hallway','outVeranda','outFront','outBack','outSide'];\nconst state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\nconst rooms = state.rooms || {};\nconst boiler = state.boiler || {};\nconst buf = Buffer.alloc(328);\nfor (let r = 0; r < 16; r++) {\n const c = rooms[roomNames[r]] || {};\n const base = r * roomSize;\n buf[base+0]=c.ha_l1_on?1:0;buf[base+1]=c.ha_l1_off?1:0;buf[base+2]=c.ha_l2_on?1:0;buf[base+3]=c.ha_l2_off?1:0;buf[base+4]=c.ha_l3_on?1:0;buf[base+5]=c.ha_l3_off?1:0;buf[base+6]=c.ha_l4_on?1:0;buf[base+7]=c.ha_l4_off?1:0;buf[base+8]=c.ha_l5_on?1:0;buf[base+9]=c.ha_l5_off?1:0;buf[base+10]=c.ha_l6_on?1:0;buf[base+11]=c.ha_l6_off?1:0;\n for (let i = 0; i < 6; i++) buf[base+12+i] = c['zigbee_sw'+(i+1)] ? 1 : 0;\n buf[base+18]=c.ha_all_on?1:0;buf[base+19]=c.ha_all_off?1:0;\n}\nconst bo = 320;\nbuf[bo+0]=boiler.ha_on?1:0;buf[bo+1]=boiler.ha_off?1:0;buf[bo+2]=boiler.schedule_on?1:0;buf[bo+3]=boiler.schedule_off?1:0;buf[bo+4]=boiler.emergency_stop?1:0;buf.writeInt16LE(boiler.max_on_time_minutes??480,bo+6);\nmsg.payload = buf;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"x": 1360,
|
||||
"y": 360,
|
||||
"wires": [
|
||||
[
|
||||
"74c2640db1c8bd3e",
|
||||
"9e9d60859e34c65e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "17814d6d1beda546",
|
||||
"type": "zigbee2mqtt-in",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"server": "4e20fc347c658518",
|
||||
"friendly_name": "Living Room Door Switch (TS0044)",
|
||||
"device_id": "0xa4c1383d7921827a",
|
||||
"state": "0",
|
||||
"outputAtStartup": true,
|
||||
"filterChanges": false,
|
||||
"enableMultiple": false,
|
||||
"x": 820,
|
||||
"y": 420,
|
||||
"wires": [
|
||||
[
|
||||
"718ba68ae647874f"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "74c2640db1c8bd3e",
|
||||
"type": "link out",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "link out 26",
|
||||
"mode": "link",
|
||||
"links": [],
|
||||
"x": 1620,
|
||||
"y": 360,
|
||||
"wires": [],
|
||||
"l": true
|
||||
},
|
||||
{
|
||||
"id": "95b626ea6980446e",
|
||||
"type": "trigger-state",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"server": "29eb84a2708d96e0",
|
||||
"version": 5,
|
||||
"inputs": 1,
|
||||
"outputs": 2,
|
||||
"exposeAsEntityConfig": "",
|
||||
"entities": {
|
||||
"entity": [
|
||||
"input_boolean.living_room_new"
|
||||
],
|
||||
"substring": [],
|
||||
"regex": []
|
||||
},
|
||||
"debugEnabled": true,
|
||||
"constraints": [],
|
||||
"customOutputs": [],
|
||||
"outputInitially": false,
|
||||
"stateType": "str",
|
||||
"enableInput": true,
|
||||
"x": 890,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"4045239f675b77d2",
|
||||
"b72750450ceb1acc"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "745066d593433c28",
|
||||
"type": "inject",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 620,
|
||||
"y": 320,
|
||||
"wires": [
|
||||
[
|
||||
"95b626ea6980446e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b72750450ceb1acc",
|
||||
"type": "debug",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "debug 36",
|
||||
"active": false,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1130,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "f891d88454556409",
|
||||
"type": "debug",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "debug 37",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1380,
|
||||
"y": 260,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "9e9d60859e34c65e",
|
||||
"type": "debug",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "debug 39",
|
||||
"active": false,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1560,
|
||||
"y": 300,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "c0ba289781c06018",
|
||||
"type": "debug",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "debug 40",
|
||||
"active": false,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1230,
|
||||
"y": 500,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b9b1a23de2bb693b",
|
||||
"type": "debug",
|
||||
"z": "7de41d810b04d992",
|
||||
"name": "debug 41",
|
||||
"active": false,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1690,
|
||||
"y": 460,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user