5 minute read

Have you ever wanted to send Text-to-Speech (TTS) notifications with Home Assistant and Node-RED? In the sections below I’m going to be using Amazon Alexa Media Player Integration to send TTS notifications. Please note you can easily switch out the TTS integration to the one of your choosing and still use this Subflow (with a few tweaks).

This is one of my favorite Subflows as it can be used easily in a lot of automations. I use it in my automations to announce:

  • Friendly state change notifications for accessibility (e.g., motion detected in room abc, lights turning on in the current room, the garage door was unlocked).
  • General announcements (e.g., welcome to our home, it’s bedtime).
  • Weather related announcements and warnings.

If you are new to Node-RED, I’d recommend checking out this awesome YouTube Node-RED playlist to get up to speed!

Alexa Media Player Custom Component

If you haven’t done so already, install the Alexa Media Player Custom Component if you are using Alexa, otherwise skip this section.

This addon is amazing as it normalizes all of your Alexa enabled devices such as the Amazon Echo or ecobee Switch+ into a media_device that can be used by Home Assistant. Once configured you can simply verify the device you want to send a message is working by calling the notify.alexa_media service with the payload:

message: "Text-to-Speech is easy"
data:
  type: "tts"
  target: ["media_player.amazon_echo_plus"]

If you don’t hear anything after calling this service, stop and check the logs. This will help diagnose why you are not getting TTS notifications before continuing.

Input Boolean to control automations

I highly recommend adding an Input Boolean that controls if audible notifications occur. It could get pretty annoying if you are constantly hearing notifications for automations that are running unless it’s desired. Here is my input_boolean that I use for this automation. If you choose a different name, be sure to update the code below.

automation_notifications:
  name: Notify when an automation is triggered
  icon: mdi:home-automation

Send Automation Speech Notification Subflow

Lets open Node-RED and create a new Subflow with a Status Node, Input Node and Output Node. I really like using the Status Node to capture all statuses as it allows for a better experience using Subflows by showing you a visual indication of the current status. Also, I like specifying an Output Node as it allows a node to be part of a series when the Subflow has completed. It’s worth noting that this Output Node will only be called if the notification is sent.

graph TD InputNode(Input) --> AFunctionNode(fas:fa-code Check For Overrides) AFunctionNode --> BCurrentStateNode(fas:fa-database Speech Notifications?) AFunctionNode --> |Announcement Override| CFunctionNode(fas:fa-code Set Speech Payload) BCurrentStateNode --> CFunctionNode CFunctionNode --> DCallServiceNode(fas:fa-play Send Speech Notification) DCallServiceNode --> OutputNode(Output 1) StatusNode(fas:fa-heartbeat Status: All) --> StatusOutputNode(Status) style StatusNode fill:#ECF5FF style StatusOutputNode fill:#FAFAFB style InputNode fill:#FAFAFB style AFunctionNode fill:#FBB68F style BCurrentStateNode fill:#66ACFD style CFunctionNode fill:#FBB68F style DCallServiceNode fill:#66ACFD style OutputNode fill:#FAFAFB linkStyle default stroke-width:2px,fill:none,stroke:#CCD0D4

Let’s break down the Subflow. The incoming message gets passed to the Check For Overrides node. This node simply checks to see if the msg.payload.announcement is set to true. If true, then it will always send the TTS notification. Otherwise it will call the Speech Notifications? Current State Node to see if the input_boolean.automation_notifications is true. To recap, we will only call Set Speech Payload if it an announcement is true or our input_boolean.automation_notifications is true.

The Set Speech Payload node will return a new payload that the Send Speech Notification Service Call Node is expecting. If you are not using the alexa_media_player Home Assistant addon, you’ll want to update both of these nodes for the correct payload and service call.

You can then trigger this Subflow by passing a message object with the following payload. I recommend using a Inject Node to test this out.

{
    "announcement": true,
    "entity_id": "media_player.amazon_echo_plus",
    "message": "test"
}

