M5Stack Dial - ESP32-S3 Smart Rotary Knob

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);
          }

Without going through all that code, I would think something like

id(my_rtttl).play("success:d=24,o=5,b=100:c,g,b");

Replace my_rtttl with whatever the id of your rtttl component is, and the string is the audio that is played

That should work, but if I was thinking about doing it, I’d create a script that plays the rtttl (and anything else I needed to do at the expiry of the countdown) and then just put

id(my_script).execute();

in the lambda.

Hi there,
is someone managed to reproduce or approach the sound that rotary encoder do on stock M5dial firmware.
I’m using this one for my tests, but it sounds like the 80’s …

rtttl.play: 'one_short:d=32,o=4,b=900:a'

I made some progress on LVGL component, and this is FASTEEEEEEEEEER than lambdas, and even look better to me!

Here's the LVGL code part (WIP):
lvgl:

  # -------------------------------------------------------------------- general
  displays:
    - my_display
  touchscreens:
    - my_touchscreen
  rotary_encoders:
    sensor: rotaryencoder
    enter_button: m5_button
  theme:
    obj:
      scrollbar_mode: "OFF"
      scrollable: false
      bg_color: pri_color
      border_width: 0
      border_color: orange_color
      radius: 10
    arc:
      arc_color: pri_color
      knob:
        bg_color: nova_color
      indicator:
        arc_color: dark_nova_color
    btn:
      bg_color: pri_color
      checked:
        bg_color: nova_color
    label:
      align: CENTER
      text_font: montserrat_18
      text_color: text_color
      clickable: false
  on_idle:
    timeout: !lambda "return (id(backlight_timeout).state * 1000);"
    then:
      - logger.log: "LVGL is idle"
      - light.turn_off: backlight
      - lvgl.pause:

  # ------------------------------------------------------------------ top layer
  top_layer:
    widgets:
      - obj:
          id: boot_screen
          width: SIZE_CONTENT 
          height: SIZE_CONTENT 
          bg_color: 0x000000
          bg_opa: COVER
          radius: 0
          pad_all: 0
          border_width: 0
          on_press:
            - lvgl.widget.hide: boot_screen
          widgets:

            - img:
                align: CENTER
                src: boot_logo
                y: -30

            - spinner:
                align: CENTER
                y: 70
                height: 50
                width: 50
                spin_time: 2s
                arc_length: 60deg
                arc_rounded: true
                arc_color: sec_color
                arc_width: 7
                indicator:
                  arc_color: blue_color
                  arc_width: 7

  # ---------------------------------------------------------------------- pages
  pages:
  
    # _____________________________________________________ page welcome
    - id: page_welcome
      <<: &page_config
        bg_color: sec_color
        scrollbar_mode: "OFF"
        scrollable: false
      widgets:
        # time
        - label:
            id: label_rtc_time
            align: TOP_MID
            text: "Heure"
            y: 5

        # date
        - label:
            id: label_rtc_date
            align: TOP_MID
            text_font: montserrat_16
            text: "Date"
            y: 28

        # MIDDLE_CONTAINER
        - obj:
            id: object_alarm_state
            align: BOTTOM_MID
            x: 0
            y: -150
            width: 250
            height: 36
            # clickable: true
            # on_press:
            #   - lvgl.page.show: page_alarm
            #   - homeassistant.service:
            #       service: input_select.select_option
            #       data:
            #         entity_id: $ha_page_selector
            #         option: page_alarm
            widgets:
              - label:
                  id: label_alarm_text
                  
                  text: "Alarme"
                  text_color: sec_color

        # LEFT_CONTAINER_1
        - obj:
            width: 150
            height: 95
            align: TOP_RIGHT
            x: -101
            y: 98
            widgets:

              ######################### COLUMN 01
              # garage
              - label:
                  id: icon_garage_state
                  <<: &icon_40_config
                    clickable: true
                    text_font: grandstander_40_regular
                    text: "x"
                  <<: &column_01
                    x: -60
                  <<: &row_01
                    align: TOP_RIGHT
                    y: -8
                  clickable: false
                  # on_long_press:
                  #   - <<: &bip
                  #       rtttl.play: 'one_short:d=32,o=4,b=900:a'

              # cargate
              - label:
                  id: icon_cargate_text
                  <<: *icon_40_config
                  <<: *column_01
                  <<: &row_02
                    align: BOTTOM_RIGHT
                    y: 2
                  clickable: false
                  # on_long_press:
                  #   - <<: *bip

              ######################### COLUMN 02
              # light
              - label:
                  id: icon_light_cour
                  <<: *icon_40_config
                  <<: &column_02
                    x: 0
                  <<: *row_01
                  on_long_press:
                    - <<: &bip
                        rtttl.play: 'one_short:d=24,o=5,b=100:g'
                    - homeassistant.service:
                        service: light.toggle
                        data:
                          entity_id: $ha_light_cour

              # handgate
              - label:
                  id: icon_handgate_text
                  <<: *icon_40_config
                  <<: *column_02
                  <<: *row_02
                  y: 4 # SPE handgate
                  on_long_press:
                    - <<: *bip
                    - homeassistant.service:
                        service: lock.unlock
                        data_template:
                          entity_id: lock.gate

        # LEFT_CONTAINER_2                          
        - obj:
            bg_opa: TRANSP
            border_opa: TRANSP
            width: 150
            height: 45
            align: TOP_RIGHT
            x: -100
            y: 195
            widgets:

              # outside temperature
              - label:
                  id: label_outside_temperature
                  x: 41
                  text_font: montserrat_20
                  text: "Temp"

              # meteo
              - label:
                  id: icon_weather_sensor
                  x: 41
                  text_font: grandstander_36_regular
                  text: "x"

        # RIGHT_1_CONTAINER
        - obj:
            bg_opa: TRANSP
            border_opa: TRANSP
            width: 110
            height: 45
            align: TOP_LEFT
            x: 147
            y: 98
            widgets:

              - label:
                  id: icon_front_door
                  <<: &icon_36_config
                    text_font: grandstander_36_regular
                    text: "x"
                  align: LEFT_MID
                  x: -5

              - label:
                  id: icon_window_group
                  <<: *icon_36_config
                  align: LEFT_MID
                  x: 40

        # RIGHT_2_CONTAINER
        - obj:
            width: 110
            height: 100
            align: TOP_LEFT
            x: 147
            y: 151
            on_long_press:
              - <<: *bip
              - lvgl.page.show: page_thermostat
              - homeassistant.service:
                  service: input_select.select_option
                  data:
                    entity_id: $ha_page_selector
                    option: page_thermostat
              - homeassistant.service:
                  service: climate.set_hvac_mode
                  data:
                    entity_id: $ha_climate_heating
                    hvac_mode: heat
            widgets:

              # temp
              - label:
                  id: label_climate_heating_current_0
                  y: -5
                  align: TOP_LEFT
                  text_font: montserrat_20
                  text: "Temp"

              # climate icon
              - label:
                  id: icon_climate_heating
                  align: TOP_LEFT
                  x: -3
                  y: 22
                  text_font: grandstander_32_regular
                  text: "x"
                    
    # __________________________________________________ page thermostat
    - id: page_thermostat
      <<: *page_config
      widgets:
        - meter:
            id: meter_climate_heating_target
            width: 210
            height: 210
            align: CENTER
            bg_opa: TRANSP
            border_width: 0
            text_color: text_color
            scales:
              range_from: 16 # = template_sensor, and arc values
              range_to: 24 # = template_sensor, and arc values
              angle_range: 250
              rotation: 145 # = arc start_angle
              ticks:
                count: 9
                width: 1
                length: 3
                color: sec_color
                major:
                  stride: 1
                  length: 1
                  color: dark_nova_color
                  label_gap: 18

        - arc:
            id: arc_climate_heating_target
            width: 210
            height: 210
            align: CENTER
            start_angle: 145 # = meter rotation value
            end_angle: 35
            min_value: 160 # idem meter range_from value * 10
            max_value: 240 # idem meter range_to value * 10
            adjustable: false

        - obj:
            id: button_climate_state_off
            align: CENTER
            width: 60
            height: 60
            radius: 60
            on_long_press:
              - <<: *bip
              - lvgl.page.show: page_welcome
              - homeassistant.service:
                  service: input_select.select_option
                  data:
                    entity_id: $ha_page_selector
                    option: page_welcome
              - homeassistant.service:
                  service: climate.set_hvac_mode
                  data:
                    entity_id: $ha_climate_heating
                    hvac_mode: "off"
            widgets:
              - label:
                  <<: *icon_36_config
                  text: "\U000F0425" # mdi:power

        - label:
            id: label_climate_heating_current_1
            text: ""
            y: 70
            text_font: montserrat_26

