Chromecast Radio with Home Assistant and Node-RED

4 minute read

Telenet, our local cable provider, decided that analog cable radio is dead and everyone should switch to either DAB (Digital Radio over the air) or DVB-C (Digital Radio via coaxial cable). For me, that’s a bit of an issue:

  • DAB radio is fine, but requires good reception. Our house is rather well insulated so radio reception is crap at best
  • DVB-C radio would require buying yet another device, which I don’t really want to do unless necessary.

Since we have good internet connectivity, why not stream?

Try 1: Raspberry Pi and Mopidy

Since I had a Pi lying around, I repurposed it, installed mopidy and lirc (to be able to use an infrared remote). After adding some M3U playlists with radio streams for our favourite radio channels, it was used for a short while.

A short while indeed, because it’s so friggin’ slow. It takes upwards of 15 seconds to start a radio stream, which is annoying at best. Sometimes it just stopped working for no good reason, either.
Since this is a solution that would get on my nerves in no time, I tried some other things.

Try 2: just use phones and Chromecast Audio’s

We have a bunch of CC Audio enabled devices around the house, so why not use these, together with the TuneIn app?
While this works great, there are a few issues:

  • Control is limited to pause/stop from the devices that didn’t start the casting
  • Changing channels means usurping the casting
  • Limited to control via smartphone / tablets

Better, but still not great.

Try 3: Home Assistant!

Third time’s a charm ;)

Based of the tutorial by Bob Visser on the HA Community, but modified to use Node-RED in the background.

Frontend

I’m using Lovelace, with the following vertical stack card: You’ll have to modify this to match your CC devices. I also use condition cards to not show the CC devices themselves when the device tracker lists them as being unavailable (away). If you don’t want that part, just throw everything else out ;)

Vertical Stack:

lovelace card

cards:
  - entities:
      - entity: input_select.chromecast_radio
      - entity: input_select.radio_station
      - input_number.chromecast_audio_volume
      - entity: script.start_cc_audio_radio
      - entity: script.stop_cc_audio_radio
    show_header_toggle: false
    title: Chromecast Radio
    type: entities
  - card:
      entities:
        - media_player.living_room_speaker
      type: entities
    conditions:
      - entity: device_tracker.cc_audio_livingroom
        state: home
    type: conditional
  - card:
      entities:
        - media_player.study_speaker
      type: entities
    conditions:
      - entity: device_tracker.cc_audio_study
        state: home
    type: conditional
  - card:
      entities:
        - media_player.jbl_link10
      type: entities
    conditions:
      - entity: device_tracker.jbllink
        state: home
    type: conditional
type: vertical-stack

Selectors and inputs:

These make the selectors used above available. Again, modify to your liking. The names chosen here are used later in the Node-RED function blocks to determine the stream to use.

input_select:
  radio_station:
    name: 'Select Radio Station:'
    options:
      - Willy
      - QMusic
      - Radio 1
      - Radio 2 OVL
      - StuBru
      - MNM
      - MNM Hits
      - Joe

  chromecast_radio:
    name: 'Select Speakers:'
    options:
      - Livingroom
      - JBL Link 10
      - Downstairs (Living + JBL)
      - Study
      - Everywhere
    initial: Livingroom
    icon: mdi:speaker-wireless

input_number:
  chromecast_audio_volume:
    icon: mdi:volume-medium
    name: Volume level
    min: 0
    max: 100
    step: 1

Scripts

These scripts do nothing, they’re merely used to get the trigger into Node-RED.

start_cc_audio_radio:
  alias: Start Playing
  sequence:

stop_cc_audio_radio:
  alias: Stop Playing
  sequence:

Backend

Node-RED is used in the backend to make the radio work.

Visually

nodered flow

There are two flows:

  • CC Audio Radio: this flow determines which CC has been chosen, which radio stream, sets the volume and starts/stops playback.
    You will need to take a look at the function nodes ‘Select CC device’ and ‘Select Radio Stream’ to configure your own devices and streams.
  • CC Audio Volume: this flow is triggered when there’s a change in volume.

Export


