Skip to content

Commit

Permalink
docs(example): Add Sonos display example
Browse files Browse the repository at this point in the history
related to #104
  • Loading branch information
xaviml committed Aug 2, 2020
1 parent 0da5eef commit 9ad1b84
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 70 deletions.
File renamed without changes
Binary file added docs/assets/img/sonos_displays_2.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/examples/index.md
Expand Up @@ -465,4 +465,5 @@ example_app5:
These are examples that are quite extensive and were extracted in separated pages:

- [Sonos/Symfonisk media player(s)](sonos)
- [Sonos display](sonos-display)
- [Tasmota SwitchMode 11](tasmota-switchmode11)
139 changes: 69 additions & 70 deletions docs/examples/Sonos display.md → docs/examples/sonos-display.md
@@ -1,30 +1,35 @@
---
title: SONOS/SYMFONISK Display example
layout: page
---


![Three different Sonos displays](/controllerx/assets/img/sonos_displays_2.jpg)

### Sonos/Symfonisk display unit with ESPHome

![Three different Sonos displays](https://github.com/htvekov/controllerx/blob/master/docs/assets/img/Sonos_displays_1.jpg)
![Three different Sonos displays](/controllerx/assets/img/sonos_displays_1.jpg)

Using ControllerX - Controlling your Sonos speakers have never been easier 😎 But the occasional wish for visual check on what’s actually playing, volume setting, media artist/title, active speakers in group etc. is still there 👀 Leaving you with no other choice than adding a display integration as the obvious solution for this need 😉

All it takes is an ESP8266 with ESPHome software, an appropriate display, a handfull of HA sensors and you’re all set to go 🚀 ESPHome is a system framework for ESP8266 units that has support for several I2C OLED/E-ink display types and numerous sensors, has direct HA integration via add-on and easy, yet powerful YAML configuration. Read more about ESPHome and how to set it up in HA here: https://esphome.io/
All it takes is an ESP8266 with ESPHome software, an appropriate display, a handfull of HA sensors and you’re all set to go 🚀 ESPHome is a system framework for ESP8266 units that has support for several I2C OLED/E-ink display types and numerous sensors, has direct HA integration via add-on and easy, yet powerful YAML configuration. Read more about ESPHome and how to set it up in HA here: [https://esphome.io/](https://esphome.io/)

### Hardware:

I initially used the simple and inexpensive (less than 2 US$ ) SSD1306 0,96" OLED display for this build. Resolution is only 128x64. But still enough, when using several pages to be displayed continously. The SSD1306 has a 'big brother' in the SSD1309 display. This display has identical resolution, is priced at some 14 US$, can use same drivers/library as SSD1306 but is much, much larger at 2,42". I really like this good sized and simple I2C display and ended up using this display in the final build, as it's much easier to read from a distance 🙂

Some links examples for hardware below. These are just some random sellers I've picked. Not necessarily the cheapest or best sellers.

Wemos D1 mini – ESP8266<br />
https://www.aliexpress.com/item/32845253497.html?spm=a2g0s.9042311.0.0.27424c4dFNzmlu
[https://www.aliexpress.com/item/32845253497.html?spm=a2g0s.9042311.0.0.27424c4dFNzmlu](https://www.aliexpress.com/item/32845253497.html?spm=a2g0s.9042311.0.0.27424c4dFNzmlu)

0,96" 12864 SSD1306 OLED display<br />
https://www.aliexpress.com/item/32896971385.html?spm=a2g0s.9042311.0.0.27424c4d7omJkq
[https://www.aliexpress.com/item/32896971385.html?spm=a2g0s.9042311.0.0.27424c4d7omJkq](https://www.aliexpress.com/item/32896971385.html?spm=a2g0s.9042311.0.0.27424c4d7omJkq)

2,42” 12864 SSD1309 OLED display (direct replacement for the much smaller 0,96” SSD1306 display and can use same library)<br />
https://www.aliexpress.com/item/33024448944.html?spm=a2g0s.9042311.0.0.27424c4d28nV4A
[https://www.aliexpress.com/item/33024448944.html?spm=a2g0s.9042311.0.0.27424c4d28nV4A](https://www.aliexpress.com/item/33024448944.html?spm=a2g0s.9042311.0.0.27424c4d28nV4A)

Or alternatively you can use a Wemos NodeMCU ESP8266 with integrated 0,96" OLED display<br />
https://www.aliexpress.com/item/4000287451981.html?spm=a2g0o.productlist.0.0.2d34c80dWlfO69
[https://www.aliexpress.com/item/4000287451981.html?spm=a2g0o.productlist.0.0.2d34c80dWlfO69](https://www.aliexpress.com/item/4000287451981.html?spm=a2g0o.productlist.0.0.2d34c80dWlfO69)

**One note on the SSD1309 display**<br />
In order to get it to work as I2C instead of SPI, you need to do a bit of soldering. On the specific display type I bought, you need to bridge (short) R5 and move R4 to R3. Remember that display will NOT work unless RES is connected to RST on ESP8266 (or any available pin and controlled in ESPHome sw). Note: Display only supports 3,3v on VCC. Some have reported that display tolerates 5v. I wouldn’t take that risk, though! I’ve kept both CS and DS ‘floating’. Haven’t had any I2C address issues so far. Use pull-up/down resistors if you experience issues.
Expand All @@ -43,19 +48,19 @@ In order to get it to work as I2C instead of SPI, you need to do a bit of solder
CS : NC (No Connection - 'floating'. Default I2C address 0x3c)
DC : NC (No Connection - 'floating')


### Display setup:

My current display setup consists of four pages that all are displayed for 5 seconds.

Following information is displayed on the screen:

**All pages:** Source (if not present, display ‘Sonos/Playlist’)
volume setting and play/pause/idle status.
volume setting and play/pause/idle status.

**Page 1:** Active master/slave speakers.

**Page 2:** Media artist/media title
(if not available from stream, display time instead)
(if not available from stream, display time instead)

**Page 3:** Time

Expand All @@ -64,20 +69,22 @@ Following information is displayed on the screen:
If you only need the Sonos playing details displayed, you can just remove page 3 & 4 from the ESPHome YAML configuration.

### True Type Fonts:

Three 'standard' Calibri TT fonts are used plus a 'special' version of Heydings Icons font in which I've included some Heydings Controls icons as well.
If you experience some strange characters on the display, you probably need to edit the glyphs in ESPHome YAML and add whatever language specific characters you find are missing.
If you experience some strange characters on the display, you probably need to edit the glyphs in ESPHome YAML and add whatever language specific characters you find are missing.

Calibri TTF fonts link: [https://www.fontdload.com/dl/calibri-font/](https://www.fontdload.com/dl/calibri-font/)

Heydings Icons special file link: [https://github.com/htvekov/controllerx/tree/master/docs/assets/img/HeydingsIconsSymbols.ttf](https://github.com/htvekov/controllerx/blob/master/docs/assets/img/HeydingsIconsSymbols.ttf)
Heydings Icons special file [link](/controllerx/assets/img/HeydingsIconsSymbols.ttf).

Copy Calibri Bold, Calibri Regular, Calibri Light fonts plus the special Heydings Icons font file to the ESPHome folder `/config/esphome/`

### Home Assistant sensors:
Below you’ll find the HA template sensors needed in `configuration.yaml `for ESPHome display to work.

Note: `media_artist` and `media_title` attributes from HA's Sonos integration *could* be swapped for some radio stations, as these attributes are split from one combined string in the stream. Some radio stations have artist - title order, others use title - artist. You really can't tell...
My danish radio stations (source list) all use the 'swapped' version, so my templates below swap these two attributes for radio stations.
Below you’ll find the HA template sensors needed in `configuration.yaml`for ESPHome display to work.

Note: `media_artist` and `media_title` attributes from HA's Sonos integration _could_ be swapped for some radio stations, as these attributes are split from one combined string in the stream. Some radio stations have artist - title order, others use title - artist. You really can't tell...
My danish radio stations (source list) all use the 'swapped' version, so my templates below swap these two attributes for radio stations.

Enter your master speaker as trigger entity ID for all templates but the first two (search for `media_player.office` and replace with your master speaker entity). Without this specific hardcoded trigger entity, templates simply doesn't always update correctly. So unfortunately they're needed for now, until I hopefully find a 'cleaner' and more dynamic solution.

Expand All @@ -89,66 +96,69 @@ sensor:
sonos_master_friendly:
friendly_name: "Sonos Master Friendly"
entity_id: group.sonos_all
value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'friendly_name') }}"
value_template: "{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'friendly_name') }}"
sonos_slaves_friendly:
friendly_name: "Sonos Slaves Friendly"
entity_id: group.sonos_all
value_template: >-
{% for entity_id in state_attr("group.sonos_all", "entity_id")[1:] -%}
{% set friendly_name = state_attr(entity_id, "friendly_name") %}
{%- if loop.last %}{{ friendly_name }}{% else %}{{ friendly_name }}, {% endif -%}
{%- endfor %}
value_template: >-
{{ "{%" }} for entity_id in state_attr("group.sonos_all", "entity_id")[1:] -%}
{{ "{%" }} set friendly_name = state_attr(entity_id, "friendly_name") %}
{{ "{%" }}- if loop.last %}{{ '{{' }} friendly_name }}{{ "{%" }} else %}{{ friendly_name }}, {{ "{%" }} endif -%}
{{ "{%" }}- endfor %}
media_title: # Swap title/artist if 'source' attribute is not present = radio
entity_id: media_player.office # Sonos master speaker
value_template: >-
{% if is_state('sensor.media_source' , "no source") %}
{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') }}
{% else %}
{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') }}
{% endif %}
{{ "{%" }} if is_state('sensor.media_source' , "no source") %}
{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') }}
{{ "{%" }} else %}
{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') }}
{{ "{%" }} endif %}
media_artist: # Swap title/artist if 'source' attribute is not present = radio
entity_id: media_player.office # Sonos master speaker
value_template: >-
{% if is_state('sensor.media_source' , "no source") %}
{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') }}
{% else %}
{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') }}
{% endif %}
{{ "{%" }} if is_state('sensor.media_source' , "no source") %}
{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_artist') }}
{{ "{%" }} else %}
{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_title') }}
{{ "{%" }} endif %}
media_album_name:
entity_id: media_player.office # Sonos master speaker
value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_album_name') }}"
value_template: "{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'media_album_name') }}"
media_source: # Remove all after 'DR P4 Fyn' as source (to fit on display)
entity_id: media_player.office # Sonos master speaker
value_template: >-
{% if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'source') %}
{{states.media_player.office.attributes.source.split('96.8')[0]}}
{% else %}
{{ "{%" }} if state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'source') %}
{{ '{{' }} states.media_player.office.attributes.source.split('96.8')[0]}}
{{ "{%" }} else %}
no source
{% endif %}
{{ "{%" }} endif %}
volume:
entity_id: media_player.office # Sonos master speaker
value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'volume_level')|float * 100 }}"
value_template: "{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'volume_level')|float * 100 }}"
sonos_master_group_entities:
entity_id: media_player.office # Sonos master speaker
value_template: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'sonos_group') }}"
value_template: "{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'sonos_group') }}"
```

### Home Assistant group:

Here you define your Sonos speaker entities. Master speaker has to be entered as first entity and all that's actually needed. Active slave speakers will dynamically be added on HA restart or when group configuration is changed (via Sonos app/HA service calls eg.) If you're only using one speaker, you still need to create the group in `groups.yaml` and populate with that single master speaker entity, as the group entity is needed in the code.

**One note on master speaker, slaves and Sonos groups**<br />
Your defined master speaker actually doesn't need to be ***the*** master speaker. As long as it's part of the group (master ***or*** slave), then display will still show data for the group. But if defined master speaker is removed from the group, it will be a 'single speaker group' on it's own, and display will reflect master speaker data only.
Your defined master speaker actually doesn't need to be **_the_** master speaker. As long as it's part of the group (master **_or_** slave), then display will still show data for the group. But if defined master speaker is removed from the group, it will be a 'single speaker group' on it's own, and display will reflect master speaker data only.

```yaml
sonos_all:
name: sonos_all
entities:
- media_player.office # This HAS to be your MASTER speaker
- media_player.office # This HAS to be your MASTER speaker
# - media_player.kitchen # Optional - SLAVE speaker #1
# - media_player.livingroom # Optional - SLAVE speaker #2
```

### Home Assistant automations:
First automation is identical with the one I've already used in my ControllerX Sonos group setup example: ([https://xaviml.github.io/controllerx/examples/sonos](https://xaviml.github.io/controllerx/examples/sonos))

First automation is identical with the one I've already used in my ControllerX Sonos group setup [example](/controllerx/examples/sonos)

Second automation is purely optional, and not really directly related to the display. It's just a quick shortcut to easily reset active speakers within group, volume and source playing to some defaults you've defined in the automation. Really nice when you have teenagers in the house, messing with active speaker entities in the group, playlists and volume all the time... 😉
The automation is written for an Ikea E1810 remote with z2m ControllerX HA integration. Here `toggle_hold`(Press and hold center button for appx. 3.5 seconds) is used as trigger.
Expand All @@ -165,7 +175,7 @@ The automation is written for an Ikea E1810 remote with z2m ControllerX HA integ
- service: group.set
data_template:
object_id: sonos_all # Name of Sonos group in groups.yaml
entities: "{{ state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'sonos_group') | join(',') }}"
entities: "{{ '{{' }} state_attr(state_attr('group.sonos_all', 'entity_id')[0], 'sonos_group') | join(',') }}"

- id: Sonos reset to defaults settings
alias: Sonos reset to defaults settings
Expand Down Expand Up @@ -195,14 +205,14 @@ The automation is written for an Ikea E1810 remote with z2m ControllerX HA integ
entity_id: media_player.office # This HAS to be your MASTER speaker
```


