Files
node-red/flows.json
T
2026-05-28 20:20:19 +02:00

881 lines
28 KiB
JSON

[
{
"type": "tab",
"label": "Home Presence",
"id": "6dfa989bb120e6c6"
},
{
"id": "pres_c_homemode",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "── Home Mode ──",
"wires": [],
"info": "",
"x": 130,
"y": 60
},
{
"id": "pres001a2b3c4d5e",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Mischa presence",
"wires": [
[
"pres002a2b3c4d5e"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"person.mischa_gorinskat"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 120,
"y": 100
},
{
"id": "pres002a2b3c4d5e",
"type": "switch",
"z": "6dfa989bb120e6c6",
"name": "Home or Away?",
"wires": [
[
"pres003a2b3c4d5e"
],
[
"pres004a2b3c4d5e"
]
],
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "home",
"vt": "str"
},
{
"t": "eq",
"v": "not_home",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 400,
"y": 100
},
{
"id": "pres003a2b3c4d5e",
"type": "api-call-service",
"z": "6dfa989bb120e6c6",
"name": "Set Home",
"wires": [
[]
],
"server": "657c7463.48e1e4",
"version": 7,
"debugenabled": false,
"action": "input_select.select_option",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"input_select.home_mode"
],
"labelId": [],
"data": "{\"option\": \"Home\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": false,
"domain": "input_select",
"service": "select_option",
"x": 660,
"y": 60
},
{
"id": "pres004a2b3c4d5e",
"type": "api-call-service",
"z": "6dfa989bb120e6c6",
"name": "Set Away",
"wires": [
[]
],
"server": "657c7463.48e1e4",
"version": 7,
"debugenabled": false,
"action": "input_select.select_option",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"input_select.home_mode"
],
"labelId": [],
"data": "{\"option\": \"Away\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": false,
"domain": "input_select",
"service": "select_option",
"x": 660,
"y": 140
},
{
"id": "pres_sm_c_header",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "── Room Presence State Machine ──",
"wires": [],
"info": "",
"x": 190,
"y": 300
},
{
"id": "pres_sm_c_person",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "Person",
"wires": [],
"info": "",
"x": 90,
"y": 360
},
{
"id": "pres_sm_c_hall",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "Hallway (direction + state)",
"wires": [],
"info": "",
"x": 150,
"y": 480
},
{
"id": "pres_sm_c_rooms",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "Room Motion Sensors",
"wires": [],
"info": "",
"x": 140,
"y": 780
},
{
"id": "pres_sm_c_sm",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "State Machine",
"wires": [],
"info": "",
"x": 450,
"y": 580
},
{
"id": "pres_sm_c_out",
"type": "comment",
"z": "6dfa989bb120e6c6",
"name": "Outputs",
"wires": [],
"info": "",
"x": 730,
"y": 580
},
{
"id": "pres_sm_person",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Person (room SM)",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"person.mischa_gorinskat"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "string",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 130,
"y": 400
},
{
"id": "pres_sm_hall1",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Hallway S1 (corner/front door)",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.hallway_motion_sensor_1_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 160,
"y": 520
},
{
"id": "pres_sm_hall2",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Hallway S2 (near living room)",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.hallway_motion_sensor_2_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 160,
"y": 580
},
{
"id": "pres_sm_hall3",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Hallway S3 (near kitchen)",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.hallway_motion_sensor_3_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 150,
"y": 640
},
{
"id": "pres_sm_hallg",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Hallway (group)",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.hallway_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 120,
"y": 700
},
{
"id": "pres_sm_lr",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Living Room Motion",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.livingroom_motion_sensor_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 130,
"y": 820
},
{
"id": "pres_sm_bed",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Bedroom Motion",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.bedroom_motion_sensor_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 120,
"y": 880
},
{
"id": "pres_sm_bath",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Bathroom Motion",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.bathroom_motion_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 120,
"y": 940
},
{
"id": "pres_sm_kit",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Kitchen Motion",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.kitchen_motion_sensor_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 120,
"y": 1000
},
{
"id": "pres_sm_func",
"type": "function",
"z": "6dfa989bb120e6c6",
"name": "Room Presence SM",
"wires": [
[
"pres_sm_api"
],
[
"pres_sm_dbg"
]
],
"func": "// === ROOM PRESENCE STATE MACHINE ===\n// msg.topic = triggering entity_id (from triggerId output property)\n// msg.payload = entity state string\n// msg.data = full HASC event data\n\nconst entityId = (msg.data && msg.data.entity_id) ? msg.data.entity_id : (msg.topic || '');\nconst newState = (msg.data && msg.data.new_state) ? msg.data.new_state.state : msg.payload;\n\n// Guest mode guard — multi-person breaks single-occupant assumptions\nconst ha = global.get('homeassistant') || {};\nconst haStates = (ha.homeAssistantLatest || {}).states || {};\nconst guestMode = (haStates['input_boolean.guest_mode'] || {}).state;\nif (guestMode === 'on') return null;\n\n// Current state from flow context\nconst currentRoom = flow.get('currentRoom') || 'unknown';\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction capitalize(s) {\n const map = {\n unknown: 'Unknown', away: 'Away', hallway: 'Hallway',\n living_room: 'Livingroom', bedroom: 'Bedroom',\n bathroom: 'Bathroom', kitchen: 'Kitchen', balcony: 'Balcony'\n };\n return map[s] || (s.charAt(0).toUpperCase() + s.slice(1));\n}\n\nfunction cancelDestTimer() {\n const h = flow.get('destTimerHandle');\n if (h) { clearTimeout(h); flow.set('destTimerHandle', null); }\n}\n\n\nfunction setDestTimer() {\n cancelDestTimer();\n const h = setTimeout(function () {\n const lr = flow.get('lastRoom') || 'unknown';\n flow.set('currentRoom', lr);\n flow.set('destTimerHandle', null);\n node.send([\n { payload: capitalize(lr) },\n { payload: '[presence] dest-timer expired → reverted to ' + lr }\n ]);\n }, 90000);\n flow.set('destTimerHandle', h);\n}\n\n\nfunction setState(newRoom) {\n const prev = flow.get('currentRoom') || 'unknown';\n if (newRoom === prev) return null; // no change needed\n flow.set('lastRoom', prev);\n flow.set('currentRoom', newRoom);\n return [\n { payload: capitalize(newRoom) },\n { payload: '[presence] ' + prev + ' → ' + newRoom + ' (via ' + entityId + ')' }\n ];\n}\n\nfunction updateHallwaySequence(sensorNum) {\n // TODO: direction detection not yet implemented — sequence data is stored but not consumed.\n // Intended to detect hallway direction (e.g. S1→S2 = heading toward living room,\n // S2→S1 = heading toward front door).\n let seq = flow.get('hallwaySequence') || [];\n const now = Date.now();\n seq = seq.filter(function (e) { return now - e.time < 15000; });\n seq.push({ sensor: sensorNum, time: now });\n flow.set('hallwaySequence', seq);\n flow.set('lastHallwayTime', now);\n}\n\n// ── Transitions ────────────────────────────────────────────────────────────\n\n// 1. Person home / away\nif (entityId === 'person.mischa_gorinskat') {\n if (newState === 'not_home') {\n cancelDestTimer();\n return setState('away');\n }\n if (newState === 'home') {\n return setState('unknown');\n }\n return null;\n}\n\n// Ignore all motion when person is away\nif (currentRoom === 'away') return null;\n\n// 2. Individual hallway sensors — direction tracking only, no state change\nif (entityId === 'binary_sensor.hallway_motion_sensor_1_occupancy') {\n if (newState === 'on') updateHallwaySequence(1);\n return null;\n}\nif (entityId === 'binary_sensor.hallway_motion_sensor_2_occupancy') {\n if (newState === 'on') updateHallwaySequence(2);\n return null;\n}\nif (entityId === 'binary_sensor.hallway_motion_sensor_3_occupancy') {\n if (newState === 'on') updateHallwaySequence(3);\n return null;\n}\n\n// 3. Hallway group — primary state change trigger\nif (entityId === 'binary_sensor.hallway_occupancy') {\n if (newState === 'on') {\n flow.set('lastRoom', currentRoom); // save room before entering hallway\n setDestTimer(); // 90s: if no room fires → revert\n return setState('hallway');\n }\n return null; // hallway turning off doesn't trigger a state change\n}\n\n// 4. Room sensors\nconst roomMap = {\n 'binary_sensor.livingroom_motion_sensor_occupancy': 'living_room',\n 'binary_sensor.bedroom_motion_sensor_occupancy': 'bedroom',\n 'binary_sensor.bathroom_motion_occupancy': 'bathroom',\n 'binary_sensor.kitchen_motion_sensor_occupancy': 'kitchen',\n 'binary_sensor.balcony_motion_occupancy': 'balcony'\n};\n\nconst targetRoom = roomMap[entityId];\nif (targetRoom && newState === 'on') {\n cancelDestTimer(); // confirmed destination\n\n const canTransition =\n currentRoom === 'hallway' ||\n currentRoom === targetRoom ||\n currentRoom === 'unknown' ||\n (targetRoom === 'living_room' && currentRoom === 'balcony') || // return from balcony\n (targetRoom === 'balcony' && currentRoom === 'living_room'); // step onto balcony\n\n if (canTransition) return setState(targetRoom);\n return null;\n}\n\nreturn null;",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 480,
"y": 620
},
{
"id": "pres_sm_api",
"type": "api-call-service",
"z": "6dfa989bb120e6c6",
"name": "Set Room Presence",
"wires": [
[]
],
"server": "657c7463.48e1e4",
"version": 7,
"debugenabled": false,
"action": "input_select.select_option",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [
"input_select.room_presence"
],
"labelId": [],
"data": "{\"option\": \"{{{payload}}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": false,
"domain": "input_select",
"service": "select_option",
"x": 780,
"y": 620
},
{
"id": "pres_sm_dbg",
"type": "debug",
"z": "6dfa989bb120e6c6",
"name": "Presence log",
"wires": [],
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 750,
"y": 680
},
{
"id": "pres_sm_balcony",
"type": "server-state-changed",
"z": "6dfa989bb120e6c6",
"name": "Balcony Motion",
"wires": [
[
"pres_sm_func"
]
],
"server": "657c7463.48e1e4",
"version": 6,
"outputs": 1,
"exposeAsEntityConfig": "",
"entities": {
"entity": [
"binary_sensor.balcony_motion_occupancy"
],
"substring": [],
"regex": []
},
"outputInitially": false,
"stateType": "str",
"ifState": "",
"ifStateType": "str",
"ifStateOperator": "is",
"outputOnlyOnStateChange": true,
"for": 0,
"forType": "num",
"forUnits": "minutes",
"ignorePrevStateNull": false,
"ignorePrevStateUnknown": false,
"ignorePrevStateUnavailable": false,
"ignoreCurrentStateUnknown": true,
"ignoreCurrentStateUnavailable": true,
"outputProperties": [
{
"property": "payload",
"propertyType": "msg",
"value": "",
"valueType": "entityState"
},
{
"property": "data",
"propertyType": "msg",
"value": "",
"valueType": "eventData"
},
{
"property": "topic",
"propertyType": "msg",
"value": "",
"valueType": "triggerId"
}
],
"x": 120,
"y": 1060
}
]