ESPHome LVGL vs openHASP

I recently undertook the porting of a smart touchscreen switch from openHASP to ESPHome LVGL. My objective was to decide whether it was worth keeping on using openHASP or switch to ESPHome entirely. I have multiple ESPHome devices in my system, and having one update mechanism for everything was enticing as this smart touchscreen was an orphan. Better investigate which way to go before I bring in some more (I have three more on order).

Disclaimer: this is a summary of a recent experience. It is not a full benchmarking exercise and I am not a GUI nor a LVGL expert. I didn’t try every possible things. I got things to a point they work for me.

I am using the US version of the Lanbon L8.

The ESPHome LVGL design is one the left, the openHASP design is on the right. I kept the exact same design because it works and to have a fair comparison. Apologies for the boob light.

Feedback welcome.

2 Likes

OPENHASP

openHASP is a complete binary that you flash to the switch plate then requires two configuration files: a JSON file for the design that gets uploaded to the device, and a YAML file located in /config/openhasp for the HA integration:

{"page":0,"id":1,"obj":"label","x":10,"y":5,"h":30,"w":62,"text":"00:00","text_color":"white","align":0,"bg_color":"#2C3E50"}
{"page":0,"id":3,"obj":"label","x":180,"y":5,"h":30,"w":45,"text":"00.0°F","text_color":"white","align":2,"bg_color":"#2C3E50"}

{"page":1,"id":1,"obj":"btn","x":10,"y":30,"w":105,"h":90,"toggle":true,"text":"\uE335\nB'way","text_font":32,"align":1,"text_color01":"orange"}
{"page":1,"id":2,"obj":"btn","x":125,"y":30,"w":105,"h":90,"toggle":true,"text":"\uE335\nGate","text_font":32,"align":1,"text_color01":"orange"}
{"page":1,"id":21,"obj":"btn","x":198,"y":30,"w":32,"h":32,"toggle":false,"text":"\uE493","text_font":24,"align":1}
{"page":1,"id":3,"obj":"btn","x":10,"y":125,"w":105,"h":90,"toggle":true,"text":"\uE335\nPatio","text_font":32,"align":1,"text_color01":"orange"}
{"page":1,"id":31,"obj":"btn","x":83,"y":125,"w":32,"h":32,"toggle":false,"text":"\uE493","text_font":24,"align":1}
{"page":1,"id":4,"obj":"btn","x":125,"y":125,"w":105,"h":90,"toggle":true,"text":"\uE769\nWall","text_font":32,"align":1,"text_color01":"orange"}
{"page":1,"id":5,"obj":"btn","x":10,"y":220,"w":105,"h":90,"toggle":true,"text":"\uF2BA\nParty","text_font":32,"align":1,"text_color01":"orange"}
{"page":1,"id":51,"obj":"btn","x":83,"y":220,"w":32,"h":32,"toggle":false,"text":"\uE493","text_font":24,"align":1}
{"page":1,"id":6,"obj":"btn","x":125,"y":220,"w":105,"h":90,"toggle":true,"text":"\uEAAC\nW'Wall","text_font":32,"align":1,"text_color01":"orange"}

{"page":2,"id":0,"back":1}
{"page":2,"id":1,"obj":"label","x":20,"y":50,"w":105,"h":90,"text":"Party\nLights","text_color":"#FFFFFF","radius":0,"border_side":0,"text_font":32,"align":1}
{"page":2,"id":2,"obj":"btn","action":"back","x":0,"y":270,"w":240,"h":50,"bg_color":"#2C3E50","text":"\uE2DC","text_color":"#FFFFFF","radius":0,"border_side":0,"text_font":32}
{"page":2,"id":3,"obj":"btn","x":10,"y":160,"w":105,"h":90,"toggle":true,"text":"\uE425","text_font":32,"align":1}
{"page":2,"id":4,"obj":"slider","x":180,"y":50,"w":30,"h":200,"max":255}

