Lanbon L9 Smart Light Switch

Drivers
- Processor: ESP32-S3-WROOM-1U
- Touchscreen: FT6336U
- Display: ST7789V2
- mmWave Prescence: MoreSense 10G Hand Sweeping Series MS10-3007D26M4
- Power Sensor: HLW8012 (CF pin only)
GPIO Pinout
i80 Display
| Pin | Function | 
|---|---|
| GPIO17 | DC Pin | 
| GPIO21 | CS Pin | 
| GPIO13 | WR Pin | 
| GPIO18 | RD Pin | 
| GPIO6 | Data D0 | 
| GPIO7 | Data D1 | 
| GPIO15 | Data D2 | 
| GPIO16 | Data D3 | 
| GPIO10 | Data D4 | 
| GPIO9 | Data D5 | 
| GPIO46 | Data D6 | 
| GPIO3 | Data D7 | 
| GPIO4 | Reset Pin | 
I²C (used for touchscreen)
| Pin | Function | 
|---|---|
| GPIO0 | i2c SCL | 
| GPIO35 | i2c SCA | 
General Purpose IO
| Pin | Function | 
|---|---|
| GPIO32 | LCD Backlight (output, ledc) | 
| GPIO36 | Load Relay (output, switch) | 
| GPIO5 | HLW8012 CF Pin (input, pulse_meter) | 
| GPIO11 | mmWave Proximity Sensor (input, binary_sensor) | 
| GPIO2 | mmWave Proximity Sensor (output, unknown) | 
| GPIO1 | mmWave Proximity Sensor (output, unknown) | 
Note, programming the proximity sensor for different sensitivity, etc. is not yet possible.
How to Flash
- Open the switch, by removing the terminal covers and the 4 small screws on the cornors.
- The front part of hte switch should "pop off", with a small amount of pressure.
- Connect a USB level shifter to the pin header on the front panel, using the pinout below. Note, you must provide 5v power, while the Tx/Rx pins use a 3.3v level.
- Connect the GPIO0 pin to ground to put the ESP32 into the flash boot mode.
- Once flashed, the screen should power up and display ESPHome, while powered via the 5v supply.

Example Configuration
Below is a hardware only configuration, that enables the display with the ESPHome test card, along with the power meter, relay, and proximity sensor.
esphome:
  name: lanbon-l9
  friendly_name: Lanbon L9
esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf
logger:
  level: DEBUG
  baud_rate: 115200
  hardware_uart: UART0
api:
  encryption:
    key: !secret api_key
ota:
  - platform: esphome
    password: !secret ota_password
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
psram:
  speed: 80MHz
output:
  - platform: ledc
    id: lcd_backlight_output
    pin:
      number: GPIO42
      inverted: false
light:
  - platform: monochromatic
    output: lcd_backlight_output
    name: Display Backlight
    id: display_backlight
    restore_mode: ALWAYS_ON
spi:
  type: octal
  id: display_spi
  clk_pin: GPIO21
  data_pins:
    - GPIO6
    - GPIO7
    - GPIO15
    - GPIO16
    - GPIO10
    - GPIO9
    - GPIO46
    - GPIO3
display:
  - platform: mipi_spi
    id: my_display
    model: st7789v
    spi_id: display_spi
    bus_mode: octal
    dc_pin: GPIO17
    reset_pin: GPIO4
    dimensions:
      height: 320
      width: 170
      offset_height: 0
      offset_width: 35
    invert_colors: true
    show_test_card: true
    data_rate: 4MHz
    color_depth: 16BIT
    color_order: BGR
    auto_clear_enabled: false
    update_interval: 250ms
i2c:
  - id: touchscreen_i2c
    scl:
      number: GPIO0
      ignore_strapping_warning: true
    sda:
      number: GPIO35
touchscreen:
  - platform: ft63x6
    id: my_touchscreen
    display: my_display
    i2c_id: touchscreen_i2c
    calibration:
      x_min: 0
      y_min: 0
      x_max: 169
      y_max: 319
