Child control for Sonos speakers
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.