{"page":3,"id":0,"back":1}
{"page":3,"id":1,"obj":"label","x":20,"y":50,"w":105,"h":90,"text":"Gate\nSconces","text_color":"#FFFFFF","radius":0,"border_side":0,"text_font":32,"align":1}
{"page":3,"id":2,"obj":"btn","action":"back","x":0,"y":270,"w":240,"h":50,"bg_color":"#2C3E50","text":"\uE2DC","text_color":"#FFFFFF","radius":0,"border_side":0,"text_font":32}
{"page":3,"id":3,"obj":"btn","x":10,"y":160,"w":105,"h":90,"toggle":true,"text":"\uE425","text_font":32,"align":1}
{"page":3,"id":4,"obj":"slider","x":180,"y":50,"w":30,"h":200,"max":255}

{"page":4,"id":0,"back":1}
{"page":4,"id":1,"obj":"label","x":20,"y":50,"w":105,"h":90,"text":"Patio\nEaves","text_color":"#FFFFFF","radius":0,"border_side":0,"text_font":32,"align":1}
{"page":4,"id":2,"obj":"btn","action":"back","x":0,"y":270,"w":240,"h":50,"bg_color":"#2C3E50","text":"\uE2DC","text_color":"#FFFFFF","radius":0,"border_side":0,"text_font":32}
{"page":4,"id":3,"obj":"btn","x":10,"y":160,"w":105,"h":90,"toggle":true,"text":"\uE425","text_font":32,"align":1}
{"page":4,"id":4,"obj":"slider","x":180,"y":50,"w":30,"h":200,"max":255}
breezewayplate:
  objects:
    - obj: "p0b1"  # temperature label on all pages
      properties:
        "text": '{{ states("sensor.time") }}'
    - obj: "p0b3"  # temperature label on all pages
      properties:
        "text": '{{ states("sensor.rounded_exterior_temp") }}°F'
    - obj: "p1b1"
      properties:
        "val": '{{ 1 if is_state("light.breezeway_sconce", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.breezeway_sconce"
    - obj: "p1b2"
      properties:
        "val": '{{ 1 if is_state("light.gate_sconces", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.gate_sconces"
    - obj: "p1b21"
      properties:
        "val": 0
      event:
        "down":
          - service: openhasp.command
            target:
              entity_id: openhasp.breezewayplate
            data:
              keyword: page
              parameters: '3'
    - obj: "p1b3"
      properties:
        "val": '{{ 1 if is_state("light.eaves_downlights_patio", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.eaves_downlights_patio"
    - obj: "p1b31"
      properties:
        "val": 0
      event:
        "down":
          - service: openhasp.command
            target:
              entity_id: openhasp.breezewayplate
            data:
              keyword: page
              parameters: '4'
    - obj: "p1b4"
      properties:
        "val": '{{ 1 if is_state("light.retaining_wall_lights", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.retaining_wall_lights"
    - obj: "p1b5"
      properties:
        "val": '{{ 1 if is_state("light.patio_party_lights", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.patio_party_lights"
    - obj: "p1b51"
      properties:
        "val": 0
      event:
        "down":
          - service: openhasp.command
            target:
              entity_id: openhasp.breezewayplate
            data:
              keyword: page
              parameters: '2'
    - obj: "p1b6"
      properties:
        "val": '{{ 1 if is_state("light.water_wall_pump", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.water_wall_pump"
    - obj: "p2b3"
      properties:
        "val": '{{ 1 if is_state("light.patio_party_lights", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.patio_party_lights"
    - obj: "p2b4"
      properties:
        "val": '{{ state_attr("light.patio_party_lights", "brightness") if state_attr("light.patio_party_lights", "brightness") != None else 0}}'
      event:
        "changed":
          - service: light.turn_on
            data:
              entity_id: "light.patio_party_lights"
              brightness: '{{ val }}'
        "up":
          - service: light.turn_on
            data:
              entity_id: "light.patio_party_lights"
              brightness: '{{ val }}'
    - obj: "p3b3"
      properties:
        "val": '{{ 1 if is_state("light.gate_sconces", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.gate_sconces"
    - obj: "p3b4"
      properties:
        "val": '{{ state_attr("light.gate_sconces", "brightness") if state_attr("light.gate_sconces", "brightness") != None else 0}}'
      event:
        "changed":
          - service: light.turn_on
            data:
              entity_id: "light.gate_sconces"
              brightness: '{{ val }}'
        "up":
          - service: light.turn_on
            data:
              entity_id: "light.gate_sconces"
              brightness: '{{ val }}'
    - obj: "p4b3"
      properties:
        "val": '{{ 1 if is_state("light.eaves_downlights_patio", "on") else 0 }}'
      event:
        "down":
          - service: homeassistant.toggle
            entity_id: "light.eaves_downlights_patio"
    - obj: "p4b4"
      properties:
        "val": '{{ state_attr("light.eaves_downlights_patio", "brightness") if state_attr("light.eaves_downlights_patio", "brightness") != None else 0}}'
      event:
        "changed":
          - service: light.turn_on
            data:
              entity_id: "light.eaves_downlights_patio"
              brightness: '{{ val }}'
        "up":
          - service: light.turn_on
            data:
              entity_id: "light.eaves_downlights_patio"
              brightness: '{{ val }}'

