Friday 20 September 2024

Updates to announcing Reolink doorbells on Alexa

A while back, I wrote a post on how I build a Node-Red flow to allow me ask specific echo devices to announce visitors pressing my ReoLink doorbell. This worked great, but there were a couple of shortcomings; 
 
  • The list of registered devices would last only as long as Node-Red was active. If I needed to restart the Node-Red docker for any reason, I would also need to re-register announcement devices. 

  • The ONVIF node the flow relies on is a little flaky, and will occasionally just disappear from Node Red. 

I've added functionality to store the list of devices in a text file, so it persists between Node-Red restarts. I also added some notes on how to recover from the self-destructing ONVIF nodes, bring this all up to v1.1.1.


The device list saving is managed automatically as part of the voice registration process, but there's also a manual trigger that can be used in testing. If you use this, you might need to adjust the save and read file locations, particularly if you're not running Node-Red in a docker, like me.

The full flow is pasted below;


Before downloading the flow, if you find this interesting or useful, please do consider buying me a coffee. Every little helps feed my often insatiable tech. appetite!

Buy Me a Coffee at ko-fi.com

[
    {
        "id": "55793b5be2d1c883",
        "type": "tab",
        "label": "Doorbell",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "d24f7050.8bd5b",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "Start listening",
        "props": [],
        "repeat": "86400",
        "crontab": "",
        "once": true,
        "onceDelay": "5",
        "topic": "",
        "x": 220,
        "y": 100,
        "wires": [
            [
                "97d0eb18.a1ee08"
            ]
        ]
    },
    {
        "id": "97d0eb18.a1ee08",
        "type": "change",
        "z": "55793b5be2d1c883",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "action",
                "pt": "msg",
                "to": "start",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 400,
        "y": 100,
        "wires": [
            [
                "545ec2db2f160340"
            ]
        ]
    },
    {
        "id": "55e40eba.19775",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "Stop listening",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 210,
        "y": 140,
        "wires": [
            [
                "3b192f32.da0b"
            ]
        ]
    },
    {
        "id": "3b192f32.da0b",
        "type": "change",
        "z": "55793b5be2d1c883",
        "name": "",
        "rules": [
            {
                "p": "action",
                "pt": "msg",
                "t": "set",
                "to": "stop",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 400,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "b8a6fb75.d6bc28",
        "type": "switch",
        "z": "55793b5be2d1c883",
        "name": "",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "RuleEngine/MyRuleDetector/Visitor",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 730,
        "y": 100,
        "wires": [
            [
                "9526130c.79b8f"
            ]
        ]
    },
    {
        "id": "9526130c.79b8f",
        "type": "switch",
        "z": "55793b5be2d1c883",
        "name": "",
        "property": "data.value",
        "propertyType": "msg",
        "rules": [
            {
                "t": "true"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 850,
        "y": 100,
        "wires": [
            [
                "4cf4791f.5d21d8"
            ]
        ]
    },
    {
        "id": "4cf4791f.5d21d8",
        "type": "delay",
        "z": "55793b5be2d1c883",
        "name": "",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "5",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "outputs": 1,
        "x": 1000,
        "y": 100,
        "wires": [
            [
                "e562683462597b36"
            ]
        ]
    },
    {
        "id": "dec68ffe455db698",
        "type": "alexa-remote-other",
        "z": "55793b5be2d1c883",
        "name": "",
        "account": "b9d166b16d287937",
        "config": {
            "option": "get",
            "value": {
                "what": "activities",
                "count": {
                    "type": "num",
                    "value": "1"
                },
                "offset": {
                    "type": "num",
                    "value": "1"
                }
            }
        },
        "x": 530,
        "y": 500,
        "wires": [
            [
                "454de8f65d88278a"
            ]
        ]
    },
    {
        "id": "454de8f65d88278a",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "Get Device Name of most recent Doorbell request",
        "func": "let deviceName = null;\n\n// Iterate through the array of activities\nfor (let i = 0; i < msg.payload.length; i++) {\n    const activity = msg.payload[i];\n\n    // Check if the activity contains voiceHistoryRecordItems\n    if (activity.data && activity.data.voiceHistoryRecordItems) {\n        for (const item of activity.data.voiceHistoryRecordItems) {\n            // Check if transcriptText exists and contains the substring \"doorbell notification\"\n            if (item.transcriptText && item.transcriptText.includes(\"doorbell alert\")) {\n                // Found the desired text, get the deviceName\n                deviceName = activity.data.device.deviceName;\n                break;\n            }\n        }\n    }\n\n    // If deviceName is found, exit the loop\n    if (deviceName) {\n        break;\n    }\n}\n\n// Set the deviceName as the result\nmsg.deviceName = deviceName;\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 830,
        "y": 500,
        "wires": [
            [
                "529424166f5fec67",
                "7536e089c87482f9"
            ]
        ]
    },
    {
        "id": "5e8d6a337df14589",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "store switch state",
        "func": "// Check the payload value\nif (msg.payload === \"on\" || msg.payload === \"off\") {\n    // Store the payload value in a flow variable called \"switchState\"\n    flow.set(\"switchState\", msg.payload);\n    return msg;\n} else {\n    // If the payload value is not \"on\" or \"off\", you can handle it here if needed\n    // For example, you can send an error message or perform other actions\n    node.error(\"Invalid payload value: \" + msg.payload);\n}",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 610,
        "y": 400,
        "wires": [
            [
                "9c7e92e0a11cb2d2"
            ]
        ]
    },
    {
        "id": "529424166f5fec67",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "save to context variable",
        "func": "// Retrieve the deviceName from the incoming msg object\nconst deviceName = msg.deviceName;\n\n// Retrieve the switchState from a flow variable\nconst switchState = flow.get(\"switchState\");\n\nif (!deviceName || !switchState) {\n    // Handle invalid data or missing values\n    msg.successStatus = 0; // Unsuccessful\n    msg.error = \"Invalid deviceName or switchState.\";\n} else {\n    // Prepare the data to be stored in context variables\n    // Use the deviceName as the key (unique identifier)\n    const deviceStatus = {\n        deviceName: deviceName,\n        switchState: switchState\n    };\n\n    // Retrieve the existing device statuses from flow context (assuming it's an object)\n    const existingDeviceStatuses = flow.get(\"deviceStatuses\") || {};\n\n    // Update the device status data with the deviceName as the key\n    existingDeviceStatuses[deviceName] = deviceStatus;\n\n    // Store the updated device status data in flow context\n    flow.set(\"deviceStatuses\", existingDeviceStatuses);\n\n    // Set the successStatus flag to 1 (successful)\n    msg.successStatus = 1;\n\n    // Include switchState in the msg payload\n    msg.switchState = switchState;\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 410,
        "y": 600,
        "wires": [
            [
                "b994947ab60e8c90"
            ]
        ]
    },
    {
        "id": "f7fe5c7996fb6604",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "Make Response",
        "func": "\n// Retrieve the switchState from the message payload\nconst switchState = msg.switchState;\n\n// Define a variable to store the result of the branching\nlet branchResult;\n\n// Check the value of switchState and branch accordingly\nif (switchState === \"on\") {\n    // If switchState is \"on\", do something (e.g., set a variable or perform an action)\n\n    msg.payload = {\n        \"text\": \"I will announce doorbells on this device\",\n        \"device\": msg.deviceName\n    }\n    branchResult = \"Switch is ON\";\n} else if (switchState === \"off\") {\n    // If switchState is \"off\", do something else (e.g., set a different variable or perform a different action)\n    msg.payload = {\n        \"text\": \"Doorbells wont announce on this device\",\n        \"device\": msg.deviceName\n    }\n    \n    branchResult = \"Switch is OFF\";\n} else {\n    // Handle other cases if needed\n    msg.payload = {\n        \"text\": \"oops. I've lost track of things. Please try that again.\",\n        \"device\": msg.deviceName\n    }\n\n\n    branchResult = \"Switch is in an unknown state\";\n}\n\n// You can use branchResult or set other variables based on the branch\nmsg.branchResult = branchResult;\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 800,
        "y": 600,
        "wires": [
            [
                "06e0fb3d494c9327"
            ]
        ]
    },
    {
        "id": "ad02a0a0c945f18f",
        "type": "alexa-remote-routine",
        "z": "55793b5be2d1c883",
        "name": "",
        "account": "b9d166b16d287937",
        "routineNode": {
            "type": "speak",
            "payload": {
                "type": "regular",
                "text": {
                    "type": "msg",
                    "value": "payload.text"
                },
                "devices": {
                    "type": "msg",
                    "value": "payload.device"
                }
            }
        },
        "x": 1160,
        "y": 600,
        "wires": [
            []
        ]
    },
    {
        "id": "b994947ab60e8c90",
        "type": "switch",
        "z": "55793b5be2d1c883",
        "name": "Success?",
        "property": "successStatus",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "1",
                "vt": "num"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 620,
        "y": 600,
        "wires": [
            [
                "f7fe5c7996fb6604",
                "067b285c5fee9abe"
            ]
        ]
    },
    {
        "id": "06e0fb3d494c9327",
        "type": "delay",
        "z": "55793b5be2d1c883",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 980,
        "y": 600,
        "wires": [
            [
                "ad02a0a0c945f18f"
            ]
        ]
    },
    {
        "id": "c4bd9e0a5d16d339",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "list devices",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 220,
        "y": 780,
        "wires": [
            [
                "bed345588330fe4b"
            ]
        ]
    },
    {
        "id": "bed345588330fe4b",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "Retrieve Device Statuses",
        "func": "// Retrieve the deviceStatuses from the flow context\nconst deviceStatuses = flow.get(\"deviceStatuses\");\n\nif (deviceStatuses) {\n    // Extract all device names and switch states from the deviceStatuses object\n    const deviceInfo = Object.keys(deviceStatuses).map((deviceName) => ({\n        deviceName: deviceName,\n        switchState: deviceStatuses[deviceName].switchState\n    }));\n\n    if (deviceInfo.length > 0) {\n        // Set the list of device names and switch states in the msg.payload\n        msg.payload = deviceInfo;\n    } else {\n        // If no device names are found, set an informative message\n        msg.payload = \"No device names found.\";\n    }\n} else {\n    // If deviceStatuses is not found, set an informative message\n    msg.payload = \"No device statuses found.\";\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 780,
        "wires": [
            [
                "d9f51aadc666bdfa"
            ]
        ]
    },
    {
        "id": "d9f51aadc666bdfa",
        "type": "debug",
        "z": "55793b5be2d1c883",
        "name": "Doorbell Alerts",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1160,
        "y": 780,
        "wires": []
    },
    {
        "id": "d4feba066cce7b62",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "DELETE devices",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 200,
        "y": 840,
        "wires": [
            [
                "80f49f79c09a0969"
            ]
        ]
    },
    {
        "id": "80f49f79c09a0969",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "Clear Devices & Statuses",
        "func": "\n// Clear the deviceStatuses variable in the flow context\nflow.set(\"deviceStatuses\", {});\n\n// Set a successStatus flag to indicate that the reset was successful (you can customize this as needed)\nmsg.successStatus = 1;\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 840,
        "wires": [
            [
                "bed345588330fe4b",
                "360975caf15b761a"
            ]
        ]
    },
    {
        "id": "df33c004d8f22261",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "test announce",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 350,
        "y": 200,
        "wires": [
            [
                "e562683462597b36"
            ]
        ]
    },
    {
        "id": "e562683462597b36",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "config anouncement for 'on' devices",
        "func": "// Retrieve the deviceStatuses from the flow context\nconst deviceStatuses = flow.get(\"deviceStatuses\");\n\nif (deviceStatuses) {\n    // Initialize an array to store device names with 'on' status\n    const devicesOn = [];\n\n    // Iterate through the deviceStatuses\n    for (const deviceName in deviceStatuses) {\n        if (deviceStatuses.hasOwnProperty(deviceName)) {\n            const switchState = deviceStatuses[deviceName].switchState;\n\n            // Check if the switch state is 'on' (you can customize this condition)\n            if (switchState === 'on') {\n                // Add the device name to the list of devices with 'on' status\n                devicesOn.push(deviceName);\n            }\n        }\n    }\n\n    if (devicesOn.length > 0) {\n        // Create the payload in the required format\n        const payload = {\n            type: 'speak',\n            payload: {\n                type: 'regular',\n                text: \"There's Someody at the door\",\n                devices: devicesOn\n            }\n        };\n\n        // Set the payload in the msg object\n        msg.payload = payload;\n    } else {\n        // If no devices are found with 'on' status, set an informative message\n        msg.payload = 'No devices are currently switched on.';\n    }\n} else {\n    // If deviceStatuses is not found, set an informative message\n    msg.payload = 'No device statuses found.';\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 620,
        "y": 200,
        "wires": [
            [
                "d8acdcd07b4e928d"
            ]
        ]
    },
    {
        "id": "9c7e92e0a11cb2d2",
        "type": "delay",
        "z": "55793b5be2d1c883",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 360,
        "y": 500,
        "wires": [
            [
                "dec68ffe455db698"
            ]
        ]
    },
    {
        "id": "446290af56c8e2f7",
        "type": "alexa-remote-routine",
        "z": "55793b5be2d1c883",
        "name": "",
        "account": "b9d166b16d287937",
        "routineNode": {
            "type": "speak",
            "payload": {
                "type": "regular",
                "text": {
                    "type": "msg",
                    "value": "payload.payload.text"
                },
                "devices": {
                    "type": "msg",
                    "value": "payload.payload.devices"
                }
            }
        },
        "x": 1160,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "d3c4ab189d5ed545",
        "type": "alexa-remote-routine",
        "z": "55793b5be2d1c883",
        "name": "",
        "account": "b9d166b16d287937",
        "routineNode": {
            "type": "sound",
            "payload": {
                "sound": {
                    "type": "str",
                    "value": "amzn_sfx_doorbell_chime_02"
                },
                "devices": {
                    "type": "msg",
                    "value": "payload.payload.devices"
                }
            }
        },
        "x": 1160,
        "y": 200,
        "wires": [
            []
        ]
    },
    {
        "id": "830b288c32f7bdd2",
        "type": "delay",
        "z": "55793b5be2d1c883",
        "name": "",
        "pauseType": "delay",
        "timeout": "3",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 980,
        "y": 260,
        "wires": [
            [
                "446290af56c8e2f7"
            ]
        ]
    },
    {
        "id": "e5618282456c5d2f",
        "type": "comment",
        "z": "55793b5be2d1c883",
        "name": "Utility Functions =========================",
        "info": "",
        "x": 220,
        "y": 720,
        "wires": []
    },
    {
        "id": "8480fc71dcbdcba5",
        "type": "comment",
        "z": "55793b5be2d1c883",
        "name": "Manage Alexa Events  ====================",
        "info": "",
        "x": 220,
        "y": 320,
        "wires": []
    },
    {
        "id": "11a64ea38762b9fc",
        "type": "comment",
        "z": "55793b5be2d1c883",
        "name": "Manage Reolink Events  ===================",
        "info": "",
        "x": 220,
        "y": 40,
        "wires": []
    },
    {
        "id": "d8acdcd07b4e928d",
        "type": "switch",
        "z": "55793b5be2d1c883",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "cont",
                "v": "No device",
                "vt": "str"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 850,
        "y": 200,
        "wires": [
            [
                "49d6f2fb0b1c1cce"
            ],
            [
                "830b288c32f7bdd2",
                "d3c4ab189d5ed545"
            ]
        ]
    },
    {
        "id": "9896d0ca01888104",
        "type": "link in",
        "z": "55793b5be2d1c883",
        "name": "doorbell - debug",
        "links": [
            "49d6f2fb0b1c1cce",
            "7536e089c87482f9"
        ],
        "x": 965,
        "y": 720,
        "wires": [
            [
                "d9f51aadc666bdfa"
            ]
        ]
    },
    {
        "id": "49d6f2fb0b1c1cce",
        "type": "link out",
        "z": "55793b5be2d1c883",
        "name": "dorbell debug call 1",
        "mode": "link",
        "links": [
            "9896d0ca01888104"
        ],
        "x": 1095,
        "y": 160,
        "wires": []
    },
    {
        "id": "7536e089c87482f9",
        "type": "link out",
        "z": "55793b5be2d1c883",
        "name": "dorbell debug call 2",
        "mode": "link",
        "links": [
            "9896d0ca01888104"
        ],
        "x": 1095,
        "y": 500,
        "wires": []
    },
    {
        "id": "11fc376f018a4408",
        "type": "comment",
        "z": "55793b5be2d1c883",
        "name": "ABOUT v1.1.1",
        "info": "This flow is intended to allow Echo devices\nbe configured to announce visitors who\npress on an OnVif compatible doorbell,\nlike a ReoLink.\n\nFor full details about how this all works,\nsee:\nhttps://mediaserver8.blogspot.com/2023/11/announce-reolink-doorbell-on-echo.html\n\nIf you find this interesting or useful,\nplease consider conributig to my coffee\nfund here: \nhttps://ko-fi.com/8d14a771a245ac7\n\nVERSION HISTORY\n1.1.1 - Added notes on how to reover from disappearing ONIV\n1.1 - Added feature to save device list for persistence\n1.0 - Initial Release",
        "x": 1170,
        "y": 40,
        "wires": []
    },
    {
        "id": "1d64d5093a5b4d53",
        "type": "link in",
        "z": "55793b5be2d1c883",
        "name": "Doorbell - Notification",
        "links": [
            "142dfcd31f9a3e88"
        ],
        "x": 305,
        "y": 400,
        "wires": [
            [
                "5e8d6a337df14589"
            ]
        ]
    },
    {
        "id": "853201bc07157620",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "Prep data",
        "func": "// @ts-nocheck\n//based on this GLOBAL var persistence flow\n\n// flow  variables separated by semicolon to be saved\nvar varlist = \"deviceStatuses\";\nvar mylist = varlist.split(\";\");\n\nvar outputs = [];\n\nfor (i=0; i<mylist.length; i++) {\n    outputs.push({ key : mylist[i], type: typeof flow.get(mylist[i]), value: flow.get(mylist[i])});\n}\n      \n      \nmsg.payload = outputs;\nreturn msg;\n\n//msg.payload=typeof global.get(\"torrent_keywords\");\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 400,
        "y": 960,
        "wires": [
            [
                "4b6ad3994c0cb8e0"
            ]
        ]
    },
    {
        "id": "9132406e5a2c2909",
        "type": "file",
        "z": "55793b5be2d1c883",
        "name": "",
        "filename": "/data/saveVars/global.json",
        "filenameType": "str",
        "appendNewline": true,
        "createDir": true,
        "overwriteFile": "true",
        "x": 750,
        "y": 960,
        "wires": [
            []
        ]
    },
    {
        "id": "4b6ad3994c0cb8e0",
        "type": "json",
        "z": "55793b5be2d1c883",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 550,
        "y": 960,
        "wires": [
            [
                "9132406e5a2c2909"
            ]
        ]
    },
    {
        "id": "8451d83eb5862c66",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "Save Flow Settings",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 210,
        "y": 920,
        "wires": [
            [
                "853201bc07157620"
            ]
        ]
    },
    {
        "id": "f2f3023afa8c4a86",
        "type": "inject",
        "z": "55793b5be2d1c883",
        "name": "Startup",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "4",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 200,
        "y": 1060,
        "wires": [
            [
                "21f1d1c39ce10c6f"
            ]
        ]
    },
    {
        "id": "21f1d1c39ce10c6f",
        "type": "file in",
        "z": "55793b5be2d1c883",
        "name": "",
        "filename": "/data/saveVars/global.json",
        "filenameType": "str",
        "format": "utf8",
        "allProps": false,
        "x": 469,
        "y": 1060,
        "wires": [
            [
                "10781edf6a557798"
            ]
        ]
    },
    {
        "id": "1bd710959b413778",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "Restore",
        "func": "var output = [];\n\nfor (var i=0; i<msg.payload.length; i++) {\n    switch (msg.payload[i].type) {\n        case 'undefined': \n            // the global variable probably had no value, nothing needs to be restored\n            output.push(msg.payload[i].key + \" is undefined.\");\n            break;\n        case 'number':\n            if (msg.payload[i].value===\"NaN\") {\n                // there is no valid value to be restored, skip this variable\n                output.push(msg.payload[i].key + \" is NaN.\");\n            } else {\n                if (msg.payload[i].value.toString().indexOf(\".\")>-1) {\n                    // the value appears to be a float\n                    flow.set(msg.payload[i].key,parseFloat(msg.payload[i].value));\n                    output.push(msg.payload[i].key + \" is restored to \" + msg.payload[i].value);\n                } else {\n                    flow.set(msg.payload[i].key,parseInt(msg.payload[i].value));\n                    output.push(msg.payload[i].key + \" is restored to \" + msg.payload[i].value);\n                }\n            }\n            break;\n        case 'string':\n            flow.set(msg.payload[i].key,msg.payload[i].value);\n            output.push(msg.payload[i].key + \" is restored to \" + msg.payload[i].value);\n            break;\n        case 'boolean':\n            flow.set(msg.payload[i].key,msg.payload[i].value===\"true\");\n            output.push(msg.payload[i].key + \" is restored to \" + msg.payload[i].value);\n            break;\n        case 'object':\n            flow.set(msg.payload[i].key,msg.payload[i].value);\n            output.push(msg.payload[i].key + \" is restored\" );\n        break;\n    }\n}\n\nmsg.payload = output;\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 210,
        "y": 1135,
        "wires": [
            [
                "bc0ebbe4a14943a8"
            ]
        ]
    },
    {
        "id": "10781edf6a557798",
        "type": "json",
        "z": "55793b5be2d1c883",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": true,
        "x": 670,
        "y": 1060,
        "wires": [
            [
                "1bd710959b413778"
            ]
        ]
    },
    {
        "id": "bc0ebbe4a14943a8",
        "type": "function",
        "z": "55793b5be2d1c883",
        "name": "deviceStatuses restore",
        "func": "msg.topic = \"deviceStatuses\";\nmsg.payload = flow.get(\"deviceStatuses\");\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 458,
        "y": 1136,
        "wires": [
            [
                "095be6c42958ef61"
            ]
        ]
    },
    {
        "id": "095be6c42958ef61",
        "type": "debug",
        "z": "55793b5be2d1c883",
        "name": "FLOW RESTORE",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 730,
        "y": 1140,
        "wires": []
    },
    {
        "id": "7c8e283ac4499d78",
        "type": "link in",
        "z": "55793b5be2d1c883",
        "name": "Save Flow Data",
        "links": [
            "067b285c5fee9abe",
            "360975caf15b761a"
        ],
        "x": 195,
        "y": 980,
        "wires": [
            [
                "853201bc07157620"
            ]
        ]
    },
    {
        "id": "067b285c5fee9abe",
        "type": "link out",
        "z": "55793b5be2d1c883",
        "name": "call save",
        "mode": "link",
        "links": [
            "7c8e283ac4499d78"
        ],
        "x": 725,
        "y": 660,
        "wires": []
    },
    {
        "id": "360975caf15b761a",
        "type": "link out",
        "z": "55793b5be2d1c883",
        "name": "call save",
        "mode": "link",
        "links": [
            "7c8e283ac4499d78"
        ],
        "x": 675,
        "y": 840,
        "wires": []
    },
    {
        "id": "595164358acba24e",
        "type": "comment",
        "z": "55793b5be2d1c883",
        "name": "ONVIF Recover",
        "info": "Sometimes ONVIF nodes delete themselves.\n\nHere's how to reinstall (from https://github.com/bartbutenaers/node-red-contrib-onvif-nodes/issues/25)\n\n1. Remove all hanging nodes in Node-Red UI, including config node\n2. In docker console, cd /data\n3. rm -rf node-red-contrib-obvif\n4. npm install node-red-contrib-onvif-nodes@0.0.1-beta.16\n\n(or the latest version from the releases page: https://github.com/bartbutenaers/node-red-contrib-onvif-nodes/releases)\n\nrestart Node-Red docker, then configure for ReoLink;\n\n1. Add back OnVif events node\n2. Configure new device 'Doorbe;;' with the following credentials;\n\nIP: 192.168.1.135\nPort: 8000\nUser: nodered\nPass: surveilanceCMX1984\n\n3. Set the action to 'Start Listeniong'",
        "x": 1340,
        "y": 40,
        "wires": []
    },
    {
        "id": "545ec2db2f160340",
        "type": "onvif-events",
        "z": "55793b5be2d1c883",
        "name": "",
        "deviceConfig": "9ec64a314a5218b8",
        "action": "start",
        "x": 590,
        "y": 100,
        "wires": [
            [
                "b8a6fb75.d6bc28"
            ]
        ]
    },
    {
        "id": "b9d166b16d287937",
        "type": "alexa-remote-account",
        "name": "",
        "authMethod": "proxy",
        "proxyOwnIp": "192.168.1.199",
        "proxyPort": "4567",
        "cookieFile": "/usr/cookie.txt",
        "refreshInterval": "3",
        "alexaServiceHost": "alexa.amazon.co.uk",
        "pushDispatchHost": "",
        "amazonPage": "amazon.co.uk",
        "acceptLanguage": "en-UK",
        "onKeywordInLanguage": "on",
        "userAgent": "",
        "autoInit": "on"
    },
    {
        "id": "9ec64a314a5218b8",
        "type": "onvif-config",
        "xaddress": "192.168.1.135",
        "port": "8000",
        "timeout": "10",
        "checkConnectionInterval": "5",
        "name": "Doorbell"
    }
]

No comments: