Notification when Alexa Timer Finishes

This node red flow allows you to send notifications/alerts when an alexa timer finishes/expires.

[
    {
        "id": "421feed439e285b3",
        "type": "tab",
        "label": "Alexa Timer Notifications",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "5df45ad21e5e1138",
        "type": "server-state-changed",
        "z": "421feed439e285b3",
        "name": "Alexa Device 1 Timer",
        "server": "f49a8008.f7cb2",
        "version": 6,
        "outputs": 1,
        "exposeAsEntityConfig": "",
        "entities": {
            "entity": [
                "sensor.alexa_1_next_timer"
            ],
            "substring": [],
            "regex": []
        },
        "outputInitially": false,
        "stateType": "str",
        "ifState": "",
        "ifStateType": "str",
        "ifStateOperator": "is_not",
        "outputOnlyOnStateChange": false,
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "ignorePrevStateNull": false,
        "ignorePrevStateUnknown": false,
        "ignorePrevStateUnavailable": false,
        "ignoreCurrentStateUnknown": false,
        "ignoreCurrentStateUnavailable": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "string",
                "valueType": "entityState"
            },
            {
                "property": "data",
                "propertyType": "msg",
                "value": "",
                "valueType": "eventData"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "",
                "valueType": "triggerId"
            }
        ],
        "x": 120,
        "y": 60,
        "wires": [
            [
                "7d32e93be352c0a1"
            ]
        ]
    },
    {
        "id": "7d32e93be352c0a1",
        "type": "function",
        "z": "421feed439e285b3",
        "name": "Read Active Timers",
        "func": "function ParseTimer(thisTimer) {\n    // build list of active timer ids.\n    id = thisTimer.id; \n    new_timers += paramS + id;\n    paramS = ':';\n    if (old_timers.indexOf(id) == -1) {\n        // New timer so set delay\n        delay = thisTimer.remainingTime;\n        payload = id;\n    }\n}\nlet old_timers = ''; // previous active timers from flow variable.\nlet new_timers = ''; // list of active timers\nlet delay = 0;\nlet payload = \"none\"; // default to none (no active timers)\nlet paramS = \"\";\nlet id = '';\nlet device = msg.data.entity_id;\n\n// Get known timers from device specific flow variable\nif(device == 'sensor.alexa_1_next_timer'){\n    old_timers = flow.get(\"alexa_active_timers_1\");\n} else if(device == 'sensor.alexa_2.next_timer'){\n    old_timers = flow.get(\"alexa_active_timers_2\");\n}\n// will be null first time flow runs\nif (old_timers === null) {\n    old_timers =\"\";\n\n}\n// build list of active timers and set message delay.\nlet attributes = msg.data.new_state.attributes;\nif (typeof attributes.sorted_active !== 'undefined'){\n    attributes.sorted_active.forEach(ParseTimer);\n}\n// store active timers in flow variables\n// these checked after delay to see if timer still active\nif (device == 'sensor.alexa_1_next_timer') {\n    flow.set(\"alexa_active_timers_1\", new_timers);\n} else if (device == 'sensor.alexa_2.next_timer') {\n    flow.set(\"alexa_active_timers_2\", new_timers);\n}\nmsg.payload = payload; // timer id\nmsg.delay = delay;     // delay or zero if no active/new timers\nmsg.device = device;   // device name\nmsg.id = id;           // for info if payload gets overwritten\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 60,
        "wires": [
            [
                "4bad8a4dfdd0f5f2"
            ]
        ]
    },
    {
        "id": "4bad8a4dfdd0f5f2",
        "type": "switch",
        "z": "421feed439e285b3",
        "name": "Remaining Time",
        "property": "delay",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "0",
                "vt": "num"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 560,
        "y": 60,
        "wires": [
            [
                "f824cf24c7541682"
            ],
            [
                "5af83be542b369ec"
            ]
        ]
    },
    {
        "id": "5af83be542b369ec",
        "type": "delay",
        "z": "421feed439e285b3",
        "name": "Delay",
        "pauseType": "delayv",
        "timeout": "5",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 330,
        "y": 140,
        "wires": [
            [
                "792b656ebadfd50f"
            ]
        ]
    },
    {
        "id": "792b656ebadfd50f",
        "type": "function",
        "z": "421feed439e285b3",
        "name": "Check Timer Still Active",
        "func": "let timers = ''; // current active timers\nlet alert = '';  // Message to be sent\nlet done = 'cancelled'; // Default to cancelled\n\n// Get appropriate timers from flow variables and set message\nif(msg.device == 'sensor.alexa_1_next_timer'){\n    timers = flow.get(\"alexa_active_timers_1\");\n    alert = 'Living Room Timer Done';\n} else if(msg.device == 'sensor.alexa_2_next_timer'){\n    timers = flow.get(\"alexa_active_timers_2\");\n    alert = 'Kitchen Timer Done';\n}\n// check if this timer is still active\nif (timers.indexOf(msg.id) > -1) {\n    done = 'Done'; // Timer completed\n\n    // remove timer id from active timers flow variable so don't get duplicate alerts.\n    let other_timers = timers.replace(msg.id, \"\");\n    if (msg.device == 'sensor.alexa_1_next_timer') {\n        flow.set(\"alexa_active_timers_1\", other_timers);\n    } else if (msg.device == 'sensor.alexa_2_next_timer') {\n        flow.set(\"alexa_active_timers_2\", other_timers);\n    }\n}\nmsg.done = done; // Completed flag\nmsg.payload = alert; // Message\nmsg.topic = 'Alexa\"'; // Message Topic\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 140,
        "wires": [
            [
                "7ed42c9c651acebe"
            ]
        ]
    },
    {
        "id": "7ed42c9c651acebe",
        "type": "switch",
        "z": "421feed439e285b3",
        "name": "Done",
        "property": "done",
        "propertyType": "msg",
        "rules": [
            {
                "t": "neq",
                "v": "Done",
                "vt": "str"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 730,
        "y": 140,
        "wires": [
            [
                "7a4feffa04569228"
            ],
            [
                "114db1131f57c14f"
            ]
        ]
    },
    {
        "id": "61c890bb2061fe93",
        "type": "server-state-changed",
        "z": "421feed439e285b3",
        "name": "Alexa Device 2 Timer",
        "server": "f49a8008.f7cb2",
        "version": 6,
        "outputs": 1,
        "exposeAsEntityConfig": "",
        "entities": {
            "entity": [
                "sensor.alexa_2_next_timer"
            ],
            "substring": [],
            "regex": []
        },
        "outputInitially": false,
        "stateType": "str",
        "ifState": "",
        "ifStateType": "str",
        "ifStateOperator": "is_not",
        "outputOnlyOnStateChange": false,
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "ignorePrevStateNull": false,
        "ignorePrevStateUnknown": false,
        "ignorePrevStateUnavailable": false,
        "ignoreCurrentStateUnknown": false,
        "ignoreCurrentStateUnavailable": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "string",
                "valueType": "entityState"
            },
            {
                "property": "data",
                "propertyType": "msg",
                "value": "",
                "valueType": "eventData"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "",
                "valueType": "triggerId"
            }
        ],
        "x": 120,
        "y": 100,
        "wires": [
            [
                "7d32e93be352c0a1"
            ]
        ]
    },
    {
        "id": "f824cf24c7541682",
        "type": "debug",
        "z": "421feed439e285b3",
        "name": "No New Active Timers. Nothing to do.",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 890,
        "y": 60,
        "wires": []
    },
    {
        "id": "7a4feffa04569228",
        "type": "debug",
        "z": "421feed439e285b3",
        "name": "Timer No Longer Active. Ignore.",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 970,
        "y": 140,
        "wires": []
    },
    {
        "id": "9704c07524f9f50a",
        "type": "function",
        "z": "421feed439e285b3",
        "name": "Format for Alexa Announce",
        "func": "var alexa = msg.alexa;\nif(alexa != null){\n    if (alexa != \"\") {\n        msg.payload = msg.alexa;\n    }\n}\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 720,
        "y": 260,
        "wires": [
            [
                "c4b15866195c778b"
            ]
        ]
    },
    {
        "id": "c4b15866195c778b",
        "type": "change",
        "z": "421feed439e285b3",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\t   \"data\": {\t       \"message\": msg.payload,\t       \"data\": {\t           \"type\": \"announce\",\t           \"method\": \"speak\"        \t       }    \t   }\t}",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 960,
        "y": 260,
        "wires": [
            [
                "ee665ca1b39659de"
            ]
        ]
    },
    {
        "id": "7354fde6e3ce990e",
        "type": "function",
        "z": "421feed439e285b3",
        "name": "Format iOS Message",
        "func": "if (typeof msg.json == 'undefined') {\n    if (typeof msg.topic == 'undefined') {\n        msg.topic = 'Home Assissant';\n    }\n    if (msg.topic == \"\") {\n        msg.topic = 'Home Assissant';\n    }\n    msg.payload = { data: { 'message': msg.payload, 'title': msg.topic } };\n} else {\n    msg.payload = msg.json;\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 380,
        "wires": [
            [
                "da10abe2d11f6090"
            ]
        ]
    },
    {
        "id": "da10abe2d11f6090",
        "type": "api-call-service",
        "z": "421feed439e285b3",
        "name": "Notify IOS Phone/iPad Via HA App",
        "server": "f49a8008.f7cb2",
        "version": 7,
        "debugenabled": false,
        "action": "notify.mobile_app_my_iphone17",
        "floorId": [],
        "areaId": [],
        "deviceId": [],
        "entityId": [],
        "labelId": [],
        "data": "",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "data"
            }
        ],
        "queue": "none",
        "blockInputOverrides": false,
        "domain": "notify",
        "service": "mobile_app_my_iphone17",
        "output_location": "payload",
        "output_location_type": "msg",
        "x": 1080,
        "y": 380,
        "wires": [
            []
        ]
    },
    {
        "id": "3198a1cc6252c238",
        "type": "function",
        "z": "421feed439e285b3",
        "name": "Format Android Data Message",
        "func": "if (typeof msg.json == 'undefined') {\n    if (typeof msg.topic == 'undefined') {\n        msg.topic = 'Home Assissant';\n    }\n    if (msg.topic == \"\") {\n        msg.topic = 'Home Assissant';\n    }\n    msg.payload = { data: { data: { 'ttl': 0, 'priority': 'high' }, 'message': msg.payload, 'title': msg.topic } };\n} else {\n    msg.payload = msg.json;\n}\nreturn msg;\n/*\ndata:\n  data:\n    ttl: 0\n    priority: high\n    push:\n      sound:\n        name: default\n        critical: 1\n        volume: 1\n  message: Blah Blah\n  title: Blah Blah\n*/",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 730,
        "y": 320,
        "wires": [
            [
                "def06971a8b354eb"
            ]
        ]
    },
    {
        "id": "def06971a8b354eb",
        "type": "api-call-service",
        "z": "421feed439e285b3",
        "name": "Notify Android Phone Via HA App",
        "server": "f49a8008.f7cb2",
        "version": 7,
        "debugenabled": false,
        "action": "notify.mobile_app_androidphone",
        "floorId": [],
        "areaId": [],
        "deviceId": [],
        "entityId": [],
        "labelId": [],
        "data": "",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "data"
            }
        ],
        "queue": "none",
        "blockInputOverrides": false,
        "domain": "notify",
        "service": "mobile_app_sm_g960f",
        "output_location": "payload",
        "output_location_type": "msg",
        "x": 1080,
        "y": 320,
        "wires": [
            []
        ]
    },
    {
        "id": "ee665ca1b39659de",
        "type": "api-call-service",
        "z": "421feed439e285b3",
        "name": "",
        "server": "f49a8008.f7cb2",
        "version": 7,
        "debugenabled": false,
        "action": "notify.alexa_device",
        "floorId": [],
        "areaId": [],
        "deviceId": [],
        "entityId": [],
        "labelId": [],
        "data": "",
        "dataType": "jsonata",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "blockInputOverrides": false,
        "domain": "notify",
        "service": "alexa_media_device",
        "x": 1210,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "114db1131f57c14f",
        "type": "debug",
        "z": "421feed439e285b3",
        "name": "Timer Finished. Choose how you want to be Notified.",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 300,
        "y": 260,
        "wires": []
    }
]

To use:

  1. Import json code above.
  2. Change the two state nodes to your specifc alexa devices. (delete a node if only one)
  3. Update sensor names in “Read Active Timers” & “Check Timer Still Active” Javascript. (sensor.alexa_1_next_timer and sensor.alexa_2_next_timer appear in both scripts twice.
  4. Setup how you want to be notified. Currently just to debug window, but Alexa anounce, iphone/andoid alerts can be used and examples provided.
  5. If you want to monitor more than 2 alexa devices, then the functions can be expanded. If only one device, no need to update alexa_2_next_timer in the code.

Test and enjoy.