There are several configurations that have to be done at many different places (MQTT broker add-on, configuring the plate WiFi then MQTT broker access, uploading the GUI design file, editing the integration file) which can make it challenging with a lot of ducks to line up.

Final design configuration and integration with HA go hand-in-hand. It is straight forward if somewhat cryptic. It offers pretty much all the predefined LVGL widgets and decoration attributes but with only absolute X/Y/H/W layout and a limited font. The predefined font does come with a good selection of MDI icons.

The product is very stable and I don’t remember encountering major issues. That OTA update was not available when upgrading from 0.6.0 to 0.7.0 is what prompted me to investigate ESPHome LVGL.

ESPHOME LVGL

ESPHome LVGL is “simply” another pre-defined component in ESPHome. Getting started is trivial, like creating any new ESPHome device. Only a single YAML configuration file defines everything, from the display and touchscreen use, the GUI design, to the HA sensor and devices that are monitored and controlled. If you are already familiar with ESPHome, the principle is very familiar.

But man, is it ever verbose!

esphome:
  name: breezeway-plate
  friendly_name: Breezeway Plate

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: *redacted*
  on_client_connected:
    lvgl.widget.hide: boot_screen

ota:
  - platform: esphome
    password: *redacted*

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Breezeway-Plate Fallback Hotspot"
    password: *redacted*

captive_portal:

psram:
  mode: octal
  speed: 80MHz

output:
  - platform: ledc
    pin: GPIO5
    id: backlight_pwm
  - platform: ledc
    pin: GPIO26
    id: moodRed
  - platform: ledc
    pin: GPIO32
    id: moodGreen
  - platform: ledc
    pin: GPIO33
    id: moodBlue
  - platform: gpio
    pin: GPIO12
    id: relay_1
  - platform: gpio
    pin: GPIO14
    id: relay_2
  - platform: gpio
    pin: GPIO27
    id: relay_3

light:
  - platform: rgb
    name: "Mood Light"
    red: moodRed
    green: moodGreen
    blue: moodBlue
  - platform: monochromatic
    name: "Backlight"
    id: backlight
    output: backlight_pwm
    restore_mode: ALWAYS_ON

spi:
  clk_pin: GPIO19
  mosi_pin: GPIO23
  miso_pin: GPIO25

i2c:
  sda: GPIO4
  scl: GPIO0

display:
  - id: langbon_L8
    platform: ili9xxx
    model: ST7789V
    invert_colors: false
    dimensions: 240x320
    cs_pin: GPIO22
    dc_pin: GPIO21
    reset_pin: GPIO18
    auto_clear_enabled: false
    update_interval: never
    rotation: 180

touchscreen:
  platform: ft63x6
  calibration:
    x_min: 0
    y_min: 0
    x_max: 230
    y_max: 312
  on_touch:
    - if:
        condition: lvgl.is_paused
        then:
          - logger.log: "LVGL resuming"
          - lvgl.resume:
          - lvgl.page.show: main_page
          - lvgl.widget.redraw
          - light.turn_on: backlight

