I had quite a few issues (ad hoc but it became too much and I also like to tinker) with Applestrudle. One time password with Amazon etc so I looked for another way to announce to use my speakers.
If you want sounds you can get them from here Alexa Skills Kit Sound Library | Alexa Skills Kit
I wanted to be able to announce on my speakers and then set the volume back to what it was before. To achieve this I created a subflow where I pass in the message and volume
eg I used a get entities node to get my speakers (I used a group so it’s easy to control) then a function to store the speakers and message to be announced
Pass that into the subflow where it performs 3 things
- Save existing volumes and sets the new volume
- Prepare the message
- Restore the volume
Here’s the subflow
[
{
"id": "8dae10acf570a6e9",
"type": "subflow",
"name": "Alexa Message and Volume",
"info": "",
"category": "",
"in": [
{
"x": 60,
"y": 100,
"wires": [
{
"id": "b76bed012ffb9b7a"
}
]
}
],
"out": [
{
"x": 1200,
"y": 260,
"wires": [
{
"id": "c9a8805c09ef69d4",
"port": 0
}
]
}
],
"env": [],
"meta": {},
"color": "#DDAA99"
},
{
"id": "b76bed012ffb9b7a",
"type": "function",
"z": "8dae10acf570a6e9",
"name": "Save old volumes & set temp volume",
"func": "// msg.payload = array of devices from Get Entities\n\nconst devices = msg.payload;\n\nlet oldVolumes = {};\nlet tempVolumes = [];\n\n// Save original devices for later (THIS IS KEY)\nflow.set('devices', devices);\n\ndevices.forEach(dev => {\n const id = dev.entity_id;\n const vol = dev.attributes.volume_level ?? 0.3;\n\n oldVolumes[id] = vol;\n\n tempVolumes.push({\n entity_id: id,\n volume_level: msg.tempVolume\n });\n});\n\nglobal.set('oldVolumes', oldVolumes);\n\n// send temp volume array (will be split)\nmsg.payload = tempVolumes;\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 270,
"y": 100,
"wires": [
[
"41166ca5f4453ba9"
]
]
},
{
"id": "41166ca5f4453ba9",
"type": "split",
"z": "8dae10acf570a6e9",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 530,
"y": 100,
"wires": [
[
"19670a8ec1fb0d42"
]
]
},
{
"id": "19670a8ec1fb0d42",
"type": "api-call-service",
"z": "8dae10acf570a6e9",
"name": "Set temporary volume",
"server": "d3151d26.c0d4f",
"version": 7,
"debugenabled": false,
"action": "media_player.volume_set",
"data": "msg.payload",
"dataType": "jsonata",
"mergeContext": "",
"outputProperties": [],
"domain": "media_player",
"service": "volume_set",
"x": 740,
"y": 100,
"wires": [
[
"f40747141a7a9273"
]
]
},
{
"id": "f40747141a7a9273",
"type": "function",
"z": "8dae10acf570a6e9",
"name": "Prepare TTS message",
"func": "// Retrieve original devices (before split)\nconst devices = flow.get('devices') || [];\n\n// Extract entity_ids\nconst deviceIds = devices.map(dev => dev.entity_id);\n\nconst sound = \"<audio src='soundbank://soundlibrary/alarms/air_horns/air_horn_02'/>\";\n\nmsg.payload = {\n target: deviceIds,\n message: msg.message,\n \n data: { type: 'tts' }\n};\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1000,
"y": 100,
"wires": [
[
"5db389bfcd452b9a"
]
]
},
{
"id": "5db389bfcd452b9a",
"type": "api-call-service",
"z": "8dae10acf570a6e9",
"name": "Send TTS",
"server": "d3151d26.c0d4f",
"version": 7,
"debugenabled": false,
"action": "notify.alexa_media",
"data": "msg.payload",
"dataType": "jsonata",
"mergeContext": "",
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "notify",
"service": "alexa_media",
"x": 1240,
"y": 100,
"wires": [
[
"45ddec71a42596f3"
]
]
},
{
"id": "45ddec71a42596f3",
"type": "trigger",
"z": "8dae10acf570a6e9",
"name": "Wait 1s after last change",
"op1": "",
"op2": "",
"op1type": "nul",
"op2type": "payl",
"duration": "10",
"extend": true,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 210,
"y": 260,
"wires": [
[
"50b9689a13ee9f5f"
]
]
},
{
"id": "50b9689a13ee9f5f",
"type": "function",
"z": "8dae10acf570a6e9",
"name": "Prepare restore volume",
"func": "const oldVolumes = global.get('oldVolumes') || {};\nconst devices = Object.keys(oldVolumes);\n\nmsg.payload = devices.map(device => ({\n entity_id: device,\n volume_level: parseFloat(oldVolumes[device])\n}));\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 550,
"y": 260,
"wires": [
[
"e1e20bcf1d82b5e5"
]
]
},
{
"id": "e1e20bcf1d82b5e5",
"type": "split",
"z": "8dae10acf570a6e9",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 790,
"y": 260,
"wires": [
[
"c9a8805c09ef69d4"
]
]
},
{
"id": "c9a8805c09ef69d4",
"type": "api-call-service",
"z": "8dae10acf570a6e9",
"name": "Restore volume",
"server": "d3151d26.c0d4f",
"version": 7,
"debugenabled": false,
"action": "media_player.volume_set",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [],
"labelId": [],
"data": "msg.payload",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": false,
"domain": "media_player",
"service": "volume_set",
"x": 1000,
"y": 260,
"wires": [
[]
]
},
{
"id": "d3151d26.c0d4f",
"type": "server",
"name": "Home Assistant Tyrrell",
"addon": true,
"rejectUnauthorizedCerts": true,
"ha_boolean": "",
"connectionDelay": false,
"cacheJson": false,
"heartbeat": false,
"heartbeatInterval": "",
"areaSelector": "friendlyName",
"deviceSelector": "friendlyName",
"entitySelector": "friendlyName",
"statusSeparator": "/",
"statusYear": "numeric",
"statusMonth": "numeric",
"statusDay": "numeric",
"statusHourCycle": "h23",
"statusTimeFormat": "h:m",
"enableGlobalContextStore": true
},
{
"id": "202537fd146685e1",
"type": "global-config",
"env": [],
"modules": {
"node-red-contrib-home-assistant-websocket": "0.80.3"
}
}
]
And the flow to pass the data into the subflow
[
{
"id": "8dae10acf570a6e9",
"type": "subflow",
"name": "Alexa Message and Volume",
"info": "",
"category": "",
"in": [
{
"x": 60,
"y": 100,
"wires": [
{
"id": "b76bed012ffb9b7a"
}
]
}
],
"out": [
{
"x": 1200,
"y": 260,
"wires": [
{
"id": "c9a8805c09ef69d4",
"port": 0
}
]
}
],
"env": [],
"meta": {},
"color": "#DDAA99"
},
{
"id": "b76bed012ffb9b7a",
"type": "function",
"z": "8dae10acf570a6e9",
"name": "Save old volumes & set temp volume",
"func": "// msg.payload = array of devices from Get Entities\n\nconst devices = msg.payload;\n\nlet oldVolumes = {};\nlet tempVolumes = [];\n\n// Save original devices for later (THIS IS KEY)\nflow.set('devices', devices);\n\ndevices.forEach(dev => {\n const id = dev.entity_id;\n const vol = dev.attributes.volume_level ?? 0.3;\n\n oldVolumes[id] = vol;\n\n tempVolumes.push({\n entity_id: id,\n volume_level: msg.tempVolume\n });\n});\n\nglobal.set('oldVolumes', oldVolumes);\n\n// send temp volume array (will be split)\nmsg.payload = tempVolumes;\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 270,
"y": 100,
"wires": [
[
"41166ca5f4453ba9"
]
]
},
{
"id": "41166ca5f4453ba9",
"type": "split",
"z": "8dae10acf570a6e9",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 530,
"y": 100,
"wires": [
[
"19670a8ec1fb0d42"
]
]
},
{
"id": "19670a8ec1fb0d42",
"type": "api-call-service",
"z": "8dae10acf570a6e9",
"name": "Set temporary volume",
"server": "d3151d26.c0d4f",
"version": 7,
"debugenabled": false,
"action": "media_player.volume_set",
"data": "msg.payload",
"dataType": "jsonata",
"mergeContext": "",
"outputProperties": [],
"domain": "media_player",
"service": "volume_set",
"x": 740,
"y": 100,
"wires": [
[
"f40747141a7a9273"
]
]
},
{
"id": "f40747141a7a9273",
"type": "function",
"z": "8dae10acf570a6e9",
"name": "Prepare TTS message",
"func": "// Retrieve original devices (before split)\nconst devices = flow.get('devices') || [];\n\n// Extract entity_ids\nconst deviceIds = devices.map(dev => dev.entity_id);\n\nconst sound = \"<audio src='soundbank://soundlibrary/alarms/air_horns/air_horn_02'/>\";\n\nmsg.payload = {\n target: deviceIds,\n message: msg.message,\n \n data: { type: 'tts' }\n};\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1000,
"y": 100,
"wires": [
[
"5db389bfcd452b9a"
]
]
},
{
"id": "5db389bfcd452b9a",
"type": "api-call-service",
"z": "8dae10acf570a6e9",
"name": "Send TTS",
"server": "d3151d26.c0d4f",
"version": 7,
"debugenabled": false,
"action": "notify.alexa_media",
"data": "msg.payload",
"dataType": "jsonata",
"mergeContext": "",
"outputProperties": [],
"queue": "none",
"blockInputOverrides": true,
"domain": "notify",
"service": "alexa_media",
"x": 1240,
"y": 100,
"wires": [
[
"45ddec71a42596f3"
]
]
},
{
"id": "45ddec71a42596f3",
"type": "trigger",
"z": "8dae10acf570a6e9",
"name": "Wait 1s after last change",
"op1": "",
"op2": "",
"op1type": "nul",
"op2type": "payl",
"duration": "10",
"extend": true,
"overrideDelay": false,
"units": "s",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 210,
"y": 260,
"wires": [
[
"50b9689a13ee9f5f"
]
]
},
{
"id": "50b9689a13ee9f5f",
"type": "function",
"z": "8dae10acf570a6e9",
"name": "Prepare restore volume",
"func": "const oldVolumes = global.get('oldVolumes') || {};\nconst devices = Object.keys(oldVolumes);\n\nmsg.payload = devices.map(device => ({\n entity_id: device,\n volume_level: parseFloat(oldVolumes[device])\n}));\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 550,
"y": 260,
"wires": [
[
"e1e20bcf1d82b5e5"
]
]
},
{
"id": "e1e20bcf1d82b5e5",
"type": "split",
"z": "8dae10acf570a6e9",
"name": "",
"splt": "\\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"property": "payload",
"x": 790,
"y": 260,
"wires": [
[
"c9a8805c09ef69d4"
]
]
},
{
"id": "c9a8805c09ef69d4",
"type": "api-call-service",
"z": "8dae10acf570a6e9",
"name": "Restore volume",
"server": "d3151d26.c0d4f",
"version": 7,
"debugenabled": false,
"action": "media_player.volume_set",
"floorId": [],
"areaId": [],
"deviceId": [],
"entityId": [],
"labelId": [],
"data": "msg.payload",
"dataType": "jsonata",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"blockInputOverrides": false,
"domain": "media_player",
"service": "volume_set",
"x": 1000,
"y": 260,
"wires": [
[]
]
},
{
"id": "e6dace0ed6c0d64a",
"type": "tab",
"label": "Flow 8",
"disabled": false,
"info": "",
"env": []
},
{
"id": "f880fb7bdf5f5f08",
"type": "ha-get-entities",
"z": "e6dace0ed6c0d64a",
"name": "Get Speakers ANNOUNCEMENTS",
"server": "d3151d26.c0d4f",
"version": 1,
"rules": [
{
"condition": "state_object",
"property": "entity_id",
"logic": "in_group",
"value": "media_player.announcements",
"valueType": "str"
}
],
"outputType": "array",
"outputEmptyResults": false,
"outputLocationType": "msg",
"outputLocation": "payload",
"outputResultsCount": 1,
"x": 400,
"y": 200,
"wires": [
[
"0ecb464075673632"
]
]
},
{
"id": "0ecb464075673632",
"type": "function",
"z": "e6dace0ed6c0d64a",
"name": "tts message and volume",
"func": "msg.devices = msg.payload;\n\nconst sound = \"<audio src='soundbank://soundlibrary/alarms/air_horns/air_horn_02'/>\";\nmsg.message = `<speak>${sound}This is your smart house. Reminding you it's bed time</speak>`;\n\nmsg.tempVolume = 0.5;\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 730,
"y": 200,
"wires": [
[
"3b3cdb769e977bb6"
]
]
},
{
"id": "3b3cdb769e977bb6",
"type": "subflow:8dae10acf570a6e9",
"z": "e6dace0ed6c0d64a",
"name": "",
"x": 1020,
"y": 200,
"wires": [
[]
]
},
{
"id": "985ce06f254ee878",
"type": "inject",
"z": "e6dace0ed6c0d64a",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 140,
"y": 200,
"wires": [
[
"f880fb7bdf5f5f08"
]
]
},
{
"id": "d3151d26.c0d4f",
"type": "server",
"name": "Home Assistant Tyrrell",
"addon": true,
"rejectUnauthorizedCerts": true,
"ha_boolean": "",
"connectionDelay": false,
"cacheJson": false,
"heartbeat": false,
"heartbeatInterval": "",
"areaSelector": "friendlyName",
"deviceSelector": "friendlyName",
"entitySelector": "friendlyName",
"statusSeparator": "/",
"statusYear": "numeric",
"statusMonth": "numeric",
"statusDay": "numeric",
"statusHourCycle": "h23",
"statusTimeFormat": "h:m",
"enableGlobalContextStore": true
},
{
"id": "6fc4e2915d80da8e",
"type": "global-config",
"env": [],
"modules": {
"node-red-contrib-home-assistant-websocket": "0.80.3"
}
}
]

