RGB Notifications for the Home

I’ve always found the RGB notifications on my phone to be very helpful in getting feedback on notifications on my phone. I decided I wanted a similar system for my house. I already get text messages when important events happen, but a notification light is a useful secondary system as I often will get the text, but still not address the problem I was being alerted of.

For this post, I’m utilizing the following:

Part 1: Enabling the color notifications

Since all the automation is happening in Node-RED, there are only a couple things that need to be updated in Home Assistant. You will need to create input boolean components for each alert you want to track. I have my Home Assistant config broken up, but here is an example of the input_boolean’s I’m using:

input_boolean.yaml:

alert_living_room_plant:
  name: Living Room Plant
  icon: mdi:alert-decagram
alert_master_bedroom_plant:
  name: Master Bedroom Plant
  icon: mdi:alert-decagram
alert_bathroom_plant:
  name: Bathroom Plant
  icon: mdi:alert-decagram
alert_aloe_plant:
  name: Aloe Plant
  icon: mdi:alert-decagram
alert_washer:
  name: Washer
  icon: mdi:alert-decagram
alert_front_door:
  name: Front Door
  icon: mdi:alert-decagram
alert_back_door:
  name: Back Door
  icon: mdi:alert-decagram
alert_pasta_part:
  name: Pasta Part
  icon: mdi:alert-decagram
alert_roomba_full:
  name: Roomba Full
  icon: mdi:alert-decagram

The second thing that needs to be configured in Home Assistant is some customization of the input_boolean.

You will want to be familiar with how to set up the customization, but once you are, you just need to add a couple lines for each input_boolean. I’ve named them all starting with “alert” as it is then possible to filter on those specific input_boolean’s later in Node-RED. The color attribute is the only important one if you only care to have the color notifications. The descrip attribute is being used for the voice notifications.

customizations/alerts.yaml:

input_boolean.alert_living_room_plant:
  color: 0,128,0 #green
  descrip: "the living room plant needs water"
input_boolean.alert_master_bedroom_plant:
  color: 0,128,0 #green
  descrip: "the master bedroom plant needs water"
input_boolean.alert_bathroom_plant:
  color: 0,128,0 #green
  descrip: "the bathroom plant needs water"
input_boolean.alert_aloe_plant:
  color: 0,128,0 #green
  descrip: "the aloe plant needs water"
input_boolean.alert_washer:
  color: 0,0,255 #blue
  descrip: "the clothes in the washer need to be moved to the dryer"
input_boolean.alert_front_door:
  color: 128,0,0 #maroon
  descrip: "the front door was left open"
input_boolean.alert_back_door:
  color: 128,0,0 #maroon
  descrip: "the back door was left open"
input_boolean.alert_pasta_part:
  color: 255,255,0 #yellow
  descrip: "there is a pasta part that needs to be removed from the freezer"
input_boolean.alert_roomba_full:
  color: 128,128,128 #grey
  descrip: "the Roomba's bin needs to be emptied"

Once those two parts are setup, everything else is done in Node-RED.

There are two triggers to start the notification flow. First, I just have it triggered from an inject node to run every 20 seconds. This is done because if there are multiple active notifications, it will allow the light to rotate between the various colors.

Secondly, I have the flow trigger any time the status of an input_boolean starting with the name “alert” is changed. This will allow for the light to instantly trigger on or off when an alert is triggered or cleared.

Active Alerts uses the newer ha-get-entities node. This does require using 0.5.0 or newer of node-red-contrib-home-assistant-websocket.

Using this node type, it’s easy to grab an array of all the alert input booleans as they all start with alert_ and I can filter just on the ones that are currently set to an on state.

It’s important to have the “Send Empty Results” option checked, otherwise, it’s not possible to determine if there are no results and won’t you be able to turn the alert light off. If that’s not checked, when there are no results, the flow will stop.

However, it check if there are results or not, I’m using a javascript function node to determine that. The function just checks if the array is empty, then returns on or off. Then a switch node can be used to decide if the light should be turned off or not.

msg.alert_status = msg.payload.length === 0 ? 'off' : 'on';

return msg;

Then there is selecting the color to display. I just have it pick a random color from one of the currently active alerts. This does mean that sometimes colors stay active for a while, but it seems a lot easier than trying to track and display in any sort of order.

For selecting the random color, I’m using another function node.

const random = Math.floor(Math.random() * msg.payload.length);

const random_alert = msg.payload[random];

const color = random_alert.attributes.color.split(',')

node.status({text: 'Color set to ' + color});

const newMsg = { 
    payload: {
        data: {
            rgb_color: color
        }
    }
    
};

return newMsg;

This function is grabbing a random alert from the array that was created in the ha-get-entities node, it’s then pulling the color that was set from the customize config. This is then being passed along to a service node where you can set the light you want to have turned on. The rgb_color info is passed into that node so the correct color will be set when the light is enabled.

