feat: add HA weather bindings, Status tab, and Temp/Status toggle wiring
This commit is contained in:
parent
434ceda91a
commit
245bb79203
3 changed files with 301 additions and 146 deletions
|
|
@ -41,20 +41,40 @@ lvgl:
|
||||||
flex_align_main: space_evenly
|
flex_align_main: space_evenly
|
||||||
widgets:
|
widgets:
|
||||||
- button:
|
- button:
|
||||||
|
id: toggle_temp_btn
|
||||||
width: 120px
|
width: 120px
|
||||||
height: 36px
|
height: 36px
|
||||||
radius: 18
|
radius: 18
|
||||||
bg_color: 0xFFFFFF
|
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:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
text: "Temp"
|
text: "Temp"
|
||||||
text_color: 0x000000
|
text_color: 0x000000
|
||||||
text_font: montserrat_16
|
text_font: montserrat_16
|
||||||
- button:
|
- button:
|
||||||
|
id: toggle_status_btn
|
||||||
width: 120px
|
width: 120px
|
||||||
height: 36px
|
height: 36px
|
||||||
radius: 18
|
radius: 18
|
||||||
bg_opa: transp
|
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:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
text: "Status"
|
text: "Status"
|
||||||
|
|
@ -74,101 +94,184 @@ lvgl:
|
||||||
y: 20
|
y: 20
|
||||||
text: "Firecrest"
|
text: "Firecrest"
|
||||||
text_font: montserrat_16
|
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:
|
- obj:
|
||||||
align: center
|
id: temp_view
|
||||||
y: -40
|
width: 100%
|
||||||
width: 200px
|
height: 100%
|
||||||
height: size_content
|
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
|
border_width: 0
|
||||||
outline_width: 0
|
outline_width: 0
|
||||||
scrollable: false
|
scrollable: false
|
||||||
|
|
@ -177,65 +280,32 @@ lvgl:
|
||||||
flex_flow: column
|
flex_flow: column
|
||||||
flex_align_cross: center
|
flex_align_cross: center
|
||||||
flex_align_track: center
|
flex_align_track: center
|
||||||
flex_align_main: center
|
flex_align_main: space_evenly
|
||||||
widgets:
|
widgets:
|
||||||
- label:
|
- label:
|
||||||
id: home_mode_label
|
id: status_condition_label
|
||||||
text: "Mode: --"
|
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
|
text_font: montserrat_20
|
||||||
- label:
|
- label:
|
||||||
id: home_temp_label
|
id: status_high_label
|
||||||
text: "-- °C"
|
text: "High: -- °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
|
text_font: montserrat_20
|
||||||
- button:
|
|
||||||
align: bottom_mid
|
|
||||||
x: 110
|
|
||||||
y: -24
|
|
||||||
width: 90px
|
|
||||||
height: 60px
|
|
||||||
radius: 30
|
|
||||||
widgets:
|
|
||||||
- label:
|
- label:
|
||||||
id: cool_to_value_label
|
id: status_low_label
|
||||||
align: center
|
text: "Low: -- °F"
|
||||||
text: "75"
|
text_font: montserrat_20
|
||||||
|
- label:
|
||||||
|
id: status_precip_label
|
||||||
|
text: "Precipitation: -- %"
|
||||||
text_font: montserrat_20
|
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
|
- id: page_sensors
|
||||||
widgets:
|
widgets:
|
||||||
- obj:
|
- obj:
|
||||||
|
|
|
||||||
84
firmware/components/weather.yaml
Normal file
84
firmware/components/weather.yaml
Normal 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"]
|
||||||
|
|
@ -50,3 +50,4 @@ packages:
|
||||||
relays: !include components/relays.yaml
|
relays: !include components/relays.yaml
|
||||||
climate: !include components/climate.yaml
|
climate: !include components/climate.yaml
|
||||||
diagnostics: !include components/diagnostics.yaml
|
diagnostics: !include components/diagnostics.yaml
|
||||||
|
weather: !include components/weather.yaml
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue