feat: add HA weather bindings, Status tab, and Temp/Status toggle wiring

This commit is contained in:
pyr0ball 2026-07-12 12:59:34 -07:00
parent 434ceda91a
commit 245bb79203
3 changed files with 301 additions and 146 deletions

View file

@ -41,20 +41,40 @@ lvgl:
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"
@ -74,101 +94,184 @@ lvgl:
y: 20
text: "Firecrest"
text_font: montserrat_16
- 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 visually
height: 360px # distinct; touch disambiguation itself comes from adv_hittest below,
# not from this size difference alone (a same-radius pair would also
# work correctly with adv_hittest on both, but differing radii keeps
# the two rings visually separable too)
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
id: temp_view
width: 100%
height: 100%
pad_all: 0
border_width: 0
outline_width: 0
scrollable: 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 visually
height: 360px # distinct; touch disambiguation itself comes from adv_hittest below,
# not from this size difference alone (a same-radius pair would also
# work correctly with adv_hittest on both, but differing radii keeps
# the two rings visually separable too)
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
- label:
id: home_temp_label
text: "-- °C"
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
@ -177,65 +280,32 @@ lvgl:
flex_flow: column
flex_align_cross: center
flex_align_track: center
flex_align_main: center
flex_align_main: space_evenly
widgets:
- label:
id: home_mode_label
text: "Mode: --"
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: home_temp_label
text: "-- °C"
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"
id: status_high_label
text: "High: -- °F"
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"
id: status_low_label
text: "Low: -- °F"
text_font: montserrat_20
- label:
id: status_precip_label
text: "Precipitation: -- %"
text_font: montserrat_20
- label:
align: bottom_right
x: -24
y: -24
text: "-- °" # outside temp stub — weather data source deferred per design spec
text_font: montserrat_20
- id: page_sensors
widgets:
- obj:

View file

@ -0,0 +1,84 @@
text_sensor:
- platform: homeassistant
id: weather_condition
entity_id: weather.forecast_home
on_value:
then:
- lvgl.label.update:
id: status_condition_label
text: !lambda |-
if (x == "clear-night") return "Clear";
if (x == "cloudy") return "Cloudy";
if (x == "exceptional") return "Exceptional";
if (x == "fog") return "Foggy";
if (x == "hail") return "Hail";
if (x == "lightning") return "Lightning";
if (x == "lightning-rainy") return "Thunderstorms";
if (x == "partlycloudy") return "Partly Cloudy";
if (x == "pouring") return "Pouring Rain";
if (x == "rainy") return "Rainy";
if (x == "snowy") return "Snowy";
if (x == "snowy-rainy") return "Snow/Rain";
if (x == "sunny") return "Sunny";
if (x == "windy") return "Windy";
if (x == "windy-variant") return "Windy";
return x;
sensor:
- platform: homeassistant
id: weather_outside_temp
entity_id: weather.forecast_home
attribute: temperature
on_value:
then:
- lvgl.label.update:
id: outside_temp_label
text:
format: "%.0f °"
args: ["x"]
- lvgl.label.update:
id: status_temp_label
text:
format: "Outside: %.0f °F"
args: ["x"]
- platform: homeassistant
id: weather_humidity
entity_id: weather.forecast_home
attribute: humidity
on_value:
then:
- lvgl.label.update:
id: status_humidity_label
text:
format: "Humidity: %.0f%%"
args: ["x"]
- platform: homeassistant
id: weather_forecast_high
entity_id: sensor.firecrest_forecast_high
on_value:
then:
- lvgl.label.update:
id: status_high_label
text:
format: "High: %.0f °F"
args: ["x"]
- platform: homeassistant
id: weather_forecast_low
entity_id: sensor.firecrest_forecast_low
on_value:
then:
- lvgl.label.update:
id: status_low_label
text:
format: "Low: %.0f °F"
args: ["x"]
- platform: homeassistant
id: weather_precip_prob
entity_id: sensor.firecrest_precip_probability
on_value:
then:
- lvgl.label.update:
id: status_precip_label
text:
format: "Precipitation: %.0f%%"
args: ["x"]

View file

@ -50,3 +50,4 @@ packages:
relays: !include components/relays.yaml
climate: !include components/climate.yaml
diagnostics: !include components/diagnostics.yaml
weather: !include components/weather.yaml