Here is the full raw node data if you’d like to import this into your Node-RED environment to play around with it.

[
    {
        "id": "a662942c.30b828",
        "type": "switch",
        "z": "97ad0127.f1ef9",
        "name": "on / off",
        "property": "alert_status",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "on",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "off",
                "vt": "str"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 2,
        "x": 640,
        "y": 120,
        "wires": [
            [
                "e8e82dec.dcc8d"
            ],
            [
                "624b249.a4b53dc"
            ]
        ]
    },
    {
        "id": "624b249.a4b53dc",
        "type": "api-call-service",
        "z": "97ad0127.f1ef9",
        "name": "Turn off Alert Light",
        "server": "53d23819.50f298",
        "service_domain": "light",
        "service": "turn_off",
        "data": "{ \"entity_id\": \"light.alert_light\" }",
        "mergecontext": "",
        "x": 810,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "3aa3af9c.f8665",
        "type": "api-call-service",
        "z": "97ad0127.f1ef9",
        "name": "Set Alert Light Color",
        "server": "53d23819.50f298",
        "service_domain": "light",
        "service": "turn_on",
        "data": "{ \"entity_id\": \"light.alert_light\"}",
        "mergecontext": "",
        "x": 820,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "14feb4c.c430a4b",
        "type": "server-state-changed",
        "z": "97ad0127.f1ef9",
        "name": "Alerts",
        "server": "53d23819.50f298",
        "entityidfilter": "input_boolean.alert",
        "entityidfiltertype": "substring",
        "outputinitially": false,
        "haltifstate": "",
        "outputs": 1,
        "x": 170,
        "y": 140,
        "wires": [
            [
                "c02d9fab.c9c0d"
            ]
        ]
    },
    {
        "id": "3c30fd5d.6fcf92",
        "type": "inject",
        "z": "97ad0127.f1ef9",
        "name": "Every 20 seconds",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "20",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "x": 130,
        "y": 100,
        "wires": [
            [
                "c02d9fab.c9c0d"
            ]
        ]
    },
    {
        "id": "738a962d.0f99b8",
        "type": "comment",
        "z": "97ad0127.f1ef9",
        "name": "Alert Light",
        "info": "",
        "x": 120,
        "y": 40,
        "wires": []
    },
    {
        "id": "c02d9fab.c9c0d",
        "type": "ha-get-entities",
        "z": "97ad0127.f1ef9",
        "server": "53d23819.50f298",
        "name": "Active Alerts",
        "rules": [
            {
                "property": "entity_id",
                "logic": "starts_with",
                "value": "input_boolean.alert_",
                "valueType": "str"
            },
            {
                "property": "state",
                "logic": "is",
                "value": "on",
                "valueType": "str"
            }
        ],
        "output_type": "array",
        "output_empty_results": true,
        "output_location_type": "msg",
        "output_location": "payload",
        "output_results_count": 1,
        "x": 350,
        "y": 120,
        "wires": [
            [
                "64c7c76e.c41b18"
            ]
        ]
    },
    {
        "id": "64c7c76e.c41b18",
        "type": "function",
        "z": "97ad0127.f1ef9",
        "name": "on / off?",
        "func": "msg.alert_status = msg.payload.length === 0 ? 'off' : 'on';\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 500,
        "y": 120,
        "wires": [
            [
                "a662942c.30b828"
            ]
        ]
    },
    {
        "id": "e8e82dec.dcc8d",
        "type": "function",
        "z": "97ad0127.f1ef9",
        "name": "Select Random Alert Color",
        "func": "const random = Math.floor(Math.random() * msg.payload.length);\n\nconst random_alert = msg.payload[random];\n\nconst color = random_alert.attributes.color.split(',')\n\nnode.status({text: 'Color set to ' + color});\n\nconst newMsg = { \n    payload: {\n        data: {\n            rgb_color: color\n        }\n    }\n    \n};\n\nreturn newMsg;",
        "outputs": 1,
        "noerr": 0,
        "x": 560,
        "y": 40,
        "wires": [
            [
                "3aa3af9c.f8665"
            ]
        ]
    },
    {
        "id": "53d23819.50f298",
        "type": "server",
        "z": "",
        "name": "Home Assistant",
        "legacy": false,
        "hassio": false,
        "rejectUnauthorizedCerts": true
    }
]

Part 2: Voice assistance for the notifications

It’s not always easy to tell what a notification light is on for. There are only so many colors. I’ve enabled two paths to get information through my Google Home.

Returning home

First, I have it read off the alerts if there are any active when someone returns home. I’m using some presence detection based somewhat on what was done by Phil Hawthorne so that I have statuses like “Just Returned”. Whenever the status is “Just Returned” and the motion sensor is triggered by the kitchen, the alerts will be read off by Google Home. This allows some time for people to get inside and not be instantly bombarded by information.