font:
  - file: "fonts/RobotoCondensed-Regular.ttf"
    id: roboto_icons_28
    size: 28
    bpp: 4
    extras:
      - file: "fonts/materialdesignicons-webfont.ttf"
        glyphs: [
          "\U000F02DC", # mdi-home
          "\U000F0335", # mdi-lightbulb
          "\U000F0425", # mdi-power
          "\U000F0493", # mdi-cog
          "\U000F091D", # mdi-wall-sconce-flat
          "\U000F12BA", # mdi-string-lights
          "\U000F1849", # mdi-wallterfall
          "\U000F179B", # mdi-light-recessed
          ]
  - file: "fonts/RobotoCondensed-Regular.ttf"
    id: header_font
    size: 12
    bpp: 4

image:
  - file: https://esphome.io/_static/favicon-512x512.png
    id: boot_logo
    resize: 200x200
    type: RGB565
    use_transparency: true

time:
  - platform: homeassistant
    id: time_comp
    on_time_sync:
      - script.execute: time_update
    on_time:
      - minutes: '*'
        seconds: 0
        then:
          - script.execute: time_update

script:
  - id: time_update
    then:
      - lvgl.label.update:
          id: current_time
          text: !lambda |-
            static char hhss[8];
            auto now = id(time_comp).now();
            snprintf(hhss, sizeof(hhss), "%02d:%02d", now.hour, now.minute);
            return hhss;

binary_sensor:
  - platform: homeassistant
    id: gate_sconces_state
    entity_id: light.gate_sconces
    on_state:
      - lvgl.label.update:
          id: gate_sconces_button_label
          text_color: !lambda |-
            return (x) ? lv_color_hex(0xffd700) : lv_color_white();
      - lvgl.label.update:
          id: gate_sconces_panel_label
          text_color: !lambda |-
            return (x) ? lv_color_hex(0xffd700) : lv_color_white();
      - lvgl.button.update:
          id: gate_sconces_panel_button
          state:
            checked: !lambda return x;
  - platform: homeassistant
    id: eaves_patio_state
    entity_id: light.eaves_downlights_patio
    on_state:
      - lvgl.label.update:
          id: eaves_patio_button_label
          text_color: !lambda |-
            return (x) ? lv_color_hex(0xffd700) : lv_color_white();
      - lvgl.label.update:
          id: eaves_patio_panel_label
          text_color: !lambda |-
            return (x) ? lv_color_hex(0xffd700) : lv_color_white();
      - lvgl.button.update:
          id: eaves_patio_panel_button
          state:
            checked: !lambda return x;
  - platform: homeassistant
    id: party_state
    entity_id: light.patio_party_lights
    on_state:
      - lvgl.label.update:
          id: party_button_label
          text_color: !lambda |-
            return (x) ? lv_color_hex(0xffd700) : lv_color_white();
      - lvgl.label.update:
          id: party_panel_label
          text_color: !lambda |-
            return (x) ? lv_color_hex(0xffd700) : lv_color_white();
      - lvgl.button.update:
          id: party_panel_button
          state:
            checked: !lambda return x;

sensor:
  - platform: homeassistant
    id: current_temp
    entity_id: sensor.weatherflow_temperature
    on_value:
      - lvgl.label.update:
          id: thermometer
          text:
            format: "%.1f°F"
            args: [ 'x' ]
  - platform: homeassistant
    id: gate_sconces_brightness
    entity_id: light.gate_sconces
    attribute: brightness
    on_value:
      - lvgl.slider.update:
          id: gate_sconces_panel_slider
          value: !lambda return x;
  - platform: homeassistant
    id: eaves_patio_brightness
    entity_id: light.eaves_downlights_patio
    attribute: brightness
    on_value:
      - lvgl.slider.update:
          id: eaves_patio_panel_slider
          value: !lambda return x;
  - platform: homeassistant
    id: party_brightness
    entity_id: light.patio_party_lights
    attribute: brightness
    on_value:
      - lvgl.slider.update:
          id: party_panel_slider
          value: !lambda return x;

