M5Stack Dial - ESP32-S3 Smart Rotary Knob

Hello, very nice! Could you share the code you used for this? It’s quite interesting. Also, the STL file for the case in this combination? Thanks in advance.

Sure, it’s definitely a work in progress, unrefined and not optimised since I’m just prototyping stuff with it - but here you go

esphome:
  name: m5dial
  friendly_name: M5Dial

globals:
  - id: screenwidth
    type: int
    restore_value: no
  - id: max_rotary_value
    type: int
    restore_value: no
    initial_value: '1'
  - id: min_rotary_value
    type: int
    restore_value: no
    initial_value: '100'

external_components:
  - source: github://the-smart-home-maker/esphome-4cello@gc9a01
    components: [ gc9a01 ]

i2c:
  sda: GPIO12
  scl: GPIO14
  id: bus_a
  #interrupt: 16

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

# Enable logging
logger:
  level: DEBUG

color:
  - id: my_red
    red: 100%
    green: 3%
    blue: 5%
  - id: my_green
    hex: 59981A
  - id: my_blue
    red: 3%
    green: 5%
    blue: 100%
  - id: my_yellow
    hex: FFFF00 
  - id: my_light_blue
    hex: 145DA0
  - id: my_light_red
    hex: fc6d6d
  - id: my_light_orange
    hex: FD7F20
  - id: my_light_yellow
    hex: B58B00

image:
  - file: mdi:volume-variant-off
    id: volume_mute
    resize: 40x40
  - file: mdi:volume-low
    id: volume_notmute
    resize: 40x40
  - file: mdi:light-recessed
    id: downlighticon
    resize: 80x80
  - file: mdi:air-conditioner
    id: airconicon
    resize: 80X80
  - file: mdi:snowflake
    id: ac_cool_40
    resize: 40x40
  - file: mdi:sun-snowflake-variant
    id: ac_heatcool_40
    resize: 40x40
  - file: mdi:fan
    id: ac_fan_40
    resize: 40x40
  - file: mdi:power-cycle
    id: power_off_icon_40
    resize: 40x40
  - file: mdi:water-percent
    id: ac_dry_40
    resize: 40x40
  - file: mdi:fire
    id: ac_heat_40
    resize: 40x40
  - file: mdi:thermometer
    id: temp_target_icon
    resize: 20x20
  - file: mdi:bullseye
    id: temp_current_icon
    resize: 20x20
  - file: mdi:snowflake
    id: ac_cool_80
    resize: 80x80
  - file: mdi:sun-snowflake-variant
    id: ac_heatcool_80
    resize: 80x80
  - file: mdi:fan
    id: ac_fan_80
    resize: 80x80
  - file: mdi:power-cycle
    id: power_off_icon_80
    resize: 80x80
  - file: mdi:water-percent
    id: ac_dry_80
    resize: 80x80
  - file: mdi:fire
    id: ac_heat_80
    resize: 80x80
  - file: mdi:knob
    id: volumeknob
    resize: 80x80

font:
  - file: "fonts/arial.ttf"
    id: my_font
    size: 40
  
  - file: "gfonts://Roboto"
    id: roboto16
    size: 16

  - file: "gfonts://Roboto"
    id: roboto20
    size: 20
  
  - file: "gfonts://Roboto"
    id: roboto24
    size: 24

# Enable Home Assistant API
api:
  encryption:
    key: !secret m4dialencryption

ota:
  password: !secret m4dialotapassword

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "M4Dial Fallback Hotspot"
    password: !secret m4fallbackpassword

captive_portal:

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

ld2410:

spi:
  mosi_pin: GPIO5
  clk_pin: GPIO6