image
image

Have to work on the alarm page now.
EDIT : Done

4 Likes

Here’s the main part of the code (had to split code to post here):

Code
packages:
  wifi: !include common/esp_common_wifi.yaml
  device_base: !include common/esp_common_device_base.yaml

<<: !include m5dial_files/colors_and_fonts.yaml
# <<: !include m5dial_files/lvgl_images_and_icons.yaml
# ----------------------------------------------------------------------- images
image:
  - id: boot_logo
    file: m5dial_files/cs_smiley.png
    type: RGB565
    use_transparency: true
    resize: 100x100
      
wifi:
  manual_ip:
    static_ip: 192.168.2.63
  ap:
    ssid: "ip063-tab-m5stack-dial-fallback"

esphome:
  on_boot: 
    priority: 600
    then:
      - pcf8563.read_time: rtc_time
      - light.turn_on: backlight

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: arduino

api:
  on_client_connected:
    - if:
        condition:
          lambda: 'return (0 == client_info.find("Home Assistant "));'
        then:
          - delay: 1s
          - lvgl.widget.hide: boot_screen
          - lvgl.page.show: page_welcome
          - homeassistant.service:
              service: input_select.select_option
              data:
                entity_id: $ha_page_selector
                option: page_welcome
  on_client_disconnected:
    - if:
        condition:
          lambda: 'return (0 == client_info.find("Home Assistant "));'
        then:
          - lvgl.widget.show: boot_screen