The top part of the flow pictured is what handles this. The initial part of the flow is just triggering when there is motion, retrieving the current presence state from Home Assistant, and then using a switch that only continues if the state is “Just Returned”. Then, there is getting the current alert status. For this, I’m using the javascript to pull the data out of the global homeassistant flow object. The getPrettyDate part of the code is optional and just has the function node display a date of when the node was triggered, much like the Home Assistant nodes do.

It should be noted, this could probably be updated to use the ha-get-entities node mentioned in the first step, but some of these flows were written before that existed and I haven’t updated them yet.

const hass = global.get('homeassistant').homeAssistant;

let alerts = [];

for (let i in hass.states) {
    if (i.substr(0, 19) === 'input_boolean.alert') {
        if (hass.states[i].state === 'on') {
            alerts.push(i);
        }
    }
}

const status = alerts.length > 0 ? 'on' : 'off';

node.status({fill: 'green', text: status + ' at: ' + getPrettyDate()});

return {'payload': status};

function getPrettyDate() {
    return new Date().toLocaleDateString('en-US', {
        month: 'short',
        day: 'numeric',
        hour12: false,
        hour: 'numeric',
        minute: 'numeric'
    });
}

This is then passed into a Home Assistant template node, which uses Home Assistant’s built in templating to render an output. This will find any active alerts and then use the descrip value placed into the customize config as part of the playback text.

{% for alert in states.input_boolean if alert.attributes.color and alert.state == 'on' %}{% if alert.attributes.color and alert.state == 'on' -%}
    {%- if loop.first %}Welcome home. You should be aware that currently, {% elif loop.last %}, and {% else %}, {% endif -%}
        {{ alert.attributes.descrip }}{%if loop.last%}.{% endif %}
    {%- endif %}
{%- endfor %}

This is finally passed to a node to play the text back on my downstairs Google Home. I’m using node-red-contrib-cast to send text to be read to the Google Home.

Directly requesting

I’ve made it so that you can also just ask Google Home for the notification status. This is what’s shown in the bottom half of the pictured flow.

To enable this, I’ve created an input_boolean in Home Assistant, input_boolean.playback_alerts. In Google Assistant, I’ve created some routines that are a shortcut to the phrase “turn on playback alerts”. When I use those routines, the boolean will be turned on, which will trigger the flow to start.

So, the first node fires whenever input_boolean.playback_alerts is set to on. The second node resets it back to off. I’m once again using a Home Assistant template node to render the text that will be read back.

{% for alert in states.input_boolean if alert.attributes.color and alert.state == 'on' %}{% if alert.attributes.color and alert.state == 'on' -%}
    {%- if loop.first %}Currently, {% elif loop.last %}, and {% else %}, {% endif -%}
        {{ alert.attributes.descrip }}{%if loop.last%}.{% endif %}
    {%- endif %}
{% else %}
    {{" There are no current alerts."}}
{%- endfor %}

That is then passed into Google Home and played back.

Here is the full raw node data for this flow if you’d like to import this into your Node-RED environment to play around with it.

