substitutions: name: sthome-ut9 friendly_name: "sthome-ut9" #ALLOWED_CHARACTERS_FULL: " !#%\"'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ¿ÁÂÄÅÉÖÚßàáâãäåæçèéêëìíîðñòóôõöøùúûüýþāăąćčďĐđēėęěğĮįıļľŁłńňőřśšťũūůűųźŻżŽžơưșțΆΈΌΐΑΒΓΔΕΖΗΘΚΜΝΠΡΣΤΥΦάέήίαβγδεζηθικλμνξοπρςστυφχψωϊόύώАБВГДЕЖЗИКЛМНОПРСТУХЦЧШЪЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђєіїјљњћ" ALLOWED_CHARACTERS: " !#%\"'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ•" DD_MAX_YEARS: "5" esphome: name: "${name}" friendly_name: "${friendly_name}" on_boot: - lambda: |- char yearstr[8]; lv_dropdown_set_selected(cal_dd_year->obj, 0); lv_dropdown_set_selected(cal_dd_month->obj, 0); lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); int year = atoi(yearstr); id(update_calendar).execute(year, 1); - lvgl.widget.hide: ind_geyser_on - delay: 5s - lvgl.widget.hide: boot_screen esp32: board: esp32dev framework: type: arduino #type: esp-idf #debug: # update_interval: 5s # Enable logging logger: level: info # Enable Home Assistant API api: encryption: key: "LI7j37zs9HsWNsUZ5c83leThmhHsgIVReAPoc9U6pVU=" on_client_connected: - if: condition: lambda: 'return (0 == client_info.find("Home Assistant "));' then: - lvgl.widget.show: lbl_hastatus - lvgl.widget.show: lbl_degree on_client_disconnected: - if: condition: lambda: 'return (0 == client_info.find("Home Assistant "));' then: - lvgl.widget.hide: lbl_hastatus - lvgl.widget.hide: ind_geyser_on - lvgl.widget.hide: lbl_degree ota: - platform: esphome password: "8ebd5bcefbdc833a5f6ddc4e8ba56e39" wifi: #ssid: !secret wifi_ssid #password: !secret wifi_password # we will use local dns server for local dns resolution power_save_mode: none domain: ".sthome.org" networks: - ssid: !secret wifi_ssid1 password: !secret wifi_password1 - ssid: !secret wifi_ssid2 password: !secret wifi_password2 - ssid: !secret wifi_ssid3 password: !secret wifi_password3 - ssid: !secret wifi_ssid4 password: !secret wifi_password4 - ssid: !secret wifi_ssid5 password: !secret wifi_password5 manual_ip: # For faster connection startup, set a static IP address # Set this to the IP of the ESP static_ip: 10.0.2.9 gateway: 10.0.0.2 subnet: 255.255.240.0 dns1: 10.0.0.1 dns2: 1.1.1.1 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${name} Fallback Hotspot" password: "iZxjpw7ucRs4" captive_portal: #psram: one_wire: - platform: gpio pin: GPIO4 id: temperature_sensors #i2c: # sda: GPIO21 # scl: GPIO22 font: # - file: "fonts/Windows/comic.ttf" # id: comic # size: 20 # bpp: 4 #2 # - file: "fonts/misc/tom-thumb.bdf" # id: tomthumb # # # gfonts://family[@weight] # - file: "gfonts://Roboto" # id: roboto_20 # size: 20 # # - file: # type: gfonts # family: Roboto # weight: 900 # id: roboto_20 # size: 20 # # - file: "fonts/Windows/arial.ttf" # id: arial_14 # size: 14 # # gfonts://family[@weight] # - file: "gfonts://Roboto" # id: roboto # size: 20 - file: "gfonts://Carrois Gothic" id: Carrois_Gothic size: 20 bpp: 4 - file: "gfonts://Roboto" id: roboto_200 size: 200 bpp: 4 glyphs: [ 0123456789,.,°,a,n, "\u0020", # space "\u003A", # colon ] - file: "gfonts://Roboto" id: roboto_192 size: 192 bpp: 4 glyphs: [ 0123456789,.,°,a,n, "\u0020", # space "\u003A", # colon ] - file: "gfonts://Roboto" id: geyser_temperature_font2 size: 60 bpp: 4 glyphs: [ °,C, ] - file: "gfonts://Roboto" id: geyser_temperature_font3 size: 30 bpp: 4 glyphs: [ b,o,m,p,t, ] - file: "gfonts://Roboto" id: roboto_100 size: 100 bpp: 4 glyphs: [ 0123456789,.,a,n, "\u0020", # space "\u003A", # colon ] # - file: "gfonts://Merriweather" # id: Merriweather # size: 30 # # - file: "gfonts://Material+Symbols+Outlined" # id: icons_50 # size: 50 # glyphs: ["\U0000e425"] # mdi-timer # # - file: "https://github.com/AlmostInteractive/ESP32-S3-Box-3-Voice-Assistant-Sensor-Dock/raw/master/fonts/materialdesignicons-webfont.ttf" # id: font_icon_tiny # size: 30 # glyphs: [ # "\U000F009A",#bell # "\U000F009C",#bell-outline # "\U000F00AD",#block-helper # "\U000F01F2",#emoticon-outline # "\U000F0375",#minus-box # "\U000F0416",#plus-box # ] # - file: "fonts/misc/materialdesignicons-webfont.ttf" id: font_icon_small size: 24 #45 glyphs: [ "\U0000F5A9", ] # - file: "https://github.com/AlmostInteractive/ESP32-S3-Box-3-Voice-Assistant-Sensor-Dock/raw/master/fonts/materialdesignicons-webfont.ttf" # id: font_icon_large # size: 65 # glyphs: [ # "\U000F004F",#arrow-left-bold-circle # "\U000F0056",#arrow-right-bold-circle # ] image: - file: https://esphome.io/_static/favicon-512x512.png id: boot_logo resize: 200x200 type: RGB565 transparency: alpha_channel - file: mdi:fire id: icon_fire resize: 100x100 type: BINARY #RGB565 # - file: "images/fire.svg" # id: img_fire # resize: 100x100 # type: RGB565 # transparency: alpha_channel # - file: "images/fire-red.svg" # id: img_fire_red # resize: 100x100 # 25x25 # type: RGB565 # transparency: alpha_channel # - file: "images/thermometer2.png" # id: thermometer_img # type: RGB565 # transparency: alpha_channel color: - id: green hex: '75D15F' - id: red hex: 'FF3131' - id: light_red hex: 'FF3340' - id: blue hex: '47B7E9' - id: blue_light hex: 'CFE2F3' - id: blue_dark hex: '085296' - id: amber hex: 'FBAB35' - id: lime hex: '20FC30' - id: pink hex: 'D92BBC' - id: yellow hex: 'FFC000' - id: black hex: '000000' - id: white hex: 'ffffff' - id: purple hex: '73264D' - id: grey_dark hex: '202020' - id: grey_light hex: 'e0e0e0' sun: id: sun_sensor latitude: !secret latitude longitude: !secret longitude #time: # - platform: sntp # timezone: Africa/Johannesburg # servers: # - ntp1.meraka.csir.co.za # 146.64.24.58 # - ntp.as3741.net # 196.4.160.4 # - ntp1.inx.net.za # 196.10.52.57 time: - platform: homeassistant id: time_source update_interval: 360min # Change sync interval from default 5min to 6 hours on_time_sync: then: - if: # Publish the time the device was last restarted, but only once. condition: lambda: 'return id(device_last_restart).state == "";' then: - text_sensor.template.publish: id: device_last_restart state: !lambda 'return id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p");' - script.execute: ind_heating_update - script.execute: time_update - script.execute: init_calendar on_time: - minutes: '*' seconds: '*' then: - script.execute: ind_heating_update - script.execute: time_update - hours: 1,2,3,4 minutes: 5 seconds: 0 then: - switch.turn_on: switch_antiburn - hours: 1,2,3,4 minutes: 35 seconds: 0 then: - switch.turn_off: switch_antiburn spi: - id: spi_bus0 clk_pin: GPIO18 mosi_pin: GPIO23 miso_pin: GPIO19 interface: any display: - platform: ili9xxx model: ili9488 id: tft_display color_palette: 8BIT data_rate: 40MHz cs_pin: GPIO15 dc_pin: GPIO2 reset_pin: GPIO27 auto_clear_enabled: false update_interval: never invert_colors: false show_test_card: true transform: swap_xy: true # landscape # mirror_x: true # landscape dimensions: height: 480 width: 320 # Define a PWM output on the ESP32 output: - platform: ledc pin: GPIO26 id: backlight_pwm # Define a monochromatic, dimmable light for the backlight light: - platform: monochromatic output: backlight_pwm name: "Display Backlight" id: back_light restore_mode: ALWAYS_ON touchscreen: platform: xpt2046 id: touch_screen cs_pin: GPIO33 transform: swap_xy: true # landscape # mirror_y: true # portrait calibration: x_min: 231 #201 #281 x_max: 3878 #3793 #3848 y_min: 221 #228 #347 y_max: 3861 #3914 #3878 # on_touch: # - lambda: |- # ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d", # touch.x, # touch.y, # touch.x_raw, # touch.y_raw # ); # on_release: # then: # - if: # condition: lvgl.is_paused # then: # - lvgl.resume: # - lvgl.widget.redraw: lvgl: # color_depth: 16 # bg_color: 0x0F0F0F default_font: unscii_8 # align: center theme: button: bg_color: grey_light #0x2F8CD8 # bg_grad_color: 0x005782 # bg_grad_dir: VER bg_opa: COVER border_color: 0x0077b3 border_width: 1 text_color: 0xFFFFFF pressed: # set some button colors to be different in pressed state bg_color: 0x006699 bg_grad_color: 0x00334d checked: # set some button colors to be different in checked state bg_color: 0x1d5f96 bg_grad_color: 0x03324A text_color: 0xfff300 switch: bg_color: 0xC0C0C0 bg_grad_color: 0xb0b0b0 bg_grad_dir: VER bg_opa: COVER checked: bg_color: 0x1d5f96 bg_grad_color: 0x03324A bg_grad_dir: VER bg_opa: COVER knob: bg_color: 0xFFFFFF bg_grad_color: 0xC0C0C0 bg_grad_dir: VER bg_opa: COVER slider: border_width: 1 border_opa: 15% bg_color: 0xcccaca bg_opa: 15% indicator: bg_color: 0x1d5f96 bg_grad_color: 0x03324A bg_grad_dir: VER bg_opa: COVER knob: bg_color: 0x2F8CD8 bg_grad_color: 0x005782 bg_grad_dir: VER bg_opa: COVER border_color: 0x0077b3 border_width: 1 text_color: 0xFFFFFF style_definitions: - id: header_footer bg_color: darkgrey #0x2F8CD8 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: clockdate_style text_font: montserrat_20 #roboto_20 #unscii_8 text_align: center text_color: 0x000000 radius: 4 pad_all: 2 - id: sty_calendar_small radius: 0 pad_all: 0 pad_row: 0 pad_column: 0 text_font: unscii_8 shadow_opa: TRANSP text_color: black bg_color: white bg_opa: COVER border_color: grey_light border_width: 1 border_opa: cover #TRANSP - id: sty_calendar_small_noborders radius: 0 pad_all: 0 pad_row: 0 pad_column: 0 text_font: unscii_8 shadow_opa: TRANSP text_color: black bg_color: white bg_opa: COVER border_color: grey_light border_width: 0 border_opa: cover #TRANSP displays: - tft_display buffer_size: 20% top_layer: widgets: - label: text: "\U0000F5A9" # "\uF1EB" id: lbl_hastatus hidden: true align: top_right x: -2 y: 1 text_font: font_icon_small #montserrat_16 text_align: right text_color: 0x202020 # 0xFFFFFF - obj: # clipping rectangle x: 0 #15 y: -24 #7 pad_all: 0 height: 90 width: 65 align: BOTTOM_RIGHT bg_color: 0x000000 border_color: 0xFFFFFF border_width: 0 radius: 0 bg_opa: LV_OPA_TRANSP scrollbar_mode: "OFF" widgets: - image: id: ind_geyser_on align: CENTER #BOTTOM_RIGHT #TOP_RIGHT src: icon_fire image_recolor: RED image_recolor_opa: 100% x: 0 #15 #15 y: 0 #-22 #7 height: 100 #25 width: 100 #25 - obj: id: boot_screen x: 0 y: 0 width: 100% height: 100% bg_color: 0xffffff bg_opa: COVER radius: 0 pad_all: 0 border_width: 0 widgets: - image: align: CENTER src: boot_logo y: -40 - spinner: align: CENTER y: 95 height: 50 width: 50 spin_time: 1s arc_length: 60deg arc_width: 8 indicator: arc_color: 0x18bcf2 arc_width: 8 on_press: - lvgl.widget.hide: boot_screen - buttonmatrix: text_font: montserrat_16 align: bottom_mid styles: header_footer pad_all: 0 outline_width: 0 id: footer width: 480 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: main_page - id: page_next text: "\uF054" on_press: then: lvgl.page.next: pages: - id: pg_calendar widgets: - button: id: cal_btn_prev_month styles: sty_calendar_small align: TOP_MID pad_all: 0 outline_width: 0 border_color: black border_width: 0 #1 border_opa: TRANSP x: -75 y: 30 width: 20 height: 20 bg_color: grey_light text_color: 0xD3D3D3 text_font: montserrat_14 widgets: - label: align: center text_font: montserrat_14 text: "<" on_press: then: lambda: |- id(update_calendar_month).execute(-1); - dropdown: id: cal_dd_year styles: sty_calendar_small text_font: montserrat_12 height: 20 width: 55 radius: 0 align_to: id: cal_btn_prev_month align: out_right_top x: 80 y: 0 #12.5% options: - 2024 - 2025 selected_index: 0 dropdown_list: text_line_space: 3 pad_all: 1 text_font: unscii_8 max_height: 260 radius: 0 selected: checked: text_color: 0xFF0000 on_value: then: - lambda: |- char yearstr[8]; lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); int month = 1 + lv_dropdown_get_selected(cal_dd_month->obj); auto year = atoi(yearstr); id(update_calendar).execute(year, month); - dropdown: id: cal_dd_month styles: sty_calendar_small text_font: montserrat_12 height: 20 width: 55 radius: 0 align_to: id: cal_dd_year align: out_right_top x: 0 y: 0 #12.5% options: - Jan - Feb - Mar - Apr - May - Jun - Jul - Aug - Sep - Oct - Nov - Dec selected_index: 0 dropdown_list: text_line_space: 3 pad_all: 1 text_font: unscii_8 max_height: 260 radius: 0 selected: checked: text_color: 0xFF0000 on_value: then: - lambda: |- char yearstr[8]; lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); auto year = atoi(yearstr); id(update_calendar).execute(year, x + 1); - button: id: cal_btn_next_month styles: sty_calendar_small align_to: id: cal_dd_month align: out_right_top x: 0 y: 0 pad_all: 0 outline_width: 0 border_color: black border_width: 0 #1 border_opa: TRANSP x: -75 y: 30 width: 20 height: 20 bg_color: grey_light text_color: 0xD3D3D3 text_font: montserrat_14 widgets: - label: align: center text_font: montserrat_14 text: ">" on_press: then: lambda: |- id(update_calendar_month).execute(1); - buttonmatrix: id: bmx_cal_header_dow styles: sty_calendar_small_noborders align_to: id: cal_btn_prev_month align: out_bottom_left x: 80 y: 0 #12.5% pad_all: 0 outline_width: 0 border_color: black border_width: 0 #1 border_opa: TRANSP x: 0 y: 0 width: 150 height: 20 bg_color: black text_color: 0xD3D3D3 items: styles: sty_calendar_small_noborders pressed: bg_color: 0x006699 bg_grad_color: 0x00334d checked: bg_color: 0x1d5f96 bg_grad_color: 0x03324A rows: - buttons: - id: r0c1 text: "Su" width: 1 - id: r0c2 text: "Mo" width: 1 - id: r0c3 text: "Tu" width: 1 - id: r0c4 text: "We" width: 1 - id: r0c5 text: "Th" width: 1 - id: r0c6 text: "Fr" width: 1 - id: r0c7 text: "Sa" width: 1 on_press: then: - lambda: |- ESP_LOGI("lvgl", "%d", x); - buttonmatrix: id: bmx_calendar styles: sty_calendar_small align_to: id: bmx_cal_header_dow align: out_bottom_left x: 0 y: 0 #12.5% pad_all: 0 outline_width: 0 border_color: black border_width: 0 #1 border_opa: TRANSP x: 0 y: 0 width: 150 height: 100 bg_color: black text_color: 0xD3D3D3 items: styles: sty_calendar_small pressed: bg_color: 0x006699 bg_grad_color: 0x00334d checked: bg_color: 0x1d5f96 bg_grad_color: 0x03324A rows: - buttons: - id: r1c1 text: " " width: 1 control: recolor: true - id: r1c2 text: " " width: 1 - id: r1c3 text: "1" width: 1 - id: r1c4 text: "2" width: 1 - id: r1c5 text: "3" width: 1 - id: r1c6 text: "4" width: 1 - id: r1c7 text: "5" width: 1 # - buttons: # - id: r2c1 # text: "6" # width: 1 # control: # recolor: true # - id: r2c2 # text: "7" # width: 1 # - id: r2c3 # text: "8" # width: 1 # - id: r2c4 # text: "9" # width: 1 # - id: r2c5 # text: "10" # width: 1 # - id: r2c6 # text: "11" # width: 1 # - id: r2c7 # text: "12" # width: 1 # - buttons: # - id: r3c1 # text: "13" # width: 1 # - id: r3c2 # text: "14" # width: 1 # - id: r3c3 # text: "15" # width: 1 # - id: r3c4 # text: "16" # width: 1 # - id: r3c5 # text: "17" # width: 1 # - id: r3c6 # text: "18" # width: 1 # - id: r3c7 # text: "19" # width: 1 # - buttons: # - id: r4c1 # text: "20" # width: 1 # - id: r4c2 # text: "21" # width: 1 # - id: r4c3 # text: "22" # width: 1 # - id: r4c4 # text: "23" # width: 1 # - id: r4c5 # text: "24" # width: 1 # - id: r4c6 # text: "25" # width: 1 # - id: r4c7 # text: "26" # width: 1 # - buttons: # - id: r5c1 # text: "27" # width: 1 # - id: r5c2 # text: "28" # width: 1 # - id: r5c3 # text: "29" # width: 1 # - id: r5c4 # text: "30" # width: 1 # - id: r5c5 # text: "31" # width: 1 # - id: r5c6 # text: " " # width: 1 # - id: r5c7 # text: " " # width: 1 # Define actions on button press on_press: then: lambda: |- auto stat = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); auto day = lv_btnmatrix_get_btn_text(bmx_calendar->obj, x); ESP_LOGI("lvgl", "%d (%s) [%d]", x, day, stat); if(stat) { lv_btnmatrix_clear_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); } else { lv_btnmatrix_set_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); } - id: main_page #pg_geyser_temp widgets: - obj: id: rect_gtoptemp x: 0 y: 0 #30 pad_all: 0 height: 290 width: 240 align: TOP_LEFT bg_color: 0x000000 border_color: 0xFFFFFF border_width: 0 radius: 0 bg_opa: COVER - obj: id: rect_gbottemp y: 0 pad_all: 0 height: 290 width: 240 align_to: id: rect_gtoptemp align: out_right_top x: 0 y: 0 #12.5% bg_color: 0x000000 #0xFF4500 border_color: 0xFFFFFF border_width: 0 radius: 0 bg_opa: COVER - label: text: " " id: lbl_gtoptemp hidden: false align: LEFT_MID x: 0 y: -10 text_font: roboto_200 text_align: center text_color: 0x0 bg_opa: LV_OPA_TRANSP bg_color: 0xffffff - label: text: " " id: lbl_gbottemp hidden: false align: RIGHT_MID x: 0 y: -10 text_font: roboto_200 text_align: center text_color: 0x0 bg_opa: LV_OPA_TRANSP bg_color: 0xffffff - label: text: "°C" id: lbl_degree hidden: false align: BOTTOM_MID x: 0 y: -30 text_font: geyser_temperature_font2 text_align: center text_color: 0x0 bg_opa: LV_OPA_TRANSP bg_color: 0xffffff - label: text: "top" id: lbl_top hidden: false align: TOP_MID x: -120 y: 20 text_font: geyser_temperature_font3 text_align: center text_color: 0x0 bg_opa: LV_OPA_TRANSP bg_color: 0xffffff - label: text: "bottom" id: lbl_bottom hidden: false align: TOP_MID x: 120 y: 20 text_font: geyser_temperature_font3 text_align: center text_color: 0x0 bg_opa: LV_OPA_TRANSP bg_color: 0xffffff - id: pg_settings widgets: - textarea: id: geyser_schedule one_line: true placeholder_text: "Enter text here" - keyboard: id: keyboard_id textarea: geyser_schedule mode: TEXT_UPPER text_font: montserrat_20 on_focus: then: - lvgl.keyboard.update: id: keyboard_id mode: number textarea: geyser_schedule on_ready: then: - logger.log: Keyboard is ready on_cancel: then: - logger.log: Keyboard cancelled - id: pg_clock widgets: - obj: # clock container height: 300 #SIZE_CONTENT width: 300 # 100% align: TOP_MID pad_all: 0 border_width: 0 bg_color: 0xFFFFFF widgets: - meter: # clock face height: 300 width: 300 align: TOP_MID bg_opa: TRANSP border_width: 0 text_color: 0x000000 scales: - range_from: 0 # minutes scale range_to: 720 angle_range: 360 rotation: 270 ticks: width: 1 count: 61 length: 10 color: 0x000000 indicators: - line: id: minute_hand width: 3 color: 0xa6a6a6 r_mod: -4 value: 0 - range_from: 1 # hours scale for labels range_to: 12 angle_range: 330 rotation: 300 ticks: width: 1 count: 12 length: 1 major: stride: 1 width: 4 length: 10 color: 0xC0C0C0 label_gap: 12 - range_from: 0 # hi-res hours scale for hand range_to: 720 angle_range: 360 rotation: 270 ticks: count: 0 indicators: - line: id: hour_hand width: 5 color: 0xa6a6a6 r_mod: -30 value: 0 # Second hand - angle_range: 360 rotation: 270 range_from: 0 range_to: 60 indicators: - line: id: second_hand width: 2 color: Red r_mod: -10 - label: align: CENTER styles: clockdate_style id: day_label y: -50 - label: align: CENTER id: date_label styles: clockdate_style y: 50 - id: pg_digital_clock widgets: - obj: id: rect_gtoptemp1 x: 0 y: 0 #30 pad_all: 0 height: 290 width: 240 align: TOP_LEFT bg_color: 0x000000 border_color: 0xFFFFFF border_width: 0 radius: 0 bg_opa: COVER - obj: id: rect_gbottemp1 y: 0 pad_all: 0 height: 290 width: 240 align_to: id: rect_gtoptemp align: out_right_top x: 0 y: 0 #12.5% bg_color: 0x000000 #0xFF4500 border_color: 0xFFFFFF border_width: 0 radius: 0 bg_opa: COVER - label: text: " " id: lbl_digitalclock hidden: false align: TOP_MID x: 0 y: 20 text_font: roboto_192 text_align: center text_color: RED bg_opa: LV_OPA_TRANSP bg_color: 0xffffff #interval: # - interval: 30s # then: # - lvgl.page.next switch: - platform: restart name: "${name} Restart" id: "restart_switch" - platform: template name: Antiburn id: switch_antiburn icon: mdi:television-shimmer optimistic: true entity_category: "config" turn_on_action: - logger.log: "Starting Antiburn" - if: condition: lvgl.is_paused then: - lvgl.resume: - lvgl.widget.redraw: - lvgl.pause: show_snow: true turn_off_action: - logger.log: "Stopping Antiburn" - if: condition: lvgl.is_paused then: - lvgl.resume: - lvgl.widget.redraw: binary_sensor: - platform: homeassistant name: "Geyser Heating" entity_id: binary_sensor.sthome_ut8_heating id: geyser_heating on_press: then: - lvgl.widget.show: ind_geyser_on on_release: then: - lvgl.widget.hide: ind_geyser_on sensor: - platform: dallas_temp address: 0xfe00000037b3d528 name: "Study Temperature" id: study_temperature update_interval: "60s" resolution: 12 one_wire_id: temperature_sensors unit_of_measurement: "°C" #icon: "mdi:water-thermometer" device_class: "temperature" state_class: "measurement" accuracy_decimals: 1 filters: - filter_out: nan # - sliding_window_moving_average: # window_size: 120 # averages over 120 update intervals # send_every: 60 # reports every 60 update intervals # Report wifi signal strength every 5 min if changed - platform: wifi_signal name: WiFi Signal id: wifi_sig update_interval: 300s filters: - delta: 10% # human readable uptime sensor output to the text sensor above - platform: uptime name: Uptime in Days id: uptime_sensor_days update_interval: 10s on_raw_value: then: - text_sensor.template.publish: id: uptime_human state: !lambda |- int seconds = round(id(uptime_sensor_days).raw_state); int days = seconds / (24 * 3600); seconds = seconds % (24 * 3600); int hours = seconds / 3600; seconds = seconds % 3600; int minutes = seconds / 60; seconds = seconds % 60; auto days_str = std::to_string(days); auto hours_str = std::to_string(hours); auto minutes_str = std::to_string(minutes); auto seconds_str = std::to_string(seconds); return ( (days ? days_str + "d " : "") + (hours ? hours_str + "h " : "") + (minutes ? minutes_str + "m " : "") + (seconds_str + "s") ).c_str(); # number of seconds since midnight - platform: template id: time_of_day name: "Time of day" accuracy_decimals: 0 unit_of_measurement: "s" lambda: |- auto currenttime = id(time_source).now(); ESPTime time_obj = currenttime; time_obj.second = 0; time_obj.minute = 0; time_obj.hour = 0; time_obj.recalc_timestamp_local(); return currenttime.timestamp - time_obj.timestamp; update_interval: 10s - platform: homeassistant name: "Geyser Top Temperature" entity_id: sensor.sthome_ut8_geyser_top_temperature id: geyser_top_temperature on_raw_value: then: - lvgl.label.update: id: lbl_gtoptemp text: !lambda |- if(isnan(x)) { return "."; } else { char buffer [10]; buffer[0] = '\0'; snprintf (buffer, 10, "%.0f", x); return buffer; } - lvgl.widget.update: id: rect_gtoptemp bg_color: !lambda |- if(isnan(x)) { return lv_color_hex(0x000000); } if(x >= 60) { return lv_color_hex(0xFF0000); } if(x >= 50) { return lv_color_hex(0xFFFF00); } if(x >= 40) { return lv_color_hex(0x00FF00); } return lv_color_hex(0x0000FF); - platform: homeassistant name: "Geyser Bottom Temperature" entity_id: sensor.sthome_ut8_geyser_bottom_temperature id: geyser_bottom_temperature on_raw_value: then: - lvgl.label.update: id: lbl_gbottemp text: !lambda |- if(isnan(x)) { return "."; } else { char buffer [10]; buffer[0] = '\0'; snprintf (buffer, 10, "%.0f", x); return buffer; } - lvgl.widget.update: id: rect_gbottemp bg_color: !lambda |- if(isnan(x)) { return lv_color_hex(0x000000); } if(x < 40) { return lv_color_hex(0x0000FF); } if(x < 50) { return lv_color_hex(0x00FF00); } if(x < 60) { return lv_color_hex(0xFFFF00); } return lv_color_hex(0xFF0000); text_sensor: - platform: template id: module_time name: "Module time" icon: mdi:clock lambda: |- auto time_obj = id(time_source).now(); return time_obj.strftime("%Y-%m-%d %H:%M:%S"); update_interval: 1s # Expose WiFi information as sensors - platform: wifi_info ip_address: name: IP mac_address: name: Mac Address entity_category: diagnostic ssid: name: "Connected SSID" id: ssid entity_category: diagnostic # human readable update text sensor from sensor:uptime - platform: template name: Uptime id: uptime_human icon: mdi:clock-start - platform: template name: 'Last Restart' id: device_last_restart icon: mdi:clock entity_category: diagnostic script: - id: time_update then: - lvgl.indicator.update: id: minute_hand value: !lambda |- auto now = id(time_source).now(); return now.minute * 12 + now.second/5; - lvgl.indicator.update: id: hour_hand value: !lambda |- auto now = id(time_source).now(); return std::fmod(now.hour, 12) * 60 + now.minute; - lvgl.indicator.update: id: second_hand value: !lambda |- auto now = id(time_source).now(); return now.second; - lvgl.label.update: id: date_label text: !lambda |- static const char * const mon_names[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; static char date_buf[8]; auto now = id(time_source).now(); snprintf(date_buf, sizeof(date_buf), "%s %2d", mon_names[now.month-1], now.day_of_month); return date_buf; - lvgl.label.update: id: day_label text: !lambda |- static const char * const day_names[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; return day_names[id(time_source).now().day_of_week - 1]; - lvgl.label.update: id: lbl_digitalclock text: !lambda |- auto time_obj = id(time_source).now(); return time_obj.strftime("%H:%M"); - id: ind_heating_update then: - lvgl.widget.update: id: ind_geyser_on hidden: !lambda return !id(geyser_heating).state; - id: init_calendar then: - lambda: |- auto now = id(time_source).now(); //ESP_LOGI("yopts before", stroptions.c_str()); int y = 0; std::string stroptions = to_string(now.year + y); while(++y < ${DD_MAX_YEARS}) { stroptions += "\n" + to_string(now.year + y); } //ESP_LOGI("yopts after", stroptions.c_str()); lv_dropdown_set_options(cal_dd_year->obj, stroptions.c_str()); lv_dropdown_set_selected(cal_dd_year->obj, 0); // this year is first index lv_dropdown_set_selected(cal_dd_month->obj, now.month-1); id(update_calendar).execute(now.year, now.month); - id: update_calendar_month parameters: increment : int then: - lambda: |- char yearstr[8]; lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); auto year = atoi(yearstr); int year_idx = lv_dropdown_get_selected(cal_dd_year->obj); int month_idx = increment + lv_dropdown_get_selected(cal_dd_month->obj); int month = 1 + month_idx; if(month > 12 && year_idx < ${DD_MAX_YEARS} - 1) { month -= 12; month_idx -= 12; year++; year_idx++; } else if(month < 1 && year_idx > 0) { month += 12; month_idx += 12; year--; year_idx--; } ESP_LOGI("cm", "month: %d, year: %d", month, year); if(month < 13 && month > 0) { lv_dropdown_set_selected(cal_dd_year->obj, year_idx); lv_dropdown_set_selected(cal_dd_month->obj, month_idx); id(update_calendar).execute(year, month); } - id: update_calendar parameters: year : int month : int then: lambda: |- int monthdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static std::string strdays[44]; static const char *pstrdays[49]; // including newline at end of week static const char *newline = "\n"; bool isLeapYear = (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)); monthdays[1] = (isLeapYear) ? 29 : 28; // calculate day of week of 1st of month using Zeller's rule // https://beginnersbook.com/2013/04/calculating-day-given-date // modified month, year int mM = month - 2; int m = mM < 1 ? 12 + mM : mM; int mY = mM < 1 ? year - 1 : year; int k = 1; // day of month int D = mY % 100; // last two digits of the year int C = trunc(mY / 100); // first two digits of the year int F = k + trunc((13 * m - 1) / 5) + D + trunc(D / 4) + trunc(C / 4) - 2 * C; int Z = F % 7; int start_of_month = Z < 0 ? Z + 7 : Z; // end of Zeller's rule lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); int previous_month = (month == 1) ? 12 : month - 1; int month_days = monthdays[month - 1]; int prev_month_days = monthdays[previous_month - 1]; int i = 0; int j = -1; //ESP_LOGI("vals", "start_of_month: %d, previous_month: %d, month_days: %d, prev_month_days: %d", start_of_month, previous_month, month_days, prev_month_days); for (int w = 0; w < 6 && i < (month_days + start_of_month); w++) { for(int wd = 0; wd < 7; wd++) { int day = i + 1 - start_of_month; if (i < start_of_month) { day += prev_month_days; strdays[i] = "#e0e0e0 " + to_string(day) + "#"; } else if (i >= (month_days + start_of_month)) { day -= month_days; strdays[i] = "#e0e0e0 " + to_string(day) + "#"; } else { strdays[i] = to_string(day); } pstrdays[++j] = strdays[i].c_str(); //ESP_LOGI("bmx", "%s, i: %d, j: %d", pstrdays[j], i, j); i++; } pstrdays[++j] = newline; //ESP_LOGI("bmxnl", "%s, i: %d, j: %d", pstrdays[j], i, j); } pstrdays[j] = NULL; // overwrites last newline lv_btnmatrix_set_map(bmx_calendar->obj, pstrdays); lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR);