Files
esphome-configs-dlitz/lanbon-l9-hf.yaml

651 lines
16 KiB
YAML

substitutions:
calibration_touchscreen_x_min: 0
calibration_touchscreen_y_min: 0
calibration_touchscreen_x_max: 169
calibration_touchscreen_y_max: 319
calibration_pulse_power_meter: 0.07874192291723468
dashboard_import:
package_import_url: github://dlitz/esphome-configs-dlitz/lanbon-l9-hf.yaml@${git_branch}
packages:
- !include common/esp32s3.yaml
- !include common/cpu-temperature.yaml
- !include common/factory-reset.yaml
- !include common/flash-write-interval.yaml
- !include common/git-branch.yaml
- !include common/ota.yaml
- !include common/ota-update-password.yaml
- !include common/restart.yaml
- !include common/safe-mode.yaml
- !include common/time.yaml
- !include common/uptime-info.yaml
- !include common/wifi-info.yaml
esp32:
cpu_frequency: 240MHz
flash_size: 16MB
esphome:
name: lanbon-l9-hf
friendly_name: Lanbon L9-HF
comment: "Lanbon L9-HF smart switch/ceiling fan speed controller"
name_add_mac_suffix: true
project:
name: "dlitz.lanbon-l9-hf"
version: "v0.0.0"
on_boot:
# Fan initialization
priority: 800
then:
- script.execute: fan_uart_init
psram:
speed: 80MHz
mode: quad
# Enable logging
logger:
# level: DEBUG
# baud_rate: 115200
hardware_uart: uart0
# Enable Home Assistant API with dynamic key
api:
encryption:
on_client_connected:
- lvgl.label.update:
id: label_apistatus
text_color: 0x00FF00
on_client_disconnected:
- lvgl.label.update:
id: label_apistatus
text_color: 0xFF0000
wifi:
# ssid: !secret wifi_ssid
# password: !secret wifi_password
# fast_connect: true
min_auth_mode: WPA2
enable_btm: true
enable_rrm: true
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
password: !secret default_ap_password
captive_portal:
network:
enable_ipv6: true
# min_ipv6_addr_count: 2
esp32_improv:
authorizer: authorize_improv_over_ble_binary_sensor
esp32_ble_tracker:
scan_parameters:
active: false
bluetooth_proxy:
active: false
time:
- id: !extend ${time_source}
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: ${time_source}
uart:
- id: fan_uart
tx_pin: GPIO38
baud_rate: 115200
debug:
direction: TX
after:
bytes: 4
script:
- id: fan_uart_command
parameters:
cmd: int
arg: int
then:
- lambda: |-
uint8_t buf[4] = { 0xef, uint8_t(cmd), uint8_t(arg), 0};
buf[3] = buf[0] ^ buf[1] ^ buf[2]; // checksum
id(fan_uart).write_array(buf, sizeof(buf));
- id: fan_uart_init
then:
- script.execute:
id: fan_uart_command
cmd: 0x01
arg: 0x4e
- id: fan_uart_set_speed
parameters:
percent: uint8_t
then:
- lambda: |-
if (percent > 100) {
ESP_LOGW("_percent", "called with too-large value %d%%", (int)percent);
percent = 100;
}
id(fan_uart_command)->execute(0x02, percent);
output:
- platform: ledc
id: lcd_backlight_output
pin:
number: GPIO42
inverted: false
- platform: gpio
id: relay_output
pin:
number: GPIO36
- platform: template
id: fan_output
type: float
write_action:
- lambda: |-
uint8_t speed = ::round(state);
const uint8_t speed_percentages[4] = { 0, 20, 55, 100 };
if (speed < 4) {
uint8_t percent = speed_percentages[speed];
id(fan_uart_set_speed)->execute(percent);
}
fan:
- id: fan_id
name: Fan
platform: speed
output: fan_output
restore_mode: RESTORE_DEFAULT_OFF
speed_count: 3
on_state:
- lvgl.widget.update:
id: fan_speed_0_button
state:
checked: !lambda 'return !x->state && id(highlight_fan_off_button).state;'
- lvgl.widget.update:
id: fan_speed_1_button
state:
checked: !lambda 'return x->state && x->speed == 1;'
- lvgl.widget.update:
id: fan_speed_2_button
state:
checked: !lambda 'return x->state && x->speed == 2;'
- lvgl.widget.update:
id: fan_speed_3_button
state:
checked: !lambda 'return x->state && x->speed == 3;'
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
- platform: template
id: lvgl_paused_sensor_id
name: LVGL Paused
entity_category: diagnostic
condition:
lvgl.is_paused:
- platform: template
id: authorize_improv_over_ble_binary_sensor
name: Authorizing Improv-over-BLE
entity_category: diagnostic
internal: true
light:
- platform: monochromatic
id: display_backlight
name: Display Backlight
output: lcd_backlight_output
restore_mode: ALWAYS_ON
entity_category: diagnostic
spi:
type: octal
id: display_spi
clk_pin: GPIO21
data_pins:
- GPIO6
- GPIO7
- GPIO15
- GPIO16
- GPIO10
- GPIO9
- ignore_strapping_warning: true
number: GPIO46
- ignore_strapping_warning: true
number: GPIO3
display:
- platform: mipi_spi
id: display_id
model: st7789v
spi_id: display_spi
bus_mode: octal
dc_pin: GPIO17
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
sda: GPIO35
scl:
number: GPIO0
ignore_strapping_warning: true
scan: true
touchscreen:
- platform: ft63x6
id: touchscreen_id
display: display_id
i2c_id: touchscreen_i2c
calibration:
x_min: ${calibration_touchscreen_x_min}
y_min: ${calibration_touchscreen_y_min}
x_max: ${calibration_touchscreen_x_max}
y_max: ${calibration_touchscreen_y_max}
on_release:
- 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: 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: ${calibration_pulse_power_meter}
total:
name: "Total Pulses" # DEBUG
- platform: total_daily_energy
id: energy
name: "Energy"
restore: false
power_id: power_pulse_meter
unit_of_measurement: "kWh"
state_class: total_increasing
device_class: energy
accuracy_decimals: 3
filters:
# Convert W to kW
- multiply: 0.001
switch:
- platform: output
name: Relay
id: relay
output: relay_output
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- lvgl.widget.update:
id: light_toggle_button
state:
checked: true
on_turn_off:
- lvgl.widget.update:
id: light_toggle_button
state:
checked: false
- platform: template
name: Antiburn
id: switch_antiburn
icon: mdi:television-shimmer
optimistic: true
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:
- platform: template
id: highlight_fan_off_button
name: Highlight Fan-Off Button
optimistic: True
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
font:
- # file: "https://github.com/Pictogrammers/pictogrammers.github.io/raw/refs/heads/main/@mdi/font/7.4.47/fonts/materialdesignicons-webfont.ttf"
file: "fonts/pictogrammers/@mdi/font/7.4.47/fonts/materialdesignicons-webfont.ttf"
id: mdi_icons
bpp: 4
size: 52
glyphs:
- "\U000F0336" # mdi:lightbulb-outline
- "\U000F06E9" # mdi:lightbulb-on-outline
- "\U000F081D" # mdi:fan-off
- "\U000F1472" # mdi:fan-speed-1
- "\U000F1473" # mdi:fan-speed-2
- "\U000F1474" # mdi:fan-speed-3
- file: "gfonts://Roboto"
id: roboto_20
size: 20
lvgl:
id: lvgl_id
displays:
- display_id
touchscreens:
- touchscreen_id: touchscreen_id
long_press_repeat_time: 100ms
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
- id: factory_reset_button_style
bg_color: 0xFF0000
top_layer:
widgets:
- obj:
align: TOP_MID
styles: header_footer
widgets:
- label:
text:
time_format: "%l:%M %p"
time: ${time_source}
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_main
- id: page_next
text: "\uF054"
on_press:
then:
lvgl.page.next:
pages:
- id: page_main
pad_all: 0
widgets:
- buttonmatrix:
id: matrix_id
width: 100%
height: 260
x: 0
y: 30
pad_top: 2
pad_bottom: 0
pad_left: 2
pad_right: 0
pad_row: 3
pad_column: 1
text_font: mdi_icons
rows:
- buttons:
- id: light_toggle_button
text: "\U000F0336" # mdi:lightbulb-outline
width: 2
control:
checkable: true
on_click:
- switch.toggle: relay
- buttons:
- id: fan_speed_0_button
text: "\U000F081D" # mdi:fan-off
control:
checkable: true
on_click:
- fan.turn_off: fan_id
- id: fan_speed_1_button
text: "\U000F1472" # mdi:fan-speed-1
control:
checkable: true
on_click:
- fan.turn_on:
id: fan_id
speed: 1
- buttons:
- id: fan_speed_2_button
text: "\U000F1473" # mdi:fan-speed-2
control:
checkable: true
on_click:
- fan.turn_on:
id: fan_id
speed: 2
- id: fan_speed_3_button
text: "\U000F1474" # mdi:fan-speed-3
control:
checkable: true
on_click:
- fan.turn_on:
id: fan_id
speed: 3
- id: page_diagnostics
widgets:
- button:
width: 164
height: 100
x: 2
y: 58
id: factory_reset_lvgl_button
styles: factory_reset_button_style
on_release:
- if:
condition:
lambda: "return id(factory_reset_lvgl_button_longpress_ms) >= 10000;"
then:
- light.turn_off: display_backlight
- button.press: button_factory_reset
else:
- lambda: |-
id(factory_reset_lvgl_button_longpress_ms) = 0;
- lvgl.style.update:
id: factory_reset_button_style
bg_color: 0xFF0000
text_color: 0xFFFFFFF
on_press:
- lambda: |-
id(factory_reset_lvgl_button_longpress_ms) = 0;
on_long_press_repeat:
- logger.log:
format: "Long press repeat %d ms"
args: ["id(factory_reset_lvgl_button_longpress_ms)"]
- lambda: |-
id(factory_reset_lvgl_button_longpress_ms) += 100;
- if:
condition:
lambda: "return id(factory_reset_lvgl_button_longpress_ms) == 10000;"
then:
- lvgl.style.update:
id: factory_reset_button_style
bg_color: 0xFFFF00
text_color: 0xFF0000
- lvgl.widget.redraw: factory_reset_lvgl_button
widgets:
- label:
id: factory_reset_lvgl_button_label
align: CENTER
text: "Hold 10s\nfor Factory Reset"
text_align: CENTER
text_font: roboto_20
- button:
width: 164
height: 100
x: 2
y: 162
id: factory_reset_lvgl_cancel_button
on_release:
- lambda: |-
id(factory_reset_lvgl_button_longpress_ms) = 0;
- lvgl.page.show: page_main
widgets:
- label:
id: factory_reset_lvgl_cancel_button_label
align: CENTER
text: "Cancel"
text_align: CENTER
text_font: roboto_20
- id: page_setup
widgets:
- button:
width: 164
height: 100
x: 2
y: 58
id: authorize_improv_over_ble_lvgl_button
on_press:
- logger.log: "Authorizing Improv-over-BLE"
- binary_sensor.template.publish:
id: authorize_improv_over_ble_binary_sensor
state: ON
on_release:
- binary_sensor.template.publish:
id: authorize_improv_over_ble_binary_sensor
state: OFF
widgets:
- label:
id: authorize_improv_over_ble_lvgl_button_label
align: CENTER
text: "Improv-over-BLE\nAuthorize"
text_align: CENTER
text_font: roboto_20
- button:
width: 164
height: 100
x: 2
y: 162
id: page_setup_lvgl_cancel_button
on_release:
- lvgl.page.show: page_main
widgets:
- label:
id: page_setup_lvgl_cancel_button_label
align: CENTER
text: "Cancel"
text_align: CENTER
text_font: roboto_20
globals:
- id: factory_reset_lvgl_button_longpress_ms
type: int
restore_value: no
initial_value: '0'