external_components:
 - source: github://clydebarrow/esphome@lvgl
   refresh: 10min
   components: [ lvgl ]

# ======================================================================================
# ======================================================================================
# =========================================================================== components

i2c:
  - id: bus_internal #required by touchscreen.ft5x06
    sda: GPIO11
    scl: GPIO12
    scan: False

touchscreen:
  platform: ft5x06
  address: 0x38
  id: my_touchscreen
  on_release:
    - if:
        condition: lvgl.is_paused
        then: &lvgl_resume
          - logger.log: "LVGL resuming"
          - lvgl.resume:
          - lvgl.widget.redraw:
          - light.turn_on: backlight

uart:
  tx_pin: GPIO2
  rx_pin: GPIO1
  baud_rate: 256000
  parity: NONE
  stop_bits: 1

spi:
  mosi_pin: GPIO5
  clk_pin: GPIO6

display:
  - platform: ili9xxx
    model: gc9a01a
    auto_clear_enabled: false
    update_interval: never
    reset_pin: GPIO8
    id: my_display
    cs_pin: GPIO7
    dc_pin: GPIO4
    dimensions: 
      height: 240
      width: 240

output:
  - platform: ledc
    id: lcd_backlight
    pin: GPIO9
    min_power: 0
    max_power: 1
  - platform: ledc
    id: rtttl_out
    pin: GPIO3

light:
  - platform: monochromatic
    id: backlight
    name: "Backlight"
    output: lcd_backlight
    default_transition_length: 250ms

rtttl:
  output: rtttl_out
  id: my_rtttl

# ======================================================================================
# ======================================================================================
# ============================================================================= specials

  # -------------------------------------------------------------- substitutions
substitutions:
  name: ip063_tab_m5stack_dial
  friendly_name: M5Stack Dial
  comment: M5stack Dial / esp32-s3 (lvgl)
  log_level: DEBUG # NONE / ERROR / WARN / INFO / DEBUG (default) / VERBOSE / VERY_VERBOSE (not recommended)

  ha_presence_sensor: binary_sensor.ip030_mmw_aqara_living_presence_sensor_6
  ha_light_cour: light.shelly_one_cour
  ha_climate_heating: climate.heatpump_sdgs_ebusd
  ha_weather_sensor: weather.84_main_city
  ha_outside_temperature: sensor.ebusd_bass2_displayedoutsidetemp_tempv
  ha_page_selector: input_select.tab_m5stack_dial_page_selector
  ha_alarm_text: sensor.tplt_alarmo
  ha_alarm_state: alarm_control_panel.alarme_maison
  ha_front_door: binary_sensor.magnet_aqara_front_door_contact
  ha_window_group: binary_sensor.windows_upstairs_bedrooms
  ha_handgate_text: binary_sensor.tplt_gate_open_too_long
  ha_cargate_text: input_boolean.test
  ha_garage_state: binary_sensor.io_frient_garage_09c6_input_1

  # -------------------------------------------------------------------- globals
globals:
  - id: lambda_nova_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0xe38de3)'

  - id: lambda_blue_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0x6b93e3)'

  - id: lambda_green_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0xa6da95)'

  - id: lambda_yellow_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0xffd17a)'

  - id: lambda_orange_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0xe0752b)'

  - id: lambda_red_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0xede455c)'

  - id: lambda_off_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0x85b6078)'

  - id: lambda_text_color
    type: lv_color_t
    restore_value: no
    initial_value: 'lv_color_hex(0xcad3f5)'

# ======================================================================================
# ======================================================================================
# =============================================================================== number
number:

  # __________________________________________________ backlight timeout
  - platform: template
    name: Backlight Timeout
    optimistic: true
    id: backlight_timeout
    unit_of_measurement: "s"
    initial_value: 30
    restore_value: true
    min_value: 10
    max_value: 90
    step: 10
    mode: auto # auto / box / slider

# ======================================================================================
# ======================================================================================
# ================================================================================= time

switch:
  - platform: template
    id: switch_antiburn
    icon: mdi:television-shimmer
    optimistic: true
    entity_category: "config"
    turn_on_action:
      - logger.log: "Starting Antiburn"
      - if:
          condition: lvgl.is_paused
          then:
            - lvgl.resume:
            - lvgl.widget.redraw:
            - delay: 1s
      - lvgl.pause:
          show_snow: true
    turn_off_action:
      - logger.log: "Stopping Antiburn"
      - if:
          condition: lvgl.is_paused
          then:
            - lvgl.resume:
            - lvgl.widget.redraw:
            - delay: 1s
            - lvgl.pause:

# ======================================================================================
# ======================================================================================
# =============================================================================== switch

