diff --git a/flows.json b/flows.json index fefc33c..54b1a7d 100644 --- a/flows.json +++ b/flows.json @@ -92,6 +92,16 @@ "type": "tab", "label": "Bathroom" }, + { + "id": "1f4d4622.7490ca", + "type": "tab", + "label": "Temperature & climate" + }, + { + "id": "6dfa989bb120e6c6", + "type": "tab", + "label": "presence" + }, { "id": "e52c44dc.71cb5", "type": "subflow", @@ -21619,24 +21629,12 @@ ] ] }, - { - "type": "tab", - "label": "Temperature & climate", - "id": "1f4d4622.7490ca" - }, { "id": "eb217ba8.833d48", "type": "server-state-changed", "z": "1f4d4622.7490ca", - "name": "Outside temperature", - "wires": [ - [ - "c8bf7086.b32ab8", - "be230668.4e6718", - "1de4cd6b31838517" - ] - ], "d": true, + "name": "Outside temperature", "server": "657c7463.48e1e4", "version": 6, "outputs": 1, @@ -21683,19 +21681,20 @@ } ], "x": 110, - "y": 120 + "y": 120, + "wires": [ + [ + "c8bf7086.b32ab8", + "be230668.4e6718", + "1de4cd6b31838517" + ] + ] }, { "id": "c8bf7086.b32ab8", "type": "api-current-state", "z": "1f4d4622.7490ca", "name": "Inside temperature", - "wires": [ - [ - "13a9b38f.3ab9d4", - "1de4cd6b31838517" - ] - ], "server": "657c7463.48e1e4", "version": 3, "outputs": 1, @@ -21723,19 +21722,19 @@ "forType": "num", "forUnits": "minutes", "x": 470, - "y": 120 + "y": 120, + "wires": [ + [ + "13a9b38f.3ab9d4", + "1de4cd6b31838517" + ] + ] }, { "id": "68aa8fe5.4cd4f", "type": "function", "z": "1f4d4622.7490ca", "name": "Determine cooler or warmer", - "wires": [ - [ - "e27e4896.4eefa", - "1de4cd6b31838517" - ] - ], "func": "var outsideTemp = parseFloat(msg.payload.outside);\nvar insideTemp = parseFloat(msg.payload.inside);\nvar notification = '';\n\nif (outsideTemp < insideTemp) {\n notification = 'Outside is cooler than inside';\n} else {\n notification = 'Outside is warmer than inside';\n}\n\nmsg.payload = notification;\n\nreturn msg;", "outputs": 1, "timeout": "", @@ -21744,16 +21743,19 @@ "finalize": "", "libs": [], "x": 880, - "y": 180 + "y": 180, + "wires": [ + [ + "e27e4896.4eefa", + "1de4cd6b31838517" + ] + ] }, { "id": "e27e4896.4eefa", "type": "api-call-service", "z": "1f4d4622.7490ca", "name": "Notify via HA", - "wires": [ - [] - ], "server": "657c7463.48e1e4", "version": 7, "debugenabled": false, @@ -21775,18 +21777,16 @@ "output_location": "", "output_location_type": "none", "x": 1130, - "y": 180 + "y": 180, + "wires": [ + [] + ] }, { "id": "be230668.4e6718", "type": "change", "z": "1f4d4622.7490ca", "name": "Clear data", - "wires": [ - [ - "2119da07.2ad496" - ] - ], "rules": [ { "t": "delete", @@ -21807,18 +21807,18 @@ "to": "", "reg": false, "x": 470, - "y": 260 + "y": 260, + "wires": [ + [ + "2119da07.2ad496" + ] + ] }, { "id": "13a9b38f.3ab9d4", "type": "change", "z": "1f4d4622.7490ca", "name": "Clear data", - "wires": [ - [ - "2119da07.2ad496" - ] - ], "rules": [ { "t": "delete", @@ -21839,18 +21839,18 @@ "to": "", "reg": false, "x": 470, - "y": 200 + "y": 200, + "wires": [ + [ + "2119da07.2ad496" + ] + ] }, { "id": "2119da07.2ad496", "type": "join", "z": "1f4d4622.7490ca", "name": "", - "wires": [ - [ - "68aa8fe5.4cd4f" - ] - ], "mode": "custom", "build": "object", "property": "payload", @@ -21867,14 +21867,18 @@ "reduceInitType": "", "reduceFixup": "", "x": 670, - "y": 180 + "y": 180, + "wires": [ + [ + "68aa8fe5.4cd4f" + ] + ] }, { "id": "1de4cd6b31838517", "type": "debug", "z": "1f4d4622.7490ca", "name": "Temperature alert", - "wires": [], "active": false, "tosidebar": true, "console": false, @@ -21884,42 +21888,35 @@ "statusVal": "", "statusType": "auto", "x": 830, - "y": 60 + "y": 60, + "wires": [] }, { "id": "9a99a3d8e416b026", "type": "comment", "z": "1f4d4622.7490ca", "name": "Temperature alert", - "wires": [], "info": "", "x": 100, - "y": 80 + "y": 80, + "wires": [] }, { "id": "a676209fc00d4d6c", "type": "comment", "z": "1f4d4622.7490ca", "name": "Climate control", - "wires": [], "info": "", "x": 100, - "y": 360 + "y": 360, + "wires": [] }, { "id": "6156c5585680e99e", "type": "server-state-changed", "z": "1f4d4622.7490ca", - "name": "Livingroom window open", - "wires": [ - [ - "ea05134bce8525ef" - ], - [ - "e2f354b6c7b22e8d" - ] - ], "d": true, + "name": "Livingroom window open", "server": "657c7463.48e1e4", "version": 6, "outputs": 2, @@ -21966,17 +21963,22 @@ } ], "x": 130, - "y": 420 + "y": 420, + "wires": [ + [ + "ea05134bce8525ef" + ], + [ + "e2f354b6c7b22e8d" + ] + ] }, { "id": "ea05134bce8525ef", "type": "api-call-service", "z": "1f4d4622.7490ca", - "name": "Activate window open mode", - "wires": [ - [] - ], "d": true, + "name": "Activate window open mode", "server": "657c7463.48e1e4", "version": 7, "debugenabled": false, @@ -22002,17 +22004,17 @@ "output_location": "", "output_location_type": "none", "x": 520, - "y": 420 + "y": 420, + "wires": [ + [] + ] }, { "id": "e2f354b6c7b22e8d", "type": "api-call-service", "z": "1f4d4622.7490ca", - "name": "Activate window open mode", - "wires": [ - [] - ], "d": true, + "name": "Activate window open mode", "server": "657c7463.48e1e4", "version": 7, "debugenabled": false, @@ -22034,33 +22036,26 @@ "output_location": "", "output_location_type": "none", "x": 520, - "y": 500 - }, - { - "type": "tab", - "label": "presence", - "id": "6dfa989bb120e6c6" + "y": 500, + "wires": [ + [] + ] }, { "id": "pres_c_homemode", "type": "comment", "z": "6dfa989bb120e6c6", "name": "── Home Mode ──", - "wires": [], "info": "", "x": 120, - "y": 60 + "y": 60, + "wires": [] }, { "id": "pres001a2b3c4d5e", "type": "server-state-changed", "z": "6dfa989bb120e6c6", "name": "Mischa presence", - "wires": [ - [ - "pres002a2b3c4d5e" - ] - ], "server": "657c7463.48e1e4", "version": 6, "outputs": 1, @@ -22107,21 +22102,18 @@ } ], "x": 120, - "y": 100 + "y": 100, + "wires": [ + [ + "pres002a2b3c4d5e" + ] + ] }, { "id": "pres002a2b3c4d5e", "type": "switch", "z": "6dfa989bb120e6c6", "name": "Home or Away?", - "wires": [ - [ - "pres003a2b3c4d5e" - ], - [ - "pres004a2b3c4d5e" - ] - ], "property": "payload", "propertyType": "msg", "rules": [ @@ -22140,16 +22132,21 @@ "repair": false, "outputs": 2, "x": 400, - "y": 100 + "y": 100, + "wires": [ + [ + "pres003a2b3c4d5e" + ], + [ + "pres004a2b3c4d5e" + ] + ] }, { "id": "pres003a2b3c4d5e", "type": "api-call-service", "z": "6dfa989bb120e6c6", "name": "Set Home", - "wires": [ - [] - ], "server": "657c7463.48e1e4", "version": 7, "debugenabled": false, @@ -22171,16 +22168,16 @@ "domain": "input_select", "service": "select_option", "x": 660, - "y": 60 + "y": 60, + "wires": [ + [] + ] }, { "id": "pres004a2b3c4d5e", "type": "api-call-service", "z": "6dfa989bb120e6c6", "name": "Set Away", - "wires": [ - [] - ], "server": "657c7463.48e1e4", "version": 7, "debugenabled": false, @@ -22202,78 +22199,76 @@ "domain": "input_select", "service": "select_option", "x": 660, - "y": 140 + "y": 140, + "wires": [ + [] + ] }, { "id": "pres_sm_c_header", "type": "comment", "z": "6dfa989bb120e6c6", "name": "── Room Presence State Machine ──", - "wires": [], "info": "", "x": 120, - "y": 220 + "y": 220, + "wires": [] }, { "id": "pres_sm_c_person", "type": "comment", "z": "6dfa989bb120e6c6", "name": "Person", - "wires": [], "info": "", "x": 120, - "y": 280 + "y": 280, + "wires": [] }, { "id": "pres_sm_c_hall", "type": "comment", "z": "6dfa989bb120e6c6", "name": "Hallway (direction + state)", - "wires": [], "info": "", - "x": 120, - "y": 360 + "x": 150, + "y": 360, + "wires": [] }, { "id": "pres_sm_c_rooms", "type": "comment", "z": "6dfa989bb120e6c6", "name": "Room Motion Sensors", - "wires": [], "info": "", "x": 120, - "y": 660 + "y": 660, + "wires": [] }, { "id": "pres_sm_c_sm", "type": "comment", "z": "6dfa989bb120e6c6", "name": "State Machine", - "wires": [], "info": "", "x": 400, - "y": 580 + "y": 580, + "wires": [] }, { "id": "pres_sm_c_out", "type": "comment", "z": "6dfa989bb120e6c6", "name": "Outputs", - "wires": [], "info": "", "x": 660, - "y": 580 + "y": 580, + "wires": [] }, { "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, @@ -22320,18 +22315,18 @@ } ], "x": 120, - "y": 320 + "y": 320, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "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, @@ -22377,19 +22372,19 @@ "valueType": "triggerId" } ], - "x": 120, - "y": 400 + "x": 160, + "y": 400, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "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, @@ -22436,18 +22431,18 @@ } ], "x": 120, - "y": 480 + "y": 480, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "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, @@ -22494,18 +22489,18 @@ } ], "x": 120, - "y": 560 + "y": 560, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "id": "pres_sm_hallg", "type": "server-state-changed", "z": "6dfa989bb120e6c6", "name": "Hallway (group)", - "wires": [ - [ - "pres_sm_func" - ] - ], "server": "657c7463.48e1e4", "version": 6, "outputs": 1, @@ -22552,18 +22547,18 @@ } ], "x": 120, - "y": 620 + "y": 620, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "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, @@ -22610,18 +22605,18 @@ } ], "x": 120, - "y": 700 + "y": 700, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "id": "pres_sm_bed", "type": "server-state-changed", "z": "6dfa989bb120e6c6", "name": "Bedroom Motion", - "wires": [ - [ - "pres_sm_func" - ] - ], "server": "657c7463.48e1e4", "version": 6, "outputs": 1, @@ -22668,18 +22663,18 @@ } ], "x": 120, - "y": 780 + "y": 780, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "id": "pres_sm_bath", "type": "server-state-changed", "z": "6dfa989bb120e6c6", "name": "Bathroom Motion", - "wires": [ - [ - "pres_sm_func" - ] - ], "server": "657c7463.48e1e4", "version": 6, "outputs": 1, @@ -22726,18 +22721,18 @@ } ], "x": 120, - "y": 860 + "y": 860, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "id": "pres_sm_kit", "type": "server-state-changed", "z": "6dfa989bb120e6c6", "name": "Kitchen Motion", - "wires": [ - [ - "pres_sm_func" - ] - ], "server": "657c7463.48e1e4", "version": 6, "outputs": 1, @@ -22784,21 +22779,18 @@ } ], "x": 120, - "y": 940 + "y": 940, + "wires": [ + [ + "pres_sm_func" + ] + ] }, { "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 haStates = (global.get('homeassistant') || {}).homeAssistantLatest\n ? global.get('homeassistant').homeAssistantLatest.states : {};\nconst guestMode = (haStates['input_boolean.guest_mode'] || {}).state;\nif (guestMode === 'on') return null;\n\n// Current state from flow context\nlet 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\nfunction cancelBalconyTimer() {\n const h = flow.get('balconyTimerHandle');\n if (h) { clearTimeout(h); flow.set('balconyTimerHandle', null); }\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\nfunction setBalconyTimer() {\n cancelBalconyTimer();\n const h = setTimeout(function () {\n const cr = flow.get('currentRoom');\n if (cr === 'living_room') {\n flow.set('currentRoom', 'balcony');\n flow.set('balconyTimerHandle', null);\n node.send([\n { payload: 'Balcony' },\n { payload: '[presence] balcony timer fired → Balcony' }\n ]);\n }\n }, 900000); // 15 minutes\n flow.set('balconyTimerHandle', h);\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 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 cancelBalconyTimer();\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 cancelBalconyTimer();\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};\n\nconst targetRoom = roomMap[entityId];\nif (targetRoom) {\n if (newState === 'on') {\n cancelDestTimer(); // confirmed destination\n cancelBalconyTimer();\n\n // Return from balcony via living room motion\n if (targetRoom === 'living_room' && currentRoom === 'balcony') {\n return setState('living_room');\n }\n // Accept if we came through hallway, are re-confirming same room, or state is unknown\n if (currentRoom === 'hallway' || currentRoom === targetRoom || currentRoom === 'unknown') {\n return setState(targetRoom);\n }\n return null;\n }\n\n // LR motion off → start balcony detection (15 min no-hallway = balcony assumed)\n if (newState === 'off' && targetRoom === 'living_room' && currentRoom === 'living_room') {\n setBalconyTimer();\n }\n return null;\n}\n\nreturn null;", "outputs": 2, "timeout": 0, @@ -22807,16 +22799,21 @@ "finalize": "", "libs": [], "x": 400, - "y": 620 + "y": 620, + "wires": [ + [ + "pres_sm_api" + ], + [ + "pres_sm_dbg" + ] + ] }, { "id": "pres_sm_api", "type": "api-call-service", "z": "6dfa989bb120e6c6", "name": "Set Room Presence", - "wires": [ - [] - ], "server": "657c7463.48e1e4", "version": 7, "debugenabled": false, @@ -22838,14 +22835,16 @@ "domain": "input_select", "service": "select_option", "x": 660, - "y": 620 + "y": 620, + "wires": [ + [] + ] }, { "id": "pres_sm_dbg", "type": "debug", "z": "6dfa989bb120e6c6", "name": "Presence log", - "wires": [], "active": true, "tosidebar": true, "console": false, @@ -22855,6 +22854,7 @@ "statusVal": "", "statusType": "auto", "x": 660, - "y": 700 + "y": 700, + "wires": [] } ] \ No newline at end of file