lvgl:
  on_idle:
    timeout: !lambda "return 10000;"  # Turn off back light if idle for 10 secs
    then:
      - logger.log: "LVGL is idle"
      - light.turn_off: backlight
      - lvgl.pause:
          show_snow: true # Prevent burn-in
  bg_color: black

  theme:
    label:
      bg_opa: TRANSP
      align: CENTER
      text_align: CENTER
      text_color: 0xFFFFFF
      text_font: roboto_icons_28
      border_width: 0
      outline_width: 0
      pad_all: 0
    button:
      bg_color: dodgerblue
      radius: 4
      border_width: 0
      outline_width: 0
      pad_all: 0
      checked:
        bg_color: gold
    slider:
      bg_color: dodgerblue
      bg_opa: 50%
      indicator:
        bg_color: dodgerblue
      knob:
        bg_color: dodgerblue
    obj:
      bg_opa: TRANSP
      border_width: 0
      outline_width: 0
      pad_all: 0

  top_layer: # Show the current time and temperature at the top of every page
    widgets:
      - obj:
          id: header
          width: 100%
          height: 20
          bg_opa: TRANSP
          align: TOP_MID
          border_width: 0
          outline_width: 0
          widgets:
            - label:
                id: current_time
                text_font: header_font
                text: "00:00"
                align: TOP_LEFT
                text_align: LEFT
            - label:
                id: thermometer
                text_font: header_font
                text: "00.0°F"
                align: TOP_RIGHT
                text_align: RIGHT
            
      - obj:
          id: boot_screen
          x: 0
          y: 0
          width: 100%
          height: 100%
          bg_color: 0xffffff
          bg_opa: COVER
          radius: 0
          pad_all: 0
          border_width: 0
          widgets:
            - image:
                align: CENTER
                src: boot_logo
                y: -40
            - spinner:
                align: CENTER
                y: 95
                height: 50
                width: 50
                spin_time: 1s
                arc_length: 60deg
                arc_width: 8
                indicator:
                  arc_color: 0x18bcf2
                  arc_width: 8
          on_press:
            - lvgl.widget.hide: boot_screen

  pages:
    - id: main_page
      widgets:
        - obj: # a properly placed container object for all these controls
            align: BOTTOM_MID
            width: 240
            height: 300
            bg_opa: TRANSP
            border_opa: TRANSP
            pad_all: 6
            layout: # enable the GRID layout for the children widgets
              type: GRID # split the rows and the columns proportionally
              grid_columns: [FR(1), FR(1)] # equal
              grid_rows: [FR(1), FR(1), FR(1)]
            widgets:
              - button:
                  id: button_11
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 0
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_short_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.breezeway_sconce
                  widgets:
                    - label:
                        id: breezeway_sconce_label
                        text: "\U000F0335\nB'way"

              - button:
                  id: button_12
                  grid_cell_column_pos: 1
                  grid_cell_row_pos: 0
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_short_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.gate_sconces
                  on_long_press:
                    - lvgl.page.show: gate_dimmer_page
                  widgets:
                    - label:
                        id: gate_sconces_button_label
                        text: "\U000F0335\nGate"
                    - label:
                        align: TOP_RIGHT
                        x: -2
                        y: +2
                        text_font: montserrat_18
                        text: "\uF013"
                  
                        
              - button:
                  id: button_21
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 1
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_short_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.eaves_downlights_patio
                  on_long_press:
                    - lvgl.page.show: eaves_dimmer_page
                  widgets:
                    - label:
                        id: eaves_patio_button_label
                        text: "\U000F179B\nPatio"
                    - label:
                        align: TOP_RIGHT
                        x: -2
                        y: +2
                        text_font: montserrat_18
                        text: "\uF013"

              - button:
                  id: button_22
                  grid_cell_column_pos: 1
                  grid_cell_row_pos: 1
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_short_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.retaining_wall_lights
                  widgets:
                    - label:
                        text: "\U000F091D\nWall"

              - button:
                  id: button_31
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_short_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.patio_party_lights
                  on_long_press:
                    - lvgl.page.show: party_dimmer_page
                  widgets:
                    - label:
                        id: party_button_label
                        text: "\U000F12BA\nParty"
                    - label:
                        align: TOP_RIGHT
                        x: -2
                        y: +2
                        text_font: montserrat_18
                        text: "\uF013"

              - button:
                  id: button_32
                  grid_cell_column_pos: 1
                  grid_cell_row_pos: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_short_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.water_wall_pump
                  widgets:
                    - label:
                        text: "\U000F1849\nW'Wall"

    - id: gate_dimmer_page
      widgets:
        - obj: # a properly placed container object for all these controls
            align: BOTTOM_MID
            width: 240
            height: 300
            bg_opa: TRANSP
            border_opa: TRANSP
            pad_all: 10
            layout: # enable the GRID layout for the children widgets
              type: GRID # split the rows and the columns proportionally
              grid_columns: [FR(1), FR(1)] # equal
              grid_rows: [FR(135), FR(135), FR(30)]
            widgets:
              - label:
                  id: gate_sconces_panel_label
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 0
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  pad_top: 30
                  text: "\U000F0335\nGate"
              - button:
                  id: gate_sconces_panel_button
                  checkable: true
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 1
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  widgets:
                    - label:
                        text_font: montserrat_32
                        text: "\uF011"
                  on_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.gate_sconces
                  
              - obj:
                  grid_cell_column_pos: 1
                  grid_cell_row_pos: 0
                  grid_cell_row_span: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  pad_top: 10
                  pad_bottom: 20
                  pad_left: 40
                  pad_right: 40
                  widgets:
                    - slider:
                        id: gate_sconces_panel_slider
                        width: 50%
                        height: 100%
                        align: CENTER
                        max_value: 255
                        on_release:
                          - homeassistant.action:
                              action: light.turn_on
                              data:
                                entity_id: light.gate_sconces
                                brightness: !lambda return int(x);

              - button:
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 2
                  grid_cell_column_span: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_click:
                    - lvgl.page.show: main_page
                  widgets:
                    - label:
                        text: "\U000F02DC"

    - id: eaves_dimmer_page
      widgets:
        - obj: # a properly placed container object for all these controls
            align: BOTTOM_MID
            width: 240
            height: 300
            bg_opa: TRANSP
            border_opa: TRANSP
            pad_all: 10
            layout: # enable the GRID layout for the children widgets
              type: GRID # split the rows and the columns proportionally
              grid_columns: [FR(1), FR(1)] # equal
              grid_rows: [FR(135), FR(135), FR(30)]
            widgets:
              - label:
                  id: eaves_patio_panel_label
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 0
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  pad_top: 30
                  text: "\U000F179B\nPatio"
              - button:
                  id: eaves_patio_panel_button
                  checkable: true
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 1
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  widgets:
                    - label:
                        text_font: montserrat_32
                        text: "\uF011"
                  on_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.eaves_downlights_patio
                  
              - obj:
                  grid_cell_column_pos: 1
                  grid_cell_row_pos: 0
                  grid_cell_row_span: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  pad_top: 10
                  pad_bottom: 20
                  pad_left: 40
                  pad_right: 40
                  widgets:
                    - slider:
                        id: eaves_patio_panel_slider
                        width: 50%
                        height: 100%
                        align: CENTER
                        max_value: 255
                        on_release:
                          - homeassistant.action:
                              action: light.turn_on
                              data:
                                entity_id: light.eaves_downlights_patio
                                brightness: !lambda return int(x);

              - button:
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 2
                  grid_cell_column_span: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_click:
                    - lvgl.page.show: main_page
                  widgets:
                    - label:
                        text: "\U000F02DC"
            
    - id: party_dimmer_page
      widgets:
        - obj: # a properly placed container object for all these controls
            align: BOTTOM_MID
            width: 240
            height: 300
            bg_opa: TRANSP
            border_opa: TRANSP
            pad_all: 10
            layout: # enable the GRID layout for the children widgets
              type: GRID # split the rows and the columns proportionally
              grid_columns: [FR(1), FR(1)] # equal
              grid_rows: [FR(135), FR(135), FR(30)]
            widgets:
              - label:
                  id: party_panel_label
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 0
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  pad_top: 30
                  text: "\U000F12BA\nParty"
              - button:
                  id: party_panel_button
                  checkable: true
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 1
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  widgets:
                    - label:
                        text_font: montserrat_32
                        text: "\uF011"
                  on_click:
                    - homeassistant.action:
                        action: light.toggle
                        data:
                          entity_id: light.patio_party_lights
                  
              - obj:
                  grid_cell_column_pos: 1
                  grid_cell_row_pos: 0
                  grid_cell_row_span: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  pad_top: 10
                  pad_bottom: 20
                  pad_left: 40
                  pad_right: 40
                  widgets:
                    - slider:
                        id: party_panel_slider
                        width: 50%
                        height: 100%
                        align: CENTER
                        max_value: 255
                        on_release:
                          - homeassistant.action:
                              action: light.turn_on
                              data:
                                entity_id: light.patio_party_lights
                                brightness: !lambda return int(x);

              - button:
                  grid_cell_column_pos: 0
                  grid_cell_row_pos: 2
                  grid_cell_column_span: 2
                  grid_cell_x_align: STRETCH
                  grid_cell_y_align: STRETCH
                  on_click:
                    - lvgl.page.show: main_page
                  widgets:
                    - label:
                        text: "\U000F02DC"