time:
  - platform: homeassistant
    on_time_sync:
      then:
        - pcf8563.write_time: rtc_time
        - logger.log: "Time Synced"
  - platform: pcf8563
    id: rtc_time
    address: 0x38
    update_interval: never
    on_time:
      - minutes: '*'
        seconds: 0
        then:
          - script.execute: time_update
      - hours: 2,3,4,5
        minutes: 5
        seconds: 0
        then:
          - switch.turn_on: switch_antiburn
      - hours: 2,3,4,5
        minutes: 35
        seconds: 0
        then:
          - switch.turn_off: switch_antiburn
      - minutes: '*'
        seconds: 0,10,20,30,40,50
        then:
          - lvgl.widget.hide: label_outside_temperature
          - lvgl.widget.show: icon_weather_sensor
      - minutes: '*'
        seconds: 5,15,25,35,45,55
        then:
          - lvgl.widget.show: label_outside_temperature
          - lvgl.widget.hide: icon_weather_sensor

# ======================================================================================
# ======================================================================================
# =============================================================================== script

script:
  - id: time_update
    then:
      - lvgl.label.update:
          id: label_rtc_time
          text: !lambda |-
            static char time_buf[10];
            auto now = id(rtc_time).now();
            int hour = now.hour;
            int minute = now.minute;
            snprintf(time_buf, sizeof(time_buf), "%02d:%02d", hour, minute);
            return time_buf;
      - lvgl.label.update:
          id: label_rtc_date
          text: !lambda |-
            static const char * const month_names[] = {"Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Sept.", "Oct.", "Nov.", "Dec."};
            static const char * const day_names[] = {"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"};
            static char combined_buf[20];
            auto now = id(rtc_time).now();
            snprintf(combined_buf, sizeof(combined_buf), "%s %2d %s", day_names[now.day_of_week - 1] , now.day_of_month , month_names[now.month-1]);
            return combined_buf;
            
  - id: climate_set_target_temperature
    mode: restart
    then:
      - delay: 5s
      - homeassistant.service:
          service: climate.set_temperature
          data:
            entity_id: $ha_climate_heating
            temperature: !lambda "return id(climate_heating_new_target).state;"

