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:
- Import json code above.
- Change the two state nodes to your specifc alexa devices. (delete a node if only one)
- 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.
- Setup how you want to be notified. Currently just to debug window, but Alexa anounce, iphone/andoid alerts can be used and examples provided.
- 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.