When triggering this Subflow it’s a good idea to pick the closest speaker to where the automation is occurring.

Here is the exported Node-RED subflow.

[
    {
        "id": "7a62a2d3.c0f4a4",
        "type": "subflow",
        "name": "Send Automation Speech Notification",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 60,
                "y": 100,
                "wires": [
                    {
                        "id": "87411254.a6ed18"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 1120,
                "y": 100,
                "wires": [
                    {
                        "id": "d44cc51a.be0668",
                        "port": 0
                    }
                ]
            }
        ],
        "status": {
            "x": 220,
            "y": 40,
            "wires": [
                {
                    "id": "aef53056.742438",
                    "port": 0
                }
            ]
        }
    },
    {
        "id": "89300b1.595bcf8",
        "type": "function",
        "z": "7a62a2d3.c0f4a4",
        "name": "Set Speech Payload",
        "func": "const entity = flow.get(\"$parent.speech_entity_id\") || (msg.payload && msg.payload.entity_id) || \"media_player.amazon_echo_plus\";\nconst message = (msg.payload && msg.payload.message) || \"Automation provided no message\";\n\nnode.status({ fill: \"green\", shape: \"dot\", text: \"TTS message:\" + message });\nreturn {\n    payload:{\n        data: {\n            message: message,\n            data: { type: \"tts\" },\n            target: [entity]\n        }\n    }\n};",
        "outputs": 1,
        "noerr": 0,
        "x": 700,
        "y": 100,
        "wires": [
            [
                "d44cc51a.be0668"
            ]
        ]
    },
    {
        "id": "d44cc51a.be0668",
        "type": "api-call-service",
        "z": "7a62a2d3.c0f4a4",
        "name": "Send Speech Notification",
        "server": "61956bd4.93df44",
        "version": 1,
        "debugenabled": false,
        "service_domain": "notify",
        "service": "alexa_media",
        "entityId": "",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "payload",
        "output_location_type": "msg",
        "mustacheAltTags": false,
        "x": 950,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "6add9bc6.4c3624",
        "type": "api-current-state",
        "z": "7a62a2d3.c0f4a4",
        "name": "Speech Notifications?",
        "server": "61956bd4.93df44",
        "version": 1,
        "outputs": 2,
        "halt_if": "true",
        "halt_if_type": "bool",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "input_boolean.automation_notifications",
        "state_type": "habool",
        "state_location": "",
        "override_payload": "none",
        "entity_location": "",
        "override_data": "none",
        "blockInputOverrides": false,
        "x": 440,
        "y": 140,
        "wires": [
            [
                "89300b1.595bcf8"
            ],
            []
        ],
        "outputLabels": [
            "",
            "enabled"
        ]
    },
    {
        "id": "aef53056.742438",
        "type": "status",
        "z": "7a62a2d3.c0f4a4",
        "name": "",
        "scope": null,
        "x": 100,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "87411254.a6ed18",
        "type": "function",
        "z": "7a62a2d3.c0f4a4",
        "name": "Check for overrides",
        "func": "const alwaysSpeak = msg.payload && msg.payload.announcement\nif (alwaysSpeak) {\n    return [msg, null];\n} else {\n    return [null, msg];\n}",
        "outputs": 2,
        "noerr": 0,
        "x": 210,
        "y": 100,
        "wires": [
            [
                "89300b1.595bcf8"
            ],
            [
                "6add9bc6.4c3624"
            ]
        ],
        "outputLabels": [
            "Bypass notification",
            "Check for notifications"
        ]
    },
    {
        "id": "61956bd4.93df44",
        "type": "server",
        "z": "",
        "name": "Home Assistant",
        "legacy": false,
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "y|yes|true|on|home|open",
        "connectionDelay": true
    }
]

Conclusion

I hope this article inspired you to create your own reusable Subflows that you can use in your various automations. I’ve found this Subflow to be super useful, let me know how it works out for you!

Join the mailing list

Get notified of new posts and related content to your inbox. I will never sell you anything and I will NEVER sell your email address.