# ======================================================================================
# ======================================================================================
# ========================================================================== text sensor
text_sensor:

  # ------------------------------------------------------------- home assistant

  # ______________________________________________________ page selector
  - platform: homeassistant
    id: page_selector_state
    entity_id: $ha_page_selector
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - if:
          condition:
            - lambda: 'return id(page_selector_state).state == "page_welcome";'
          then:
            - lvgl.page.show: page_welcome
      - if:
          condition:
            - lambda: 'return id(page_selector_state).state == "page_thermostat";'
          then:
            - lvgl.page.show: page_thermostat

  # ________________________________________________________ alarm state
  - platform: homeassistant
    id: alarm_state
    entity_id: $ha_alarm_state
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.widget.update:
          id: object_alarm_state
          bg_color: !lambda |-
            if (x == "armed_away") {
              return id(lambda_nova_color);
            } else if (x == "armed_home") {
              return id(lambda_blue_color);
            } else if (x == "armed_night") {
              return id(lambda_nova_color);
            } else if (x == "arming" or x == "pending") {
              return id(lambda_yellow_color);
            } else if (x == "triggered") {
              return id(lambda_red_color);
            } else if (x == "disarmed") {
              return id(lambda_text_color);
            } else {
              return id(lambda_off_color);
            }

  # _________________________________________________________ alarm text
  - platform: homeassistant
    id: alarm_text
    entity_id: $ha_alarm_text
    on_value:
      - lvgl.label.update:
          id: label_alarm_text
          text:
            format: "%s"
            args: [ 'x.c_str()' ]
    
  # ___________________________________________________ front door state
  - platform: homeassistant
    id: front_door # off / on
    entity_id: $ha_front_door
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.widget.update:
          id: icon_front_door
          text_color: !lambda |-
            if (x == "off") {
              return id(lambda_text_color);
            } else if (x == "on") {
              return id(lambda_blue_color);
            } else {
              return id(lambda_off_color);
            }
      - lvgl.label.update:
          id: icon_front_door
          text: !lambda |-
            if (x == "off") {
              return "\U000F10AF"; // mdi:door-closed-lock
            } else if (x == "on") {
              return "\U000F081C"; // mdi:door-open
            } else {
              return "x";
            }
    
  # _________________________________________________ window group state
  - platform: homeassistant
    id: window_group # off / on
    entity_id: $ha_window_group
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.widget.update:
          id: icon_window_group
          text_color: !lambda |-
            if (x == "off") {
              return id(lambda_text_color);
            } else if (x == "on") {
              return id(lambda_yellow_color);
            } else {
              return id(lambda_off_color);
            }
      - lvgl.label.update:
          id: icon_window_group
          text: !lambda |-
            if (x == "off") {
              return "\U000F11DB"; // mdi:window-closed-variant
            } else if (x == "on") {
              return "\U000F11DC"; // mdi:window-open-variant
            } else {
              return "x";
            }

  # _______________________________________________________ garage state
  - platform: homeassistant
    id: garage_state # off / on
    entity_id: $ha_garage_state
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.widget.update:
          id: icon_garage_state
          text_color: !lambda |-
            if (x == "off") {
              return id(lambda_text_color);
            } else if (x == "on") {
              return id(lambda_yellow_color);
            } else {
              return id(lambda_off_color);
            }
      - lvgl.label.update:
          id: icon_garage_state
          text: !lambda |-
            if (x == "off") {
              return "\U000F12D3"; // mdi:garage-variant
            } else if (x == "on") {
              return "\U000F12D4"; // mdi:garage-open-variant
            } else {
              return "x";
            }

  # ______________________________________________________ cargate state
  - platform: homeassistant
    id: cargate_text # off / on
    entity_id: $ha_cargate_text
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.widget.update:
          id: icon_cargate_text
          text_color: !lambda |-
            if (x == "off") {
              return id(lambda_text_color);
            } else if (x == "on") {
              return id(lambda_yellow_color);
            } else {
              return id(lambda_off_color);
            }
      - lvgl.label.update:
          id: icon_cargate_text
          text: !lambda |-
            if (x == "off") {
              return "\U000F0E8B"; // mdi:boom-gate-outline
            } else if (x == "on") {
              return "\U000F0E87"; // mdi:boom-gate-alert
            } else {
              return "x";
            }

  # ______________________________________________________ handgate text
  - platform: homeassistant
    id: handgate_text # closed / open / alert
    entity_id: $ha_handgate_text
    attribute: esphome_text_sensor
    filters:
      - map:
        - closed -> Ferme
        - open -> Ouvert
        - alert -> Defaut
    on_raw_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.label.update:
          id: icon_handgate_text
          text: !lambda |-
            if (x == "closed") {
              return "\U000F0299"; // mdi:gate
            } else if (x == "open") {
              return "\U000F116A"; // mdi:gate-open
            } else if (x == "alert") {
              return "\U000F17F8"; // mdi:gate-alert
            } else {
              return "x";
            }
      - lvgl.widget.update:
          id: icon_handgate_text
          text_color: !lambda |-
            if (x == "closed") {
              return id(lambda_text_color);
            } else if (x == "open") {
              return id(lambda_yellow_color);
            } else if (x == "alert") {
              return id(lambda_red_color);
            } else {
              return id(lambda_off_color);
            }

  # ______________________________________________________ climate state
  - platform: homeassistant
    id: climate_heating_state
    entity_id: $ha_climate_heating
    on_value:
      - lvgl.label.update:
          id: icon_climate_heating
          text: !lambda |-
            if (x == "auto") {
              return "\U000F1B17"; // mdi:thermostat-auto
            } else if (x == "heat") {
              return "\U000F0238"; // mdi:fire
            } else if (x == "off") {
              return "\U000F0AD8"; // mdi:radiator-off
            } else {
              return "x";
            }
      - lvgl.widget.update:
          id: icon_climate_heating
          text_color: !lambda |-
            if (x == "auto") {
              return id(lambda_green_color);
            } else if (x == "heat") {
              return id(lambda_orange_color);
            } else if (x == "off") {
              return id(lambda_text_color);
            } else {
              return id(lambda_off_color);
            }

  # _____________________________________________________ weather sensor
  - platform: homeassistant
    id: weather_sensor
    entity_id: $ha_weather_sensor
    on_value:
      - lvgl.label.update:
          id: icon_weather_sensor
          text: !lambda |-
            if (x == "clear-night") {
              return "\U000F0594";
            } else if (x == "cloudy") {
              return "\U000F0590";
            } else if (x == "exceptional") {
              return "\U000F0F2F";
            } else if (x == "fog") {
              return "\U000F0591";
            } else if (x == "hail") {
              return "\U000F0592";
            } else if (x == "lightning") {
              return "\U000F0593";
            } else if (x == "lightning-rainy") {
              return "\U000F067E";
            } else if (x == "partlycloudy") {
              return "\U000F0595";
            } else if (x == "pouring") {
              return "\U000F0596";
            } else if (x == "rainy") {
              return "\U000F0597";
            } else if (x == "snowy") {
              return "\U000F0598";
            } else if (x == "snowy-rainy") {
              return "\U000F067F";
            } else if (x == "sunny") {
              return "\U000F0599";
            } else if (x == "windy") {
              return "\U000F059D";
            } else if (x == "windy-variant") {
              return "\U000F059E";
            } else if (x == "sunny-off") {
              return "\U000F14E4";
            } else {
              return "x";
            }

  # _________________________________________________________ light cour
  - platform: homeassistant
    id: light_cour
    entity_id: $ha_light_cour
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.label.update:
          id: icon_light_cour
          text: !lambda |-
            if (x == "on") {
              return "\U000F06E8"; // mdi:lightbulb-on
            } else if (x == "off") {
              return "\U000F0336"; // mdi:lightbulb-outline
            } else {
              return "x";
            }
      - lvgl.widget.update:
          id: icon_light_cour
          text_color: !lambda |-
            if (x == "on") {
              return id(lambda_yellow_color);
            } else if (x == "off") {
              return id(lambda_text_color);
            } else {
              return id(lambda_off_color);
            }

