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 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 bg_opa: transp 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: -- °C" 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