[
    {
        "id": "6ef20074.76734",
        "type": "server-state-changed",
        "z": "97ad0127.f1ef9",
        "name": "Breakfast Bar Motion",
        "server": "53d23819.50f298",
        "entityidfilter": "binary_sensor.breakfast_bar_sensor",
        "entityidfiltertype": "substring",
        "haltifstate": "off",
        "outputs": 1,
        "x": 120,
        "y": 480,
        "wires": [
            [
                "55a5a13e.40677"
            ]
        ]
    },
    {
        "id": "55a5a13e.40677",
        "type": "api-current-state",
        "z": "97ad0127.f1ef9",
        "name": "Current Presense State",
        "server": "53d23819.50f298",
        "halt_if": "",
        "override_topic": true,
        "override_payload": true,
        "entity_id": "input_select.presence_status",
        "outputs": 1,
        "x": 350,
        "y": 480,
        "wires": [
            [
                "cef719dd.7810f8"
            ]
        ]
    },
    {
        "id": "cef719dd.7810f8",
        "type": "switch",
        "z": "97ad0127.f1ef9",
        "name": "Presense Just Returned",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "Just Returned",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 310,
        "y": 540,
        "wires": [
            [
                "c48f2fe2.5235a"
            ]
        ]
    },
    {
        "id": "454d4ac.3a7bcb4",
        "type": "api-render-template",
        "z": "97ad0127.f1ef9",
        "name": "Alert Template",
        "server": "53d23819.50f298",
        "template": "{% for alert in states.input_boolean if alert.attributes.color and alert.state == 'on' %}{% if alert.attributes.color and alert.state == 'on' -%}\n    {%- if loop.first %}Welcome home. You should be aware that currently, {% elif loop.last %}, and {% else %}, {% endif -%}\n        {{ alert.attributes.descrip }}{%if loop.last%}.{% endif %}\n    {%- endif %}\n{%- endfor %}",
        "x": 760,
        "y": 540,
        "wires": [
            [
                "3575f352.9e7f8c"
            ]
        ]
    },
    {
        "id": "2b7a83e.bba7f7c",
        "type": "comment",
        "z": "97ad0127.f1ef9",
        "name": "Play Alerts",
        "info": "",
        "x": 120,
        "y": 420,
        "wires": []
    },
    {
        "id": "5ca97814.793848",
        "type": "server-state-changed",
        "z": "97ad0127.f1ef9",
        "name": "Playback Alerts",
        "server": "53d23819.50f298",
        "entityidfilter": "input_boolean.playback_alerts",
        "entityidfiltertype": "substring",
        "outputinitially": false,
        "haltifstate": "off",
        "outputs": 1,
        "x": 100,
        "y": 680,
        "wires": [
            [
                "d3454e46.15795"
            ]
        ]
    },
    {
        "id": "1c2da6ec.cd0759",
        "type": "api-render-template",
        "z": "97ad0127.f1ef9",
        "name": "Alert Template",
        "server": "53d23819.50f298",
        "template": "{% for alert in states.input_boolean if alert.attributes.color and alert.state == 'on' %}{% if alert.attributes.color and alert.state == 'on' -%}\n    {%- if loop.first %}Currently, {% elif loop.last %}, and {% else %}, {% endif -%}\n        {{ alert.attributes.descrip }}{%if loop.last%}.{% endif %}\n    {%- endif %}\n{% else %}\n    {{\" There are no current alerts.\"}}\n{%- endfor %}",
        "x": 500,
        "y": 680,
        "wires": [
            [
                "f2b60039.47edf"
            ]
        ]
    },
    {
        "id": "d3454e46.15795",
        "type": "api-call-service",
        "z": "97ad0127.f1ef9",
        "name": "Turn off Playback Alert",
        "server": "53d23819.50f298",
        "service_domain": "input_boolean",
        "service": "turn_off",
        "data": "{\"entity_id\":\"input_boolean.playback_alerts\"}",
        "render_data": false,
        "mergecontext": "",
        "x": 300,
        "y": 680,
        "wires": [
            [
                "1c2da6ec.cd0759"
            ]
        ]
    },
    {
        "id": "f2b60039.47edf",
        "type": "delay",
        "z": "97ad0127.f1ef9",
        "name": "",
        "pauseType": "delay",
        "timeout": "4",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 660,
        "y": 680,
        "wires": [
            [
                "3575f352.9e7f8c"
            ]
        ]
    },
    {
        "id": "c48f2fe2.5235a",
        "type": "function",
        "z": "97ad0127.f1ef9",
        "name": "Current Alert Status",
        "func": "const hass = global.get('homeassistant').homeAssistant;\n\nlet alerts = [];\n\nfor (let i in hass.states) {\n    if (i.substr(0, 19) === 'input_boolean.alert') {\n        if (hass.states[i].state === 'on') {\n            alerts.push(i);\n        }\n    }\n}\n\nconst status = alerts.length > 0 ? 'on' : 'off';\n\nnode.status({fill: 'green', text: status + ' at: ' + getPrettyDate()});\n\nreturn {'payload': status};\n\nfunction getPrettyDate() {\n    return new Date().toLocaleDateString('en-US', {\n        month: 'short',\n        day: 'numeric',\n        hour12: false,\n        hour: 'numeric',\n        minute: 'numeric'\n    });\n}",
        "outputs": 1,
        "noerr": 0,
        "x": 550,
        "y": 540,
        "wires": [
            [
                "454d4ac.3a7bcb4"
            ]
        ]
    },
    {
        "id": "3575f352.9e7f8c",
        "type": "cast-to-client",
        "z": "97ad0127.f1ef9",
        "name": "Downstairs",
        "url": "",
        "contentType": "",
        "message": "",
        "language": "en",
        "ip": "downstairs.gh",
        "port": "",
        "volume": "",
        "x": 770,
        "y": 600,
        "wires": [
            []
        ],
        "icon": "node-red-contrib-cast/google-home1.svg"
    },
    {
        "id": "53d23819.50f298",
        "type": "server",
        "z": "",
        "name": "Home Assistant",
        "legacy": false,
        "hassio": false,
        "rejectUnauthorizedCerts": true
    }
]

I hope this wasn’t too much info and that I didn’t skim over any sections. Feel free to comment below with any suggestions for improvements or questions on this.

Leave a Reply

Your email address will not be published. Required fields are marked *