[{"id":"2a248e28.12611a","type":"server-state-changed","z":"8ca6e97e.06058","name":"CC Volume Changed","server":"5f01146c.501bec","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.chromecast_audio_volume","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"x":120,"y":460,"wires":[["e0b7feef.4ccf38"]]},{"id":"3fd74670.2961fa","type":"server-events","z":"8ca6e97e.06058","name":"Filter call_service calls","server":"5f01146c.501bec","event_type":"call_service","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":120,"y":200,"wires":[["c08a97dc.4ecc1"]]},{"id":"c08a97dc.4ecc1","type":"switch","z":"8ca6e97e.06058","name":"CC Audio Radio","property":"payload.event.service","propertyType":"msg","rules":[{"t":"eq","v":"start_cc_audio_radio","vt":"str"},{"t":"eq","v":"stop_cc_audio_radio","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":320,"y":200,"wires":[["ae2df3b4.dd1ce8"],["e9f15753.1e3c6"]]},{"id":"ffdf1379.94e5f","type":"function","z":"8ca6e97e.06058","name":"Select CC device","func":"const globalHomeAssistant = global.get('homeassistant');\nconst selected_cc = globalHomeAssistant.homeAssistant.states[\"input_select.chromecast_radio\"].state;\n\n\nif (selected_cc == \"Livingroom\") {\n    msg.audio_cc_target = \"media_player.living_room_speaker\";\n} else if (selected_cc == \"Downstairs (Living + JBL)\") {\n    msg.audio_cc_target =  \"media_player.downstairs_speakers\";\n} else if (selected_cc == \"JBL Link 10\") {\n    msg.audio_cc_target =  \"media_player.jbl_link10\";\n} else if (selected_cc == \"Study\") {\n    msg.audio_cc_target =  \"media_player.study_speaker\";\n} else if (selected_cc == \"Everywhere\") {\n    msg.audio_cc_target = \"media_player.all_chromecast_audio\";\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":200,"wires":[["6e3924e9.5c58bc"]]},{"id":"d337d852.486ed","type":"function","z":"8ca6e97e.06058","name":"Select Radio Stream","func":"const globalHomeAssistant = global.get('homeassistant');\nconst selected_stream = globalHomeAssistant.homeAssistant.states[\"input_select.radio_station\"].state;\n\nvar temp;\n\nif (selected_stream == \"Willy\") {\n    temp = \"http://20043.live.streamtheworld.com/WILLY.mp3\";\n} else if (selected_stream == \"QMusic\") {\n    temp =  \"http://21633.live.streamtheworld.com/QMUSIC.mp3\";\n} else if (selected_stream == \"StuBru\") {\n    temp =  \"http://icecast.vrtcdn.be/stubru-high.mp3\";\n} else if (selected_stream == \"MNM Hits\") {\n    temp =  \"http://icecast.vrtcdn.be/mnm_hits-high.mp3\";\n} else if (selected_stream == \"MNM\") {\n    temp =  \"http://icecast.vrtcdn.be/mnm-high.mp3\";\n} else if (selected_stream == \"Radio 1\") {\n    temp =  \"http://icecast.vrtcdn.be/radio1-high.mp3\";\n} else if (selected_stream == \"Radio 2 OVL\") {\n    temp =  \"http://icecast.vrtcdn.be/ra2ovl-high.mp3\";\n} else if (selected_stream == \"Joe\") {\n    temp =  \"http://playerservices.streamtheworld.com/api/livestream-redirect/JOE.mp3\";\n}\n\nmsg.audio_cc_stream = temp\nreturn msg;","outputs":1,"noerr":0,"x":780,"y":280,"wires":[["6230e6de.d3a8a"]]},{"id":"6230e6de.d3a8a","type":"api-call-service","z":"8ca6e97e.06058","name":"Start playback","server":"5f01146c.501bec","version":1,"debugenabled":false,"service_domain":"media_player","service":"play_media","entityId":"{{audio_cc_target}}","data":"{\"media_content_id\":\"{{{audio_cc_stream}}}\",\"media_content_type\":\"music\"}","dataType":"json","mergecontext":"","output_location":"blaat","output_location_type":"msg","mustacheAltTags":false,"x":760,"y":340,"wires":[["792d400e.1f8fc8"]]},{"id":"ae2df3b4.dd1ce8","type":"change","z":"8ca6e97e.06058","name":"Record Start","rules":[{"t":"set","p":"audio_cc_state","pt":"msg","to":"play_media","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":180,"wires":[["ffdf1379.94e5f"]]},{"id":"e9f15753.1e3c6","type":"change","z":"8ca6e97e.06058","name":"Record Stop","rules":[{"t":"set","p":"audio_cc_state","pt":"msg","to":"media_stop","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":220,"wires":[["ffdf1379.94e5f"]]},{"id":"792d400e.1f8fc8","type":"debug","z":"8ca6e97e.06058","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":930,"y":340,"wires":[]},{"id":"46bff032.8b671","type":"api-call-service","z":"8ca6e97e.06058","name":"Stop playback","server":"5f01146c.501bec","version":1,"debugenabled":false,"service_domain":"media_player","service":"media_stop","entityId":"{{audio_cc_target}}","data":"","dataType":"json","mergecontext":"","output_location":"blaat","output_location_type":"msg","mustacheAltTags":false,"x":500,"y":340,"wires":[[]]},{"id":"6e3924e9.5c58bc","type":"switch","z":"8ca6e97e.06058","name":"Start/Stop?","property":"audio_cc_state","propertyType":"msg","rules":[{"t":"eq","v":"play_media","vt":"str"},{"t":"eq","v":"media_stop","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":510,"y":280,"wires":[["d337d852.486ed","1271548.40d60ac"],["46bff032.8b671"]]},{"id":"e0b7feef.4ccf38","type":"function","z":"8ca6e97e.06058","name":"Select CC device","func":"const globalHomeAssistant = global.get('homeassistant');\nconst selected_cc = globalHomeAssistant.homeAssistant.states[\"input_select.chromecast_radio\"].state;\n\n\nif (selected_cc == \"Livingroom\") {\n    msg.audio_cc_target = \"media_player.living_room_speaker\";\n} else if (selected_cc == \"Downstairs (Living + JBL)\") {\n    msg.audio_cc_target =  \"media_player.downstairs_speakers\";\n} else if (selected_cc == \"JBL Link 10\") {\n    msg.audio_cc_target =  \"media_player.jbl_link10\";\n} else if (selected_cc == \"Study\") {\n    msg.audio_cc_target =  \"media_player.study_speaker\";\n} else if (selected_cc == \"Everywhere\") {\n    msg.audio_cc_target = \"media_player.all_chromecast_audio\";\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":460,"wires":[["1271548.40d60ac"]]},{"id":"1a87ad8c.63413a","type":"api-call-service","z":"8ca6e97e.06058","name":"Set volume","server":"5f01146c.501bec","version":1,"debugenabled":false,"service_domain":"media_player","service":"volume_set","entityId":"{{audio_cc_target}}","data":"{\"volume_level\":{{payload}}}","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":930,"y":460,"wires":[[]]},{"id":"dd506809.312d88","type":"comment","z":"8ca6e97e.06058","name":"CC Audio Radio","info":"","x":100,"y":160,"wires":[]},{"id":"1271548.40d60ac","type":"function","z":"8ca6e97e.06058","name":"Calculate volume","func":"const globalHomeAssistant = global.get('homeassistant');\nconst volume_level = globalHomeAssistant.homeAssistant.states[\"input_number.chromecast_audio_volume\"].state;\n\nmsg.payload = volume_level / 100;\n\nreturn msg;","outputs":1,"noerr":0,"x":750,"y":460,"wires":[["1a87ad8c.63413a"]]},{"id":"9fb0e13b.076a18","type":"comment","z":"8ca6e97e.06058","name":"CC Audio Volume","info":"","x":110,"y":420,"wires":[]},{"id":"5f01146c.501bec","type":"server","z":"","name":"Home Assistant"}]

Comments