# ======================================================================================
# ======================================================================================
# ======================================================================== binary sensor

binary_sensor:

  # --------------------------------------------------------------------- button
  - platform: gpio
    pin:
      number: GPIO42
      inverted: true
    id: m5_button
    name: M5 Button
    on_press:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lvgl.page.show: page_welcome
      - homeassistant.service:
          service: input_select.select_option
          data:
            entity_id: $ha_page_selector
            option: page_welcome

  # ------------------------------------------------------------- home assistant

  # ____________________________________________________ presence sensor
  - platform: homeassistant
    id: presence_sensor
    entity_id: $ha_presence_sensor
    publish_initial_state: true
    on_state:
      - if:
          condition:
            - binary_sensor.is_on: presence_sensor
          then:
            - if:
                condition: lvgl.is_paused
                then: *lvgl_resume
            - lvgl.page.show: page_welcome
            - homeassistant.service:
                service: input_select.select_option
                data:
                  entity_id: $ha_page_selector
                  option: page_welcome

# ======================================================================================
# ======================================================================================
# =============================================================================== sensor
sensor:

  # ------------------------------------------------------------- rotary encoder
  - platform: 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
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume

    # ________________________________________________________ clockwise
    on_clockwise:
      - if:
          condition:
            - lambda: 'return id(page_selector_state).state == "page_thermostat";'
          then:
            - lambda: |-
                return id(climate_heating_new_target).publish_state(id(climate_heating_new_target).state + 0.5);

    # ___________________________________________________ anti-clockwise
    on_anticlockwise:
      - if:
          condition:
            - lambda: 'return id(page_selector_state).state == "page_thermostat";'
          then:
            - lambda: |-
                return id(climate_heating_new_target).publish_state(id(climate_heating_new_target).state - 0.5);

  # ------------------------------------------------------------- home assistant

  # _____________________________________________ climate heating target
  - platform: homeassistant
    id: climate_heating_target
    entity_id: $ha_climate_heating
    attribute: temperature
    on_value:
      - if:
          condition: lvgl.is_paused
          then: *lvgl_resume
      - lambda: |-
          id(climate_heating_new_target).publish_state(x);

  # ____________________________________________ climate new targetvalue
  - platform: template
    id: climate_heating_new_target
    icon: mdi:thermometer-lines
    filters:
      - clamp:
          min_value: 16 # idem meter range_from value
          max_value: 24 # idem meter range_from value
    on_value:
      - lvgl.arc.update:
          id: arc_climate_heating_target
          value: !lambda return x * 10;
      - script.execute: climate_set_target_temperature

  # ____________________________________________ climate heating current
  - platform: homeassistant
    id: climate_heating_current
    entity_id: $ha_climate_heating
    attribute: current_temperature
    on_value:
      - lvgl.label.update:
          id: label_climate_heating_current_0
          text:
            format: "%2.1f°C"
            args: [ 'x' ]
      - lvgl.label.update:
          id: label_climate_heating_current_1
          text:
            format: "%2.1f°C"
            args: [ 'x' ]

  # ________________________________________________ outside temperature
  - platform: homeassistant
    id: outside_temperature
    entity_id: $ha_outside_temperature
    on_value:
      - lvgl.label.update:
          id: label_outside_temperature
          text:
            format: "%2.1f°C"
            args: [ 'x' ]
<<: !include m5dial_files/colors_and_fonts.yaml
color:
  # Nova Colors
  - id: nova_color # pink
    hex: "e38de3"
  - id: dark_nova_color # pink
    hex: "764a76"

  - id: pri_color # light (mantle)
    hex: "24273a"
  - id: sec_color # dark (crust)
    hex: "181926"

  - id: text_color
    hex: "cad3f5"
  - id: off_color
    hex: "5b6078"

  # Color Colors
  - id: blue_color
    hex: "6b93e3"
  - id: dark_blue_color
    hex: "344b79"

  - id: green_color
    hex: "a6da95"
  - id: dark_green_color
    hex: "374825"

  - id: yellow_color
    hex: "ffd17a"

  - id: orange_color
    hex: "e0752b"
  - id: dark_orange_color
    hex: "5f3e25"

  - id: red_color
    hex: "de455c"
  - id: dark_red_color
    hex: "642b2b"

