Lanbon L9 LCD Smart Light Switch with Motion Sensor

Ordered a new one and the latest version cannot be reflashed.

My Lanbon L9 (Matter) died after a firmware update as well.
Not even worth the time to argue with the manufacturer, went into the junk bin.

This version can be flashed, you just need to have some thin tools to pry off the back half of the case. There are three clips on both of the long sides. They seem rather robust, so shouldn’t be too much of an issue.

That being said, I just re-flashed the exact same firmware* to my Lanbon L9 via wifi OTA that’s been sitting around since September and something which has been updated in the dependancies broke the ESPHome templates. Screen will not turn on nor respond to Esphome Builder.

-* Well, the exact same firmware wouldn’t flash in the newer ESPHome. The template was throwing errors for the psram. The templates (and my previously working firmware) used the following:

psram:
speed: 80MHz

Received the following error when trying to compile:
ESP32S3 requires PSRAM mode selection; one of quad, octal Selection of the wrong mode for the board will cause a runtime failure to initialise PSRAM. speed: 80MHz

I’m a fool tinkerer, so an AI tool suggested putting the following because " This error occurs because newer versions of ESPHome require you to explicitly define the PSRAM mode for ESP32-S3 chips (it no longer defaults automatically).":

psram:
mode: octal
speed: 80MHz

Maybe need to try again with quad?

BTW, got my device working again after hardwiring it back to my computer to flash.

The key is that the psram needs to be mode: quad. OItherwise the templates as provided appear to work.

psram:
mode: quad
speed: 80MHz

Thanks for this update. I hope you won’t mind if I steal your photo to update the device database.

Go right ahead. Also update the templates’ psram lines too!

Where are the latest template files? The above links are broke. Thx

Hi, just wanted to share that I managed to get the board version L9-HS_CB_V_V3.3 20250319 working (bought from Amazon in Jan 2026). This template is modified based on templates provided by lylavoie (Lincoln Lavoie) Ā· GitHub and dlitz (Darsey Litzenberger) Ā· GitHub. All credits to them.

Here’s the yaml template config for the 5 button layout seen:

# Lanbon L9-HF
# MAC: 

esphome:
  name: lanbonl9-porch-entry
  friendly_name: Porch Entry Keypad

esp32:
  board: esp-wrover-kit
  framework:
    type: esp-idf

# Enable logging
logger:
  level: DEBUG

api:
  encryption:
    key: !secret living_keypad_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 living_keypad_ota_password

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

  manual_ip:
    static_ip: 10.0.0.100
    gateway: 10.0.0.1
    subnet: 255.255.255.0
    dns1: 10.0.0.1

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

psram:
  mode: quad
  speed: 80MHz

time:
  - platform: homeassistant
  #- platform: sntp
  #  timezone: America/Edmonton
    id: current_time
    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: current_time

output:
  - platform: ledc
    pin: GPIO21
    id: lcd_backlight_output

light:
  - platform: monochromatic
    name: "Display Backlight"
    id: display_backlight
    output: lcd_backlight_output
    restore_mode: ALWAYS_ON

i2c:
  - id: touchscreen_i2c
    sda: GPIO4
    scl:
      number: GPIO0
      ignore_strapping_warning: true
    frequency: 100kHz
    scan: true

spi:
  id: display_spi
  clk_pin: GPIO26
  mosi_pin: GPIO14

display:
  - id: my_display
    platform: mipi_spi
    model: st7789v
    spi_id: display_spi
    bus_mode: single
    dc_pin: GPIO27
    cs_pin: GPIO25
    dimensions:
      height: 320
      width: 170
      offset_height: 0
      offset_width: 35
    invert_colors: true
    show_test_card: false
    data_rate: 40MHz
    color_depth: 16BIT
    color_order: BGR
    auto_clear_enabled: false
    update_interval: 250ms

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

  - platform: internal_temperature
    name: "Internal Temperature"

  - platform: pulse_meter
    name: Power
    id: power_pulse_meter
    pin: GPIO35
    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: GPIO15
    device_class: motion
#   filters:
#     - delayed_on_off:
#         time_on: 1s
#         time_off: 60s
    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

#    on_release: 
#      - logger.log: "No motion detected"
#      - light.turn_off: display_backlight
#      - lvgl.pause:

  - platform: homeassistant
    id: stairs_state
    entity_id: switch.1_stairs
    attribute: group_all_on
    trigger_on_initial_state: true
    on_state:
      then:
        lvgl.widget.update:
          id: stair_switch_button
          state:
            checked: !lambda return x;

  - platform: homeassistant
    id: garage_state
    entity_id: switch.1_garage
    attribute: group_all_on
    trigger_on_initial_state: true
    on_state:
      then:
        lvgl.widget.update:
          id: garage_switch_button
          state:
            checked: !lambda return x;

  - platform: homeassistant
    id: living_state
    entity_id: switch.2_living_room
    attribute: group_all_on
    trigger_on_initial_state: true
    on_state:
      then:
        lvgl.widget.update:
          id: living_switch_button
          state:
            checked: !lambda return x;

  - platform: homeassistant
    id: kitchen_state
    entity_id: switch.2_kitchen
    attribute: group_all_on
    trigger_on_initial_state: true
    on_state:
      then:
        lvgl.widget.update:
          id: kitchen_switch_button
          state:
            checked: !lambda return x;

switch:
  - platform: gpio
    name: Relay
    id: relay
    pin:
      number: GPIO32
    on_turn_on:
      - lvgl.widget.update:
          id: light_switch
          state:
            checked: true
      - lvgl.label.update:
          id: label_light_switch
