[
    {
        "id": "da15ab6686cd5efb",
        "type": "group",
        "z": "dfe38ef7f6290d51",
        "name": "",
        "style": {
            "stroke": "#0070c0",
            "label": true
        },
        "nodes": [
            "27e994197cfa4826",
            "affce01e959aa68d",
            "d418fe1384db0f0d",
            "96d1d5393ca1c8b4",
            "a25b9866c264cbd3",
            "bece25a244a0c206",
            "4b9e677b58ec01a5",
            "a6cd0a7b551027a7",
            "fb82de2ae03bd9e2",
            "76043328c73955ea"
        ],
        "x": 174,
        "y": 339,
        "w": 1372,
        "h": 202
    },
    {
        "id": "27e994197cfa4826",
        "type": "server-state-changed",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "Motion Sensor Listener",
        "server": "",
        "version": 6,
        "outputs": 1,
        "exposeAsEntityConfig": "",
        "entities": {
            "entity": [],
            "substring": [],
            "regex": [
                "binary_sensor\\..*_multisensor_motion_detection"
            ]
        },
        "outputInitially": false,
        "stateType": "str",
        "ifState": "",
        "ifStateType": "str",
        "ifStateOperator": "is",
        "outputOnlyOnStateChange": true,
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "ignorePrevStateNull": false,
        "ignorePrevStateUnknown": false,
        "ignorePrevStateUnavailable": false,
        "ignoreCurrentStateUnknown": false,
        "ignoreCurrentStateUnavailable": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "data",
                "propertyType": "msg",
                "value": "",
                "valueType": "eventData"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "",
                "valueType": "triggerId"
            },
            {
                "property": "state",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "node",
                "propertyType": "msg",
                "value": "Motion Sensor Listener",
                "valueType": "str"
            }
        ],
        "x": 320,
        "y": 420,
        "wires": [
            [
                "affce01e959aa68d"
            ]
        ]
    },
    {
        "id": "affce01e959aa68d",
        "type": "function",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "Handle Motion Sensor Input",
        "func": "// Motion sensor ID (e.g., binary_sensor.entrance_multisensor_motion_detection)\nconst sensorId = msg.topic;\n// Motion state (e.g., \"on\" or \"off\")\nconst sensorValue = msg.payload;\n\n// Retrieve sensorToLightMap from global context\nconst sensorToLightMap = global.get('sensorToLightMap');\n\n// Get the current sensor data from global context\nlet sensorData = global.get('motionSensorData') || {};\n\nlet timestampData = new Date();\n// Extract the components\nlet year = timestampData.getFullYear();\nlet month = String(timestampData.getMonth() + 1).padStart(2, '0'); // Months are 0-based\nlet date = String(timestampData.getDate()).padStart(2, '0');\nlet hours = String(timestampData.getHours()).padStart(2, '0');\nlet minutes = String(timestampData.getMinutes()).padStart(2, '0');\nlet seconds = String(timestampData.getSeconds()).padStart(2, '0');\n\nconst sensorMapping = sensorToLightMap[sensorId] || {};\n\n// Determine the associated and linked_dependency values\nconst associatedValue = sensorMapping.associated || \"no\";\nconst linkedDependency = sensorMapping.linked_dependency || \"no\";\n\n// Get the timeoutValue in minutes\nlet timeoutValue;\nif (sensorMapping.noMotionTimeout > 0) { \n    timeoutValue = sensorMapping.noMotionTimeout / 1000 / 60 \n} else { \n    timeoutValue = null\n};\n\n// Update the state and timestamp of this sensor\nsensorData[sensorId] = { \n    value: sensorValue, \n    associated: associatedValue,\n    linked_dependency: linkedDependency,\n    timeout_mins: timeoutValue,\n    timestamp: Date.now(), \n    readable_date: `${year}/${month}/${date} ${hours}:${minutes}:${seconds}` \n    };\nglobal.set('motionSensorData', sensorData); // Store the updated global state\n\n// Send data to file writer\nmsg.payload = JSON.stringify(sensorData, null, 2); // Prepare the file content\nmsg.filename = '/mnt/logs/motion_sensor_log.json'; // Set the filename for the file node\nmsg.node = \"Handle Motion Sensor Input\";\n\nreturn msg; // Pass the message to the next node\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 660,
        "y": 420,
        "wires": [
            [
                "4b9e677b58ec01a5",
                "fb82de2ae03bd9e2"
            ]
        ]
    },
    {
        "id": "d418fe1384db0f0d",
        "type": "inject",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "Inject Check for No Motion",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "node",
                "v": "Inject Check for No Motion",
                "vt": "str"
            }
        ],
        "repeat": "60",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 340,
        "y": 500,
        "wires": [
            [
                "96d1d5393ca1c8b4"
            ]
        ]
    },
    {
        "id": "96d1d5393ca1c8b4",
        "type": "function",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "Check for No Motion",
        "func": "const homeAssistant = global.get('homeassistant') || {};\n\n// Retrieve sensorToLightMap from global context\nconst sensorToLightMap = global.get('sensorToLightMap') || {};\n\nconst sensorData = global.get('motionSensorData') || {};\nconst currentTime = Date.now();\nconst messages = [];\n\n// Get the latest motion sensor data (timestamp and sensorId) from sensorData.\nfunction getLatestMotionSensorData(sensorData) {\n    let latestSensor = null;\n    let latestTimestamp = 0;\n\n    Object.entries(sensorData).forEach(([sensorId, data]) => {\n        const motionTime = new Date(data.readable_date).getTime();\n        if (motionTime > latestTimestamp) {\n            latestTimestamp = motionTime;\n            latestSensor = sensorId;\n        }\n    });\n\n    return { sensorId: latestSensor, timestamp: latestTimestamp };\n}\n\n// Get the latest motion sensor information (both sensorId and timestamp)\nconst latestMotionData = getLatestMotionSensorData(sensorData);\n\nObject.entries(sensorData).forEach(([sensorId, data]) => {\n    const sensorMapping = sensorToLightMap[sensorId];\n\n    // Skip if no light is mapped for this sensor, or associated is set to 'no'\n    if (!sensorMapping || sensorMapping.associated === 'no') return;\n\n    // Handle single or multiple lights for the sensor\n    const lights = Array.isArray(sensorMapping.lights) ? sensorMapping.lights : [sensorMapping.lights];\n\n    lights.forEach(lightEntry => {\n        const lightEntityId = lightEntry.light;\n\n        // Get Home Assistant entity state for this light\n        const lightEntity = homeAssistant.homeAssistant.states[lightEntityId];\n\n        // Determine effective associated and linked_dependency values\n        const effectiveAssociated = lightEntry.associated || sensorMapping.associated;\n        const effectiveLinkedDependency = lightEntry.linked_dependency || sensorMapping.linked_dependency || 'no';\n        const effectiveNoMotionTimeout = lightEntry.noMotionTimeout || sensorMapping.noMotionTimeout;\n\n        // Skip if the light is not associated\n        if (effectiveAssociated === 'no') return;\n\n        if (lightEntity.state === \"on\") {\n            const timeElapsed = currentTime - data.timestamp;\n\n            // Conditions for turning off the light\n            const isSensorOff = data.value === 'off';\n            const timeoutPassed = timeElapsed > effectiveNoMotionTimeout;\n            const lastMotionTime = new Date(data.readable_date).getTime();\n\n            if (effectiveLinkedDependency === 'no') {\n                // If linked_dependency is 'no', turn off light if no motion timeout has passed\n                if (isSensorOff && timeoutPassed) {\n                    messages.push({\n                        payload: { entity_id: lightEntityId },\n                        topic: lightEntityId,\n                        sensor: sensorMapping,\n                        node: 'Check for No Motion (linked_dependency = no)',\n                        timeout_mins: data.timeout_mins,\n                        state_was: lightEntity.state,\n                        last_motion_was: data.readable_date\n                    });\n                }\n            } else if (effectiveLinkedDependency === 'yes') {\n                // If linked_dependency is 'yes', turn off light only if another sensor had more recent motion\n                if (isSensorOff && timeoutPassed && lastMotionTime < latestMotionData.timestamp) {\n                    messages.push({\n                        payload: { entity_id: lightEntityId },\n                        topic: lightEntityId,\n                        sensor: sensorMapping,\n                        node: 'Check for No Motion (linked_dependency = yes)',\n                        timeout_mins: data.timeout_mins,\n                        state_was: lightEntity.state,\n                        last_motion_was: data.readable_date,\n                        latest_motion_sensor: latestMotionData.sensorId,\n                        latest_motion_timestamp: new Date(latestMotionData.timestamp).toISOString()\n                    });\n                }\n            }\n        }\n    });\n});\n\nif (messages.length > 0) {\n    return [messages]; // Send all the \"turn off light\" messages\n} else {\n    return null; // No motion detected, no messages to send\n}\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1200,
        "y": 500,
        "wires": [
            [
                "a25b9866c264cbd3"
            ]
        ]
    },
    {
        "id": "a25b9866c264cbd3",
        "type": "api-call-service",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "Turn Off Light",
        "server": "",
        "version": 7,
        "debugenabled": false,
        "action": "",
        "floorId": [],
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "{{payload.entity_id}}"
        ],
        "labelId": [],
        "data": "",
        "dataType": "jsonata",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "blockInputOverrides": false,
        "domain": "light",
        "service": "turn_off",
        "x": 1440,
        "y": 500,
        "wires": [
            []
        ]
    },
    {
        "id": "bece25a244a0c206",
        "type": "file",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "",
        "filename": "filename",
        "filenameType": "msg",
        "appendNewline": true,
        "createDir": false,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 1160,
        "y": 420,
        "wires": [
            []
        ]
    },
    {
        "id": "4b9e677b58ec01a5",
        "type": "switch",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "is not null",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 960,
        "y": 420,
        "wires": [
            [
                "bece25a244a0c206"
            ]
        ]
    },
    {
        "id": "a6cd0a7b551027a7",
        "type": "function",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "Map Associated Sensors",
        "func": "// Define the sensorToLightMap globally (once)\nconst sensorToLightMap = {\n    'binary_sensor.entrance_multisensor_motion_detection': {\n        motion_sensor: 'binary_sensor.entrance_multisensor_motion_detection',\n        friendly_name: 'Entrance MultiSensor',\n        associated: 'yes',\n        linked_dependency: 'no',\n        lights: [\n            {\n            light: 'light.entrance_spotlights',\n            associated: 'yes',\n            noMotionTimeout: 1 * 60 * 1000,\n            linked_dependency: 'no'\n            }\n        ]\n    },\n    'binary_sensor.guest_bathroom_multisensor_motion_detection': {\n        motion_sensor: 'binary_sensor.guest_bathroom_multisensor_motion_detection',\n        friendly_name: 'Guest Bathroom MultiSensor',\n        associated: 'yes',\n        linked_dependency: 'yes',\n        lights: [\n            {\n            light: 'light.guest_bathroom_spotlights',\n            associated: 'yes',\n            noMotionTimeout: 5 * 60 * 1000,\n            linked_dependency: 'yes'\n            }\n        ]\n    },\n    'binary_sensor.guest_bedroom_multisensor_motion_detection': {\n        motion_sensor: 'binary_sensor.guest_bedroom_multisensor_motion_detection',\n        friendly_name: 'Guest Bedroom MultiSensor',\n        associated: 'yes',\n        linked_dependency: 'no',\n        lights: [\n            {\n            light: 'light.guest_bedroom_spotlights',\n            associated: 'yes',\n            noMotionTimeout: 5 * 60 * 1000,\n            linked_dependency: 'no'\n            }\n        ]\n    },\n    'binary_sensor.laundry_multisensor_motion_detection': {\n        motion_sensor: 'binary_sensor.laundry_multisensor_motion_detection',\n        friendly_name: 'Laundry MultiSensor',\n        associated: 'yes',\n        linked_dependency: 'no',\n        lights: [\n            {\n            light: 'light.laundry_spotlights',\n            associated: 'yes',\n            noMotionTimeout: 5 * 60 * 1000,\n            linked_dependency: 'no'\n            }\n        ]\n    }\n};\n\n// Store the map globally in Node-RED's context\nglobal.set('sensorToLightMap', sensorToLightMap);\n\nmsg.node = \"Map Associated Sensors\";\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 650,
        "y": 380,
        "wires": [
            []
        ]
    },
    {
        "id": "fb82de2ae03bd9e2",
        "type": "switch",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "is off",
        "property": "state",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "off",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 950,
        "y": 460,
        "wires": [
            [
                "96d1d5393ca1c8b4"
            ]
        ]
    },
    {
        "id": "76043328c73955ea",
        "type": "inject",
        "z": "dfe38ef7f6290d51",
        "g": "da15ab6686cd5efb",
        "name": "SET sensorToLightMap",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "node",
                "v": "SET sensorToLightMap",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "2",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 330,
        "y": 380,
        "wires": [
            [
                "a6cd0a7b551027a7"
            ]
        ]
    }
]