feat: scaffold classic+ variant as a baseline fork of the classic display
This commit is contained in:
parent
9e1a9ebe9f
commit
5c948377d5
2 changed files with 631 additions and 0 deletions
577
firmware/components/display-classic-plus.yaml
Normal file
577
firmware/components/display-classic-plus.yaml
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
lvgl:
|
||||
id: lvgl_comp
|
||||
displays:
|
||||
- lcd_display
|
||||
touchscreens:
|
||||
- touch_gt911
|
||||
pages:
|
||||
- id: page_home
|
||||
widgets:
|
||||
- obj:
|
||||
width: 100%
|
||||
height: 100%
|
||||
pad_all: 0
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
widgets:
|
||||
- label:
|
||||
align: top_left
|
||||
x: 70
|
||||
y: 20
|
||||
text: "-- --" # static placeholder clock — live time is a fast-follow, no time: component this pass
|
||||
text_font: montserrat_16
|
||||
- obj:
|
||||
id: mode_toggle
|
||||
align: top_mid
|
||||
y: 12
|
||||
width: 400px
|
||||
height: 44px
|
||||
radius: 22
|
||||
bg_color: 0x333333
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
pad_all: 4
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: row
|
||||
flex_align_cross: center
|
||||
flex_align_track: center
|
||||
flex_align_main: space_evenly
|
||||
widgets:
|
||||
- button:
|
||||
id: toggle_temp_btn
|
||||
width: 120px
|
||||
height: 36px
|
||||
radius: 18
|
||||
bg_color: 0xFFFFFF
|
||||
on_click:
|
||||
- lvgl.widget.show: temp_view
|
||||
- lvgl.widget.hide: status_view
|
||||
- lvgl.widget.update:
|
||||
id: toggle_temp_btn
|
||||
bg_color: 0xFFFFFF
|
||||
- lvgl.widget.update:
|
||||
id: toggle_status_btn
|
||||
bg_opa: transp
|
||||
widgets:
|
||||
- label:
|
||||
text: "Temp"
|
||||
text_color: 0x000000
|
||||
text_font: montserrat_16
|
||||
- button:
|
||||
id: toggle_status_btn
|
||||
width: 120px
|
||||
height: 36px
|
||||
radius: 18
|
||||
bg_opa: transp
|
||||
on_click:
|
||||
- lvgl.widget.show: status_view
|
||||
- lvgl.widget.hide: temp_view
|
||||
- lvgl.widget.update:
|
||||
id: toggle_status_btn
|
||||
bg_color: 0xFFFFFF
|
||||
- lvgl.widget.update:
|
||||
id: toggle_temp_btn
|
||||
bg_opa: transp
|
||||
widgets:
|
||||
- label:
|
||||
text: "Status"
|
||||
text_font: montserrat_16
|
||||
- button:
|
||||
width: 120px
|
||||
height: 36px
|
||||
radius: 18
|
||||
bg_opa: transp
|
||||
widgets:
|
||||
- label:
|
||||
text: "My Home"
|
||||
text_font: montserrat_16
|
||||
- label:
|
||||
align: top_right
|
||||
x: -16
|
||||
y: 20
|
||||
text: "Firecrest"
|
||||
text_font: montserrat_16
|
||||
- obj:
|
||||
id: temp_view
|
||||
width: 100%
|
||||
height: 100%
|
||||
pad_all: 0
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
bg_opa: transp
|
||||
# LVGL's base obj is clickable by default (obj->flags = LV_OBJ_FLAG_CLICKABLE in
|
||||
# lv_obj_create) and lv_obj_hit_test() requires that flag before checking geometry.
|
||||
# Being a full-screen, topmost sibling of mode_toggle, this container was silently
|
||||
# swallowing every tap over the toggle bar (no on_click, so nothing visibly happened)
|
||||
# before the touch search could reach mode_toggle underneath — same z-order class of
|
||||
# bug as the bg_opa fix above, but for touch capture instead of rendering.
|
||||
clickable: false
|
||||
widgets:
|
||||
- arc:
|
||||
id: dial_bg_arc
|
||||
align: center
|
||||
y: -20
|
||||
width: 400px
|
||||
height: 400px
|
||||
start_angle: 315
|
||||
end_angle: 225
|
||||
min_value: 0
|
||||
max_value: 1
|
||||
value: 0
|
||||
adjustable: false
|
||||
# arc_color/arc_width are flattened to top-level (MAIN part) rather than nested
|
||||
# under a "main:" key — an explicit "main:" sub-block silently suppresses the
|
||||
# implicit top-level MAIN-part properties (align/width/height/x/y), verified via
|
||||
# isolated test compile. indicator:/knob: below are unaffected since those are
|
||||
# genuinely separate, non-MAIN parts that require nesting.
|
||||
arc_color: 0x444444
|
||||
arc_width: 10
|
||||
indicator:
|
||||
arc_opa: transp
|
||||
knob:
|
||||
arc_opa: transp
|
||||
- arc:
|
||||
id: heat_arc
|
||||
align: center
|
||||
y: -20
|
||||
width: 400px
|
||||
height: 400px
|
||||
start_angle: 315
|
||||
end_angle: 225
|
||||
min_value: 60
|
||||
max_value: 90
|
||||
value: 70
|
||||
adjustable: true
|
||||
adv_hittest: true # LVGL's default touch hit-test just uses each widget's full
|
||||
# bounding square (any topmost widget under the touch point
|
||||
# wins), which is why the radius difference alone didn't fix
|
||||
# touch disambiguation. adv_hittest switches to LVGL's precise
|
||||
# ring-based hit test (see LV_EVENT_HIT_TEST in lv_arc.c),
|
||||
# which actually respects each arc's own radius.
|
||||
arc_opa: transp # flattened, see dial_bg_arc comment above
|
||||
indicator:
|
||||
arc_color: 0xFF3B30
|
||||
arc_width: 10
|
||||
knob:
|
||||
bg_color: 0xFF3B30
|
||||
on_value:
|
||||
then:
|
||||
- climate.control:
|
||||
id: main_thermostat
|
||||
target_temperature_low: !lambda "return (x - 32.0f) * 5.0f / 9.0f;"
|
||||
- lvgl.label.update:
|
||||
id: heat_to_value_label
|
||||
text:
|
||||
format: "%.0f"
|
||||
args: ["x"]
|
||||
- arc:
|
||||
id: cool_arc
|
||||
align: center
|
||||
y: -20
|
||||
width: 360px # smaller radius than heat_arc (400px) so the two rings are physically
|
||||
height: 360px # distinct. LVGL's adv_hittest arc hit test only accepts touches within
|
||||
# the arc's own ring band (radius - arc_width to radius, in its angular
|
||||
# span) — confirmed via lv_arc.c LV_EVENT_HIT_TEST. Same-radius arcs
|
||||
# occupy an identical ring band, so adv_hittest alone CANNOT disambiguate
|
||||
# them (verified on hardware: making both 400px left only the topmost,
|
||||
# cool_arc, draggable). Differing radii are required, not optional.
|
||||
start_angle: 315
|
||||
end_angle: 225
|
||||
mode: REVERSE
|
||||
min_value: 60
|
||||
max_value: 90
|
||||
value: 75
|
||||
adjustable: true
|
||||
adv_hittest: true # see heat_arc comment above — precise ring-based hit test
|
||||
arc_opa: transp # flattened, see dial_bg_arc comment above
|
||||
indicator:
|
||||
arc_color: 0x0A84FF
|
||||
arc_width: 10
|
||||
knob:
|
||||
bg_color: 0x0A84FF
|
||||
on_value:
|
||||
then:
|
||||
- climate.control:
|
||||
id: main_thermostat
|
||||
target_temperature_high: !lambda "return (x - 32.0f) * 5.0f / 9.0f;"
|
||||
- lvgl.label.update:
|
||||
id: cool_to_value_label
|
||||
text:
|
||||
format: "%.0f"
|
||||
args: ["x"]
|
||||
- obj:
|
||||
align: center
|
||||
y: -40
|
||||
width: 200px
|
||||
height: size_content
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: center
|
||||
flex_align_track: center
|
||||
flex_align_main: center
|
||||
widgets:
|
||||
- label:
|
||||
id: home_mode_label
|
||||
text: "Mode: --"
|
||||
text_font: montserrat_20
|
||||
on_click:
|
||||
lvgl.widget.show: mode_select_overlay
|
||||
- label:
|
||||
id: home_temp_label
|
||||
text: "-- °F"
|
||||
text_font: montserrat_48
|
||||
- button:
|
||||
align: bottom_left
|
||||
x: 24
|
||||
y: -24
|
||||
width: 90px
|
||||
height: 90px
|
||||
radius: 45
|
||||
on_click:
|
||||
climate.control:
|
||||
id: main_thermostat
|
||||
preset: away
|
||||
widgets:
|
||||
- label:
|
||||
align: center
|
||||
y: -10
|
||||
text: "Away"
|
||||
text_font: montserrat_16
|
||||
- button:
|
||||
align: bottom_mid
|
||||
x: -110
|
||||
y: -24
|
||||
width: 90px
|
||||
height: 60px
|
||||
radius: 30
|
||||
widgets:
|
||||
- label:
|
||||
id: heat_to_value_label
|
||||
align: center
|
||||
text: "70"
|
||||
text_font: montserrat_20
|
||||
- button:
|
||||
align: bottom_mid
|
||||
x: 110
|
||||
y: -24
|
||||
width: 90px
|
||||
height: 60px
|
||||
radius: 30
|
||||
widgets:
|
||||
- label:
|
||||
id: cool_to_value_label
|
||||
align: center
|
||||
text: "75"
|
||||
text_font: montserrat_20
|
||||
- label:
|
||||
id: outside_temp_label
|
||||
align: bottom_right
|
||||
x: -24
|
||||
y: -24
|
||||
text: "-- °"
|
||||
text_font: montserrat_20
|
||||
- obj:
|
||||
id: status_view
|
||||
hidden: true
|
||||
width: 100%
|
||||
height: 100%
|
||||
pad_all: 16
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
bg_opa: transp
|
||||
clickable: false # see temp_view comment above — same touch-capture fix
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: center
|
||||
flex_align_track: center
|
||||
flex_align_main: space_evenly
|
||||
widgets:
|
||||
- label:
|
||||
id: status_condition_label
|
||||
text: "--"
|
||||
text_font: montserrat_32
|
||||
- label:
|
||||
id: status_temp_label
|
||||
text: "Outside: -- °F"
|
||||
text_font: montserrat_24
|
||||
- label:
|
||||
id: status_humidity_label
|
||||
text: "Humidity: -- %"
|
||||
text_font: montserrat_20
|
||||
- label:
|
||||
id: status_high_label
|
||||
text: "High: -- °F"
|
||||
text_font: montserrat_20
|
||||
- label:
|
||||
id: status_low_label
|
||||
text: "Low: -- °F"
|
||||
text_font: montserrat_20
|
||||
- label:
|
||||
id: status_precip_label
|
||||
text: "Precipitation: -- %"
|
||||
text_font: montserrat_20
|
||||
- id: page_sensors
|
||||
widgets:
|
||||
- obj:
|
||||
width: 100%
|
||||
height: 510px
|
||||
pad_all: 16
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: center
|
||||
flex_align_track: center
|
||||
flex_align_main: space_evenly
|
||||
widgets:
|
||||
- label:
|
||||
id: sensors_temp_label
|
||||
text: "Temperature: -- °F"
|
||||
text_font: montserrat_28
|
||||
- label:
|
||||
id: sensors_humidity_label
|
||||
text: "Humidity: -- %"
|
||||
text_font: montserrat_28
|
||||
- label:
|
||||
id: sensors_pressure_label
|
||||
text: "Pressure: -- hPa"
|
||||
text_font: montserrat_28
|
||||
- id: page_settings
|
||||
widgets:
|
||||
- obj:
|
||||
width: 100%
|
||||
height: 510px
|
||||
pad_all: 16
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: center
|
||||
flex_align_track: start
|
||||
flex_align_main: start
|
||||
widgets:
|
||||
- label:
|
||||
id: settings_ip_label
|
||||
text: "IP: --"
|
||||
text_font: montserrat_20
|
||||
- label:
|
||||
id: settings_uptime_label
|
||||
text: "Uptime: -- min"
|
||||
text_font: montserrat_20
|
||||
- label:
|
||||
text: "Manual relay override (bypasses thermostat):"
|
||||
text_font: montserrat_16
|
||||
- obj:
|
||||
width: 100%
|
||||
height: size_content
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: row_wrap
|
||||
flex_align_cross: start
|
||||
flex_align_track: start
|
||||
flex_align_main: start
|
||||
widgets:
|
||||
- checkbox:
|
||||
text: "Fan"
|
||||
on_click:
|
||||
switch.toggle: relay_fan
|
||||
- checkbox:
|
||||
text: "Cool 1"
|
||||
on_click:
|
||||
switch.toggle: relay_cool1
|
||||
- checkbox:
|
||||
text: "Cool 2"
|
||||
on_click:
|
||||
switch.toggle: relay_cool2
|
||||
- checkbox:
|
||||
text: "Heat 1"
|
||||
on_click:
|
||||
switch.toggle: relay_heat1
|
||||
- checkbox:
|
||||
text: "Heat 2"
|
||||
on_click:
|
||||
switch.toggle: relay_heat2
|
||||
- id: page_schedule
|
||||
widgets:
|
||||
- obj:
|
||||
width: 100%
|
||||
height: 510px
|
||||
pad_all: 16
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: center
|
||||
flex_align_track: center
|
||||
flex_align_main: space_evenly
|
||||
widgets:
|
||||
- label:
|
||||
text: "Preset"
|
||||
text_font: montserrat_28
|
||||
- obj:
|
||||
width: 100%
|
||||
height: size_content
|
||||
border_width: 0
|
||||
outline_width: 0
|
||||
scrollable: false
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: row
|
||||
flex_align_cross: center
|
||||
flex_align_track: center
|
||||
flex_align_main: space_evenly
|
||||
widgets:
|
||||
- button:
|
||||
width: 300px
|
||||
height: 100px
|
||||
on_click:
|
||||
climate.control:
|
||||
id: main_thermostat
|
||||
preset: home
|
||||
widgets:
|
||||
- label:
|
||||
text: "Home"
|
||||
text_font: montserrat_28
|
||||
- button:
|
||||
width: 300px
|
||||
height: 100px
|
||||
on_click:
|
||||
climate.control:
|
||||
id: main_thermostat
|
||||
preset: away
|
||||
widgets:
|
||||
- label:
|
||||
text: "Away"
|
||||
text_font: montserrat_28
|
||||
- label:
|
||||
text: "Time-based scheduling runs in Home Assistant, not on this device."
|
||||
text_font: montserrat_16
|
||||
top_layer:
|
||||
widgets:
|
||||
- button:
|
||||
id: hamburger_btn
|
||||
align: top_left
|
||||
x: 16
|
||||
y: 16
|
||||
width: 60px
|
||||
height: 60px
|
||||
radius: 8
|
||||
on_click:
|
||||
lvgl.widget.show: nav_menu_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "="
|
||||
text_font: montserrat_28
|
||||
- obj:
|
||||
id: nav_menu_overlay
|
||||
hidden: true
|
||||
align: top_left
|
||||
x: 16
|
||||
y: 90px
|
||||
width: 280px
|
||||
height: size_content
|
||||
pad_all: 8
|
||||
radius: 8
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: stretch
|
||||
flex_align_track: start
|
||||
flex_align_main: start
|
||||
widgets:
|
||||
- button:
|
||||
on_click:
|
||||
- lvgl.page.show: page_home
|
||||
- lvgl.widget.hide: nav_menu_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Home"
|
||||
text_font: montserrat_20
|
||||
- button:
|
||||
on_click:
|
||||
- lvgl.page.show: page_sensors
|
||||
- lvgl.widget.hide: nav_menu_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Sensors"
|
||||
text_font: montserrat_20
|
||||
- button:
|
||||
on_click:
|
||||
- lvgl.page.show: page_settings
|
||||
- lvgl.widget.hide: nav_menu_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Settings"
|
||||
text_font: montserrat_20
|
||||
- button:
|
||||
on_click:
|
||||
- lvgl.page.show: page_schedule
|
||||
- lvgl.widget.hide: nav_menu_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Schedule"
|
||||
text_font: montserrat_20
|
||||
- obj:
|
||||
id: mode_select_overlay
|
||||
hidden: true
|
||||
align: center
|
||||
width: 280px
|
||||
height: size_content
|
||||
pad_all: 8
|
||||
radius: 8
|
||||
layout:
|
||||
type: flex
|
||||
flex_flow: column
|
||||
flex_align_cross: stretch
|
||||
flex_align_track: start
|
||||
flex_align_main: start
|
||||
widgets:
|
||||
- button:
|
||||
on_click:
|
||||
- climate.control:
|
||||
id: main_thermostat
|
||||
mode: HEAT
|
||||
- lvgl.widget.hide: mode_select_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Heat"
|
||||
text_font: montserrat_20
|
||||
- button:
|
||||
on_click:
|
||||
- climate.control:
|
||||
id: main_thermostat
|
||||
mode: COOL
|
||||
- lvgl.widget.hide: mode_select_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Cool"
|
||||
text_font: montserrat_20
|
||||
- button:
|
||||
on_click:
|
||||
- climate.control:
|
||||
id: main_thermostat
|
||||
mode: "OFF"
|
||||
- lvgl.widget.hide: mode_select_overlay
|
||||
widgets:
|
||||
- label:
|
||||
text: "Off"
|
||||
text_font: montserrat_20
|
||||
54
firmware/waveshare-thermostat-classic-plus.yaml
Normal file
54
firmware/waveshare-thermostat-classic-plus.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
substitutions:
|
||||
name: firecrest-classic-plus
|
||||
friendly_name: "Firecrest Classic+"
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
variant: esp32s3
|
||||
flash_size: 16MB
|
||||
framework:
|
||||
type: esp-idf
|
||||
sdkconfig_options:
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB: y
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: y
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
ap:
|
||||
ssid: "${friendly_name} Fallback"
|
||||
password: "thermostat1234"
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret ha_api_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
hardware_uart: UART0 # this board's "UART" USB-C port (used for flashing) also does log printing
|
||||
# per Waveshare's docs — ESPHome defaults S3 boards to USB_SERIAL_JTAG,
|
||||
# a different physical port, which is why logs went silent
|
||||
|
||||
packages:
|
||||
panel: !include components/panel.yaml
|
||||
display: !include components/display-classic-plus.yaml
|
||||
sensors: !include components/sensors.yaml
|
||||
relays: !include components/relays.yaml
|
||||
climate: !include components/climate.yaml
|
||||
diagnostics: !include components/diagnostics.yaml
|
||||
weather: !include components/weather.yaml
|
||||
# status_mirror: !include components/status-mirror.yaml # added in Task 3
|
||||
Loading…
Reference in a new issue