A virtual Grandfather Clock using Home Assistant, Node-RED and Chromecast Audio

Here’s a bit of background. Feel free to skip down to the implementation section if you’re not interested

I’ve been running Home Assistant for a while and leveraging my Amazon Alexa for whole-house text-to-speech with moderate success; my biggest problem being getting them all to talk at the same time. Unfortunately, groups weren’t an option because two of my Alexas are actually Ecobee 4’s. I was also tired of the stock Alexa voice and wanted more options.

I then stumbled across Amazon Polly and the Michael voice. I had to have him as the voice of my Home Assistant system.

After going down a rabbit-hole trying to get Amazon Polly to speak through my Echos, I realized that I was going to have to use something that natively supported the media_player component as I could only get Polly working through my Nvidia SHIELD. This meant I had two problems:

  • I needed a way to broadcast sound throughout my house from a single source
  • That single source probably needed to be a Google device to really work well

It was then I realized that I already had speakers in every room in the form of an ancient (1976) NuTone IMA-303 intercom system. The system even has RCA Phono and Tape inputs that could easily carry the output of an audio device. A bit of digging turned up the (now discontinued) Chromecast Audio that not only was supported by media_player but was small enough to even fit inside the intercom system’s master station.

$39 sent to an eBay seller along with a couple of 3.5mm to RCA adapters later, I had the Chromecast Audio tied into the intercom system and working reasonably well, including the ability to make the Amazon Polly Michael voice speak throughout the house.

I should mention that I ran into a snag activating the Chromecast Audio with my iPhone–it would always hang after discovering it. I don’t know if it was a problem unique to me, but the only way I could get it configured was to start the setup process, then manually connect to the Chromecast Audio’s WiFi from my phone.

After a considerable amount of rework, I switched all of my whole-house announcements off of Alexa and over to Michael through the Chromecast Audio playing through the intercom system.

When going through that process, I did run into a couple of minor snags:

  • The Chromecast Audio likes to sleep after being idle for a couple of minutes. The solution to this was to play a 2-second silent clip every two minutes. It came from here.
  • Commands to play media on the Chromecast Audio are asynchronous so it’s necessary to do “busy tests” with random back-offs before playing sound. I wrote a Node-RED Action Flow to handle that.

Once I had everything working reliably, I was kind of bored and had the idea that it’d be cool to have a grandfather clock. Here’s how I did that.

Implementing the grandfather clock

I really wanted to have a grandfather clock that played Winchester chimes on each quarter hour as well as striking at the top of the hour. After a bit of searching, I discovered a set of high-quality samples that were taken from the inside of a clock. These can be found here..

I renamed the files as follows and placed them in the .homeassistant/www directory to be served by the local web server on Home Assistant:

grandfather-clock-chime-01.wav  grandfather-clock-chime-06.wav  grandfather-clock-chime-11.wav
grandfather-clock-chime-02.wav  grandfather-clock-chime-07.wav  grandfather-clock-chime-12.wav
grandfather-clock-chime-03.wav  grandfather-clock-chime-08.wav  grandfather-clock-chime-one-half.wav
grandfather-clock-chime-04.wav  grandfather-clock-chime-09.wav  grandfather-clock-chime-one-quarter.wav
grandfather-clock-chime-05.wav  grandfather-clock-chime-10.wav  grandfather-clock-chime-three-quarter.wav

Each of the grandfather-clock-chime-xx.wav files are the hourly chimes to be played at the top of the hour while the one-quarter, one-half and three-quarter files are to be played at 15, 30 and 45 minutes past the hour respectively.

My first experiments with the chimes made it clear that playing them at 100% volume simply was not going to work. Considerable futzing with the media_player.volume_set dealt me fits from Node-RED as it appeared to reset the volume after each call. In retrospect, I think I was doing something wrong, but I did come across the node-red-contrib-cast for Node-RED that seemed to work better.