### ESPHome YAML configuration:

As ESPHome currently don't support attributes, all data to be displayed has to be in separate HA sensors. Hence the huge amount of sensors.

Two entities needs to be entered. Your Sonos master speaker and optional temperature sensor.
If temperature sensor is omitted, you can just delete page 3 & 4 from the display configuration.

Revise `sonos_status` and `outdoor_temp` sensors in YAML below, to match your HA entities for Sonos master speaker and outdoor temperature sensor. Create a new ESPHome node and configure it with your WiFi credentials. Edit node and copy/paste revised YAML below (from `time:` and onwards) to your node. Save it, upload and enjoy! 🎉😎
Revise `sonos_status` and `outdoor_temp` sensors in YAML below, to match your HA entities for Sonos master speaker and outdoor temperature sensor. Create a new ESPHome node and configure it with your WiFi credentials. Edit node and copy/paste revised YAML below (from `time:` and onwards) to your node. Save it, upload and enjoy! 🎉😎

```yaml
esphome:
Expand Down Expand Up @@ -233,7 +243,7 @@ time:
id: esptime

sensor:
# Outdoor temperature sensor - only used in lambda page 4
# Outdoor temperature sensor - only used in lambda page 4
- platform: homeassistant
id: outdoor_temp
entity_id: sensor.your_temperature_sensor
Expand All @@ -244,12 +254,12 @@ sensor:
internal: true

text_sensor:
# Sonos master speaker
- platform: homeassistant
# Sonos master speaker
- platform: homeassistant
id: sonos_status
entity_id: media_player.your_master_speaker
internal: true
- platform: homeassistant
- platform: homeassistant
id: media_source
entity_id: sensor.media_source
internal: true
Expand All @@ -269,7 +279,7 @@ text_sensor:
id: sonos_slaves
entity_id: sensor.sonos_slaves_friendly
internal: true

font:
- file: "Calibri Bold.ttf"
id: font_large
Expand All @@ -290,20 +300,20 @@ font:
- file: "HeydingsIconsSymbols.ttf"
id: font_icons_small
size: 11
glyphs: '067HADJabjsmx'
glyphs: "067HADJabjsmx"
- file: "HeydingsIconsSymbols.ttf"
id: font_icons_medium
size: 19
glyphs: '067HADJabjsmx'
glyphs: "067HADJabjsmx"
- file: "HeydingsIconsSymbols.ttf"
id: font_icons_14
size: 14
glyphs: '067HADJabjsmx'
glyphs: "067HADJabjsmx"

i2c:
scl: D1 # SCL - SSD1306/1309
sda: D2 # SDA - SSD1306/1309

interval:
- interval: 5s # Display period for each page
then:
Expand Down Expand Up @@ -338,7 +348,7 @@ display:
it.printf(64, 53, id(font_small_bold), TextAlign::BOTTOM_CENTER, "%s", id(sonos_master).state.c_str());
it.printf(00, 65, id(font_icons_small), TextAlign::BOTTOM_LEFT, "a"); // chain sign for slave speaker(s)
it.printf(64, 65, id(font_small), TextAlign::BOTTOM_CENTER, "%s", id(sonos_slaves).state.c_str());
- id: page2
lambda: |-
// it.rectangle(0, 0, 128, 64); For visual check of display edges when playing with different fonts
Expand All @@ -363,8 +373,7 @@ display:
} else {
it.strftime(64, 42, id(font_large), TextAlign::TOP_CENTER, "%H:%M:%S", id(esptime).now());
}

- id: page3
lambda: |-
// it.rectangle(0, 0, 128, 64); For visual check of display edges when playing with different fonts
Expand All @@ -382,8 +391,8 @@ display:
}
it.printf(107, 22, id(font_medium), TextAlign::TOP_RIGHT, "%s", id(sonos_status).state.c_str());
it.strftime(64, 42, id(font_large), TextAlign::TOP_CENTER, "%H:%M:%S", id(esptime).now());
- id: page4
- id: page4
lambda: |-
// it.rectangle(0, 0, 128, 64); For visual check of display edges when playing with different fonts
if (id(media_source).state != "no source") {
Expand All @@ -403,6 +412,7 @@ display:
```

## Future improvement plans

- Improve/simplify templates
- Remove need for master entity everywhere in config files
- Display 'shuffle' and 'mute' sign
Expand All @@ -415,14 +425,3 @@ display:
Thank you Xavi for providing the perfect solution for some of my templating issues 👍😎

_[@htvekov](https://github.com/htvekov)_


<!--stackedit_data:
eyJoaXN0b3J5IjpbODcxOTE4MzEwLC03MDAwOTQ3ODYsLTEyMz
I1MzQ3MDYsLTc5ODkwODA3NiwxMzAyMzM3NzY3LC0xNDk0NTg3
Mzg3LC03MjYxODUwNDgsMTA3ODg1NDM4MSwxNjkyMzU0MTAsLT
YxMjAyMjkwOSwxMzU0NzE3NjY3LC0xMjAyODQ4NDgyLDE4NjE2
NzcyNDcsMjEzOTU1MTYwOCwtMTk5NjU1OTYyNywyMjE4OTI5NT
IsNjY5NzgzNjIzLDgwNzAxMDQ0LC0yNjgwMTI1MjMsNjg2OTIz
NzY1XX0=
-->

0 comments on commit 9ad1b84

Please sign in to comment.