packages: - !include common/wifi.yaml - !include common/canbus.yaml substitutions: name: sthome-ut10 friendly_name: "sthome-ut10" #ALLOWED_CHARACTERS_FULL: " !#%\"'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ¿ÁÂÄÅÉÖÚßàáâãäåæçèéêëìíîðñòóôõöøùúûüýþāăąćčďĐđēėęěğĮįıļľŁłńňőřśšťũūůűųźŻżŽžơưșțΆΈΌΐΑΒΓΔΕΖΗΘΚΜΝΠΡΣΤΥΦάέήίαβγδεζηθικλμνξοπρςστυφχψωϊόύώАБВГДЕЖЗИКЛМНОПРСТУХЦЧШЪЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђєіїјљњћ" ALLOWED_CHARACTERS: " !#%\"'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ•" DD_MAX_YEARS: "5" globals: - id: g_month_idx type: int restore_value: yes initial_value: '0' - id: g_year_idx type: int restore_value: yes initial_value: '0' - id: g_options_year type: char[1 + ${DD_MAX_YEARS} * 5] restore_value: yes # initial_value: "{2022\n2023\n2024\n2025\n2026}" # CAN BUS - id: can_msgctr type: int restore_value: no - id: g_cb_cache type: solar::cbf_cache restore_value: no esphome: name: "${name}" friendly_name: "${friendly_name}" includes: - source # copies folder with files to relevant to be included in esphome compile - # angle brackets ensure file is included above globals in main.cpp. Make sure to use include GUARDS in the file to prevent double inclusion - - - - - - on_boot: - priority: 600 # This is where most sensors are set up (higher number means higher priority) then: - ds1307.read_time: # read the RTC time - lambda: |- id(can_msgctr) = 0; id(update_heating_display).execute(false); esp32: board: esp32-s3-devkitc-1 flash_size: 16MB framework: type: esp-idf # sdkconfig_options: # CONFIG_ESP32_S3_BOX_BOARD: "y" psram: mode: octal speed: 80MHz # Enable logging logger: level: very_verbose initial_level: very_verbose logs: canbus: INFO touchscreen: INFO xpt2046: INFO script: INFO lvgl: INFO ili9xxx: INFO # Enable Home Assistant API api: encryption: key: "NApAUAUGkzfbNhOikFb2k+AIE8LMr7O8Ck15IaszrUU=" ota: - platform: esphome password: "d669ae14ff964bb0f195e820118af8e3" wifi: power_save_mode: none manual_ip: static_ip: 10.0.2.10 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${name} Fallback Hotspot" password: "YmuU9F5YgBZ1" captive_portal: time: - platform: ds1307 # repeated synchronization is not necessary unless the external RTC # is much more accurate than the internal clock update_interval: never - platform: homeassistant id: time_source #update_interval: 360min # Change sync interval from default 5min to 6 hours on_time_sync: then: - ds1307.write_time: # - 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: time_update # - script.execute: init_calendar # on_time: # - minutes: '*' # seconds: '*' # then: # - script.execute: time_update # - lambda: |- # id(get_calendar_days_state).execute("T"); #- script.execute: get_calendar_days_state # # - 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 font: - file: "gfonts://Roboto" id: roboto_200 size: 200 bpp: 4 glyphs: [ 0123456789,.,°,a,n, "\u0020", # space "\u002D", # minus "\u003A", # colon "\u003F", # question mark ] - file: "gfonts://Roboto" id: roboto_192 size: 192 bpp: 4 glyphs: [ 0123456789,.,°,a,n, "\u0020", # space "\u002D", # minus "\u003A", # colon "\u003F", # question mark ] - 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: "fonts/misc/materialdesignicons-webfont.ttf" id: font_icon_small size: 24 #45 glyphs: [ "\U0000F5A9", ] color: - id: grey_light hex: 'e0e0e0' 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 - file: mdi:transmission-tower id: icon_utility resize: 80x80 type: BINARY sun: id: sun_sensor latitude: !secret latitude longitude: !secret longitude interval: - interval: 30s then: - script.execute: send_info_request - interval: 500ms then: - script.execute: send_quick_update_request spi: - id: spi_bus0 clk_pin: GPIO12 mosi_pin: GPIO11 miso_pin: GPIO13 interface: any - id: spi_bus1 clk_pin: GPIO42 mosi_pin: GPIO21 miso_pin: GPIO20 interface: any i2c: - id: i2c_bus0 sda: GPIO8 scl: GPIO9 scan: true #one_wire: # - platform: gpio # pin: GPIO4 # id: temperature_sensors # CAN BUS canbus: - platform: mcp2515 cs_pin: GPIO10 spi_id: spi_bus0 id: canbus_sthome can_id: ${CB_CANBUS_ID10} bit_rate: 500KBPS on_frame: - can_id: 0 can_id_mask: 0 then: - lambda: |- id(can_msgctr)++; using namespace solar; auto time_obj = id(time_source).now(); //ESP_LOGI("CB REC", "%s", id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p")); if(time_obj.is_valid()) { if(can_id >= 0x350 && can_id < 0x380) { auto cbitem = cbf_store_pylon(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); bool publish = id(g_cb_cache).additem(cbitem); if(publish) { //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); switch (can_id) { case cbf_pylon::CB_BATTERY_STATE: { uint soc = static_cast((x[1] << 8) + x[0]); uint soh = static_cast((x[3] << 8) + x[2]); ESP_LOGI(cbitem.tag().c_str(), "SOC= %d%% SOH= %d%%", soc, soh); } } } } else if(can_id >= 0x400 && can_id <= 0x580) { auto cbitem = cbf_store_sthome(id(can_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); bool publish = id(g_cb_cache).additem(cbitem); //ESP_LOGI(cbitem.tag().c_str(), "%s P[%s]", cbitem.to_string().c_str(), publish ? "Y" : "N"); if(publish) { ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); switch (can_id) { case cbf_sthome::CB_CONTROLLER_STATES: { uint8_t alarms = x[0]; uint8_t states = x[1]; uint8_t modes = x[2]; id(update_heating_display).execute(states & 0x80 != 0); //if(states & 0x40 == 0) { // lv_obj_add_flag(id(ind_geyser_relay_on), LV_OBJ_FLAG_HIDDEN); //} //else { // lv_obj_clear_flag(id(ind_geyser_relay_on), LV_OBJ_FLAG_HIDDEN); //} if(states & 0x20 == 0) { lv_obj_add_flag(id(ind_utility_on), LV_OBJ_FLAG_HIDDEN); } else { lv_obj_clear_flag(id(ind_utility_on), LV_OBJ_FLAG_HIDDEN); } } break; case cbf_sthome::CB_GEYSER_TEMPERATURE_TOP: { double temperature = (x[1] == 0xFF && x[0] == 0xFF) ? std::numeric_limits::quiet_NaN() : (double) static_cast(256 * x[1] + x[0]) / 256; if(!isnan(temperature)) { id(update_temp_display).execute(temperature, rect_gtoptemp, ind_utility_on, lbl_gtoptemp); } } break; case cbf_sthome::CB_GEYSER_TEMPERATURE_BOTTOM: { double temperature = (x[1] == 0xFF && x[0] == 0xFF) ? std::numeric_limits::quiet_NaN() : (double) static_cast(256 * x[1] + x[0]) / 256; if(!isnan(temperature)) { id(update_temp_display).execute(temperature, rect_gbottemp, ind_geyser_heating_on, lbl_gbottemp); } } break; } } } else { ESP_LOGI("WARN", "CAN ID within unhandled range: 0x%X", can_id); } } display: - platform: ili9xxx model: ili9488 id: tft_display color_palette: 8BIT data_rate: 40MHz spi_id: spi_bus1 cs_pin: GPIO41 dc_pin: GPIO39 reset_pin: GPIO40 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: GPIO38 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 spi_id: spi_bus1 cs_pin: GPIO47 interrupt_pin: GPIO19 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 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: 12% 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_heating_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: # clipping rectangle x: 0 #15 y: 2 #-24 #7 pad_all: 0 height: 80 width: 65 align: TOP_LEFT bg_color: 0x000000 border_color: 0xFFFFFF border_width: 0 radius: 0 bg_opa: LV_OPA_TRANSP scrollbar_mode: "OFF" widgets: - image: id: ind_utility_on align: CENTER #BOTTOM_RIGHT #TOP_RIGHT src: icon_utility image_recolor: grey #!lambda 'return lv_color_hex(0x000000);' image_recolor_opa: 100% x: 0 #15 #15 y: 0 #-22 #7 height: 80 #25 width: 80 #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: |- # id(update_calendar).execute(); # - 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: |- # id(update_calendar).execute(); # - 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("day of week", "%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 # # Define actions on button press # on_press: # then: # lambda: |- # //lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); # //lv_btnmatrix_set_one_checked(bmx_calendar->obj, false); # //id(get_calendar_days_state).execute("P1"); # //auto stat = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); # //ESP_LOGI("on press", "day: %s, stat: %d", lv_btnmatrix_get_btn_text(bmx_calendar->obj, x), stat); # //id(get_calendar_days_state).execute("P2"); # on_release: # then: # lambda: |- # id(get_calendar_days_state).execute("R1"); # //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); # // 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); # // } # //auto stat = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, x, LV_BTNMATRIX_CTRL_CHECKED); # //ESP_LOGI("on relse", "day: %s, stat: %d", lv_btnmatrix_get_btn_text(bmx_calendar->obj, x), stat); # //id(get_calendar_days_state).execute("R2"); - 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 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: 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: 60s #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: 60s # # # 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: send_quick_update_request then: - lambda: |- for(int i = 0; i < ${CB_MAX_RETRANSMISSIONS}; i++) { id(send_request_block1).execute(); } - id: send_info_request then: - lambda: |- for(int i = 0; i < ${CB_MAX_RETRANSMISSIONS}; i++) { id(send_request_block2).execute(); } - id: send_request_block1 mode: queued then: - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_sthome::CB_CONTROLLER_STATES);" - delay: 20ms - id: send_request_block2 mode: queued then: - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_pylon::CB_BATTERY_STATE);" - delay: 50ms - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_sthome::CB_GEYSER_TEMPERATURE_TOP);" - delay: 50ms - lambda: "id(g_cb_cache).send_request(id(canbus_sthome), solar::cbf_sthome::CB_GEYSER_TEMPERATURE_BOTTOM);" - delay: 50ms - id: update_temp_display mode: queued parameters: value: double rect: lv_obj_t* indicator: lv_obj_t* label: lv_obj_t* then: - lambda: |- char buffer [4]; buffer[0] = '\0'; snprintf (buffer, 4, "%.0f", value); auto bgcolor = lv_color_hex(0xFF0000); auto ind_color = lv_color_hex(0xFF0000); if(value < 40) { bgcolor = lv_color_hex(0x0000FF); } else if(value < 50) { bgcolor = lv_color_hex(0x00FF00); } else if(value < 60) { bgcolor = lv_color_hex(0xFFFF00); } else { ind_color = lv_color_hex(0xFFFF00); // make different to bgcolor } lv_obj_set_style_bg_color(rect, bgcolor, LV_PART_MAIN); lv_obj_set_style_img_recolor(indicator, ind_color, LV_PART_MAIN); if(isnan(value)) lv_label_set_text(label, "??"); else lv_label_set_text(label, buffer); - id: update_heating_display parameters: heat_on: bool then: - lvgl.widget.update: id: ind_geyser_heating_on hidden: !lambda 'return !heat_on;' # - 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: 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(g_year_idx) = 0; # id(update_calendar).execute(); # lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); # - 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(g_year_idx) = year_idx; # id(update_calendar).execute(); # } # # - id: update_calendar # then: # lambda: |- # char yearstr[8]; # 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"; # id(persist_calendar).execute(); # lv_dropdown_get_selected_str(cal_dd_year->obj, yearstr, sizeof(yearstr)); # int year = atoi(yearstr); # int month = 1 + lv_dropdown_get_selected(cal_dd_month->obj); # 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 # 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; // terminator, overwrites last newline # //ESP_LOGW("day", "terminating at: %d", j); # lv_btnmatrix_set_btn_ctrl_all(bmx_calendar->obj, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_RECOLOR); # 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); # # - id: persist_calendar # then: # lambda: |- # id(g_year_idx) = lv_dropdown_get_selected(cal_dd_year->obj); # id(g_month_idx) = lv_dropdown_get_selected(cal_dd_month->obj); # // copy year options to persistent globals # const char* opts = lv_dropdown_get_options(cal_dd_year->obj); # int opt_store_size = sizeof(id(g_options_year)); # strncpy(id(g_options_year), opts, opt_store_size); # id(g_options_year)[opt_store_size] = '\0'; # //ESP_LOGI("year options", id(g_options_year)); # # - id: get_calendar_days_state # parameters: # flag: std::string # then: # lambda: |- # // count buttons # int num_buttons = 0; # auto* buttonmap = lv_btnmatrix_get_map(bmx_calendar->obj); # int i = 0; # for (; buttonmap[i] != NULL && buttonmap[i][0] != '\0' && i < 48; i++) { # bool isNewLine = strcmp(buttonmap[i], "\n") == 0; # if (!isNewLine) { # num_buttons++; # } # } # std::string sch_holidays = ""; # std::string pub_holidays = ""; # std::string vac_days = ""; # int h = 0; # for(int i = 0; i < num_buttons; i++) { # bool isSchoolHoliday = lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, i, LV_BTNMATRIX_CTRL_CHECKED); # bool isPublicHoliday = false; //lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, i, LV_BTNMATRIX_CTRL_CUSTOM_1); # bool isVacationDay = false; //lv_btnmatrix_has_btn_ctrl(bmx_calendar->obj, i, LV_BTNMATRIX_CTRL_CUSTOM_2); # if(isSchoolHoliday || isPublicHoliday || isVacationDay) { # sch_holidays = sch_holidays + lv_btnmatrix_get_btn_text(bmx_calendar->obj, i) + " "; # h++; # } # } # if(h > 0) { # ESP_LOGI("day", "[%s] s: %s \tp: %s \tv: %s", flag.c_str(), sch_holidays.c_str(), pub_holidays.c_str(), vac_days.c_str()); # }