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?
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.