font:
  - id: roboto_16_bold
    file:
      type: gfonts
      family: Roboto
      weight: bold
    size: 16
    <<: &extras
      extras:
        - file: "fonts/materialdesignicons-webfont.ttf"
          glyphs: [
            "\U000F1999", # mdi:rotate-360
            "\U000F0741", # mdi:gesture-tap
            "\U000F0046", # mdi:arrow-down-thick
            "\U000F0594", # clear-night
            "\U000F0590", # cloudy
            "\U000F0F2F", # exceptional
            "\U000F0591", # fog
            "\U000F0592", # hail
            "\U000F0593", # lightning
            "\U000F067E", # lightning-rainy
            "\U000F0595", # partlycloudy
            "\U000F0596", # pouring
            "\U000F0597", # rainy
            "\U000F0598", # snowy
            "\U000F067F", # snowy-rainy
            "\U000F0599", # sunny
            "\U000F059D", # windy
            "\U000F059E", # windy-variant
            "\U000F14E4", # sunny-off
            "\U000F073A", # mdi:cancel
            "\U000F0425", # mdi:power
            "\U000F0238", # mdi:fire
            "\U000F1B17", # mdi:thermostat-auto
            "\U000F0AD8", # mdi:radiator-off
            "\U000F0299", # mdi:gate
            "\U000F116A", # mdi:gate-open
            "\U000F17F8", # mdi:gate-alert
            "\U000F0E8B", # mdi:boom-gate-outline
            "\U000F0E87", # mdi:boom-gate-alert
            "\U000F10AF", # mdi:door-closed-lock
            "\U000F081C", # mdi:door-open
            "\U000F11DB", # mdi:window-closed-variant
            "\U000F11DC", # mdi:window-open-variant
            "\U000F12D3", # mdi:garage-variant
            "\U000F12D4", # mdi:garage-open-variant
            "\U000F06E8", # mdi:lightbulb-on
            "\U000F0336", # mdi:lightbulb-outline
            ]
  - id: roboto_20_regular
    file:
      type: gfonts
      family: Roboto
      weight: regular
    size: 20
    <<: *extras
  - id: roboto_20_bold
    file:
      type: gfonts
      family: Roboto
      weight: bold
    size: 20
    <<: *extras
  - id: roboto_24_regular
    file: 
      type: gfonts
      family: Roboto
      weight: regular
    size: 24
    <<: *extras
  - id: roboto_24_bold
    file: 
      type: gfonts
      family: Roboto
      weight: bold
    size: 24
    <<: *extras
  - id: roboto_48_bold
    file: 
      type: gfonts
      family: Roboto
      weight: bold
    size: 48
    <<: *extras


  - id: grandstander_32_regular
    file: 
      type: gfonts
      family: Grandstander
      weight: regular
    size: 32
    <<: *extras
  - id: grandstander_36_regular
    file: 
      type: gfonts
      family: Grandstander
      weight: regular
    size: 36
    <<: *extras
  - id: grandstander_40_regular
    file: 
      type: gfonts
      family: Grandstander
      weight: regular
    size: 40
    <<: *extras
2 Likes

Hello! I have this simplified code in which I need to use the internal bus and the A bus.

i2c:
  - id: busi
    sda: GPIO11
    scl: GPIO12
    scan: True
    frequency: 400kHz

  - id: busa
    sda: GPIO13
    scl: GPIO15
    scan: True
    frequency: 400kHz


sensor:
  - platform: vl53l0x
    i2c_id: busa
    name: "VL53L0x Distance"
    update_interval: 1s


touchscreen:
  platform: ft5x06
  i2c_id: busi
  address: 0x38

spi:
  mosi_pin: GPIO5
  clk_pin: GPIO6

display:
  - platform: ili9xxx
    model: gc9a01a
    reset_pin: GPIO8
    cs_pin: GPIO7
    dc_pin: GPIO4

When I run it and it reaches the device on the A bus, it restarts.

[14:12:28][I][app:029]: Running through setup()...
[14:12:28][I][i2c.arduino:218]: Performing I2C bus recovery
[14:12:28][I][i2c.arduino:218]: Performing I2C bus recovery
[14:12:28][D][spi:039]: Setting up SPI bus...
[14:12:28][D][ili9xxx:034]: Setting up ILI9xxx
[14:12:28][D][spi_device:379]: mode 0, data_rate 40000kHz
[14:12:28][D][ili9xxx:030]: Wrote MADCTL 0x48
[14:12:28][D][vl53l0x:034]: 'VL53L0x Distance' - setup BEGIN
[14:12:33]E (6003) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
[14:12:33]E (6003) task_wdt:  - loopTask (CPU 1)
[14:12:33]E (6003) task_wdt: Tasks currently running:
[14:12:33]E (6003) task_wdt: CPU 0: IDLE
[14:12:33]E (6003) task_wdt: CPU 1: IDLE
[14:12:33]E (6003) task_wdt: Aborting.
[14:12:33]ESP-ROM:esp32s3-20210327
[14:12:33]Build:Mar 27 2021
[14:12:33]rst:0xc (RTC_SW_CPU_RST),boot:0x2b (SPI_FAST_FLASH_BOOT)
[14:12:33]Saved PC:0x4037793c
WARNING Decoded 0x4037793c: esp_restart_noos at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/port/soc/esp32s3/system_internal.c:143 (discriminator 1)
[14:12:33]SPIWP:0xee
[14:12:33]mode:DIO, clock div:1
[14:12:33]load:0x3fce3808,len:0x43c
[14:12:33]load:0x403c9700,len:0xbec
[14:12:33]load:0x403cc700,len:0x2a3c
[14:12:33]entry 0x403c98d8
[14:12:34]E (188) psram: PSRAM ID read error: 0x00ffffff

