Aussie living in the San Francisco Bay Area.
Coding since 1998.
.NET Foundation member. C# fan
https://d.sb/
Mastodon: @dan@d.sb

  • 0 Posts
  • 13 Comments
Joined 2 years ago
cake
Cake day: June 14th, 2023

help-circle








  • Put the image in the media directory in your Home Assistant config directory then use /media/… as the URL. This works for me.

    For my security cameras, I have a Node-RED flow:

    It:

    • Listens for MQTT events from Blue Iris
    • Builds a message based on the AI detection result and camera name (e.g. “Person detected at Front”)
    • Write image file from MQTT event to media/alerts/ directory
    • Send notification to phones and TV (currently just goes to one Shield - I need to set up the second one eventually)
    • If a person is detected, send notification to Google speakers

    Here’s the JSON for the flow:

    JSON
    [
        {
            "id": "329ba4b7632a95c8",
            "type": "tab",
            "label": "Camera",
            "disabled": false,
            "info": "",
            "env": []
        },
        {
            "id": "f64fc2c05427d1ff",
            "type": "mqtt in",
            "z": "329ba4b7632a95c8",
            "name": "BlueIris/+/alert-image-b64",
            "topic": "BlueIris/+/alert-image-b64",
            "qos": "2",
            "datatype": "json",
            "broker": "e811c8eb637d26a5",
            "nl": false,
            "rap": true,
            "rh": 0,
            "inputs": 0,
            "x": 130,
            "y": 40,
            "wires": [
                [
                    "5d5d6c7623201f34"
                ]
            ]
        },
        {
            "id": "5d5d6c7623201f34",
            "type": "function",
            "z": "329ba4b7632a95c8",
            "name": "Process payload and build message",
            "func": "const {img, ...otherPayloadFields} = msg.payload;\n// TODO: Filter these out in Blue Iris AI config\nconst objectsToFilter = new Set([\n    'ball',\n    'sports ball',\n    'horse',\n    'book',\n    'suitcase',\n]);\n\nlet formattedObjectNames = 'Nothing';\nconst objectsData = (otherPayloadFields.json || [])\n    .find(x => x.api === 'objects');\n// Would normally use `objectData?.found?.success` but Node-RED doesn't support it\nconst hasObjects = objectsData && objectsData.found && objectsData.found.success;\nif (hasObjects) {\n    // Using Array.from(new Set(...)) to dedupe object names\n    const objectNames = Array.from(new Set(\n        (objectsData.found.predictions || [])\n            .sort(x => -x.confidence)\n            .map(x => x.label)\n            .filter(x => !objectsToFilter.has(x))\n    ));\n    if (objectNames.length > 0) {\n        // Capitalise first letter of first object\n        objectNames[0] = objectNames[0][0].toUpperCase() + objectNames[0].substring(1)\n       formattedObjectNames = objectNames.join(' and ');\n    }\n}\n\nconst message = formattedObjectNames + ' detected at ' + otherPayloadFields.name;\nreturn {\n    image_filename: Date.now() + '-' + otherPayloadFields.id + '.jpg',\n    message,\n    object_names: formattedObjectNames,\n    payload: Buffer.from(img, 'base64'),\n    original_payload: otherPayloadFields,\n};",
            "outputs": 1,
            "timeout": "",
            "noerr": 0,
            "initialize": "",
            "finalize": "",
            "libs": [],
            "x": 430,
            "y": 40,
            "wires": [
                [
                    "31138938746cf6fd",
                    "53f4a59537f2893e"
                ]
            ]
        },
        {
            "id": "31138938746cf6fd",
            "type": "file",
            "z": "329ba4b7632a95c8",
            "name": "write image file",
            "filename": "\"/media/alerts/\" & image_filename",
            "filenameType": "jsonata",
            "appendNewline": false,
            "createDir": false,
            "overwriteFile": "true",
            "encoding": "none",
            "x": 700,
            "y": 40,
            "wires": [
                [
                    "6490fa5f55043f2a",
                    "1ca18fe555c917fa"
                ]
            ]
        },
        {
            "id": "6490fa5f55043f2a",
            "type": "api-call-service",
            "z": "329ba4b7632a95c8",
            "name": "Notify phones",
            "server": "b6f128b45d1d76f7",
            "version": 5,
            "debugenabled": false,
            "domain": "notify",
            "service": "all_phones",
            "areaId": [],
            "deviceId": [],
            "entityId": [],
            "data": "{\t   \"message\": message,\t   \"data\": {\t        \"clickAction\": \"http://cameras.vpn.d.sb:81/ui3.htm?maximize=1&tab=alerts&cam=\" & original_payload.camera & \"&rec=\" & original_payload.id,\t        \"image\": \"/media/local/alerts/\" & image_filename,\t        \"actions\": [{\t            \"action\": \"URI\",\t            \"title\": \"Live View\",\t            \"uri\": \"http://cameras.vpn.d.sb:81/ui3.htm?maximize=1&cam=\" & original_payload.camera\t        }, {\t            \"action\": \"URI\",\t            \"title\": \"View Clip\",\t            \"uri\": \"http://cameras.vpn.d.sb:81/ui3.htm?maximize=1&tab=alerts&cam=\" & original_payload.camera & \"&rec=\" & original_payload.id\t        }],\t       \t        \"channel\": \"Camera\",\t        \"ttl\": 0,\t        \"priority\": \"high\",\t        \"importance\": \"high\"\t    }\t}",
            "dataType": "jsonata",
            "mergeContext": "",
            "mustacheAltTags": false,
            "outputProperties": [],
            "queue": "none",
            "x": 900,
            "y": 40,
            "wires": [
                []
            ]
        },
        {
            "id": "1ca18fe555c917fa",
            "type": "api-call-service",
            "z": "329ba4b7632a95c8",
            "name": "Notify TV",
            "server": "b6f128b45d1d76f7",
            "version": 5,
            "debugenabled": false,
            "domain": "notify",
            "service": "shield",
            "areaId": [],
            "deviceId": [],
            "entityId": [],
            "data": "{\t   \"message\": message,\t   \"data\": {\t        \"image\": {\t            \"path\": \"/media/alerts/\" & image_filename\t        }\t    }\t}",
            "dataType": "jsonata",
            "mergeContext": "",
            "mustacheAltTags": false,
            "outputProperties": [],
            "queue": "none",
            "x": 880,
            "y": 100,
            "wires": [
                []
            ]
        },
        {
            "id": "53f4a59537f2893e",
            "type": "switch",
            "z": "329ba4b7632a95c8",
            "name": "Is person?",
            "property": "message",
            "propertyType": "msg",
            "rules": [
                {
                    "t": "cont",
                    "v": "person",
                    "vt": "str"
                },
                {
                    "t": "cont",
                    "v": "Person",
                    "vt": "str"
                }
            ],
            "checkall": "false",
            "repair": false,
            "outputs": 2,
            "x": 710,
            "y": 160,
            "wires": [
                [
                    "31ec03e4d62909c3"
                ],
                [
                    "31ec03e4d62909c3"
                ]
            ]
        },
        {
            "id": "31ec03e4d62909c3",
            "type": "api-call-service",
            "z": "329ba4b7632a95c8",
            "name": "Notify Google Speakers",
            "server": "b6f128b45d1d76f7",
            "version": 3,
            "debugenabled": false,
            "service": "google_assistant_sdk",
            "entityId": "",
            "data": "{\"message\": message}",
            "dataType": "jsonata",
            "mustacheAltTags": false,
            "outputProperties": [],
            "queue": "none",
            "service_domain": "notify",
            "mergecontext": "",
            "x": 930,
            "y": 160,
            "wires": [
                []
            ]
        },
        {
            "id": "e811c8eb637d26a5",
            "type": "mqtt-broker",
            "name": "MQTT",
            "broker": "mqtt.int.d.sb",
            "port": "1883",
            "clientid": "",
            "autoConnect": true,
            "usetls": false,
            "protocolVersion": "4",
            "keepalive": "60",
            "cleansession": true,
            "birthTopic": "",
            "birthQos": "0",
            "birthPayload": "",
            "birthMsg": {},
            "closeTopic": "",
            "closeQos": "0",
            "closePayload": "",
            "closeMsg": {},
            "willTopic": "",
            "willQos": "0",
            "willPayload": "",
            "willMsg": {},
            "userProps": "",
            "sessionExpiry": ""
        },
        {
            "id": "b6f128b45d1d76f7",
            "type": "server",
            "name": "Home Assistant",
            "version": 2,
            "addon": false,
            "rejectUnauthorizedCerts": true,
            "ha_boolean": "y|yes|true|on|home|open",
            "connectionDelay": true,
            "cacheJson": true,
            "heartbeat": false,
            "heartbeatInterval": "30"
        }
    ]