Skip to content

Instantly share code, notes, and snippets.

@widget
Last active February 6, 2026 22:13
Show Gist options
  • Select an option

  • Save widget/32588e0da3c24db6a3442a6b931e7402 to your computer and use it in GitHub Desktop.

Select an option

Save widget/32588e0da3c24db6a3442a6b931e7402 to your computer and use it in GitHub Desktop.
# Enables display with a few pages
# starting - shows friendly_name
# clock - shows time
# off - blank
# play - scrolls title and artist
#
# Something in the display lambda is very expensive and really slows down
# the main loop. To combat that, this runs the CPU faster
defaults:
font_small: 14
api:
on_client_connected:
then:
- display.page.show: page_clock
- component.update: spi_display
esp32:
# override CPU freq
cpu_frequency: 240MHz
logger:
logs:
# don't log missing glyph errors
font: ERROR
spi:
clk_pin: GPIO18
mosi_pin: GPIO23
miso_pin: GPIO19
id: spi_bus_display
time:
- platform: homeassistant
id: esptime
font:
- file: 'gfonts://Material+Symbols+Outlined'
id: icons_small
size: ${font_small}
glyphs:
- "\ue019" # album
- "\ue01a" # artist
- "\U000fffd8" # music notes
- file:
type: gfonts
family: Ubuntu
weight: regular
id: text_small
size: ${font_small}
glyphsets:
- GF_Latin_Core # European alphabets too
# For the clock page
- file:
type: gfonts
family: Roboto
weight: regular
id: text_big
size: 40
glyphs: [' ', ':', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
text_sensor:
# These don't have names, they don't need sharing back to HA (MA knows)
- platform: sendspin
type: title
id: ss_title
- platform: sendspin
type: artist
id: ss_artist
- platform: sendspin
type: album
id: ss_album
switch:
- platform: template
name: "Screen"
id: switch_screen
optimistic: true
restore_mode: ALWAYS_ON
turn_on_action:
- if:
condition:
api.connected:
then:
- if:
condition:
media_player.is_idle: ${speaker_id}
then:
- display.page.show: page_clock
else:
- display.page.show: page_play
- component.update: spi_display
turn_off_action:
- display.page.show: page_off
- component.update: spi_display
media_player:
- platform: speaker_source
id: !extend ${speaker_id}
on_play:
- display.page.show: page_play
- component.update: spi_display
- switch.template.publish:
id: switch_screen
state: ON
on_idle:
- display.page.show: page_clock
- component.update: spi_display
display:
- platform: ssd1306_spi
model: SH1106 128x64
id: spi_display
spi_id: spi_bus_display
cs_pin:
number: ${display_cs}
ignore_strapping_warning: true
dc_pin: ${display_dc}
reset_pin: ${display_reset}
update_interval: 200ms
data_rate: 20MHz
pages:
- id: page_startup
lambda: |-
it.printf(it.get_width() / 2, 20,
id(text_small), TextAlign::CENTER, "${friendly_name}");
it.printf(it.get_width() / 2, 40,
id(text_small), TextAlign::CENTER, "connecting...");
# Selected by api.on_connection
- id: page_clock
lambda: |-
// display clock
auto hours = id(esptime).now().hour;
auto minutes = id(esptime).now().minute;
auto seconds = id(esptime).now().second;
auto dot = seconds % 2 == 0 ? " " : ":";
it.printf(it.get_width() / 2,
it.get_height() / 2,
id(text_big),
TextAlign::CENTER,
"%d%s%02d", hours, dot, minutes);
- id: page_off
lambda: return;
# Selected by the speaker on_play
- id: page_play
lambda: |-
const int PADDING_LEFT = 18, PADDING_RIGHT = 28;
const int LINE_HEIGHTS[] = {10, 32, 54};
int x1, y1, new_h, new_w,
title_width_offset = PADDING_LEFT,
artist_width_offset = PADDING_LEFT,
album_width_offset = PADDING_LEFT;
bool increment = false;
static int iteration = 0;
// Draw some icons
it.print(0, LINE_HEIGHTS[0],
id(icons_small),
TextAlign::CENTER_LEFT,
"\U000fffd8"); // music icon
it.print(0, LINE_HEIGHTS[1],
id(icons_small),
TextAlign::CENTER_LEFT,
"\ue01a"); // artist icon
it.print(0, LINE_HEIGHTS[2],
id(icons_small),
TextAlign::CENTER_LEFT,
"\ue019"); // album icon
it.start_clipping( Rect(PADDING_LEFT,
0,
it.get_width(),
it.get_height()));
it.get_text_bounds(PADDING_LEFT, 0,
id(ss_title).state.c_str(),
id(text_small),
TextAlign::CENTER_LEFT,
&x1, &y1, &new_w, &new_h);
if (new_w > it.get_width()) {
auto missing_artist_text = new_w - it.get_width();
increment = true;
title_width_offset = PADDING_LEFT - (iteration % (missing_artist_text + PADDING_RIGHT));
}
it.print(title_width_offset, LINE_HEIGHTS[0],
id(text_small),
TextAlign::CENTER_LEFT,
id(ss_title).state.c_str());
// Do this again for the artist
it.get_text_bounds(PADDING_LEFT, 0,
id(ss_artist).state.c_str(),
id(text_small),
TextAlign::CENTER_LEFT,
&x1, &y1, &new_w, &new_h);
if (new_w > it.get_width()) {
auto missing_title_text = new_w - it.get_width();
increment = true;
artist_width_offset = PADDING_LEFT - (iteration % (missing_title_text + PADDING_RIGHT));
}
it.print(artist_width_offset, LINE_HEIGHTS[1],
id(text_small),
TextAlign::CENTER_LEFT,
id(ss_artist).state.c_str());
// And the album
it.get_text_bounds(PADDING_LEFT, 0,
id(ss_album).state.c_str(),
id(text_small),
TextAlign::CENTER_LEFT,
&x1, &y1, &new_w, &new_h);
if (new_w > it.get_width()) {
auto missing_album_text = new_w - it.get_width();
increment = true;
album_width_offset = PADDING_LEFT - (iteration % (missing_album_text + PADDING_RIGHT));
}
it.print(album_width_offset, LINE_HEIGHTS[2],
id(text_small),
TextAlign::CENTER_LEFT,
id(ss_album).state.c_str());
it.end_clipping();
if (increment) {
iteration++;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment