My boring Blog

Mauro Frigerio blog

Child control for Sonos speakers

29-09-2021 6 min read Article

TL;DR

Using an ESP32 and Home Assistant with ESPhome you can control Sonos speakers (or other media players) without smartphone application. This is my project to allow my daughter to manage the music in our home (whenever we want).

Intro

We are in the process of fixing up a house and it’s time to do some major cleaning among all the stuff we’ve accumulated. In a cleaning raid an old baby cassette recorder was discovered, unfortunately it is no longer working (I will get it working eventually). This find made us alert to the fact that our little girl can’t easily listen to music since it’s all streaming now and you need apps on your phone to listen to it. Not like it was in our day when we were young….

The baby could easily handle the smartphone, but it’s a little too early. So I developed an alternative solution, creating a box that would allow her to simply manage Sonos speakers.

Hardware

The house is equipped with several Sonos speakers in the important rooms, one of which is the little girl’s room. In her room I installed a wooden box with buttons and an RFID card reader (rc522). Everything is managed by an ESP32. At first I tried to connect the ESP to a battery, but I gave up because the duration of a charge was only a few weeks. As soon as I have some time, however, I will work on fixing this aspect as well.

Software

Home Assistant is a fantastic platform and allows you to interconnect various systems with each other. Often a manufacturer develops their own system with only their own products in mind. Rarely is the platform open and allows use from an external system. For example, IKEA lamps are only controllable by IKEA switches. While Sonos speakers are only manageable with the manufacturer’s own app. Home Assistant destroys these barriers and allows (almost always) to connect everything with everything.

ESPhome

The ESP32 in the control box is programmed via ESPhome. In the box I added these switches:

  • play / pause
  • next song
  • increase volume
  • reduce volume
  • RFID card presence

The first four commands are clear, while the last one needs a bit of explanation. I added a switch to know when an RFID card is inserted so that I can save power consumption and activate the reader only when necessary.

This is the configuration of ESPhome:

substitutions:
  display_name: Ci Player
esphome:
  name: ci-player-power
  platform: ESP32
  board: firebeetle32

logger:
api:
  password: !secret esphome_secret
ota:
  password: !secret esphome_secret

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23
  miso_pin: GPIO19

rc522_spi:
  cs_pin: GPIO21
  reset_pin: GPIO22

binary_sensor:
  - platform: gpio
    pin:
      number: 25
    id: ci_player_power_play
    name: ${display_name} Play
    filters:
      - delayed_off: 10ms

  - platform: gpio
    pin:
      number: 26
    id: ci_player_power_next
    name: ${display_name} Next
    filters:
      - delayed_off: 10ms

  - platform: gpio
    pin:
      number: 14
    id: ci_player_power_volume_up
    name: ${display_name} Volume up
    filters:
      - delayed_off: 10ms

  - platform: gpio
    pin:
      number: 27
    id: ci_player_power_volume_down
    name: ${display_name} Volume down
    filters:
      - delayed_off: 10ms

  - platform: gpio
    pin:
      number: 4
    id: ci_player_power_card_presence
    name: ${display_name} Card presence
    filters:
      - delayed_off: 10ms

  - platform: rc522
    uid: AB-35-B5-22
    name: ${display_name} Playlist 1
  - platform: rc522
    uid: 1B-8E-B6-22
    name: ${display_name} Playlist 2
  - platform: rc522
    uid: 5B-AE-A2-22
    name: ${display_name} Playlist 3
  - platform: rc522
    uid: 20-25-5C-2F
    name: ${display_name} Playlist 4

Home Assistant automation

The connection between the control box and the Sonos speakers is made by two Home Assistant automations. The experience and management is made better by the following variables:

  • input_boolean.ci_player_block: this is a switch to block the ability to control the speakers. Useful when the child is not being good or has to sleep.
  • input_number.ci_player_volume_min: the minimum value of the volume. Currently only minimum or maximum volume is possible.
  • input_number.ci_player_volume_max: maximum volume value
  • input_number.ci_player_delay: delay between commands. So the child can listen to something
  • input_select.ci_player_playlist: last played playlist
  • input_select.ci_player_speaker: choose the speaker to control with the command box