If I remove the touch, it doesn’t work. Then, if I remove the display, it doesn’t work. Finally, when I remove SPI, it works.

[14:22:54][I][app:029]: Running through setup()...
[14:22:54][I][i2c.arduino:218]: Performing I2C bus recovery
[14:22:54][I][i2c.arduino:218]: Performing I2C bus recovery
[14:22:54][D][vl53l0x:034]: 'VL53L0x Distance' - setup BEGIN
[14:22:54][D][vl53l0x:258]: 'VL53L0x Distance' - setup END
[14:22:54][D][vl53l0x:310]: 'VL53L0x Distance' - Got distance 0.054 m
[14:22:54][D][sensor:094]: 'VL53L0x Distance': Sending state 0.05400 m with 2 decimals of accuracy
[14:22:55][D][vl53l0x:310]: 'VL53L0x Distance' - Got distance 0.052 m

Please help, I don’t know what else to do.

Couldn’t help myself, and instead of completing this project, I bought one of these to test…

1 Like

Amoled screen… should look great.
Let us know!

Hi, i have some compilation probleme since a few days with lvgl.

esphome say me that the “btn” config must be rename into “button”.

and after that, i have no config error but compilation errors.

Have you any ideas ?

.piolibdeps/knob-salle-a-manger/lvgl/src/extra/widgets/animimg/lv_animimg.h:22:2: error: #error “lv_animimg: lv_img is required. Enable it in lv_conf.h (LV_USE_IMG 1)”
#error “lv_animimg: lv_img is required. Enable it in lv_conf.h (LV_USE_IMG 1)”
^~~~~
Compiling .pioenvs/knob-salle-a-manger/lib18f/WiFi/WiFiUdp.cpp.o
Compiling .pioenvs/knob-salle-a-manger/lib6ca/FS/FS.cpp.o
Compiling .pioenvs/knob-salle-a-manger/lib6ca/FS/vfs_api.cpp.o
Compiling .pioenvs/knob-salle-a-manger/libb83/Update/HttpsOTAUpdate.cpp.o
Archiving .pioenvs/knob-salle-a-manger/lib6ca/libFS.a
Compiling .pioenvs/knob-salle-a-manger/libb83/Update/Updater.cpp.o
.piolibdeps/knob-salle-a-manger/lvgl/src/extra/widgets/animimg/lv_animimg.h:37:5: error: ‘lv_img_t’ does not name a type; did you mean ‘lv_btn_t’?
lv_img_t img;
^~~~~~~~
lv_btn_t
Compiling .pioenvs/knob-salle-a-manger/libff1/ESPAsyncWebServer-esphome/AsyncEventSource.cpp.o
Archiving .pioenvs/knob-salle-a-manger/libb83/libUpdate.a
Compiling .pioenvs/knob-salle-a-manger/libff1/ESPAsyncWebServer-esphome/AsyncWebSocket.cpp.o
Compiling .pioenvs/knob-salle-a-manger/libff1/ESPAsyncWebServer-esphome/WebAuthentication.cpp.o
Archiving .pioenvs/knob-salle-a-manger/lib18f/libWiFi.a
Compiling .pioenvs/knob-salle-a-manger/libff1/ESPAsyncWebServer-esphome/WebHandlers.cpp.o
Compiling .pioenvs/knob-salle-a-manger/libff1/ESPAsyncWebServer-esphome/WebRequest.cpp.o
*** [.pioenvs/knob-salle-a-manger/src/main.cpp.o] Error 1

I’ve not done an update for a while, but I’d try a clean build files to see if it can force an update of all the backend/lvgl components

image

Have you received it yet? How does it compare to the M5Stack Dial in terms of screen?

Hard to get a great shot, but it’s sharper and better contrast. But it’s also noticeably smaller.

The dial is probably better overall, just an ugly orange and grey

3 Likes

Can you share your code. I love that background image

Hey @dgaust how did you go with the t-encoder?

I’ve had much time to work on it unfortunately. The touchscreen is still not working, and I’ve not really had an opportunity to work on converting the driver into an esphome component (also, I am most definitely not a competent c++ coder).

1 Like

Hello @mrgrlscz Steven,

I’m in testing mode of this device and starting with LGVL. Then I check your yaml files and I have questions about line using " <<: & and <<: * "
This is a kind of macro and if yes, where it’s start and where it’s finish ?
I don’t see any documentation in esphome where I saw this kind of usage.

Hi all, I am excited to read the progress you have all made collaborating and how active it is.

I am admittedly just trying to get my toes wet here, so I updated a couple samples that others have posted and

I keep getting the error “Unable to find action with the name ‘homeassistant.service’.”

I see the homeassistant.service in the yaml file but do not know what it specifically references or how to resolved this . Can somehow give a hint?

Hi, it’s called yaml anchors, not reserved for home assistant.
Here’s a (one of many) link to How it works

1 Like