#          text: "\U000F06E9"      #  lightbulb lit
          text: "\U000F1054"       #  outdoor lamp
          text_color: 0xFFEE00
    on_turn_off:
      - lvgl.widget.update:
          id: light_switch
          state:
            checked: false
      - lvgl.label.update:
          id: label_light_switch
#          text: "\U000F0336"      #  lightbulb unlit
          text: "\U000F1054"       #  outdoor lamp
          text_color: 0xFFFFFF

  - platform: template
    name: Antiburn
    id: switch_antiburn
    icon: mdi:television-shimmer
    optimistic: true
    #entity_category: config
    entity_category: diagnostic
    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",
      "\U000F0D35",
      "\U000F1054"
    ]

lvgl:
  displays:
    - my_display
  touchscreens:
    - my_touchscreen
  bg_color: 0x000000
  buffer_size: 25%
  log_level: WARN
#  styles:
#    - id: btn_primary
#      bg_color: 0x2F8CD8
#      text_color: 0xFFFFFF
#      pressed:
#        bg_color: 0x31302F
#    - id: label_style
#      text_color: 0xFFFFFF
#      text_align: CENTER
  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_color: 0x000000
#      bg_grad_color: 0x005782
      bg_grad_color: 0x000000
      bg_grad_dir: VER
      bg_opa: COVER
      border_opa: TRANSP
      radius: 0
      pad_all: 0
      pad_row: 0
      pad_column: 0
#      border_color: 0x0077b3
      border_color: 0x000000
      text_color: 0xFFFFFF
      width: 100%
      height: 30

    - id: button_default
      bg_color: 0x6F7072        # Your Slate Gray
      bg_opa: COVER
      radius: 5
      text_color: 0xFFFFFF

    - id: button_pressed
      bg_color: 0x00F7FC        # Your Electric Cyan
      bg_opa: COVER
      radius: 5
      text_color: 0xFFFFFF

  top_layer:
    widgets:
      - obj:
          align: TOP_MID
          styles: header_footer
          widgets:
          - label:
              text:
                time_format: "%l:%M %p"
                time: current_time
              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:
            id: light_switch
            x: 10
            y: 35
            width: 150
            height: 110
            checkable: true
            styles: button_default
            on_click:
              switch.toggle: relay
            widgets:
              - label:
                  id: label_light_switch
                  align: CENTER
                  text: "\U000F1054"
#                  text: "\U000F0D35"     # lightbulb unlit
                  text_align: CENTER
                  text_font: mdi_icons
        - button:
            id: stair_switch_button
            x: 10
            y: 150
            width: 73
            height: 65
            checkable: true
            styles: button_default
            on_click:
              - logger.log: "Stairs button was pressed!"
              - homeassistant.service:
                  service: switch.toggle
                  data:
                    entity_id: "switch.1_stairs"
            widgets:
              - label:
                  text: "Stair"
                  align: center
                  text_color: "0xFFFFFF"
        - button:
            id: garage_switch_button
            x: 87
            y: 150
            width: 73
            height: 65
            checkable: true
            styles: button_default
            on_click:
              - logger.log: "Garage button was pressed!"
              - homeassistant.service:
                  service: switch.toggle
                  data:
                    entity_id: "switch.1_garage"
            widgets:
              - label:
                  text: "Garage"
                  align: center
                  text_color: "0xFFFFFF"
        - button:
            id: living_switch_button
            x: 10
            y: 220
            width: 73
            height: 65
            checkable: true
            styles: button_default
            on_click:
              - logger.log: "Living button was pressed!"
              - homeassistant.service:
                  service: switch.toggle
                  data:
                    entity_id: "switch.2_living_room"
            widgets:
              - label:
                  text: "Living"
                  align: center
                  text_color: "0xFFFFFF"
        - button:
            id: kitchen_switch_button
            x: 87
            y: 220
            width: 73
            height: 65
            checkable: true
            styles: button_default
            on_click:
              - logger.log: "Kitchen button was pressed!"
              - homeassistant.service:
                  service: switch.toggle
                  data:
                    entity_id: "switch.2_kitchen"
            widgets:
              - label:
                  text: "Kitchen"
                  align: center
                  text_color: "0xFFFFFF"

text_sensor:
  - platform: wifi_info
    ssid:
      name: ESP Connected SSID
    bssid:
      name: ESP Connected BSSID
    mac_address:
      name: ESP Mac Wifi Address
    scan_results:
      name: ESP Latest Scan Results
    dns_address:
      name: ESP DNS Address

Are you successfully able to get it to dim when idle, and brighten up when people walk by? I’m having an issue with mine that it either goes dim and stays that way, or never goes dim.

Yes, it dims whenever I come close and turns off when idle. I did make add a script to the default config such that the screen will stay on for 30 secs whenever it detects presence. This is to remove the annoying turn off when you are still in front of the switch:

script:
  - id: display_timer
    mode: restart # This is key: it kills the old 30s timer and starts over
    then:
      - lvgl.resume:
      - light.turn_on: display_backlight
      - delay: 30s
      - light.turn_off: display_backlight
      - lvgl.pause:
      
binary_sensor:
  - platform: gpio
    name: "Proximity Sensor"
    id: proximity_sensor
    pin: GPIO15
    device_class: motion
    on_press:
      then:
        - script.execute: display_timer

Strangely enough, my L9-HS is the newer one, but it has a T1-U-HL-IPEX soldered into the empty spot shown, and it has two antennas. I have the dual relay model.