diff --git a/docs/examples/sonos-display.md b/docs/examples/sonos-display.md index fd17feba..ba70aa56 100644 --- a/docs/examples/sonos-display.md +++ b/docs/examples/sonos-display.md @@ -5,34 +5,45 @@ layout: page ![Three different Sonos displays](/controllerx/assets/img/sonos_displays_2.jpg) -### Sonos/Symfonisk display unit with ESPHome +### Sonos/Symfonisk display with ESPHome - v1.1 +**Changelog at last page** ![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 😉 +Using ControllerX - Controlling your Sonos speakers have never been easier 😎 But the occasional wish for a 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/](https://esphome.io/) +All it takes is an ESP8266 with ESPHome software, an appropriate display, a handfull (well actually 2 handfulls and then some) 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/ + +**Current v1.1 display code uses newly merged display on/off and brightness commands. Use ESPHome version [1.15.0b4](https://github.com/esphome/esphome/releases/tag/v1.15.0b4) or newer !** + +YAML has been tested on both NodeMCUv2 , Wemos D1 Mini and NodeMCU with integrated display using both SSD1306 & SSD1309 displays (I2C connected). ### 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 as SSD1306, 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 🙂 + +An optional PIR sensor or microwave radar sensor can be added for automatic dimming (brightness control) and turning the display on/off completely. The RCWL 0516 sensor is cheap, but can be somewhat difficult to use in 'tight' builds as it's somewhat sensitive to many things - WiFi in particular. So you could experience some false triggers using this sensor if fitted very close to the ESP8266. AM 312 is a cheap and simple 'no nonsense' PIR sensor that just always works as expected. Sensor can also be used for other purposes as well in HA 🙂 Sensor is configured in YAML using pin D5 (GPIO14). Display is set to dim down after 5 minutes with no PIR triggers and completely off after additional 10 minutes without registered movement. + +Please note that (at least on my display version) SSD1309 display can't be turned completely off with `id(display_id).set_brightness(0)` command, but this works perfectly on my SSD1306 display. Instead SSD1309 has to be turned on/off with specific `id(display_id).turn_on()`/`id(display_id).turn_off()` commands. Check what works on **your** display and revise implementation method/ESPHome yaml config code accordingly. + +I've collected some hardware link examples below. These are just some random sellers I've picked. Not necessarily the cheapest or best sellers. -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 🙂 +[Wemos D1 mini – ESP8266](https://www.aliexpress.com/item/32845253497.html) -Some links examples for hardware below. These are just some random sellers I've picked. Not necessarily the cheapest or best sellers. +[0,96" 12864 SSD1306 OLED display](https://www.aliexpress.com/item/32896971385.html) -Wemos D1 mini – ESP8266
-[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) +[2,42” 12864 SSD1309 OLED display](https://www.aliexpress.com/item/33024448944.html) +(direct replacement for the much smaller 0,96” SSD1306 display and can use same library) -0,96" 12864 SSD1306 OLED display
-[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) +Alternatively you can use a [Wemos NodeMCU ESP8266 with integrated 0,96" OLED display](https://www.aliexpress.com/item/4000287451981.html) -2,42” 12864 SSD1309 OLED display (direct replacement for the much smaller 0,96” SSD1306 display and can use same library)
-[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) +**Optional sensors to dim display when no movement is detected.** -Or alternatively you can use a Wemos NodeMCU ESP8266 with integrated 0,96" OLED display
-[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) +Movement sensor 1: [RCWL 0516 Microwave Micro Wave Radar Sensor Board](https://www.aliexpress.com/item/33011567518.html) + +Movement sensor 2: [AM312 # PIR Motion Human Sensor](https://www.aliexpress.com/item/32921030810.html) **One note on the SSD1309 display**
-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. +In order to get display to work with 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 (some might). 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. **Connections:** **SSD1306/1309 --> Wemos D1 mini** @@ -48,43 +59,39 @@ 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: +### 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. +**All pages:** Source (if not present, display ‘Sonos/Playlist’), mute sign, + volume setting and play/pause/idle status. Also displays shuffle + sign when active for playlists **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 **Page 4:** Outdoor temperature sensor value -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/) +Calibri TTF fonts [link](https://www.fontdload.com/dl/calibri-font/) -Heydings Icons special file [link](/controllerx/assets/img/HeydingsIconsSymbols.ttf). +Heydings Icons special file [link](https://github.com/xaviml/controllerx/blob/dev/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/` +Copy Calibri Bold, Calibri Regular, Calibri Light fonts plus the special Heydings Icons Symbols 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. ESPHome will establish some four HA sensors as well, presented on HA frontend: PIR sensor, connection status, WiFi strength and display on/off sensor. If display on/off is turned off from HA, then triggering PIR will not turn on display or alter brightness. -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. +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. @@ -96,69 +103,66 @@ 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**
-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](/controllerx/examples/sonos) +First automation is identical with the one I've already used in my ControllerX Sonos group setup example [link](https://xaviml.github.io/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. @@ -175,7 +179,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 @@ -205,34 +209,51 @@ 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: +### 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. +If you're not using a movement sensor in your build, you could (but actually don't need to) revise YAML. If you experience issues with the 'floating' GPIO used for the PIR sensor, just pull pin D5 permanently low or high. + +Two entities needs to be entered. Your Sonos master speaker and optional temperature sensor. If temperature sensor is omitted, you can just revise YAML and delete page 3 & 4 from the display lambda configuration. Also remember to revise `interval` page count from 4 to 2. + +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 ESP8266 board settings and WiFi credentials. Edit node and copy/paste revised YAML below to your node. Remember to insert your node's autogenerated WiFi ap settings to YAML. 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! 🎉😎 +**One final note on current YAML configuration** +ESPHome is at **max** with all these included sensors, schedulers running and the quite extensive display lambda. Addding just one extra sensor to current YAML, will make ESPHome crash on boot. Omitting `fast_connect: true`from WiFi configuration in YAML will also send ESPHome into an eternal stack trace error boot loop 🚀💀 +So 'tweak' YAML with care! 😁😉 ```yaml +substitutions: + devicename: sonos_display + friendly_name: Sonos Display + device_description: Sonos SSD1306/1309 display for Sonos groups + esphome: - name: ssd1309 + name: $devicename + comment: ${device_description} platform: ESP8266 board: d1_mini - + wifi: - ssid: "your ssid" - password: "your password" - - # Enable fallback hotspot (captive portal) in case wifi connection fails + ssid: "your_ssid" + password: "your_password" + fast_connect: true # Mandatory for fast WiFi connect to avoid stack trace error on boot + manual_ip: + static_ip: 192.168.XX.XX # Enter your static IP address. Needed for fast WiFi connect to avoid stack trace error on boot + gateway: 192.168.XX.XX # Enter your gateway + subnet: 255.255.255.0 # Enter your subnet + + # Enable fallback hotspot (captive portal) in case wifi connection fails. Replace with your own node settings ap: - ssid: "Ssd1309 Fallback Hotspot" - password: "Autogenerated password" + ssid: "Sonos Display Fallback Hotspot" + password: "your_autogenerated_password" + captive_portal: # Enable logging logger: - + # Enable Home Assistant API api: @@ -242,44 +263,144 @@ time: - platform: homeassistant id: esptime +switch: + # ** Not used - Currently ESPHome can't handle more sensors/switches than already installed *** + #- platform: restart + # name: "${friendly_name} Restart" + # icon: "mdi:restart" + - platform: template + name: "${friendly_name} On/Off" + id: sonos_display + turn_on_action: + - switch.template.publish: + id: sonos_display + state: ON + - lambda: |- + id(sonos).turn_on(); + id(sonos).set_brightness(1); + turn_off_action: + - switch.template.publish: + id: sonos_display + state: OFF + - lambda: |- + id(sonos).turn_off(); + sensor: - # Outdoor temperature sensor - only used in lambda page 4 + # Outdoor temperature sensor - only used in display lambda page 4 - platform: homeassistant id: outdoor_temp entity_id: sensor.your_temperature_sensor internal: true + - platform: homeassistant id: sonos_volume entity_id: sensor.volume internal: true + + # Create WiFi signal sensor in HA + - platform: wifi_signal + name: "${friendly_name} WiFi Signal" + update_interval: 60s 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 + - platform: homeassistant id: media_artist entity_id: sensor.media_artist internal: true + - platform: homeassistant id: media_title entity_id: sensor.media_title internal: true + + # ** Not yet used - Currently ESPHome can't handle more sensors than already installed *** + #- platform: homeassistant + # id: media_album_title + # entity_id: sensor.media_album_title // Not in use yet + # internal: true + - platform: homeassistant id: sonos_master entity_id: sensor.sonos_master_friendly internal: true + - platform: homeassistant id: sonos_slaves entity_id: sensor.sonos_slaves_friendly internal: true +binary_sensor: + - platform: homeassistant + id: mute + entity_id: binary_sensor.is_volume_muted + internal: true + + - platform: homeassistant + id: shuffle + entity_id: binary_sensor.shuffle + internal: true + + - platform: gpio + pin: D5 + name: "${friendly_name} PIR" + device_class: motion + on_press: + then: + - binary_sensor.template.publish: + id: dim_display + state: ON + - binary_sensor.template.publish: + id: display_off + state: ON + on_release: + then: + - binary_sensor.template.publish: + id: dim_display + state: OFF + - binary_sensor.template.publish: + id: display_off + state: OFF + + # Create HA connected sensor + - platform: status + name: "${friendly_name} Status" + + - platform: template + id: dim_display + filters: + - delayed_off: 5min # Dim display after 5 minutes + on_press: # brightness is float (from 0 to 1). 1 = 100% + then: + - lambda: |- + if (id(sonos_display).state == true) { + id(sonos).turn_on(); + id(sonos).set_brightness(1); + } + on_release: # brightness is float (from 0 to 1). 0.01 = 1% + then: + - lambda: |- + id(sonos).set_brightness(0.01); + + - platform: template + id: display_off + filters: + - delayed_off: 15min # Turn off display after 15 minutes + on_release: + then: + - lambda: |- + id(sonos).turn_off(); + font: - file: "Calibri Bold.ttf" id: font_large @@ -298,130 +419,129 @@ font: size: 12 glyphs: '!%"()+,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz█£$@æøåÆØÅ&#''´’?üöäé' - file: "HeydingsIconsSymbols.ttf" - id: font_icons_small - size: 11 - glyphs: "067HADJabjsmx" + id: font_icons_large + size: 23 + glyphs: '0679HADJabjsmx' - file: "HeydingsIconsSymbols.ttf" id: font_icons_medium size: 19 - glyphs: "067HADJabjsmx" + glyphs: '0679HADJabjsmx' - file: "HeydingsIconsSymbols.ttf" id: font_icons_14 size: 14 - glyphs: "067HADJabjsmx" - -i2c: - scl: D1 # SCL - SSD1306/1309 - sda: D2 # SDA - SSD1306/1309 + glyphs: '0679HADJabjsmx' + - file: "HeydingsIconsSymbols.ttf" + id: font_icons_small + size: 11 + glyphs: '0679HADJabjsmx' + +globals: + - id: display_page + type: int + restore_value: no + initial_value: '0' # On first boot, value=0 initiates display.turn_on() command. Can't run as on_boot command interval: - - interval: 5s # Display period for each page + - interval: 5s # Show each page for 5 seconds then: - - display.page.show_next: sonos - - component.update: sonos - + - lambda: |- + if (id(display_page) == 0) { + id(sonos_display).turn_on(); + } + if (id(display_page) < 4) { + id(display_page)++; + } else { + id(display_page) = 1; + } + +i2c: + sda: D1 + scl: D2 + frequency: 100khz # Default 50kHz. Min. setting at 100kHz needed. Otherwise lambda is so slow that warnings appear in log + display: - platform: ssd1306_i2c model: "SSD1306 128x64" - reset_pin: D0 # RST - SSD1306/1309. Used to turn on/off display - address: 0x3C # Default I2C adress - brightness: 100% # Select brightness + reset_pin: D0 + address: 0x3C # Default address for most SSD1306/1309 displays + brightness: 100% + update_interval: 1s id: sonos pages: - - id: page1 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") { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "%.12s", id(media_source).state.c_str()); // + it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "%.12s", id(media_source).state.c_str()); } else { + if (id(display_page) == 1 or (id(display_page) == 3)) { it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "Sonos"); // if no source list attribute, display Sonos Playlist instead + } else { + it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "Playlist"); // if no source list attribute, display Sonos Playlist instead + } + if (id(shuffle).state) { + it.printf(127, 17, id(font_icons_large), TextAlign::BOTTOM_RIGHT, "x"); // shuffle playlist sign at top right position + } } - it.printf(0, 20, id(font_icons_medium), TextAlign::TOP_LEFT, "m"); // speaker on sign - it.printf(21, 22, id(font_medium), TextAlign::TOP_LEFT, "%.f", id(sonos_volume).state); - if (id(sonos_status).state != "playing") { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "7"); // pause sign - } else { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "6"); // play sign - } - it.printf(107, 22, id(font_medium), TextAlign::TOP_RIGHT, "%s", id(sonos_status).state.c_str()); - it.printf(00, 53, id(font_icons_small), TextAlign::BOTTOM_LEFT, "s"); // star sign for master speaker - 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 - if (id(media_source).state != "no source") { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "%.12s", id(media_source).state.c_str()); // + if (id(mute).state) { + it.printf(0, 20, id(font_icons_medium), TextAlign::TOP_LEFT, "0"); // speaker mute sign } else { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "Playlist"); // if no source list attribute, display 'Sonos Playlist' instead + it.printf(0, 20, id(font_icons_medium), TextAlign::TOP_LEFT, "m"); // speaker on sign } - it.printf(0, 20, id(font_icons_medium), TextAlign::TOP_LEFT, "m"); // speaker on sign it.printf(21, 22, id(font_medium), TextAlign::TOP_LEFT, "%.f", id(sonos_volume).state); - if (id(sonos_status).state != "playing") { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "7"); // pause sign + if (id(sonos_status).state == "playing") { + it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "6"); // pause sign + } else if (id(sonos_status).state == "paused") { + it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "7"); // play sign } else { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "6"); // play sign + it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "9"); // stop sign } it.printf(107, 22, id(font_medium), TextAlign::TOP_RIGHT, "%s", id(sonos_status).state.c_str()); - if (id(media_title).state != "None") { - it.printf(73, 53, id(font_small), TextAlign::BOTTOM_CENTER, "%.24s", id(media_artist).state.c_str()); - it.printf(73, 65, id(font_small), TextAlign::BOTTOM_CENTER, "%.24s", id(media_title).state.c_str()); - it.printf(00, 53, id(font_icons_small), TextAlign::BOTTOM_LEFT, "A"); // person sign (artist) - it.printf(01, 63, id(font_icons_14), TextAlign::BOTTOM_LEFT, "j"); // note sign (title) - } else { + + if (id(display_page) == 1) { + it.printf(00, 53, id(font_icons_small), TextAlign::BOTTOM_LEFT, "s"); // star sign for master speaker + 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()); + } else if (id(display_page) == 2) { + if (id(media_title).state != "None") { + it.printf(73, 53, id(font_small), TextAlign::BOTTOM_CENTER, "%.24s", id(media_title).state.c_str()); + it.printf(73, 65, id(font_small), TextAlign::BOTTOM_CENTER, "%.24s", id(media_artist).state.c_str()); + it.printf(00, 51, id(font_icons_14), TextAlign::BOTTOM_LEFT, "j"); // note sign (title) + it.printf(00, 65, id(font_icons_small), TextAlign::BOTTOM_LEFT, "A"); // person sign (artist) + } else { + it.strftime(64, 42, id(font_large), TextAlign::TOP_CENTER, "%H:%M:%S", id(esptime).now()); + } + } else if (id(display_page) == 3) { 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 - if (id(media_source).state != "no source") { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "%.12s", id(media_source).state.c_str()); // - } else { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "Sonos"); // if no source list attribute, display Sonos Playlist instead - } - it.printf(0, 20, id(font_icons_medium), TextAlign::TOP_LEFT, "m"); // speaker on sign - it.printf(21, 22, id(font_medium), TextAlign::TOP_LEFT, "%.f", id(sonos_volume).state); - if (id(sonos_status).state != "playing") { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "7"); // pause sign } else { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "6"); // play sign + it.printf(64, 42, id(font_large), TextAlign::TOP_CENTER, "Out: %.1f°C", id(outdoor_temp).state); } - 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 - 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") { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "%.12s", id(media_source).state.c_str()); // - } else { - it.printf(64, 0, id(font_large), TextAlign::TOP_CENTER, "Playlist"); // if no source list attribute, display Sonos Playlist instead - } - it.printf(0, 20, id(font_icons_medium), TextAlign::TOP_LEFT, "m"); // speaker on sign - it.printf(21, 22, id(font_medium), TextAlign::TOP_LEFT, "%.f", id(sonos_volume).state); - if (id(sonos_status).state != "playing") { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "7"); // pause sign - } else { - it.printf(127, 18, id(font_icons_medium), TextAlign::TOP_RIGHT, "6"); // play sign - } - it.printf(107, 22, id(font_medium), TextAlign::TOP_RIGHT, "%s", id(sonos_status).state.c_str()); - it.printf(64, 42, id(font_large), TextAlign::TOP_CENTER, "Out: %.1f°C", id(outdoor_temp).state); ``` -## Future improvement plans - -- Improve/simplify templates +## Change log +- Intitial version published July, 2020 +- v1.1 published September, 2020 +- Added optional sensor for brightness control & display on/off +- Added 'shuffle' & 'mute' signs +- When idle, display 'stop' sign and 'idle' text +- Revised display lambda page code +- Four ESPHome sensors exposed in HA + - Connected status + - WiFi strength + - Display on/off + - PIR + +Future plans: +- Design 2,9" E-paper display version +- Improve/simplify HA sensor templates (if possible) - Remove need for master entity everywhere in config files -- Display 'shuffle' and 'mute' sign -- Implement brightness control on display -- Implement display on/off (via local ESPHome PIR sensor) -- Add more information on display using a 2,9" E-ink display -- Add png file for current media playing for e-paper displays -- Use e-paper display as status display when Sonos is idle Thank you Xavi for providing the perfect solution for some of my templating issues 👍😎 +September, 2020 _[@htvekov](https://github.com/htvekov)_ + + + \ No newline at end of file