display:
- platform: gc9a01
  reset_pin: GPIO8
  id: my_lcd
  cs_pin: GPIO7
  dc_pin: GPIO4
  rotation: 180
  pages:
    - id: page1
      lambda: |-
        if (id(dimmersensor).state == "ON")
        {
          it.start_clipping(0, (id(dimmersensor_brightness).state * -1) + 255, 250, 250);
          it.filled_circle(it.get_width() / 2, it.get_height(), 255, my_light_yellow);
          it.end_clipping();
        }
        it.image(it.get_width() / 2, it.get_height() / 2 - 55, downlighticon, ImageAlign::TOP_CENTER);
        it.printf(it.get_width() / 2, it.get_height() / 2 + 35, id(roboto24), TextAlign::TOP_CENTER, "%s", id(dimmersensor).state.c_str()); 
        it.filled_circle(it.get_width() / 2 -12, 225, 3);
        it.circle(it.get_width() / 2, 225, 3);
        it.circle(it.get_width() / 2 +12, 225, 3);
    - id: page2
      lambda: |-
        if (id(climatedevice).state == "cool")
        {
          // cooling
          it.filled_circle(it.get_width() / 2, it.get_height() / 2, 120, my_light_blue);
          it.image(it.get_width() / 2, it.get_height() / 2 + -55, ac_cool_80, ImageAlign::TOP_CENTER); 
        }
        else if (id(climatedevice).state == "heat")
        {
          // heating
          it.filled_circle(it.get_width() / 2, it.get_height() / 2, 120, my_light_red);
          it.image(it.get_width() / 2, it.get_height() / 2 + -55, ac_heat_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "auto")
        {
          // auto heat or cool
          it.filled_circle(it.get_width() / 2, it.get_height() / 2, 120, my_light_orange);
          it.image(it.get_width() / 2, it.get_height() / 2 + -55, ac_heatcool_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "fan_only")
        {
          // fan only
          it.filled_circle(it.get_width() / 2, it.get_height() / 2, 120, my_light_orange);
          it.image(it.get_width() / 2, it.get_height() / 2 + -55, ac_fan_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "dry")
        {
          // dry
          it.filled_circle(it.get_width() / 2, it.get_height() / 2, 120, my_green);
          // it.image(it.get_width() / 2 - 80, it.get_height() / 2 - 20, ac_dry_40, ImageAlign::TOP_CENTER);
          it.image(it.get_width() / 2, it.get_height() / 2 + -55, ac_dry_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "off")
        {
          // off
          it.image(it.get_width() / 2, it.get_height() / 2 + -55, power_off_icon_80, ImageAlign::TOP_CENTER);
        }
        it.printf(it.get_width() / 2, it.get_height() / 2 - 95, id(roboto24), TextAlign::TOP_CENTER, "Air-Con");
        it.printf(it.get_width() / 2 + 10, it.get_height() / 2 + 35, id(roboto24), TextAlign::TOP_CENTER, "%.0f", id(climatedevice_setpoint).state); 
        it.printf(it.get_width() / 2 + 10, it.get_height() / 2 + 65, id(roboto24), TextAlign::TOP_CENTER, "%.0f", id(climatedevice_currenttemp).state); 
        it.line(it.get_width() / 2 - 40, it.get_height() / 2 + 65, it.get_width() / 2 + 40, it.get_height() / 2 + 65);
        it.image(it.get_width() / 2 - 30, it.get_height() / 2 + 40, temp_current_icon, ImageAlign::TOP_CENTER);
        it.image(it.get_width() / 2 - 30, it.get_height() / 2 + 70, temp_target_icon, ImageAlign::TOP_CENTER);
        it.circle(it.get_width() / 2 -12, 225, 3);
        it.filled_circle(it.get_width() / 2, 225, 3);
        it.circle(it.get_width() / 2 +12, 225, 3);
    - id: page3
      lambda: |-
        if (id(mediaplayermute).state == "on")
        {
          it.image(it.get_width() / 2 + 80, it.get_height() / 2 -25, volume_mute, ImageAlign::TOP_CENTER);
        }
        else
        {
          it.image(it.get_width() / 2 + 80, it.get_height() / 2 -25, volume_notmute, ImageAlign::TOP_CENTER);
        }
        it.image(it.get_width() / 2, it.get_height() / 2 - 55, volumeknob, ImageAlign::TOP_CENTER);
        it.printf(it.get_width() / 2, it.get_height() / 2 + 35, id(roboto24), TextAlign::TOP_CENTER, "%.0f%%", id(beam_volume).state * 100); 
        it.circle(it.get_width() / 2 -12, 225, 3);
        it.circle(it.get_width() / 2, 225, 3);
        it.filled_circle(it.get_width() / 2 +12, 225, 3);
  on_page_change:
      - to: page1
        then:
          - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda return (id(dimmersensor_brightness).state / 255) * 100;
          - component.update: my_lcd
      - to: page2
        then:
          - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda return id(climatedevice_setpoint).state;
          - component.update: my_lcd
      - to: page3
        then:
          - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda 'return id(beam_volume).state * 100;'
          - component.update: my_lcd

binary_sensor:
  - platform: gpio
    pin: GPIO42
    name: "BacklightButton"
    on_press:
        - if:
            condition:
              - display.is_displaying_page: page1
            then:
              - homeassistant.service:
                  service: light.toggle
                  data:
                    entity_id: light.great_room_dimmer_4
              - component.update: my_lcd
        - if:
            condition:
              - display.is_displaying_page: page2
            then:
              if: 
                condition:
                    lambda: return id(climatedevice).state == "off";
                then:
                  - homeassistant.service:
                      service: climate.turn_on
                      data:
                        entity_id: climate.doris
                else:
                  - homeassistant.service:
                      service: climate.turn_off
                      data:
                        entity_id: climate.doris
        - if:
            condition:
              - display.is_displaying_page: page3
            then:
              if:
                condition:
                  lambda: return id(mediaplayermute).state == "off";
                then:
                  - logger.log: 
                      format: "The mediaplayer sensor reports value %s"
                      args: [ 'id(mediaplayermute).state' ]
                  - homeassistant.service:
                     service: media_player.volume_mute
                     data:
                      entity_id: media_player.living_room
                      is_volume_muted: 'true'
                else:
                  - logger.log: 
                      format: "The mediaplayer sensor reports value %s"
                      args: [ 'id(mediaplayermute).state' ]
                  - homeassistant.service:
                     service: media_player.volume_mute
                     data:
                      entity_id: media_player.living_room
                      is_volume_muted: 'false'
              

  - platform: ld2410
    has_target:
      name: Presence
    has_moving_target:
      name: Moving Target
    has_still_target:
      name: Still Target

sensor:
  - platform: rotary_encoder
    name: "Rotary Encoder"
    id: rotaryencoder
    max_value: 31
    min_value: 17
    pin_a: 
      number: GPIO40
      mode:
       input: true
       pullup: true
    pin_b: 
      number: GPIO41
      mode:
       input: true
       pullup: true
    accuracy_decimals: 1
    #on_value:
    #  - component.update: my_lcd
    on_clockwise: 
      - display.page.show_next: my_lcd
      - component.update: my_lcd
    on_anticlockwise:
      - display.page.show_previous: my_lcd
      - component.update: my_lcd
  - platform: homeassistant
    name: "Media Volume"
    id: "beam_volume"
    entity_id: media_player.living_room
    attribute: volume_level
    on_value:
      - if:
          condition:
            - display.is_displaying_page: page3
          then:
           - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda 'return id(beam_volume).state;'
      - component.update: my_lcd
  - platform: ld2410
    detection_distance:
      name: Detection Distance
      id: ddistance
      on_value: 
        then:
          if:
            condition:
              - lambda: 'return id(ddistance).state < 150;'  
            then:
              if:
                condition:
                  - light.is_off: backlight
                then:              
                  - light.turn_on: backlight
            else:
              - light.turn_off: 
                  id: backlight
                  transition_length: 1s           
    light:
      name: light
    moving_distance:
      name : Moving Distance
    still_distance:
      name: Still Distance
    moving_energy:
      name: Move Energy
    still_energy:
      name: Still Energy
  - platform: homeassistant
    name: "Dimmer Light Brightness"
    id: "dimmersensor_brightness"
    entity_id: light.great_room_dimmer_4
    attribute: brightness
    on_value:
      - if:
          condition:
            - display.is_displaying_page: page1
          then:
           - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda return (id(dimmersensor_brightness).state / 255) * 100;
      - component.update: my_lcd    
  - platform: homeassistant
    name: "Climate Device Current Temperature"
    id: "climatedevice_currenttemp"
    entity_id: climate.doris
    attribute: current_temperature
    on_value:
      - if:
          condition:
            - display.is_displaying_page: page2
          then:
           - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda return id(climatedevice_currenttemp).state;
      - component.update: my_lcd
  - platform: homeassistant
    name: "Climate Device Setpoint"
    id: "climatedevice_setpoint"
    entity_id: climate.doris
    attribute: temperature
    on_value:
      - component.update: my_lcd

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

output:
  - id: oledbacklight
    platform: ledc
    # pin: GPIO9
    pin: GPIO21
    max_power: 1
    min_power: 0

switch:
  - platform: ld2410
    engineering_mode:
      name: "engineering mode"
    bluetooth:
      name: "control bluetooth"

text_sensor:
  - platform: ld2410
    version:
      name: "firmware version"
    mac_address:
      name: "mac address"
  - platform: homeassistant
    name: "Dimmer Light Sensor"
    id: "dimmersensor"
    entity_id: light.great_room_dimmer_4
    filters:
      - to_upper:
    on_value:
      - component.update: my_lcd
  - platform: homeassistant
    name: "Climate Device"
    id: "climatedevice"
    entity_id: climate.doris
    filters:
    - map:
      - heat_cool -> auto
    on_value:
      - component.update: my_lcd 
  - platform: homeassistant
    name: "Media Player Mute"
    id: "mediaplayermute"
    entity_id: media_player.living_room
    attribute: is_volume_muted
    on_value:
      then:
        - component.update: my_lcd
4 Likes

To answer your question directly, yes. I needed to change the pin from GPIO9 (LCD_BL) to GPIO21 (RGB LED)

I could then use output.set_level to change the brightness of the screen.

output:
  - id: oledbacklight
    platform: ledc
    # pin: GPIO9
    pin: GPIO21
    max_power: 1
    min_power: 0

I could then link this to a luminosity sensor to change the brightness value depending on the surrounding light levels- (similar to the below).

- platform: homeassistant
    name: "Room Lux Sensor"
    id: luxsensor
    entity_id: sensor.sauron_illuminance
    on_value:
      - logger.log: 
          format: "The illumincance sensor reports value %.0f"
          args: [ 'id(luxsensor).state' ]
      - lambda: |- 
          float lux = id(luxsensor).state;
          if (lux < 15) 
          { 
            id(oledbacklight).set_level(0.1);
          }
          else if (lux < 50)
          {
            id(oledbacklight).set_level(0.3);
          }
          else if (lux < 125)
          {
            id(oledbacklight).set_level(0.5);
          }
          else if (lux < 200)
          {
            id(oledbacklight).set_level(0.6);
          }
          else
          {
            id(oledbacklight).set_level(1);
          }

1% brightness

30% brightness

2 Likes

Any chance you can put this info in a repo? It would be great to be able to keep up and test out your progress on this.

Sure

4 Likes

Much appreciated. I had never used ESPHome until I saw this post, and I was not looking forward to having to do any C++, so seeing that getting this thing working in this manner was a pretty huge deal, lol.


Edit, I am going to have to try and strip all that down to the bare minimum and work my way up from there. I tried to remove/change the obvious things that were non-existent in my setup, but have yet to get the backlight working. I can tell something is displayed, as I can barely see a small icon, but that’s about all I can tell that is going on.

Is there an example somewhere of a minimal template of just the necessary things that one could build up from, that anyone is aware of? Perhaps from a different but similar device?

Hey, that’s very nice! How did you connect it? Can you share a photo or a diagram so that I can connect it too?

can you share this file pleas ?

i have this erro code ?

Could not find file '/config/esphome/fonts/arial.ttf'. Please make sure it exists (full path: /config/esphome/fonts/arial.ttf).

I don’t use it in the actual code, it’s legacy, so you can either remove the reference or find it online from any of the numerous free font sites.

Couldn't find ID 'roboto24'. Please check you have defined an ID with that name in your configuration.
      lambda: !lambda |-
        float screenheight = it.get_height();
        float screenwidth = it.get_width();
        float halfscreenheight = screenheight / 2;
        float halfscreenwidth = screenwidth /2;
        if (id(climatedevice).state == "cool")
        {
          // cooling
          it.filled_circle(halfscreenwidth, halfscreenheight, 120, my_light_blue);
          it.image(halfscreenwidth, halfscreenheight + -55, ac_cool_80, ImageAlign::TOP_CENTER); 
        }
        else if (id(climatedevice).state == "heat")
        {
          // heating
          it.filled_circle(halfscreenwidth, halfscreenheight, 120, my_light_red);
          it.image(halfscreenwidth, halfscreenheight + -55, ac_heat_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "auto")
        {
          // auto heat or cool
          it.filled_circle(halfscreenwidth, halfscreenheight, 120, my_light_orange);
          it.image(halfscreenwidth, halfscreenheight + -55, ac_heatcool_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "fan_only")
        {
          // fan only
          it.filled_circle(halfscreenwidth, halfscreenheight, 120, my_light_orange);
          it.image(halfscreenwidth, halfscreenheight + -55, ac_fan_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "dry")
        {
          // dry
          it.filled_circle(halfscreenwidth, halfscreenheight, 120, my_green);
          // it.image(halfscreenwidth - 80, halfscreenheight - 20, ac_dry_40, ImageAlign::TOP_CENTER);
          it.image(halfscreenwidth, halfscreenheight + -55, ac_dry_80, ImageAlign::TOP_CENTER);
        }
        else if (id(climatedevice).state == "off")
        {
          // off
          it.image(halfscreenwidth, halfscreenheight - 55, power_off_icon_80, ImageAlign::TOP_CENTER);
        }
        it.printf(halfscreenwidth, halfscreenheight - 95, id(roboto24), TextAlign::TOP_CENTER, "Air-Con");
        it.printf(halfscreenwidth + 10, halfscreenheight + 35, id(roboto24), TextAlign::TOP_CENTER, "%.0f", id(climatedevice_setpoint).state); 
        it.printf(halfscreenwidth + 10, halfscreenheight + 65, id(roboto24), TextAlign::TOP_CENTER, "%.0f", id(climatedevice_currenttemp).state); 
        it.line(halfscreenwidth - 40, halfscreenheight + 65, it.get_width() / 2 + 40, it.get_height() / 2 + 65);
        it.image(halfscreenwidth - 30, halfscreenheight + 40, temp_current_icon, ImageAlign::TOP_CENTER);
        it.image(halfscreenwidth - 30, halfscreenheight + 70, temp_target_icon, ImageAlign::TOP_CENTER);
        it.circle(halfscreenwidth -12, 225, 3);
        it.filled_circle(halfscreenwidth, 225, 3);
        it.circle(halfscreenwidth +12, 225, 3); [source /config/esphome/m5dial.yaml:192]

this erro next ?

It’s defined in the fonts section of my file (line 149). So you will need to check what you’ve done.

Big thanks to @dgaust for sharing your code.
I stripped it back for others to play with.
Main changes in my code

  • backlight to come on upon boot
  • backlight pin changed back to 9
  • added NFC reader
  • removed arial font

I am no coder, but this should be able to help those just wanting to build from a starting block.

  name: workshop-rotary
  friendly_name: workshop-rotary
  on_boot:
    priority: 100
    then:
     - logger.log: "Startup - Backlight on"
     - lambda: |-
          id(oledbacklight).set_level(0.7);

          
# Enable Home Assistant API
api:
  encryption:
    key: !secret rotary_key

ota:
  password: !secret ota_pass

globals:
  - id: screenwidth
    type: int
    restore_value: no
  - id: max_rotary_value
    type: int
    restore_value: no
    initial_value: '1'
  - id: min_rotary_value
    type: int
    restore_value: no
    initial_value: '100'

external_components:
  - source: github://the-smart-home-maker/esphome-4cello@gc9a01
    components: ["gc9a01"]

i2c:
  - id: bus_internal
    sda: GPIO11
    scl: GPIO12
  - id: bus_porta
    sda: 13
    scl: 15
#  interrupt: 14

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

# Enable logging
logger:
  level: DEBUG


image:
  - file: mdi:volume-low
    id: volume_notmute_80
    resize: 80x80


font:  
  - file: "gfonts://Roboto"
    id: roboto16
    size: 16

  - file: "gfonts://Roboto"
    id: roboto20
    size: 20
  
  - file: "gfonts://Roboto"
    id: roboto24
    size: 24


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password


  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Rotary Fallback Hotspot"
    password: !secret fallback_pass

captive_portal:

rc522_i2c:
  i2c_id:  bus_internal
  address: 0x28
  on_tag:
    then:
      - homeassistant.tag_scanned: !lambda 'return x;'

touchscreen:
  - platform: lilygo_t5_47
    interrupt_pin: GPIO14
    i2c_id: bus_internal
    id: my_touchscreen
    on_touch:
      - logger.log: "touched"

spi:
  mosi_pin: GPIO5
  clk_pin: GPIO6

display:
- platform: gc9a01
  reset_pin: GPIO8
  id: my_lcd
  cs_pin: GPIO7
  dc_pin: GPIO4
  rotation: 90
  pages:
    - id: page1
      lambda: |-
        float screenheight = it.get_height();
        float screenwidth = it.get_width();
        float halfscreenheight = screenheight / 2;
        float halfscreenwidth = screenwidth /2;
        it.image(halfscreenwidth, halfscreenheight -55, volume_notmute_80, ImageAlign::TOP_CENTER);
        it.filled_circle(halfscreenwidth -12, 225, 3);
        it.circle(halfscreenwidth, 225, 3);
        it.circle(halfscreenwidth +12, 225, 3);

  on_page_change:
      - to: page1
        then:
          - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda 'return id(workshop_volume).state * 100;'
          - component.update: my_lcd



binary_sensor:
  - platform: gpio
    pin: GPIO42
    name: "BacklightButton"
 
sensor:
  - platform: rotary_encoder
    name: "Rotary Encoder"
    id: rotaryencoder
    max_value: 100
    min_value: 0
    pin_a: 
      number: GPIO40
      mode:
       input: true
       pullup: true
    pin_b: 
      number: GPIO41
      mode:
       input: true
       pullup: true
    accuracy_decimals: 1
    on_value:
      - lambda: |-
          id(oledbacklight).set_level(0.7);
    #  - component.update: my_lcd
    on_clockwise: 
      - display.page.show_next: my_lcd
      - component.update: my_lcd
    on_anticlockwise:
      - display.page.show_previous: my_lcd
      - component.update: my_lcd

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

output:
  - id: oledbacklight
    platform: ledc
    pin: GPIO9
    #pin: GPIO21
    max_power: 1
    min_power: 0
6 Likes

thanks for this code

i get this errro code when i want to install you code

INFO ESPHome 2023.11.6
INFO Reading configuration /config/esphome/m5stack-dial.yaml...
Failed config

display.gc9a01: [source /config/esphome/m5stack-dial.yaml:105]
  platform: gc9a01
  reset_pin: 
    number: 8
    mode: 
      output: True
      input: False
      open_drain: False
      pullup: False
      pulldown: False
    drive_strength: 20.0
    inverted: False
    ignore_strapping_warning: False
  id: my_lcd
  cs_pin: 
    number: 7
    mode: 
      output: True
      input: False
      open_drain: False
      pullup: False
      pulldown: False
    drive_strength: 20.0
    inverted: False
    ignore_strapping_warning: False
  dc_pin: 
    number: 4
    mode: 
      output: True
      input: False
      open_drain: False
      pullup: False
      pulldown: False
    drive_strength: 20.0
    inverted: False
    ignore_strapping_warning: False
  rotation: 90
  pages: 
    - id: page1
      lambda: !lambda |-
        float screenheight = it.get_height();
        float screenwidth = it.get_width();
        float halfscreenheight = screenheight / 2;
        float halfscreenwidth = screenwidth /2;
        it.image(halfscreenwidth, halfscreenheight -55, volume_notmute_80, ImageAlign::TOP_CENTER);
        it.filled_circle(halfscreenwidth -12, 225, 3);
        it.circle(halfscreenwidth, 225, 3);
        it.circle(halfscreenwidth +12, 225, 3);
  on_page_change: 
    - to: page1
      then: 
        - sensor.rotary_encoder.set_value: 
            id: rotaryencoder
            
            Couldn't find ID 'workshop_volume'. Please check you have defined an ID with that name in your configuration.
            value: !lambda |-
              return id(workshop_volume).state * 100;
        - component.update: 
            id: my_lcd
  auto_clear_enabled: True
  update_interval: 5s
  width: 240
  height: 240
  offset_y: 0
  offset_x: 0
  eight_bit_color: True

You can remove the section -

      - to: page1
        then:
          - sensor.rotary_encoder.set_value:
              id: rotaryencoder
              value: !lambda 'return id(workshop_volume).state * 100;'
          - component.update: my_lcd

workshop_volume is an entity in my HA setup.
Only left it in there to show how dgaust updated the values when the page shown was changed.

Has anyone been able to get the RTC to work. I have found a few randomize almost documentation-less examples but not been able to get them to work.

I think the issue is esphome assumes a type that these components have not implemented and even their examples will not compile for me.

I have the paper too. Anyone know w bit more about the guts here to understand what is happening? I want to use the RTC because the yaml I am working on runs this thing off of battery as a portable remote. I want to shut things down and keep the wifi off as long as possible.

I had not, but I am playing around with it since it looks like it uses the PCF8563 RTC chip PCF8563 Time Source — ESPHome

The below appears to work and will display (a currently incorrect) time

image

esphome:
  name: m4dial
  friendly_name: M4Dial
  on_boot:
    then:
      # read the RTC time once when the system boots
      - pcf8563.read_time: my_time
      - text_sensor.template.publish:
          id: template_text
          state: !lambda |-
              char str[17];
              time_t currTime = id(my_time).now().timestamp;
              strftime(str, sizeof(str), "%Y-%m-%d %H:%M", localtime(&currTime));
              return  { str };
time:
  - platform: pcf8563
    id: my_time
    address: 0x38
    # repeated synchronization is not necessary unless the external RTC
    # is much more accurate than the internal clock
    update_interval: never
  - platform: homeassistant
    # instead try to synchronize via network repeatedly ...
    on_time_sync:
      then:
        # ... and update the RTC when the synchronization was successful
        - pcf8563.write_time: my_time
        - logger.log: "time synced"
text_sensor:
  - platform: template
    name: "RTC Sensor"
    id: template_text

There are three i2c components on the internal bus at

image

0x28 is the RFID scanner
0x38 appears to be the RTC chip
0x51 is something I haven’t looked at yet

2 Likes

It’s starting to look more and more that I will have to learn some c++, and the esphome codebase, to turn these ft3267 drivers into a custom esphome component to get the touchscreen working since the pull request isn’t getting much traction

Same in regards to the screen, it is doing something curious where it needs the entire (seemingly) esphome codebase and compile time takes forever. I started to pull it apart, but it is working so…kind of meh.

I know C but never made a component. Looking at the shell of them now, but it is less about knowing C than knowing the libraries and structures expected here.

Hey, have you managed to get the RFID sensor working to add the new card and make the current card work?