A good portion is boilerplate declaration that defines the device itself: the display, the touchscreen, the backlight and mood light controls (thanks to @Jon_White for providing a complete solution) – stuff that is built in the openHASP you use.

Because I’m controlling six lights with the same switch plate – three of them as dimmers, there is a lot of repetitive code which I am sure will re-open the debate of integrating jinja in ESPHome YAML. I for one would have appreciated it.

You are a lot closer to LVGL so there is a lot of power and flexibility. For example, I was able to use long-press to trigger the dimmer sub-panel rather than using a sub-button like I had to do in openHASP. I ran into a few issues that were not detected by the YAML editor, and some that caused errors in the generated C++ code rather than being caught in YAML time. Being an experienced C++ programmer, it was easy for me to figure out how to fix the problem.

The main frustration is that ESPHome LVGL is lacking in creating pre-defined bindings between HA entities and LGVL widgets. You have to do everything: trigger a widget state update whenever the HA entity changes and trigger the HA entity whenever an event happens on a widget. For example, it would be nice if it were possible to bind a light entity to a button and have it handle the ON/OFF transitions back and forth on its own. Same with a slider widget and any numerical entity like a dimmer.

2 Likes

WHICH ONE?

I did the openHASP version a while ago so I don’t remember how long it took. I’m also somewhat versed in LVGL via SquareLine Studio which I then use on other ESP-based touchscreen devices, so the concepts of binding a GUI to an application were familiar.