The first automation takes care of managing the commands (the switches) and the second one modifies the playlist to be listened to according to the RFID card inserted.

The content of the playlists is managed through the Sonos app and must be visible as an audio source when you add the media player card in Lovelance of the Sonos speaker.

Managin commands

alias: Ci-player POWER - comandi
description: PLAY - NEXT - VOLUME
trigger:
  - platform: state
    entity_id: binary_sensor.ci_player_power_play
    from: 'off'
    to: 'on'
    id: play
  - platform: state
    entity_id: binary_sensor.ci_player_power_next
    from: 'off'
    to: 'on'
    id: next
  - platform: state
    entity_id: binary_sensor.ci_player_power_volume_down
    from: 'off'
    to: 'on'
    id: volume_down
  - platform: state
    entity_id: binary_sensor.ci_player_power_volume_up
    from: 'off'
    to: 'on'
    id: volume_up
condition:
  - condition: state
    entity_id: input_boolean.ci_player_blocco
    state: 'off'
action:
  - choose:
      - conditions:
          - condition: state
            state: 'on'
            entity_id: binary_sensor.ci_player_power_play
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: >-
                      {{ is_state(states('input_select.ci_player_speaker'),
                      'playing') }}                      
                sequence:
                  - service: media_player.media_play_pause
                    data_template:
                      entity_id: '{{ states (''input_select.ci_player_speaker'') }}'
            default:
              - service: media_player.select_source
                data_template:
                  source: '{{ states(''input_select.ci_player_playlist'') }}'
                  entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_next
            state: 'on'
        sequence:
          - service: media_player.media_next_track
            data_template:
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_volume_down
            state: 'on'
        sequence:
          - service: media_player.volume_set
            data_template:
              volume_level: '{{ states(''input_number.ci_player_volume_min'') }}'
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_volume_up
            state: 'on'
        sequence:
          - service: media_player.volume_set
            data_template:
              volume_level: '{{ states(''input_number.ci_player_volume_max'') }}'
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
    default: []
  - delay: >-
      00:00:{% if states('input_number.ci_player_delay') | int <
      10-%}0{{states('input_number.ci_player_delay') | int}} {% else
      %}{{states('input_number.ci_player_delay') | int}} {% endif %}      
mode: single

Playlist selection

alias: Ci-player POWER - playlist
description: Selezione delle playlist
trigger:
  - platform: state
    entity_id: binary_sensor.ci_player_power_card_presence
    from: 'off'
    to: 'on'
condition:
  - condition: state
    entity_id: input_boolean.ci_player_blocco
    state: 'off'
action:
  - delay:
      hours: 0
      minutes: 0
      seconds: 1
      milliseconds: 0
  - choose:
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_playlist_1
            state: 'on'
        sequence:
          - service: media_player.select_source
            data_template:
              source: Z_Playlist 1
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
          - service: input_select.select_option
            data:
              option: Z_Playlist 1
              entity_id: input_select.ci_player_playlist
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_playlist_2
            state: 'on'
        sequence:
          - service: media_player.select_source
            data_template:
              source: Z_Playlist 2
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
          - service: input_select.select_option
            data:
              option: Z_Playlist 2
              entity_id: input_select.ci_player_playlist
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_playlist_3
            state: 'on'
        sequence:
          - service: media_player.select_source
            data_template:
              source: Z_Playlist 3
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
          - service: input_select.select_option
            data:
              option: Z_Playlist 3
              entity_id: input_select.ci_player_playlist
      - conditions:
          - condition: state
            entity_id: binary_sensor.ci_player_power_playlist_4
            state: 'on'
        sequence:
          - service: media_player.select_source
            data_template:
              source: Z_Playlist 4
              entity_id: '{{ states(''input_select.ci_player_speaker'' ) }}'
          - service: input_select.select_option
            data:
              option: Z_Playlist 4
              entity_id: input_select.ci_player_playlist
    default: []
mode: single

Conclusion

Thanks to the box, the child can be a little dj, starting and stopping the music whenever she wants. Or change the playlist by choosing the music she likes best.