How big are the images you’re including?
Not so many but maybe they were too big…
I just didn’t think a sec to this point.
It’s OK now, thank you… forced a resize to 120x120px, as this is just for testing.
- id: img_startup
file: m5dial_files/cs_smiley.png
type: RGBA
resize: 120x120 #added to save flash memory
So, after lot of digging in this post and espHome documentation: here’s my code.
Just a base for what i want to do, i only achieved the welcome page.
Code
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
manual_ip:
static_ip: !secret ip_m5stack_dial
gateway: !secret wifi_gtw
subnet: !secret wifi_sub
ap:
ssid: "m5stack-dial-fallback"
password: !secret wifi_ap_password
ota:
password: !secret ota_password
logger:
level: DEBUG
api:
encryption:
key: !secret api_encryption_key
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
then:
- light.turn_on: backlight
- delay: 3s
- display.page.show: page_01
- pcf8563.read_time: my_time
external_components:
- source: github://dgaust/esphome@gc9a01
components: [ gc9a01, ft3267 ]
refresh: 0s
<<: !include m5dial_files/colors_and_fonts.yaml
<<: !include m5dial_files/images.yaml
spi: #required by display.ili9xxx
mosi_pin: GPIO5
clk_pin: GPIO6
i2c:
- id: bus_internal #required by touchscreen.ft3267
sda: GPIO11
scl: GPIO12
scan: false
rc522_i2c: # RFiD
i2c_id: bus_internal
address: 0x28
on_tag:
then:
- rtttl.play: 'two_short:d=4,o=5,b=100:16e6,16e6'
- homeassistant.tag_scanned: !lambda 'return x;'
rtttl: # buzzer
output: rtttl_out
id: my_rtttl
output:
- id: lcdbacklight # screen backlight
platform: ledc
pin: GPIO9
min_power: 0
max_power: 1
- platform: ledc # buzzer
pin: GPIO3
id: rtttl_out
substitutions:
name: tab-m5stack-dial
friendly_name: M5Stack Dial
lux_sensor_id: sensor.ip030_mmw_aqara_living_luminance
alarm_panel_id: alarm_control_panel.alarme_maison
gate_sensor_id: binary_sensor.tplt_gate_open_too_long
windows_group_id: binary_sensor.windows_upstairs
# ======================================================================================
# ======================================================================================
# ================================================================================= time
time:
- platform: pcf8563
id: my_time
address: 0x38
update_interval: never # repeated synchronization is not necessary unless the external RTC is much more accurate than the internal clock
- platform: homeassistant
on_time_sync: # instead try to synchronize via network repeatedly ...
then:
- pcf8563.write_time: my_time # ... and update the RTC when the synchronization was successful
- logger.log: "time synced"
# ======================================================================================
# ======================================================================================
# =============================================================================== lights
light:
# ------------------------------------------------------------------ backlight
- platform: monochromatic
id: backlight
name: "Backlight"
output: lcdbacklight
default_transition_length: 500ms
# ======================================================================================
# ======================================================================================
# ============================================================================== selects
select:
- platform: template
name: Page Selector
id: page_selector
options:
- "page_off" # index 0
- "page_00" # index 1
- "page_01" # index 2
- "page_a1" # index 3
- "page_b1" # index 4
- "page_c1" # index 5
- "page_d1" # index 6
initial_option: "page_00"
optimistic: true
on_value:
- if:
condition:
- lambda: 'return id(page_selector).state == "page_off";'
then:
- light.turn_off: backlight
else:
- if:
condition:
- light.is_off: backlight
then:
- light.turn_on: backlight
- display.page.show: page_01
- if:
condition:
- lambda: 'return id(page_selector).state == "page_00";'
then:
- display.page.show: page_00
- if:
condition:
- lambda: 'return id(page_selector).state == "page_01";'
then:
- display.page.show: page_01
- if:
condition:
- lambda: 'return id(page_selector).state == "page_a1";'
then:
- display.page.show: page_a1
- if:
condition:
- lambda: 'return id(page_selector).state == "page_b1";'
then:
- display.page.show: page_b1
- if:
condition:
- lambda: 'return id(page_selector).state == "page_c1";'
then:
- display.page.show: page_c1
- if:
condition:
- lambda: 'return id(page_selector).state == "page_d1";'
then:
- display.page.show: page_d1
# ======================================================================================
# ======================================================================================
# ====================================================================== display + pages
touchscreen:
platform: ft3267
on_touch:
then:
- logger.log:
format: 'Touch ID: %d at _____________________ (%d, %d)'
args: [touch.id, touch.x, touch.y]
- if:
condition:
- light.is_off: backlight
then:
- light.turn_on: backlight
display:
- platform: ili9xxx
model: gc9a01a
reset_pin: GPIO8
id: screen
cs_pin: GPIO7
dc_pin: GPIO4
dimensions:
height: 240
width: 240
on_page_change:
- to: page_00
then:
- select.set:
id: page_selector
option: page_00
- to: page_01
then:
- select.set:
id: page_selector
option: page_01
- to: page_a1
then:
- select.set:
id: page_selector
option: page_a1
- to: page_b1
then:
- select.set:
id: page_selector
option: page_b1
- to: page_c1
then:
- select.set:
id: page_selector
option: page_c1
- to: page_d1
then:
- select.set:
id: page_selector
option: page_d1
pages:
# _____________________________________________________ startup page
- id: page_00
lambda: |-
it.image(120, 120, img_00, ImageAlign::CENTER);
# ___________________________________________________ welcome page
- id: page_01
lambda: |-
// variables =========================================================
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth /2;
// background draws ==================================================
it.filled_rectangle(0, 0, halfscreenwidth, halfscreenheight, pri_color);
it.filled_rectangle(halfscreenwidth, halfscreenheight, 155, 155, pri_color);
it.filled_rectangle(0, halfscreenheight, halfscreenwidth, 155, pri_color);
it.filled_rectangle(halfscreenwidth, 0, 155, halfscreenheight, pri_color);
it.line(0, halfscreenheight, screenwidth, halfscreenheight, off_color);
it.line(halfscreenwidth, 0, halfscreenwidth, screenheight, off_color);
// icons =============================================================
// alarm icon ----------------------------------------------------
if (id(alarm_text).state == "armed_away") {
it.image(65, 75, mdi_shield_lock, ImageAlign::CENTER, nova_color);
} else if (id(alarm_text).state == "armed_night") {
it.image(65, 75, mdi_shield_moon, ImageAlign::CENTER, nova_color);
} else if (id(alarm_text).state == "armed_home") {
it.image(65, 75, mdi_shield_account, ImageAlign::CENTER, blue_color);
} else if (id(alarm_text).state == "arming") {
it.image(65, 75, mdi_shield_sync, ImageAlign::CENTER, text_color);
} else if (id(alarm_text).state == "pending") {
it.image(65, 75, mdi_shield_lock_open, ImageAlign::CENTER, yellow_color);
} else if (id(alarm_text).state == "triggered") {
it.image(65, 75, mdi_shield_alert, ImageAlign::CENTER, red_color);
} else {
it.image(65, 75, mdi_shield_off, ImageAlign::CENTER, off_color);
}
// gate icon -----------------------------------------------------
if (id(gate_text).state == "closed") {
it.image(175, 75, mdi_gate_closed, ImageAlign::CENTER, text_color);
} else if (id(gate_text).state == "open") {
it.image(175, 75, mdi_gate_open, ImageAlign::CENTER, blue_color);
} else {
it.image(175, 75, mdi_gate_alert, ImageAlign::CENTER, yellow_color);
}
// thermostat icon -----------------------------------------------
it.image(65, 165, mdi_thermostat, ImageAlign::CENTER, text_color);
// other icon ----------------------------------------------------
if (id(windows_text).state == "off") {
it.image(175, 165, mdi_window_closed, ImageAlign::CENTER, text_color);
} else {
it.image(175, 165, mdi_window_open, ImageAlign::CENTER, blue_color);
}
// foreground draws ==================================================
it.filled_regular_polygon(halfscreenwidth, halfscreenheight -220, 144, EDGES_OCTAGON, VARIATION_FLAT_TOP, sec_color);
it.filled_regular_polygon(halfscreenwidth, halfscreenheight +220, 144, EDGES_OCTAGON, VARIATION_FLAT_TOP, sec_color);
it.regular_polygon(halfscreenwidth, halfscreenheight -220, 145, EDGES_OCTAGON, VARIATION_FLAT_TOP, off_color);
it.regular_polygon(halfscreenwidth, halfscreenheight +220, 145, EDGES_OCTAGON, VARIATION_FLAT_TOP, off_color);
// top text draw =====================================================
it.strftime(halfscreenwidth, 20, id(roboto_16_with_icons), orange_color, TextAlign::CENTER, "%d/%m %X", id(my_time).now());
// bottom text draw ==================================================
it.print(halfscreenwidth, screenheight -20, id(roboto_16_with_icons), orange_color, TextAlign::CENTER, "\U000F0046 ETEINDRE");
# ___________________________________________________ alarm page
- id: page_a1
lambda: |-
// variables ======================================================
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth /2;
// text draw ==================================================
it.print(halfscreenwidth, halfscreenheight, id(roboto_16_with_icons), orange_color, TextAlign::CENTER, "A1 PAGE");
# ___________________________________________________ gate page
- id: page_b1
lambda: |-
// variables ======================================================
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth /2;
// text draw ==================================================
it.print(halfscreenwidth, halfscreenheight, id(roboto_16_with_icons), orange_color, TextAlign::CENTER, "B1 PAGE");
# ___________________________________________________ thermostat page
- id: page_c1
lambda: |-
// variables ======================================================
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth /2;
// text draw ==================================================
it.print(halfscreenwidth, halfscreenheight, id(roboto_16_with_icons), orange_color, TextAlign::CENTER, "C1 PAGE");
# ___________________________________________________ windows page
- id: page_d1
lambda: |-
// variables ======================================================
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth /2;
// text draw ==================================================
it.print(halfscreenwidth, halfscreenheight, id(roboto_16_with_icons), orange_color, TextAlign::CENTER, "D1 PAGE");
# ======================================================================================
# ======================================================================================
# ============================================================================== sensors
sensor:
# ----------------------------------------------------------------- lux sensor
- platform: homeassistant
entity_id: $lux_sensor_id
name: "Lux Sensor"
id: sensor_lux
accuracy_decimals: 0
on_value:
- logger.log:
format: "$lux_sensor_id: %.0f"
args: [ 'id(sensor_lux).state' ]
- if:
condition:
- light.is_on: backlight
then:
- lambda: |-
float lux = id(sensor_lux).state;
if (lux < 10) {
id(lcdbacklight).set_level(0.1);
}
else if (lux < 40) {
id(lcdbacklight).set_level(0.3);
}
else if (lux < 80) {
id(lcdbacklight).set_level(0.5);
}
else {
id(lcdbacklight).set_level(0.7);
}
# ------------------------------------------------------------- rotary encoder
- platform: rotary_encoder
name: Rotary Encoder
id: rotaryencoder
resolution: 1
pin_a:
number: GPIO40
mode:
input: true
pullup: true
pin_b:
number: GPIO41
mode:
input: true
pullup: true
accuracy_decimals: 0
# accuracy_decimals: 1
on_clockwise:
- logger.log: "rotary_encoder : _____________________ turned_clockwise"
- if:
<<: &select_off
condition:
- lambda: 'return id(page_selector).state == "page_off";'
then:
- select.set:
id: page_selector
option: page_01
on_anticlockwise:
- logger.log: "rotary_encoder : _____________________ turned_ANTIclockwise"
- if:
<<: *select_off
# ======================================================================================
# ======================================================================================
# ======================================================================= binary sensors
binary_sensor:
# --------------------------------------------------------------------- button
- platform: gpio
pin:
number: GPIO42
inverted: true
name: M5 Button
on_press:
- logger.log: "button_pushed _____________________ button_pushed"
- if:
condition:
- lambda: 'return id(page_selector).state == "page_00";'
then:
- select.set:
id: page_selector
option: page_off
else:
- if:
condition:
- lambda: 'return id(page_selector).state == "page_01";'
then:
- select.set:
id: page_selector
option: page_00
else:
- select.set:
id: page_selector
option: page_01
# ================================================================= touch inputs
# -------------------------------------------------------------- startup
- platform: touchscreen
id: touch_00
internal: true
x_min: 0
x_max: 240
y_min: 0
y_max: 240
page_id: page_00
on_press:
- logger.log: "touch_input OVER page_00 : _____________________ startup"
on_release:
- if:
condition:
- light.is_on: backlight
then:
- display.page.show: page_01
# -------------------------------------------------------------- welcome
- platform: touchscreen
id: touch_01_tl
internal: true
x_min: 0
x_max: 118
y_min: 0
y_max: 118
page_id: page_01
on_press:
- logger.log: "touch_input OVER page_01 : _____________________ top_left"
on_release:
- if:
condition:
- light.is_on: backlight
then:
- display.page.show: page_a1
- platform: touchscreen
id: touch_01_tr
internal: true
x_min: 122
x_max: 240
y_min: 0
y_max: 118
page_id: page_01
on_press:
- logger.log: "touch_input OVER page_01 : _____________________ top_right"
on_release:
- if:
condition:
- light.is_on: backlight
then:
- display.page.show: page_b1
- platform: touchscreen
id: touch_01_bl
internal: true
x_min: 0
x_max: 118
y_min: 122
y_max: 240
page_id: page_01
on_press:
- logger.log: "touch_input OVER page_01 : _____________________ bottom_left"
on_release:
- if:
condition:
- light.is_on: backlight
then:
- display.page.show: page_c1
- platform: touchscreen
id: touch_01_br
internal: true
x_min: 122
x_max: 240
y_min: 122
y_max: 240
page_id: page_01
on_press:
- logger.log: "touch_input OVER page_01 : _____________________ bottom_right"
on_release:
- if:
condition:
- light.is_on: backlight
then:
- display.page.show: page_d1
# ======================================================================================
# ======================================================================================
# ============================================================================== numbers
number:
- platform: template
id: color_hue
initial_value: 0
min_value: 0
max_value: 359.9
step: 0.1
optimistic: True
- platform: template
id: color_saturation
initial_value: 0
min_value: 0
max_value: 100
step: 0.1
optimistic: True
- platform: template
id: dimmer_value
initial_value: 0
min_value: 0
max_value: 1
step: 0.001
optimistic: True
# ======================================================================================
# ======================================================================================
# ============================================================================== scripts
script:
- id: update_touch_script
mode: restart
parameters:
x: int
y: int
then:
- lambda: |-
// adjust center or coordinate plane to (120, 120)
float polar_x = x - 120;
float polar_y = 120 - y; // Screen y is opposite of cartesian.
// convert to polar coords
float r = sqrt(pow(polar_x, 2) + pow(polar_y, 2));
float theta = atan(polar_y / polar_x);
if (polar_x < 0) {
theta += M_PI; // Adjust atan for quadrants 2 & 3.
} else if (polar_y < 0) {
theta += 2 * M_PI; // Make quadrant 4 value positive.
}
// Update dimmer value.
float min_theta = M_PI * 310 / 180;
float max_theta = M_PI * 590 / 180;
// Normalize the relative positioning of the values.
float test_theta = theta;
if (test_theta < min_theta) {
test_theta += M_PI * 2;
}
ESP_LOGD("THETA", "%f < %f < %f", min_theta, test_theta, max_theta);
// page interactions
if (id(screen).get_active_page() == id(page_00)) {
id(color_hue).publish_state(360 - theta / M_PI * 180);
id(color_saturation).publish_state(min(r, (float)100));
}
if (id(screen).get_active_page() == id(page_01)) {
id(color_hue).publish_state(360 - theta / M_PI * 180);
id(color_saturation).publish_state(min(r, (float)100));
}
# - component.update: screen
# ======================================================================================
# ======================================================================================
# ============================================================================== text sensors
text_sensor:
- platform: homeassistant
id: alarm_text
entity_id: $alarm_panel_id
- platform: homeassistant
id: gate_text
entity_id: $gate_sensor_id
attribute: esphome_text_sensor
- platform: homeassistant
id: windows_text
entity_id: $windows_group_id
I didn’t find the way to reduce those parts with lambdas… Any guess?
Select component
on_value:
- if:
condition:
- lambda: 'return id(page_selector).state == "page_off";'
then:
- light.turn_off: backlight
else:
- if:
condition:
- light.is_off: backlight
then:
- light.turn_on: backlight
- display.page.show: page_01
- if:
condition:
- lambda: 'return id(page_selector).state == "page_00";'
then:
- display.page.show: page_00
- if:
condition:
- lambda: 'return id(page_selector)....... etc etc etc
Display component:
on_page_change:
- to: page_00
then:
- select.set:
id: page_selector
option: page_00
- to: page_01
then:
- select.set:
id: page_selector
option: page_01
- to: page_a1....... etc etc etc
In the video I shared a few posts up, the guy ‘Volos Projects’ has made not only a nice looking, but very responsive UI. I am not sure how he implemented it, but I am pretty sure he shares his code somewhere, maybe you could look at it and see?
This videos are only in German but the github repo is in english:
SmartHome-yourself/m5-dial-for-esphome
But has added touch switch between entities (swiping left and right) and switch between modes (swiping up and down)
https://www.youtube.com/watch?v=4dE7YONEYVk <= lights
https://www.youtube.com/watch?v=7TZfAWmYNzo <= update with more entities
Hello, has anyone already managed to operate the NFC tag via the M5Stack dial? I would like to perform this option with him. Thanks in advance
I’ve now started looking at the lvgl component that is in development for esphome
It’s only early days of bringing things across, but it’s a lot faster than the lambda display method that we needed to use
edit: updated with a loading screen and a little more visible due to colour changes (still can’t work out how to change the blue colour for the arc )
edit 2: I figured out how to change the blue colour 32s after posting the initial edit )
Yeah… Look at post #48!
that’s awesome ! could you share the code ? i have been looking on how to implement a squareline studio ui to the M5 for so long and feel like you have found the way !
You didn’t reply to anyone specific, but some of the code I use in this thread is here
The dial_lvgl is essentially what is in the video above.
Do you have something to share from Squareline?
Hey all. I’m very close to finishing up programming my first Dial. I have pages working with the dial, etc. I am basically setting up a timer that is “supposed” to buzz when it expires. For whatever reason, the buzzer isn’t making any sound. I went back to the default firmware and I can hear it beep when I click on things, so I don’t think it’s a hardware issue.
output:
- platform: ledc
pin: GPIO3
id: buzzer
sensor:
- platform: rotary_encoder
id: encoder
pin_a: GPIO40
pin_b: GPIO41
on_clockwise:
then:
- output.turn_on: buzzer
- logger.log: "Next page"
on_anticlockwise:
then:
- output.turn_off: buzzer
- logger.log: "Previous page"
I tried the above code just to simply test the buzzer, but nothing happens. In the past, I’ve used ESP dev boards and attached passive buzzers to pins and that is how I turned them on/off. Does anyone have any experiences with the buzzer on the Dial? Any thoughts/tips?
The buzzer needs to be controlled with rtttl
https://esphome.io/components/rtttl.html
on_clockwise:
- rtttl.play: quick_e:d=4,o=5,b=100:16e6
Unfortunately, there is a fair bit of blocking code so it doesn’t work perfectly.
Gotcha. I was hoping to use it without rttl. I need something that will continue to make noise until a user interacts with it. I’ll play with it though. Thanks for the response.
Any idea if there is a way to start rtttl from inside a lambda? With my countdown, it tests for the end of the countdown and takes actions inside lambdas. Oddly, Google isn’t helping me much.
Here is the section of yaml. I commented where I need the rtttl to start (and hopefully continue non-stop until the user presses a button).
lambda: |-
int mins = 0;
int secs = 0;
if (id(esptime).now().day_of_week == 1) { // it's Sunday, do nothing.
it.fill(id(Color::WHITE));
it.image(120, 60, id(cfa_logo), ImageAlign::CENTER);
it.print(120, 120, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "It's a day of rest!");
it.print(120, 160, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "No alarms until tomorrow.");
id(backlight_output).set_level(0.2);
} else if (id(esptime).now().hour < id(starttime) || id(esptime).now().hour > id(endtime)) { // After Hours
it.fill(id(Color::WHITE));
it.image(120, 60, id(cfa_logo), ImageAlign::CENTER);
it.print(120, 120, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "Good night!");
it.print(120, 160, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "No alarms");
it.print(120, 180, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "until tomorrow.");
id(countdown) = ${countdown_length};
id(backlight_output).set_level(0.2);
} else if (id(countdown) > 0) { // Countdown is active
id(countdown)--;
secs = id(countdown) % 60;
mins = (id(countdown) - secs) / 60;
it.fill(id(Color::WHITE));
it.image(120, 60, id(cfa_logo), ImageAlign::CENTER);
it.printf(120, 120, id(roboto32), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "%2d:%02d", mins, secs);
it.print(120, 175, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "Current Time");
it.strftime(120, 200, id(roboto20), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "%I:%M %p", id(esptime).now());
id(backlight_output).set_level(0.6);
} else { // Countdown has expired. Let's make some noise!
id(alarming) = "true";
// start rtttl
it.fill(id(Color::WHITE));
it.image(120, 60, id(cfa_logo), ImageAlign::CENTER);
it.print(120, 120, id(roboto32), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "Time to clean!");
it.print(120, 195, id(roboto16), id(cfa_black), TextAlign::CENTER_HORIZONTAL, "Rotate for tasks");
id(backlight_output).set_level(1.0);
}