Node Red and Alexa without Applestrudle - Use Action Node

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

  1. Save existing volumes and sets the new volume
  2. Prepare the message
  3. 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"
        }
    }
]