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