I’m guessing the overall effort to be roughly the same (thank you copy/paste!)

Now that the work is done and creating new instances will be mostly copy/paste, I’m going to go with ESPHome because of the consistency it offers with the other devices I already have and reduces the number of integrations I have to keep up to date. And ESPHome’s OTA has been rock solid throughout many upgrades.

Feedback welcome.

2 Likes

What caused me to change to esphome was that suddenly the backlight wouldn’t turn off automatically on all my switches, I think it was related to the zigbee2mqtt upgrade. I hit the same issue where I couldn’t do a ota to the new version of openhasp so took that as an opportunity do convert to esphome. While I agree it is more verbose and a pain that every layout change is a recompile - it is easier to add a home assistant entity and not have to maintain multiple yaml files.

Even if lvgl on esphome is pretty new it’s still worthed over openhasp, if you ask me. I’ve had openhasp on my LCD for a while, too, but i soon came to problems - one of them is that i couldn’t do simple thing as activate relay for 0.5s fixed when i touch a button. It seems that it’s either toggle or “on” until i keep button pressed.

There are however down sides, like each change requires recompile and OTA reload (but that’s just the way esphome is built), and currently slide-to-change-page is not supported yet, etc… but, overall i think that there are more good than bad things.

1 Like

You guys are starting to persuade me …

2 Likes

Yeah I really like the fact that ESPHome uses a much more recent version of LVGL then openHASP. This lets you do a lot more things.

the lanbon l8 is a great switch but have you looked at the lanbon l9? It fits in a standard Decora switch plate and has mmware radar behind the screen! I’m not a programmer and I keep hoping somebody will get this working in ESPHome.

It look like they have it more or less working over at openHASP.

I’ve seen it but it does not expose the flashing pins. I’d use it in an instant otherwise (and told Lanbon as much).