sensor:
  - platform: pulse_meter
    name: Power
    id: power_pulse_meter
    pin: GPIO5
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    internal_filter_mode: PULSE
    accuracy_decimals: 1
    filters:
      - filter_out: nan
      - throttle: 15s
      - multiply: 0.0813287514318442 # Calibration may be needed
binary_sensor:
  - platform: gpio
    name: Proximity Sensor
    id: proximity_sensor
    pin: GPIO11
switch:
  - platform: gpio
    name: Relay
    id: relay
    pin:
      number: GPIO36
Detailed Configuration with LVGL

The following configuration implements the following features and examples from the LVGL component.
- Button with status update based on load relay gpio
- Screen time-out (backlight) and activation via touch or proximity
- Load power measurement via HLW8012
- Anti-burn
- Header icon showing API status
- Header label showing current time via SNTP
- Footer for page navigation
esphome:
  name: lanbon-l9
  friendly_name: Lanbon L9
esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf
# Enable logging
logger:
  level: DEBUG
  baud_rate: 115200
  hardware_uart: UART0
# Enable Home Assistant API
api:
  encryption:
    key: !secret api_key
  reboot_timeout: 0s
  on_client_connected:
    - lvgl.label.update:
        id: label_apistatus
        text_color: 0x00FF00
  on_client_disconnected:
    - lvgl.label.update:
        id: label_apistatus
        text_color: 0xFF0000
ota:
  - platform: esphome
    password: !secret ota_password
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
psram:
  speed: 80MHz
time:
  - platform: sntp
    id: my_sntp
    timezone: America/New_York
    on_time:
      - hours: 23,4
        minutes: 5
        seconds: 0
        then:
          - switch.turn_on: switch_antiburn
      - hours: 23,4
        minutes: 35
        seconds: 0
        then:
          - switch.turn_off: switch_antiburn
      - seconds: 0
        minutes: "*"
        then:
          - lvgl.label.update:
              id: label_time
              text:
                time_format: "%l:%M %p"
                time: my_sntp
output:
  - platform: ledc
    id: lcd_backlight_output
    pin:
      number: GPIO42
      inverted: false
light:
  - platform: monochromatic
    output: lcd_backlight_output
    name: Display Backlight
    id: display_backlight
    restore_mode: ALWAYS_ON
spi:
  type: octal
  id: display_spi
  clk_pin: GPIO21
  data_pins:
    - GPIO6
    - GPIO7
    - GPIO15
    - GPIO16
    - GPIO10
    - GPIO9
    - GPIO46
    - GPIO3
display:
  - platform: mipi_spi
    id: my_display
    model: st7789v
    spi_id: display_spi
    bus_mode: octal
    dc_pin: GPIO17
    reset_pin: GPIO4
    dimensions:
      height: 320
      width: 170
      offset_height: 0
      offset_width: 35
    invert_colors: true
    show_test_card: false
    data_rate: 4MHz
    color_depth: 16BIT
    color_order: BGR
    auto_clear_enabled: false
    update_interval: 250ms
i2c:
  - id: touchscreen_i2c
    scl:
      number: GPIO0
      ignore_strapping_warning: true
    sda:
      number: GPIO35
touchscreen:
  - platform: ft63x6
    id: my_touchscreen
    display: my_display
    i2c_id: touchscreen_i2c
    calibration:
      x_min: 0
      y_min: 0
      x_max: 169
      y_max: 319
    on_release:
      - if:
          condition: lvgl.is_paused
          then:
            - if:
                condition: lvgl.is_paused
                then:
                  - logger.log: "LVGL resuming due to screen touch."
                  - lvgl.resume:
                  - lvgl.widget.redraw:
                  - light.turn_on: display_backlight
