DEV Community

loading...
Cover image for Get Google Home alarms & timers as notifications

Get Google Home alarms & timers as notifications

MHA
Home Automation
Updated on ・5 min read

TL;DR
Using Home Assistant and Node-RED I receive actionable notifications on my android phone for Google Home alarms and timers. The notifications show all information, they are grouped and have a countdown.

Table of contents

  1. Alarms & Timers
  2. Google Home integration
  3. Goal
  4. Node-RED
    1. The subflow
    2. Top flow
    3. Bottom flow
    4. How to use

I'm a big fan of the Google(Nest) smart devices and have multiple Hub and Mini devices in my smart home setup. Paired with Home Assistant and Node-RED I use them to control every device and automation flow.

Alarms & Timers

The alarm and timer functions on the devices are the most frequent used by me, I use them every day.
They work fine, but I'm missing some functionality to make them awesome.

  • They don't show up on my phone
  • I can't control them by phone
  • When the device rings and I'm not in the same room I won't notice the alarm / timer ringing

Home Assistant Google Home integration

This week I discovered a new community integration called "Google Home" which exposes the alarms and timers as sensors in HA.

GitHub logo leikoilja / ha-google-home

Home Assistant Google Home custom component


I installed it via HACS and it works really nice, the sensor entities for alarms and timers are created per device.

I wondered if I could use it for some new automations which would add more functionality for me, especially have them as notifications on my phone.

Goal

Alt Text

  • Show alarms and timers as notifications on my phone
  • Let them countdown to the time they fire
  • When they ring the notification should show this
  • When I dismiss a notification the alarm / timer should be deleted
  • If they are deleted on the google device the notification should be removed

Node-RED

I use Node-RED for all my automations, I like the way it visualizes the flows. It's often described as a low code solution, as a developer I like the option to apply code logic as well.

What you see next is the end result of some trial and error programming, usually I start with just 1 sensor input and a normal flow. Which I then finetune and often convert to a reusable subflow.

What the main flow will look like:

The flow

In the end result I wanted to have just 1 sublfow which would control everything, having multiple alarm and timer sensors as input.

The subflow

Alt Text
The top flow handles all input.
The bottom flow handles cleared notifications.

Top flow

  • Parse the input from the sensors
  • Determine if it is an alarm or timer sensor
  • Cache the active items (with type and sensor info) in memory
  • For every active item create/update a notification message
  • For every inactive item make a clear_notification message
  • Send the messages to the Call service node

Alt Text

Let's start with the "Parse alarms & timers" function node which does all the heavy lifting.
I use the "Setup" tab of the function node to set some defaults which are used in the flow:

// Set type defaults
const defaults = {
    timer: {
        id: 'timer_id',
        label: '',
        items: 'timers',
        name: 'Timers',
        deleteService: 'delete_timer'
    },
    alarm: {
        id: 'alarm_id',
        label: '',
        items: 'alarms',
        name: 'Alarms',
        deleteService: 'delete_alarm'
    },
}
flow.set('defaults', defaults);
Enter fullscreen mode Exit fullscreen mode

The "Function" tab

const defaults = flow.get('defaults');
const service = env.get('service');
const messages = [];
let type = '';
if(!msg.data) {
    return null;
}
// Determine type: alarm or timer
if(typeof msg.data.new_state.attributes.timers !== 'undefined' || typeof msg.data.old_state.attributes.timers !== 'undefined' ) {
    type = 'timer';
} else if (typeof msg.data.new_state.attributes.alarms !== 'undefined' || typeof msg.data.old_state.attributes.alarms !== 'undefined') {
    type = 'alarm';
} else {
    return null;
}

const typeValues = defaults[type];

// Create a alarm/timer item ready to be used by the service
const createItem = (item, sensor, type, device) => {
    const doneTime = new Date(item.fire_time * 1000).toTimeString().substr(0, 5);
    let label = `${typeValues.label}`;
    let vibration = '';
    let chronometer = true;
    if(item.status === 'ringing') {
        label = item.label ? `${label} ${item.label} RINGING` : `${label} RINGING`;
        vibration = '100, 1000, 100, 1000, 100, 100, 1000, 100, 1000, 100';
        chronometer = false;
    } else {
        label = item.label ? `${label} ${doneTime} ${item.label}` : `${label} ${doneTime}`;
    }
    let title = label;
    if(item.duration) {
        title = `${label} (${item.duration})`;
    }
    return { 
        id: item[typeValues.id],
        sensor: sensor,
        device: device,
        type: type,
        status: item.status,
        data:{
            title: title,
            message: `${device}`,
            data: {
                tag: item[typeValues.id],
                chronometer: chronometer,
                when: item.fire_time,
                sticky: 'true',
                group: typeValues.name,
                vibrationPattern: vibration 
            }            
        }
    }
};

const mapItems = (arr) => {
    return arr.map((item) => { return createItem(item, msg.data.entity_id, type, msg.data.old_state.attributes.friendly_name) })
}
const activeItems = mapItems(msg.data.new_state.attributes[typeValues.items] || []).filter(item => item.status !== 'none');

const cachedItems = flow.get('cachedItems') || new Map();

// Update or create notifications for active items
activeItems.forEach((item) => {
    messages.push({
        payload: {
            service: service,
            data: item.data    
        }
    });
    cachedItems.set(item.id, item);
})

// Clear expired/deleted notifications
cachedItems.forEach((item, id) => {
    const findItem = activeItems.find(newItem => newItem.id === id);
    if(!findItem && item.sensor === msg.data.entity_id) {
        messages.push({
            payload: {
                service: service,
                data: {
                    message: 'clear_notification',
                    data: {
                        tag: id
                    }
                }    
            }
        });
        cachedItems.delete(id);
    }
})

flow.set('cachedItems', cachedItems);

// Send notifications as a stream
messages.forEach((msg) => node.send(msg));

// All done
node.done();
Enter fullscreen mode Exit fullscreen mode

Bottom flow

  • Detect cleared notifications
  • Look it up in the cache
  • When found call the delete_alarm/timer service
  • Remove the item from the cache Alt Text
const cachedItems = flow.get('cachedItems');
const defaults = flow.get('defaults');

// Find cleared item
const findItem = cachedItems.get(msg.payload.event.tag);
if(!findItem) {
    return null
}
node.send(
    {
        payload: {
            service: defaults[findItem.type].deleteService,
            data: {
                entity_id: findItem.sensor,
                [defaults[findItem.type].id]: findItem.id
            }
        }
    }
)

// Clean up
cachedItems.delete(findItem.id);
flow.set('cachedItems', cachedItems);        
node.done();
Enter fullscreen mode Exit fullscreen mode

How to use

  1. Import the subflow in NR:
  2. Then edit the Environment variable for the service:Alt Text
  3. Add the subflow and connect some alarm and/or timer sensors as events_state nodes. The flow
  4. All done, try it by creating a alarm / timer on the google home device, within a few seconds you should receive the notification on your phone.

Discussion (0)

Forem Open with the Forem app