DEV Community

Cover image for Get Google Home alarms & timers as notifications
MHA
MHA

Posted on • Edited on

Get Google Home alarms & timers as notifications

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.

Top comments (4)

Collapse
 
sycx2 profile image
sycx2 • Edited

Hello,

Thanks for your flow, I'm using it but sometimes it didn't work.
It's the line

const cachedItems = flow.get('cachedItems') || new Map();
Enter fullscreen mode Exit fullscreen mode

flow.get() returns an object so the functions Map.set() and Map.forEach() aren't working and the function block stops with an error.

Just change it to e.g.

const cachedItems = new Map(Object.entries(flow.get('cachedItems'))) || new Map();
Enter fullscreen mode Exit fullscreen mode

and it should work. I'll test it further and report back if I still get errors:)

I really appreciate your work

Collapse
 
chale94 profile image
charlie hale

Love this but cant get it to work. I'm not exactly great with computers but I try lol.
I've changed the service to my phone id and when I set an alarm it does count down but I get no "RINGING" notification and when I dismiss the notification it doesnt stop or delete the alarm.
Any help would be great because its an awesome idea

Collapse
 
sk8er000 profile image
sk8er000

hello,
I'm trying to use this flow for 2 mobiles phones but only the first one is working.
I've tried to use 2 subflows (with different eviroment variable) and 2 distinct flow but only the first one is working. Am I doing something wrong?

Thank you

Collapse
 
mikeflare303 profile image
MikeFlare303

Hi there,
I loved your scripts. It was working fine. But now I get the following errror:

node: Parse alarms & timers
function : (error)
"TypeError: Cannot convert undefined or null to object"