sensor:
  - platform: wifi_signal
    name: Wifi Signal
    update_interval: 60s
    entity_category: diagnostic
  - platform: uptime
    name: Uptime
    entity_category: diagnostic
  - platform: pulse_meter
    name: Power
    id: power_pulse_meter
    pin: GPIO5
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    internal_filter_mode: PULSE
    accuracy_decimals: 1
    filters:
      - filter_out: nan
      - throttle: 15s
      - multiply: 0.0813287514318442 # Calibration may be needed
binary_sensor:
  - platform: gpio
    name: Proximity Sensor
    id: proximity_sensor
    pin: GPIO11
    on_press:
      then:
        - if:
            condition: lvgl.is_paused
            then:
              - logger.log: "LVGL resuming due to proximity sensor."
              - lvgl.resume:
              - lvgl.widget.redraw:
              - light.turn_on: display_backlight
switch:
  - platform: gpio
    name: Relay
    id: relay
    pin:
      number: GPIO36
    on_turn_on:
      - lvgl.widget.update:
          id: light_switch
          state:
            checked: true
      - lvgl.label.update:
          id: label_light_switch
          text: "\U000F06E9"
          text_color: 0xFFEE00
    on_turn_off:
      - lvgl.widget.update:
          id: light_switch
          state:
            checked: false
      - lvgl.label.update:
          id: label_light_switch
          text: "\U000F0336"
          text_color: 0xFFFFFF
  - platform: template
    name: Antiburn
    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:
      - lvgl.pause:
          show_snow: true
      - light.turn_off: display_backlight
    turn_off_action:
      - logger.log: "Stopping Antiburn"
      - if:
          condition: lvgl.is_paused
          then:
            - lvgl.resume:
            - lvgl.widget.redraw:
font:
  - file: "https://github.com/Pictogrammers/pictogrammers.github.io/raw/refs/heads/main/@mdi/font/7.4.47/fonts/materialdesignicons-webfont.ttf"
    id: mdi_icons
    bpp: 4
    size: 52
    glyphs: ["\U000F0336", "\U000F06E9"]
lvgl:
  displays:
    - my_display
  touchscreens:
    - my_touchscreen
  buffer_size: 25%
  log_level: WARN
  on_idle:
    timeout: 30s
    then:
      - logger.log: "LVGL is idle"
      - light.turn_off: display_backlight
      - lvgl.pause:
  style_definitions:
    - id: header_footer
      bg_color: 0x2F8CD8
      bg_grad_color: 0x005782
      bg_grad_dir: VER
      bg_opa: COVER
      border_opa: TRANSP
      radius: 0
      pad_all: 0
      pad_row: 0
      pad_column: 0
      border_color: 0x0077b3
      text_color: 0xFFFFFF
      width: 100%
      height: 30
  top_layer:
    widgets:
      - obj:
          align: TOP_MID
          styles: header_footer
          widgets:
            - label:
                text:
                  time_format: "%l:%M %p"
                  time: my_sntp
                id: label_time
                align: CENTER
                text_align: CENTER
                text_color: 0xFFFFFF
            - label:
                text: "\uF1EB"
                id: label_apistatus
                align: top_right
                x: -2
                y: 7
                text_align: right
                text_color: 0xFFFFFF
      - buttonmatrix:
          align: bottom_mid
          styles: header_footer
          pad_all: 0
          outline_width: 0
          id: top_layer
          items:
            styles: header_footer
          rows:
            - buttons:
                - id: page_prev
                  text: "\uF053"
                  on_press:
                    then:
                      lvgl.page.previous:
                - id: page_home
                  text: "\uF015"
                  on_press:
                    then:
                      lvgl.page.show: page_light_switch
                - id: page_next
                  text: "\uF054"
                  on_press:
                    then:
                      lvgl.page.next:
  pages:
    - id: page_light_switch
      widgets:
        - button:
            x: 35
            y: 60
            width: 100
            height: 200
            id: light_switch
            checkable: true
            on_click:
              switch.toggle: relay
            widgets:
              - label:
                  id: label_light_switch
                  align: CENTER
                  text: "\U000F0336"
                  text_align: CENTER
                  text_font: mdi_icons