Hi there!
I have made my own integration using ESPHome. If anyone is interested, here you can find the full code and instructions.
Hope you like it!
Hi there!
I have made my own integration using ESPHome. If anyone is interested, here you can find the full code and instructions.
Hope you like it!
@Brano69 @EnsconcE I was able to fix the error by changing the C++ lambda and was able to compile under ESPHome 2025.5.2. Maybe there are some breaking changes in the newer ESPHome releases.
I have raised an issue on GitHub Compile error and solution · Issue #2 · Skons/M5Dial · GitHub
Pretty basic and I need to upload the transparent web icons although these are easy to find online and are in the esphome directory directly. YAML needs some tweaking. Hope to have some updates. Not a developer so a lot of it is pieces (specifically the LVGL) from others I managed to get working together.
Please help!!!
The installation of âMr. Avocadoâ proceeded without any problems. The values of the state of the vacuum cleaner, lights, air conditioner loaded and loaded without problems and displayed on the displayâŠHowever, when I want to set a value (give a command) from the display nothing happensâŠIf you can help!!!
âŠ
I solved it
I forgot to enable Allow the device to perform Home Assistant actions"
I am glad you got It working!
This one has popped up in more threads probably then any other issue regarding whyy something in HA canât be controlled from an ESPHome device. I had the same issue with the first device that either controlled or read sensor data from HA and not something directly attached to the ESP32 pins. I know itâs documented but itâs just one of those things deep in the settings were most donât go. I think I spent a good 2 hours the first time, assuming I was doing something wrong, before doing a search and immediate face palm after checking one box resolved everything. Wasnât even aware it was an option until then. The good thing is you will never forget it in the future.
Hello to all
Iâm using this code for a several time that is working like a charme, until some new update for the ESPHome BuilderâŠ
Now I have this errors when I try to perform the update availabl via ESPHome Builder: `*****************************************************************
44 | #include âNetwork.hâ
| ^~~~~~~~~~~
compilation terminated.
*** [.pioenvs/m5dial/lib64d/WiFi/WiFi.cpp.o] Error 1
*** [.pioenvs/m5dial/lib64d/WiFi/WiFiAP.cpp.o] Error 1
In file included from /data/cache/platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiSTA.h:30,
from /data/cache/platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFi.h:34,
from /data/cache/platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/AP.cpp:7:
/data/cache/platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiGeneric.h:44:10: fatal error: Network.h: No such file or directory
44 | #include âNetwork.hâ
| ^~~~~~~~~~~
compilation terminated.
*** [.pioenvs/m5dial/lib64d/WiFi/AP.cpp.o] Error 1`
Can some one please help me in oder to solve this issue? Thank you
Too late haha.
Spent a while wrestling with IP addresses and ended up using the serial port and usb connection to flash instead of ESPhome/web. The ESPhome integration doesnât require a static IP⊠right?
I / AI have referenced many different configs and have eventually ended up with this Music Assistant/Home Assistant media controller (although I think you can change it to use a non music assistant action and playlist easily enough). If anyone is interested I can put it on github properly.
You use the rotary to select a option/button, and the M5 button (the one on the front of the rotary) to click âenterâ. The touch screen is not used.
Brightness and screen are exposed to home assistant for two-way sync, e.g in automations like dimming the screen at night. Accent colour can use a HA sensor that contains a hex code, or a hardcoded one.
substitutions:
wifi_ssid: "REDACTED_WIFI_SSID"
wifi_password: "REDACTED_WIFI_PASSWORD"
api_encryption_key: "REDACTED_API_ENCRYPTION_KEY"
ota_password: "REDACTED_OTA_PASSWORD"
node_name: dial
friendly_node_name: Dial
media_player_id: media_player.speakers
svc_play_pause: media_player.media_play_pause
svc_next: media_player.media_next_track
svc_prev: media_player.media_previous_track
svc_volume_set: media_player.volume_set
svc_shuffle_set: media_player.shuffle_set
svc_repeat_set: media_player.repeat_set
svc_ma_play_playlist: music_assistant.play_media
accent_color: sensor.accent_colour # hex codes also work
volume_step: "0.05"
playlist_slots: "4"
playlist_0_name: "Playlist 1"
playlist_1_name: "Playlist 2"
playlist_2_name: "Playlist 3"
playlist_3_name: "Playlist 4"
playlist_4_name: "-"
playlist_5_name: "-"
playlist_0_id: "library://playlist/REDACTED_1" # music assistant library urls
playlist_1_id: "library://playlist/REDACTED_2"
playlist_2_id: "library://playlist/REDACTED_3"
playlist_3_id: "library://playlist/REDACTED_4"
playlist_4_id: ""
playlist_5_id: ""
screen_rotation_degrees: "90"
esphome:
name: ${node_name}
friendly_name: ${friendly_node_name}
platformio_options:
board_build.flash_mode: dio
on_boot:
then:
- lambda: |-
pinMode(46, OUTPUT);
digitalWrite(46, HIGH);
- pcf8563.read_time: rtctime
- light.turn_on:
id: display_backlight
brightness: 80%
- lambda: |-
id(allow_ui_sync) = true;
- script.execute: sync_page
esp32:
board: m5stack-stamps3
variant: esp32s3
framework:
type: arduino
flash_size: 8MB
psram:
mode: octal
speed: 80MHz
logger:
level: WARN
baud_rate: 115200
hardware_uart: USB_SERIAL_JTAG
api:
encryption:
key: ${api_encryption_key}
ota:
- platform: esphome
password: ${ota_password}
wifi:
ssid: ${wifi_ssid}
password: ${wifi_password}
power_save_mode: none
on_connect:
then:
- if:
condition:
lambda: "return id(allow_ui_sync);"
then:
- script.execute: sync_page
http_request:
useragent: esphome-m5dial
timeout: 10s
external_components:
- source: github://dgaust/esphome@gc9a01
components: [gc9a01]
refresh: never
globals:
- id: allow_ui_sync
type: bool
restore_value: no
initial_value: "false"
- id: ui_mode
type: int
restore_value: no
initial_value: "0"
- id: menu_index
type: int
restore_value: no
initial_value: "0"
- id: playlist_index
type: int
restore_value: no
initial_value: "0"
- id: shuffle_is_on
type: bool
restore_value: no
initial_value: "false"
- id: repeat_mode
type: int
restore_value: no
initial_value: "0"
- id: vol_target
type: float
restore_value: no
initial_value: "0.5"
- id: marquee_tick
type: int
restore_value: no
initial_value: "0"
- id: media_position_est
type: float
restore_value: no
initial_value: "0.0"
- id: media_position_est_last_ms
type: uint32_t
restore_value: no
initial_value: "0"
i2c:
- id: internal_i2c
sda: GPIO11
scl: GPIO12
scan: false
spi:
id: spi_bus
mosi_pin: GPIO5
clk_pin: GPIO6
font:
- id: font_small
file: "gfonts://Roboto"
size: 14
glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :-_./%|><*,&'\""
- id: font_medium
file: "gfonts://Roboto"
size: 18
glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:-_ '\"()%|><*,/&"
- id: font_large
file: "gfonts://Roboto"
size: 22
glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:-_ '\"()%|><*,/&"
- id: font_xlarge
file: "gfonts://Roboto"
size: 38
glyphs: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:-_ '\"()%|><*,/&"
image:
- file: mdi:arrow-left
id: ic_back
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:skip-backward
id: ic_prev
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:skip-forward
id: ic_next
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:volume-high
id: ic_volume
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:shuffle
id: ic_shuffle_on
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:shuffle-disabled
id: ic_shuffle_off
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:format-list-bulleted
id: ic_playlist
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:play-pause
id: ic_play_pause
type: BINARY
transparency: chroma_key
resize: 28x28
- file: mdi:repeat-off
id: ic_repeat_off
type: BINARY
transparency: chroma_key
resize: 26x26
- file: mdi:repeat
id: ic_repeat
type: BINARY
transparency: chroma_key
resize: 26x26
- file: mdi:repeat-once
id: ic_repeat_once
type: BINARY
transparency: chroma_key
resize: 26x26
output:
- platform: ledc
pin: GPIO9
id: backlight_output
light:
- platform: monochromatic
id: display_backlight
output: backlight_output
default_transition_length: 0s
internal: true
number:
- platform: template
name: "Backlight Brightness"
id: backlight_brightness
optimistic: true
min_value: 10
max_value: 100
step: 1
initial_value: 80
restore_value: true
set_action:
- light.turn_on:
id: display_backlight
brightness: !lambda "return x / 100.0f;"
select:
- platform: template
name: "Current Page"
id: page_select
optimistic: true
options:
- "Now Playing"
- "Volume"
- "Playlists"
initial_option: "Now Playing"
set_action:
- lambda: |-
if (x == "Now Playing") {
id(ui_mode) = 0;
} else if (x == "Volume") {
id(ui_mode) = 1;
} else {
id(ui_mode) = 2;
}
- script.execute: sync_page
interval:
- interval: 100ms
then:
- if:
condition:
lambda: "return id(allow_ui_sync) && id(ui_mode) == 0;"
then:
- lambda: "id(marquee_tick)++;"
- component.update: round_display
- interval: 500ms
then:
- lambda: |-
auto state = id(media_state).state;
float p = id(media_position).state;
float d = id(media_duration).state;
uint32_t now = millis();
if (state == "playing") {
if (!isnan(p) && p > 0.01f) {
id(media_position_est) = p;
id(media_position_est_last_ms) = now;
} else {
if (id(media_position_est_last_ms) == 0) {
id(media_position_est_last_ms) = now;
} else {
float dt = (now - id(media_position_est_last_ms)) / 1000.0f;
if (dt > 0.0f && dt < 5.0f)
id(media_position_est) += dt;
id(media_position_est_last_ms) = now;
}
}
if (!isnan(d) && d > 0.0f && id(media_position_est) > d)
id(media_position_est) = d;
} else if (state == "paused") {
if (!isnan(p) && p >= 0.0f)
id(media_position_est) = p;
id(media_position_est_last_ms) = now;
} else {
if (!isnan(p) && p >= 0.0f)
id(media_position_est) = p;
id(media_position_est_last_ms) = 0;
}
time:
- platform: pcf8563
id: rtctime
i2c_id: internal_i2c
address: 0x51
update_interval: never
- platform: homeassistant
id: esptime
on_time_sync:
then:
- pcf8563.write_time: rtctime
display:
- platform: gc9a01
id: round_display
cs_pin: GPIO7
reset_pin: GPIO8
dc_pin: GPIO4
rotation: ${screen_rotation_degrees}
update_interval: never
pages:
- id: PlayingPage
lambda: |-
const int cx = 120;
auto BG = Color(0x00, 0x00, 0x00);
auto FG = Color(0xFF, 0xFF, 0xFF);
auto DIM = Color(0xFF, 0xFF, 0xFF);
auto BTN = Color(0xFF, 0xFF, 0xFF);
auto SEL = Color(0xFF, 0xFF, 0xFF);
auto DARK = Color(0x00, 0x00, 0x00);
auto ACC = Color(0x7D, 0xD3, 0xFC);
int acc_r = 0, acc_g = 0, acc_b = 0;
const char *acc_src = id(accent_color_text).state.c_str();
if (sscanf(acc_src, "#%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3 ||
sscanf(acc_src, "%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3 ||
sscanf("${accent_color}", "#%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3 ||
sscanf("${accent_color}", "%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3) {
ACC = Color(acc_r, acc_g, acc_b);
}
bool paused = id(media_state).state == "paused";
it.fill(BG);
char title_buf[64];
char artist_buf[64];
auto build_scroll = [&](const std::string &src, int window_chars, char *out, size_t out_sz) {
std::string s = src;
if (s.empty())
s = "-";
if ((int) s.size() <= window_chars) {
snprintf(out, out_sz, "%s", s.c_str());
return;
}
std::string gap = " ";
std::string padded = s + gap;
int span = (int) padded.size();
int off = id(marquee_tick) % span;
std::string loop = padded + padded;
std::string view = loop.substr(off, window_chars);
snprintf(out, out_sz, "%s", view.c_str());
};
// Large title font needs a tighter char window so long titles still marquee.
build_scroll(id(media_title).state, 11, title_buf, sizeof(title_buf));
std::string artist_raw = id(media_artist).state;
if (artist_raw.empty())
artist_raw = "No media";
build_scroll(artist_raw, 16, artist_buf, sizeof(artist_buf));
it.printf(cx, 24, id(font_medium), FG, TextAlign::TOP_CENTER, "%s", artist_buf);
it.printf(cx, 56, id(font_xlarge), FG, TextAlign::TOP_CENTER, "%s", title_buf);
char pos_buf[8];
char dur_buf[8];
float p = id(media_position).state;
float d = id(media_duration).state;
if (isnan(p) || p <= 0.0f) {
p = id(media_position_est);
if (id(media_state).state == "playing" && id(media_position_est_last_ms) > 0) {
float dt = (millis() - id(media_position_est_last_ms)) / 1000.0f;
if (dt > 0.0f && dt < 120.0f)
p += dt;
}
}
if (isnan(p) || p < 0) {
snprintf(pos_buf, sizeof(pos_buf), "--:--");
} else {
int ps = (int) p;
snprintf(pos_buf, sizeof(pos_buf), "%02d:%02d", (ps / 60) % 100, ps % 60);
}
if (isnan(d) || d < 0) {
snprintf(dur_buf, sizeof(dur_buf), "--:--");
} else {
int ds = (int) d;
snprintf(dur_buf, sizeof(dur_buf), "%02d:%02d", (ds / 60) % 100, ds % 60);
}
it.printf(cx, 126, id(font_medium), FG, TextAlign::CENTER, "%s / %s", pos_buf, dur_buf);
float v = id(media_volume).state;
if (isnan(v))
v = 0.0f;
if (v < 0.0f)
v = 0.0f;
if (v > 1.0f)
v = 1.0f;
int pct = (int) (v * 100.0f + 0.5f);
it.printf(cx, 158, id(font_medium), FG, TextAlign::CENTER, "%d%%", pct);
// Left->right visual order: playlist, volume, previous, play/pause, next, shuffle, repeat.
const int arc_cx = 120;
const int arc_cy = 121;
const int arc_r = 84;
const int count = 7;
const float start_deg = 170.0f;
const float end_deg = 10.0f;
for (int i = 0; i < 7; i++) {
float t = (count <= 1) ? 0.0f : (float) i / (count - 1);
float a = (start_deg + t * (end_deg - start_deg)) * 3.14159265f / 180.0f;
int bx = (int) roundf(arc_cx + cosf(a) * arc_r);
int by = (int) roundf(arc_cy + sinf(a) * arc_r);
bool sel = id(menu_index) == i;
int r = sel ? 22 : 18;
Color face = sel ? SEL : BTN;
if (i == 5 && id(shuffle_is_on))
face = ACC;
if (i == 6 && id(repeat_mode) > 0)
face = ACC;
if (i == 3 && paused)
face = ACC;
it.filled_circle(bx, by, r, face);
if (i == 0) {
it.image(bx, by, id(ic_playlist), ImageAlign::CENTER, DARK);
} else if (i == 1) {
it.image(bx, by, id(ic_volume), ImageAlign::CENTER, DARK);
} else if (i == 2) {
it.image(bx, by, id(ic_prev), ImageAlign::CENTER, DARK);
} else if (i == 3) {
it.image(bx, by, id(ic_play_pause), ImageAlign::CENTER, DARK);
} else if (i == 4) {
it.image(bx, by, id(ic_next), ImageAlign::CENTER, DARK);
} else if (i == 5) {
it.image(bx, by, id(shuffle_is_on) ? id(ic_shuffle_on) : id(ic_shuffle_off), ImageAlign::CENTER, DARK);
} else if (i == 6) {
if (id(repeat_mode) == 2) {
it.image(bx, by, id(ic_repeat_once), ImageAlign::CENTER, DARK);
} else if (id(repeat_mode) == 1) {
it.image(bx, by, id(ic_repeat), ImageAlign::CENTER, DARK);
} else {
it.image(bx, by, id(ic_repeat_off), ImageAlign::CENTER, DARK);
}
}
}
- id: VolumePage
lambda: |-
auto BG = Color(0x00, 0x00, 0x00);
auto FG = Color(0xFF, 0xFF, 0xFF);
auto TRACK = Color(0xFF, 0xFF, 0xFF);
auto ACTIVE = Color(0x7D, 0xD3, 0xFC);
int acc_r = 0, acc_g = 0, acc_b = 0;
const char *acc_src = id(accent_color_text).state.c_str();
if (sscanf(acc_src, "#%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3 ||
sscanf(acc_src, "%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3 ||
sscanf("${accent_color}", "#%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3 ||
sscanf("${accent_color}", "%02x%02x%02x", &acc_r, &acc_g, &acc_b) == 3) {
ACTIVE = Color(acc_r, acc_g, acc_b);
}
it.fill(BG);
float v = id(media_volume).state;
if (isnan(v))
v = 0.0f;
if (v < 0.0f)
v = 0.0f;
if (v > 1.0f)
v = 1.0f;
int pct = (int) (v * 100.0f + 0.5f);
const int steps = 110;
int lit = (int) roundf(v * steps);
// Circular slider: dark full track + bright active portion from bottom clockwise.
for (int i = 0; i < steps; i++) {
float a = (90.0f + (i * 360.0f / steps)) * 3.14159265f / 180.0f;
int x1 = (int) roundf(120.0f + cosf(a) * 98.0f);
int y1 = (int) roundf(120.0f + sinf(a) * 98.0f);
int x2 = (int) roundf(120.0f + cosf(a) * 114.0f);
int y2 = (int) roundf(120.0f + sinf(a) * 114.0f);
Color c = (i < lit) ? ACTIVE : TRACK;
it.line(x1, y1, x2, y2, c);
}
if (lit > 0) {
float ka = (90.0f + ((lit - 1) * 360.0f / steps)) * 3.14159265f / 180.0f;
int kx = (int) roundf(120.0f + cosf(ka) * 114.0f);
int ky = (int) roundf(120.0f + sinf(ka) * 114.0f);
it.filled_circle(kx, ky, 6, ACTIVE);
}
it.printf(120, 122, id(font_xlarge), FG, TextAlign::CENTER, "%d%%", pct);
it.filled_circle(120, 188, 22, FG);
it.image(120, 188, id(ic_back), ImageAlign::CENTER, Color(0x00, 0x00, 0x00));
- id: PlaylistPage
lambda: |-
auto BG = Color(0x00, 0x00, 0x00);
auto FG = Color(0xFF, 0xFF, 0xFF);
auto DIM = Color(0xFF, 0xFF, 0xFF);
it.fill(BG);
int slots = ${playlist_slots};
if (slots > 6)
slots = 6;
if (slots < 1)
slots = 1;
const int visible = 4;
int cursor = id(playlist_index);
if (cursor < 0)
cursor = 0;
if (cursor > slots)
cursor = slots;
int anchor = cursor;
if (anchor >= slots)
anchor = slots - 1;
int start = 0;
if (slots > visible) {
start = anchor - visible / 2;
if (start < 0)
start = 0;
if (start > slots - visible)
start = slots - visible;
}
for (int row = 0; row < visible; row++) {
int idx = start + row;
if (idx >= slots)
break;
bool sel = idx == cursor;
const char *name = "-";
switch (idx) {
case 0:
name = "${playlist_0_name}";
break;
case 1:
name = "${playlist_1_name}";
break;
case 2:
name = "${playlist_2_name}";
break;
case 3:
name = "${playlist_3_name}";
break;
case 4:
name = "${playlist_4_name}";
break;
case 5:
name = "${playlist_5_name}";
break;
}
it.printf(52, 32 + row * 34, sel ? id(font_large) : id(font_medium),
sel ? FG : DIM, TextAlign::TOP_LEFT, "%s", name);
}
bool back_sel = cursor == slots;
int rr = back_sel ? 24 : 20;
it.filled_circle(120, 206, rr, FG);
it.image(120, 206, id(ic_back), ImageAlign::CENTER, Color(0x00, 0x00, 0x00));
sensor:
- platform: homeassistant
id: media_volume
entity_id: ${media_player_id}
attribute: volume_level
on_value:
then:
- component.update: round_display
- platform: homeassistant
id: media_position
entity_id: ${media_player_id}
attribute: media_position
on_value:
then:
- lambda: |-
if (!isnan(x) && x >= 0.0f) {
id(media_position_est) = x;
id(media_position_est_last_ms) = millis();
}
- component.update: round_display
- platform: homeassistant
id: media_duration
entity_id: ${media_player_id}
attribute: media_duration
on_value:
then:
- component.update: round_display
- platform: rotary_encoder
id: encoder
resolution: 1
pin_a:
number: GPIO40
mode:
input: true
pullup: true
pin_b:
number: GPIO41
mode:
input: true
pullup: true
on_clockwise:
then:
- logger.log: "Encoder CW"
- script.execute: handle_rotate_cw
on_anticlockwise:
then:
- logger.log: "Encoder CCW"
- script.execute: handle_rotate_ccw
text_sensor:
- platform: homeassistant
id: accent_color_text
entity_id: ${accent_color}
internal: true
on_value:
then:
- component.update: round_display
- platform: homeassistant
id: media_title
entity_id: ${media_player_id}
attribute: media_title
on_value:
then:
- lambda: |-
float p = id(media_position).state;
id(media_position_est) = (!isnan(p) && p >= 0.0f) ? p : 0.0f;
id(media_position_est_last_ms) = millis();
- component.update: round_display
- platform: homeassistant
id: media_artist
entity_id: ${media_player_id}
attribute: media_artist
on_value:
then:
- component.update: round_display
- platform: homeassistant
id: media_state
entity_id: ${media_player_id}
on_value:
then:
- component.update: round_display
- platform: homeassistant
id: media_shuffle_text
entity_id: ${media_player_id}
attribute: shuffle
on_value:
then:
- lambda: |-
auto s = id(media_shuffle_text).state;
bool on = (s == "on" || s == "True" || s == "true" || s == "1");
id(shuffle_is_on) = on;
- component.update: round_display
- platform: homeassistant
id: media_repeat_text
entity_id: ${media_player_id}
attribute: repeat
on_value:
then:
- lambda: |-
auto s = id(media_repeat_text).state;
if (s == "one" || s == "track" || s == "single") {
id(repeat_mode) = 2;
} else if (s == "all" || s == "context" || s == "on") {
id(repeat_mode) = 1;
} else {
id(repeat_mode) = 0;
}
- component.update: round_display
binary_sensor:
- platform: gpio
id: front_button
internal: true
pin: GPIO42
filters:
- delayed_on: 12ms
- delayed_off: 12ms
on_press:
then:
- logger.log: "Dial button (GPIO42) pressed"
- script.execute: handle_button
script:
- id: apply_volume_target
then:
- homeassistant.service:
service: ${svc_volume_set}
data:
entity_id: ${media_player_id}
volume_level: !lambda "return id(vol_target);"
- script.execute: sync_page
- id: sync_page
mode: restart
then:
- if:
condition:
lambda: "return id(allow_ui_sync);"
then:
- if:
condition:
lambda: "return id(ui_mode) == 0;"
then:
- lambda: |-
id(page_select).publish_state("Now Playing");
- display.page.show: PlayingPage
else:
- if:
condition:
lambda: "return id(ui_mode) == 1;"
then:
- lambda: |-
id(page_select).publish_state("Volume");
- display.page.show: VolumePage
else:
- lambda: |-
id(page_select).publish_state("Playlists");
- display.page.show: PlaylistPage
- component.update: round_display
- id: handle_rotate_cw
then:
- lambda: |-
const int slots = ${playlist_slots};
if (id(ui_mode) == 0) {
id(menu_index) = (id(menu_index) + 6) % 7;
} else if (id(ui_mode) == 1) {
float v = id(media_volume).state;
if (isnan(v))
v = 0.0f;
float step = static_cast<float>(atof("${volume_step}"));
float nv = std::min(1.0f, v + step);
id(vol_target) = nv;
} else if (id(ui_mode) == 2) {
int s = slots;
if (s < 1)
s = 1;
id(playlist_index) = (id(playlist_index) + 1) % (s + 1);
}
- if:
condition:
lambda: "return id(ui_mode) == 1;"
then:
- homeassistant.service:
service: ${svc_volume_set}
data:
entity_id: ${media_player_id}
volume_level: !lambda "return id(vol_target);"
- script.execute: sync_page
- id: handle_rotate_ccw
then:
- lambda: |-
const int slots = ${playlist_slots};
if (id(ui_mode) == 0) {
id(menu_index) = (id(menu_index) + 1) % 7;
} else if (id(ui_mode) == 1) {
float v = id(media_volume).state;
if (isnan(v))
v = 0.0f;
float step = static_cast<float>(atof("${volume_step}"));
float nv = std::max(0.0f, v - step);
id(vol_target) = nv;
} else if (id(ui_mode) == 2) {
int s = slots;
if (s < 1)
s = 1;
id(playlist_index) = (id(playlist_index) + s) % (s + 1);
}
- if:
condition:
lambda: "return id(ui_mode) == 1;"
then:
- homeassistant.service:
service: ${svc_volume_set}
data:
entity_id: ${media_player_id}
volume_level: !lambda "return id(vol_target);"
- script.execute: sync_page
- id: handle_button
then:
- if:
condition:
lambda: "return id(ui_mode) == 1;"
then:
- lambda: "id(ui_mode) = 0;"
- script.execute: sync_page
else:
- if:
condition:
lambda: "return id(ui_mode) == 2;"
then:
- if:
condition:
lambda: "return id(playlist_index) >= ${playlist_slots};"
then:
- lambda: "id(ui_mode) = 0;"
- script.execute: sync_page
else:
- script.execute: play_selected_playlist
- lambda: "id(ui_mode) = 0;"
- script.execute: sync_page
else:
- script.execute: main_menu_action
- id: main_menu_action
then:
- if:
condition:
lambda: "return id(menu_index) == 0;"
then:
- lambda: "id(ui_mode) = 2;"
- script.execute: sync_page
- if:
condition:
lambda: "return id(menu_index) == 1;"
then:
- lambda: "id(ui_mode) = 1;"
- script.execute: sync_page
- if:
condition:
lambda: "return id(menu_index) == 2;"
then:
- homeassistant.service:
service: ${svc_prev}
data:
entity_id: ${media_player_id}
- if:
condition:
lambda: "return id(menu_index) == 3;"
then:
- homeassistant.service:
service: ${svc_play_pause}
data:
entity_id: ${media_player_id}
- if:
condition:
lambda: "return id(menu_index) == 4;"
then:
- homeassistant.service:
service: ${svc_next}
data:
entity_id: ${media_player_id}
- if:
condition:
lambda: "return id(menu_index) == 5;"
then:
- homeassistant.service:
service: ${svc_shuffle_set}
data:
entity_id: ${media_player_id}
shuffle: !lambda "return !id(shuffle_is_on);"
- if:
condition:
lambda: "return id(menu_index) == 6;"
then:
- if:
condition:
lambda: "return id(repeat_mode) == 0;"
then:
- homeassistant.service:
service: ${svc_repeat_set}
data:
entity_id: ${media_player_id}
repeat: all
- if:
condition:
lambda: "return id(repeat_mode) == 1;"
then:
- homeassistant.service:
service: ${svc_repeat_set}
data:
entity_id: ${media_player_id}
repeat: one
- if:
condition:
lambda: "return id(repeat_mode) == 2;"
then:
- homeassistant.service:
service: ${svc_repeat_set}
data:
entity_id: ${media_player_id}
repeat: "off"
- id: play_selected_playlist
then:
- if:
condition:
lambda: "return id(playlist_index) == 0 && strlen(\"${playlist_0_id}\") > 3;"
then:
- homeassistant.service:
service: ${svc_ma_play_playlist}
data:
entity_id: ${media_player_id}
media_type: playlist
media_id: ${playlist_0_id}
- if:
condition:
lambda: "return id(playlist_index) == 1 && strlen(\"${playlist_1_id}\") > 3;"
then:
- homeassistant.service:
service: ${svc_ma_play_playlist}
data:
entity_id: ${media_player_id}
media_type: playlist
media_id: ${playlist_1_id}
- if:
condition:
lambda: "return id(playlist_index) == 2 && strlen(\"${playlist_2_id}\") > 3;"
then:
- homeassistant.service:
service: ${svc_ma_play_playlist}
data:
entity_id: ${media_player_id}
media_type: playlist
media_id: ${playlist_2_id}
- if:
condition:
lambda: "return id(playlist_index) == 3 && strlen(\"${playlist_3_id}\") > 3;"
then:
- homeassistant.service:
service: ${svc_ma_play_playlist}
data:
entity_id: ${media_player_id}
media_type: playlist
media_id: ${playlist_3_id}
- if:
condition:
lambda: "return id(playlist_index) == 4 && strlen(\"${playlist_4_id}\") > 3;"
then:
- homeassistant.service:
service: ${svc_ma_play_playlist}
data:
entity_id: ${media_player_id}
media_type: playlist
media_id: ${playlist_4_id}
- if:
condition:
lambda: "return id(playlist_index) == 5 && strlen(\"${playlist_5_id}\") > 3;"
then:
- homeassistant.service:
service: ${svc_ma_play_playlist}
data:
entity_id: ${media_player_id}
media_type: playlist
media_id: ${playlist_5_id}
What parts did you use for the Schneider frame? It looks good!
The Schneider ones, I cut a hole in a 'fake socket/shutter' plate (in France this is the p/n: S520666) , attached the Dial on it and that's it.