One additional Node-RED contribution I leveraged was Action Flows. They’re a really slick way to create reusable modules that are like subroutines. I wrote one called WaitIntercom that simply waits for the intercom to not be busy.

Here’s what I ended up with:

Chromecast Audio Keepalive
Check to make sure Chromecast Audio is on and play two seconds of silence every two minutes.

[{"id":"41788896.531cb8","type":"comment","z":"134f7bb.16fde84","name":"Keep Chromecast Audio awake","info":"","x":170,"y":1020,"wires":[]},{"id":"e5cf6e4a.ca1bd","type":"inject","z":"134f7bb.16fde84","name":"Fire every 2 minutes","topic":"","payload":"","payloadType":"date","repeat":"120","crontab":"","once":true,"onceDelay":0.1,"x":140,"y":1080,"wires":[["170439bb.377566"]]},{"id":"b3e09f0d.c6102","type":"api-call-service","z":"134f7bb.16fde84","name":"Play 2 seconds of silence","server":"bd0ac36a.ed0df","service_domain":"media_player","service":"play_media","data":"{\"entity_id\":\"media_player.kitchen_speaker\",\"media_content_type\":\"music\",\"media_content_id\":\"http://hass:8123/local/2-seconds-of-silence.mp3\"}","mergecontext":"","output_location":"","output_location_type":"none","x":810,"y":1060,"wires":[[]]},{"id":"62a29579.17a87c","type":"api-current-state","z":"134f7bb.16fde84","name":"Check Busy","server":"bd0ac36a.ed0df","outputs":2,"halt_if":"idle","halt_if_type":"str","halt_if_compare":"is_not","override_topic":false,"entity_id":"media_player.kitchen_speaker","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","x":590,"y":1060,"wires":[["b3e09f0d.c6102"],[]]},{"id":"170439bb.377566","type":"api-current-state","z":"134f7bb.16fde84","name":"Chromecast Audio off?","server":"bd0ac36a.ed0df","outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"media_player.kitchen_speaker","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","x":380,"y":1080,"wires":[["62a29579.17a87c"],["3d8b3c3b.c22e74"]]},{"id":"3d8b3c3b.c22e74","type":"api-call-service","z":"134f7bb.16fde84","name":"Turn Chromecast Audio on","server":"bd0ac36a.ed0df","service_domain":"media_player","service":"turn_on","data":"{\"entity_id\":\"media_player.kitchen_speaker\"}","mergecontext":"","output_location":"","output_location_type":"none","x":640,"y":1100,"wires":[[]]},{"id":"bd0ac36a.ed0df","type":"server","z":"","name":"Home Assistant","legacy":false,"hassio":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true}]

Virtual Grandfather Clock
This flow plays the appropriate chime wav file at each quarter hour and the correct hour wav file at the top at 25% volume, then restores the volume to 100%. It’s silenced at night.

[{"id":"e19218c2.4386c8","type":"moment","z":"a6536f45.5ee66","name":"","topic":"","input":"","inputType":"msg","inTz":"America/Chicago","adjAmount":0,"adjType":"days","adjDir":"add","format":"object","locale":"en_US","output":"","outputType":"msg","outTz":"America/Chicago","x":520,"y":480,"wires":[["cf284d0b.90b7f"]]},{"id":"ab158084.9df9b","type":"inject","z":"a6536f45.5ee66","name":"tick-tock","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":480,"wires":[["a7b3211e.fa73a"]]},{"id":"cf284d0b.90b7f","type":"switch","z":"a6536f45.5ee66","name":"Quarter hour?","property":"payload.minutes","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"15","vt":"num"},{"t":"eq","v":"30","vt":"str"},{"t":"eq","v":"45","vt":"str"}],"checkall":"false","repair":false,"outputs":4,"x":740,"y":480,"wires":[["fc302930.1f7cf8"],["f93915c8.f42d88"],["349ea3e5.b8530c"],["29d10bd5.91ca04"]]},{"id":"fc302930.1f7cf8","type":"function","z":"a6536f45.5ee66","name":"Determine filename by hour","func":"var hour = msg.payload.hours;\n\nif (hour > 12) {\n hour = hour - 12;\n}\n\nvar url = \"http://hass:8123/local/grandfather-clock-chime-\" + String(hour).padStart(2, '0') + \".wav\";\n\nmsg.url = url;\n\nreturn msg;","outputs":1,"noerr":0,"x":980,"y":420,"wires":[["edabfbd.56b9108"]]},{"id":"31fabc06.938854","type":"comment","z":"a6536f45.5ee66","name":"Virtual grandfather clock","info":"","x":150,"y":420,"wires":[]},{"id":"f7120475.badd48","type":"cast-to-client","z":"a6536f45.5ee66","name":"","url":"http://hass:8123/local/grandfather-clock-chime-one-quarter.wav","contentType":"","message":"","language":"en","ip":"","port":"","volume":"25","x":510,"y":600,"wires":[["2713dcfc.ce9374"]]},{"id":"349ea3e5.b8530c","type":"change","z":"a6536f45.5ee66","name":"Play half hour","rules":[{"t":"set","p":"url","pt":"msg","to":"http://hass:8123/local/grandfather-clock-chime-one-half.wav","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":500,"wires":[["edabfbd.56b9108"]]},{"id":"29d10bd5.91ca04","type":"change","z":"a6536f45.5ee66","name":"Play three-quarters hour","rules":[{"t":"set","p":"url","pt":"msg","to":"http://hass:8123/local/grandfather-clock-chime-three-quarter.wav","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":970,"y":540,"wires":[["edabfbd.56b9108"]]},{"id":"f93915c8.f42d88","type":"change","z":"a6536f45.5ee66","name":"Play one-quarter hour","rules":[{"t":"set","p":"url","pt":"msg","to":"http://hass:8123/local/grandfather-clock-chime-one-quarter.wav","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":960,"y":460,"wires":[["edabfbd.56b9108"]]},{"id":"edabfbd.56b9108","type":"actionflows","z":"a6536f45.5ee66","info":"Describe your action API here.","untilproptype":"num","proptype":"msg","name":"WaitIntercom","prop":"loop","untilprop":0,"until":"gt","loop":"none","scope":"global","perf":false,"seq":false,"x":1210,"y":480,"wires":[["f7120475.badd48"]]},{"id":"4d05a614.91c7b8","type":"actionflows","z":"a6536f45.5ee66","info":"Describe your action API here.","untilproptype":"num","proptype":"msg","name":"WaitIntercom","prop":"loop","untilprop":0,"until":"gt","loop":"none","scope":"global","perf":false,"seq":false,"x":870,"y":600,"wires":[["9331e1d.049ad2"]]},{"id":"9331e1d.049ad2","type":"api-call-service","z":"a6536f45.5ee66","name":"Set volume 100%","server":"bd0ac36a.ed0df","service_domain":"media_player","service":"volume_set","data":"{\"entity_id\":\"media_player.kitchen_speaker\",\"volume_level\":\"1.0\"}","mergecontext":"","output_location":"","output_location_type":"none","x":1070,"y":600,"wires":[[]]},{"id":"53da3c30.857234","type":"inject","z":"a6536f45.5ee66","name":"Test Quarter-Hour","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":730,"y":420,"wires":[["f93915c8.f42d88"]]},{"id":"2713dcfc.ce9374","type":"delay","z":"a6536f45.5ee66","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":700,"y":600,"wires":[["4d05a614.91c7b8"]]},{"id":"a7b3211e.fa73a","type":"time-range-switch","z":"a6536f45.5ee66","name":"Waking hours only","lat":"","lon":"","startTime":"05:45","endTime":"22:15","startOffset":0,"endOffset":0,"x":290,"y":480,"wires":[["e19218c2.4386c8"],[]]},{"id":"bd0ac36a.ed0df","type":"server","z":"","name":"Home Assistant","legacy":false,"hassio":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true}]

Actionflow to wait for the intercom to be free
A simple test/back-off loop that checks to see if the media player is busy or not. It uses a random delay so it can be called by multiple flows simultaneously.

[{"id":"e1e7c75.7730638","type":"api-current-state","z":"2f09d230.395bfe","name":"Check Busy","server":"bd0ac36a.ed0df","outputs":2,"halt_if":"idle","halt_if_type":"str","halt_if_compare":"is_not","override_topic":false,"entity_id":"media_player.kitchen_speaker","state_type":"str","state_location":"","override_payload":"none","entity_location":"","override_data":"none","x":310,"y":380,"wires":[["408d7671.3c3638"],["f945ecb1.ee5d4"]]},{"id":"f945ecb1.ee5d4","type":"delay","z":"2f09d230.395bfe","name":"Random Delay","pauseType":"random","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"500","randomLast":"1500","randomUnits":"milliseconds","drop":false,"x":320,"y":440,"wires":[["e1e7c75.7730638"]]},{"id":"3a49759a.eaf41a","type":"actionflows_in","z":"2f09d230.395bfe","name":"WaitIntercom","priority":"50","links":[],"scope":"global","x":130,"y":380,"wires":[["e1e7c75.7730638"]]},{"id":"408d7671.3c3638","type":"actionflows_out","z":"2f09d230.395bfe","name":"WaitIntercom","links":[],"x":490,"y":380,"wires":[]},{"id":"764a736d.68ea5c","type":"comment","z":"2f09d230.395bfe","name":"Wait for intercom to not be busy","info":"","x":330,"y":320,"wires":[]},{"id":"bd0ac36a.ed0df","type":"server","z":"","name":"Home Assistant","legacy":false,"hassio":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true}]

To implement the virtual Grandfather Clock, make sure you have the following Node-RED modules:


Also, download and unzip the grandfather clock samples from here and the two seconds of silence sample from here.

Upload the grandfather clock wav files as well as the 2-seconds-of-silence.mp3 file to the .homeassistant/www directory and rename them as follows (take the xxxxxx__joedeshon__ off the front of the filenames):

grandfather-clock-chime-01.wav  grandfather-clock-chime-06.wav  grandfather-clock-chime-11.wav
grandfather-clock-chime-02.wav  grandfather-clock-chime-07.wav  grandfather-clock-chime-12.wav
grandfather-clock-chime-03.wav  grandfather-clock-chime-08.wav  grandfather-clock-chime-one-half.wav
grandfather-clock-chime-04.wav  grandfather-clock-chime-09.wav  grandfather-clock-chime-one-quarter.wav
grandfather-clock-chime-05.wav  grandfather-clock-chime-10.wav  grandfather-clock-chime-three-quarter.wav

Next, past the three flows into a tab on Node-RED and make the following changes:

Update entity_ids

Correct the media_player entity_id in each of the following nodes to point to your Chromecast Audio:

  • In the Keep Chromecast Audio awake flow, update the entity_id in the Check Busy, Play 2 seconds of silence and Turn Chromecast Audio on nodes
  • In the WaitIntercom Action flow, update the entity_id in the Check Busy node
  • In the Virtual Grandfather Clock flow, update the entity_id in the Set volume 100% node

Correct URLs for Home Assistant media

  • In the Keep Chromecast Audio awake flow, update the Play 2 seconds of silence node as well as the Play one-quarter, half and three-quarters hour nodes in the Virtual Grandfather Clock to the correct URL for your Home Assistant instance. You should only have to change the hostname. The port, path and filename should be the same.
  • In the Virtual Grandfather Clock flow, edit the Determine filename by hour node script and correct the URL as well

Other modifications

  • In the Virtual Grandfather Clock flow, change the yellow Cast node to have the correct IP address of your media player.
  • You’ll probably want to update the Waking Hours Only node with your preferred active times as well as the Date/Time Formatter node for the correct timezone.

Seems to work, except for one thing that I found in my setup. Despite the keepalive making sure that the media_player.kitchen_speaker (yes, that’s what I had named mine as well) being constantly idle, every 15 minutes the “bloink” sound of the speaker fires off before playing the clock sound. I’ve confirmed that the empty sound is playing every 2 minutes, but it doesn’t seem to matter.

Does this project have its own repo on github?

Unfortunately, I was never able to work around the “bloink” sound. As far as I can tell, the Chromecast Audio always makes that sound when you adjust the volume. It seems to be played at the new volume level to give you an indication of what it sounds like. One possibility that just occurred to me might be to run the chimes through a sound editor to reduce the recorded volume, thereby eliminating the need to reduce the volume to 25% for playback. This site may help: http://mp3gain.flowsoft7.com/

With regard to a github repo, no; just this posting. If there is enough interest, I can see about setting one up.

Well, so far, other than the inability to eliminate the Google sound notification that the clock is going to chime, it’s working well.

We did find one problem, which is probably inherent to Google Home. When my wife is playing a podcast through Google Home from IOS, if the clock chimes in, it stops playback and she has to fire it up again.

I’m going to start working on modifying the flow to incorporate seasonal themes so the sounds of the chimes will fit the occasion, and occasionally play random selections at the right times.

Thanks for the flow. The logic of the actionflows is a bit confusing since I haven’t quite figured out what actionflows do or how I can use them…but it seems promising.

On another note, the site where you posted your audio files is a big fat fail. Their password retrieval system doesn’t seem to work so I had to use Ableton Live and Soundflower to re-record all of the clock sounds.


I’m glad you were able to get it working. I really don’t have much experience with Google Home personally. About the only other thing I use it for is to bridge my iPhone to play music over the intercom system. Interestingly, the chimes just get queued-up rather than interrupting the playback.

Now that I’m thinking about it in the context of your Action Flow question, did you implement the WaitIntercom action flow? It should recognize that Google Home is busy and prevent the chimes from playing.

With regard to your question about Action Flows, they’re very similar to subroutines if you’ve ever done any programming. Think of them as a self-contained function that can be used by any other flow to perform some duty. In this case, I have Action Flows called SpeakALL and SpeakHP in addition to WaintIntercom that are used by a number of other Node-Red flows to talk to the intercom system.

Finally, regarding the site, those files aren’t mine; I just happened to find them.

Let me know if I can clarify any further on the Action Flows.


It took a minute, but I think I figured out what the action flows are good for. One of the things I think of when I think of action flows is using them to check global state variables.

For example, in the middle of a flow, I want to verify whether or not the house is in away mode, or vacation mode, or hospitality mode, etc. So, I send the message off to a routine that runs a check on that and if the condition is met, it would be returned back to the flow to allow it to proceed.

Rather than putting a switch to check the same condition over and over again on each flow, I just route the message to the action flow and back to do the same check on multiple flows.

Now, making that actually happen is another story. I have only imagined it. I’m wondering if there’s a way, conditionally, to route link nodes based upon where they came from, or if the link nodes are simply one to one routes.

So, for example, if on the “Lighting” tab, I send a message to see if anyone is home to the “Action flow”, can I dynamically return the message to the lighting flow after it passes through the Action Flow, or does the Action Flow output find its way to every actionflow input that matches the Action Flow name?

Regarding Google Home…if I had my way, I’d eliminate it completely and just use microphones and speakers throughout the house to handle media and not be dependent upon cloud services. I haven’t experienced anything being queued up but that’s probably because the messages are never that close together.

I’m not 100% certain what the “waitintercom” action flow is doing, but I do see the random time bopping about. :slight_smile:

The way you’ve imagined it is exactly how Action Flows work. The flow on the Lighting tab pauses, turns control over to the Action Flow you’ve referenced, then when it’s done, control is returned back to the flow on the Lighting tab right where it left off. The idea being that you have a chunk of code (like checking a global variable) that you use in lots of flows but don’t want to put copies in each one.

WaitIntercom checks the current status of the Google Home media player device to see if it’s busy. If it is, it waits a random amount of time, then checks again. It keeps doing this until it detects the media player is free, then it returns.

The idea behind this is that two (or more) flows could try to play (or say) something at the same time, causing them to interrupt each other or the message to be lost. The method I used is very common in the networking world; the best parallel I’ve heard is to imagine you and someone else trying to talk at the same time. Breaking it down into steps:

  1. You listen to see if the other person is talking
  2. If they are talking, you wait a random amount of time, then go back to step 1
  3. They’re not talking, so you can talk

Why wait only a random period of time? Why not a specific time frame?

My next project on this topic is to adjust the logic to include themes throughout the year so it wouldn’t just be a chime…but thematic notifications.

Let’s say that the delay is 1 second. Now, you have two flows that want to use the Google Home at the exact same time.

They both try to use it simultaneously, detect that it’s busy, then wait 1 second… then they both try again. Simultaneously. Lather, rinse and repeat.

That’s why a random “back-off” period is important.

1 Like

I really like the theme idea. I’m putting in colored landscape lighting that I plan to change by season and holiday. The sounds would be cool to tie into that.

I also just switched up media players and installed Volumio on a spare RPi3, ran that directly into my studio monitors and now can bypass any “Bleeps” that Google Home makes just before playing a sound.

So here’s a question regarding action flows.

Test Setup:

Inject --> action --> debug

action in --> switch --> action out

I have a 30 second inject that sets a global variable called test1 which basically checks a dummy input boolean. If the switch is on, global.set(“test1”, true);

The switch in the action flow looks at the value of test1. If true, output on 1, if false, output on 2.

This would be how I would check for away mode, or vacation mode, or whether or not it’s a holiday, etc.

If I leave output 2 on the switch empty, and test1 = ‘false’, the message goes nowhere and the action flow node remains in a running state.

If I switch the switch on then inject a 2nd time, the action flow seems to start over and returns the msg to the debug node.

Is there a way to kill the running action node if it’s waiting too long for a response, or does it not matter?

Very nice! I’m thinking about upgrading my old NuTone IM-303 to a more recent (outdated) NuTone NM-100 intercom system. I may look into an alternative to Google Home at that point as well.

The fix for this issue is to tie output 2 over to your “action out” node. Basically, you have to ensure that all code paths ultimately lead to the exit node. I’m not sure, however, that this solution fits your needs.

There’s no way I’m aware of to kill one from outside the context of an Action Flow. I definitely wouldn’t just leave them running, as they’re consuming resources and likely would eventually fill up the stack.

As a side note, be careful using global variables with Node-RED. There are a number of challenges related to code that runs in parallel, accessing a common resource that can introduce bugs that are a bitch to find. Concepts like “locks” and “semaphores” were introduced to address this issue.

The example we previously discussed regarding WaitIntercom is an excellent one. Consider that the Chromecast Audio is a resource that’s accessible from any Node-RED flow as well as external sources such as your wife using it. Because Node-RED flows all run at the same time, you have to build in coordination code that considers what other flows (or your wife) can do to impact that global resource. In the cast of the Chromecast Audio, it has a flag for when it’s busy or not. I check this flag to see if some other flow (or something outside of Home Assistant) is playing something.

It’s a difficult concept to explain but remember that a given flow can be called multiple times in succession, before it completes the prior run. When that happens, there are multiple instances of the flow running at the same time, each with the same access to those global resources. If you don’t consider the implications of this in your code, you run the risk of creating bugs.

Just keep in mind that this isn’t likely going to be an issue unless you’re making changes to the global flag inside of your flow.

I realize I’ve done a horrible job of explaining this. It’s a complicated concept and is an entire programming discipline referred to as parallel programming.