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":"192.168.1.122","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:
node-red-contrib-actionflows
node-red-contrib-cast
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 theentity_id
in theCheck Busy
,Play 2 seconds of silence
andTurn Chromecast Audio on
nodes - In the
WaitIntercom
Action flow, update theentity_id
in theCheck Busy
node - In the
Virtual Grandfather Clock
flow, update theentity_id
in theSet volume 100%
node
Correct URLs for Home Assistant media
- In the
Keep Chromecast Audio awake
flow, update thePlay 2 seconds of silence
node as well as thePlay one-quarter
,half
andthree-quarters hour
nodes in theVirtual 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 theDetermine filename by hour
node script and correct the URL as well
Other modifications
- In the
Virtual Grandfather Clock
flow, change the yellowCast 192.168.1.122
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 theDate/Time Formatter
node for the correct timezone.