configs/sthome-ut9.yaml
2025-08-23 17:02:53 +02:00

1809 lines
60 KiB
YAML

packages:
- !include common/wifi.yaml
- !include common/canbus.yaml
substitutions:
name: sthome-ut9
friendly_name: "sthome-ut9"
#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}"
- id: g_geyser_heating_on
type: bool
restore_value: no
initial_value: '0'
- id: g_utility_on
type: bool
restore_value: no
initial_value: '0'
- id: g_geyser_top_temperature
type: double
restore_value: yes
initial_value: '0'
- id: g_geyser_bottom_temperature
type: double
restore_value: yes
initial_value: '0'
- id: can_lastid
type: uint32_t
restore_value: no
- id: can_lastframe
type: std::vector<uint8_t>
restore_value: no
esphome:
name: "${name}"
friendly_name: "${friendly_name}"
esp32:
# board: nodemcu-32s
board: esp32dev
framework:
type: arduino
#type: esp-idf
#debug:
# update_interval: 5s
# Enable logging
logger:
level: DEBUG
logs:
canbus: INFO
# Enable Home Assistant API
api:
encryption:
key: "LI7j37zs9HsWNsUZ5c83leThmhHsgIVReAPoc9U6pVU="
ota:
- platform: esphome
password: "8ebd5bcefbdc833a5f6ddc4e8ba56e39"
wifi:
power_save_mode: none # stops display flickering
manual_ip:
static_ip: 10.0.2.9
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${name} Fallback Hotspot"
password: "iZxjpw7ucRs4"
captive_portal:
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
# - 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
"\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: "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
#
#psram:
sun:
id: sun_sensor
latitude: !secret latitude
longitude: !secret longitude
#interval:
# - interval: 10s
# then:
# - canbus.send:
# canbus_id: canbus_sthome
# data: [0x48, 0x45, 0x4C, 0x4C, 0x4F]
# - lambda: |-
# ESP_LOGI("SND:${CB_CANBUS_ID9}", "HELLO");
spi:
- id: spi_bus0
clk_pin: GPIO18
mosi_pin: GPIO23
miso_pin: GPIO19
interface: any
- id: spi_bus1
clk_pin: GPIO17
mosi_pin: GPIO16
miso_pin: GPIO25
interface: any
#one_wire:
# - platform: gpio
# pin: GPIO4
# id: temperature_sensors
# CAN BUS
canbus:
- platform: mcp2515
cs_pin: GPIO05
spi_id: spi_bus1
id: canbus_sthome
mode: LISTENONLY
can_id: ${CB_CANBUS_ID9}
#mode: NORMAL #LISTENONLY
bit_rate: 500KBPS
on_frame:
- can_id: 0
can_id_mask: 0
then:
- lambda: |-
id(dump_can_message).execute(x, can_id, remote_transmission_request);
- can_id: ${CB_BATTERY_STATE}
then:
- lambda: |-
auto value = static_cast<uint16_t>((x[1] << 8) + x[0]);
id(battery_soc).publish_state(value);
value = static_cast<uint16_t>((x[3] << 8) + x[2]);
id(battery_soh).publish_state(value);
- can_id: ${CB_BATTERY_STATUS}
then:
- lambda: |-
float value = 0.01 * static_cast<int16_t>((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system
// ESP_LOGW("REC: ${CB_BATTERY_STATUS}", "Voltage: %f", value);
id(battery_system_voltage).publish_state(value);
value = 0.1 * static_cast<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current
// ESP_LOGW("REC: ${CB_BATTERY_STATUS}", "Current: %f", value);
id(battery_system_current).publish_state(value);
value = 0.1 * static_cast<int16_t>((x[5] << 8) + x[4]); // unit = 0.1°C
id(battery_average_cell_temperature).publish_state(value);
- can_id: ${CB_BATTERY_SETTING}
then:
- lambda: |-
float value = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V
id(battery_charge_voltage_limit).publish_state(value);
value = 0.1 * static_cast<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A
id(battery_charge_current_limit).publish_state(value);
value = 0.1 * static_cast<int16_t>((x[5] << 8) + x[4]); // unit = 0.1A
id(battery_discharge_current_limit).publish_state(value);
- can_id: ${CB_BATTERY_INFO}
then:
- lambda: |-
char buffer[16];
uint8_t protection1 = x[0];
uint8_t protection2 = x[1];
uint8_t alarm1 = x[2];
uint8_t alarm2 = x[3];
uint8_t module_numbers = x[4];
char ch5 = x[5];
char ch6 = x[6];
id(battery_discharge_over_current).publish_state(protection1 & 0x80);
id(battery_cell_under_temperature).publish_state(protection1 & 0x10);
id(battery_cell_over_temperature).publish_state(protection1 & 0x08);
id(battery_cell_or_module_under_voltage).publish_state(protection1 & 0x04);
id(battery_cell_or_module_over_voltage).publish_state(protection1 & 0x02);
id(battery_system_error).publish_state(protection2 & 0x8);
id(battery_charge_over_current).publish_state(protection2 & 0x01);
id(battery_discharge_high_current).publish_state(alarm1 & 0x80);
id(battery_cell_low_temperature).publish_state(alarm1 & 0x10);
id(battery_cell_high_temperature).publish_state(alarm1 & 0x08);
id(battery_cell_or_module_low_voltage).publish_state(alarm1 & 0x04);
id(battery_cell_or_module_high_voltage).publish_state(alarm1 & 0x02);
id(battery_internal_communication_fail).publish_state(alarm2 & 0x8);
id(battery_charge_high_current).publish_state(alarm2 & 0x01);
snprintf(buffer, sizeof(buffer), "%d %c%c", module_numbers, ch5, ch6);
id(battery_module_numbers).publish_state(buffer);
- can_id: ${CB_BATTERY_REQUEST_FLAG}
then:
- lambda: |-
uint8_t request_flag = x[0];
id(battery_charge_enable).publish_state(request_flag & 0x80);
id(battery_discharge_enable).publish_state(request_flag & 0x40);
id(battery_request_force_charge1).publish_state(request_flag & 0x20);
id(battery_request_force_charge2).publish_state(request_flag & 0x10);
id(battery_request_full_charge).publish_state( request_flag & 0x08);
- can_id: ${CB_BATTERY_MANUFACTURER}
then:
- lambda: |-
std::string str(x.begin(), x.end());
id(battery_manufacturer).publish_state(str);
# - can_id: ${CB_GEYSER_ENERGISED}
# then:
# - lvgl.widget.update:
# id: ind_geyser_on
# hidden: !lambda |-
# std::string on_state(x.begin(), x.end());
# //ESP_LOGI("REC:${CB_GEYSER_ENERGISED}", "GEYSER IS: %s", on_state.c_str());
# if(on_state == "ON") {
# id(g_geyser_heating_on) = true;
# return false; // not hidden
# }
# else if(on_state == "OFF") {
# id(g_geyser_heating_on) = false;
# return true; // hidden
# }
# //ESP_LOGW("REC:${CB_GEYSER_ENERGISED}", "Invalid ON/OFF value: %s", on_state.c_str());
# return true; // default
# - can_id: ${CB_UTILITY_POWER_ON}
# then:
# - lvgl.widget.update:
# id: ind_utility_on
# hidden: !lambda |-
# std::string on_state(x.begin(), x.end());
# ESP_LOGI("REC:${CB_UTILITY_POWER_ON}", "UTILITY IS: %s", on_state.c_str());
# if(on_state == "ON") {
# id(g_utility_on) = true;
# return false; // not hidden
# }
# else if(on_state == "OFF") {
# id(g_utility_on) = false;
# return true; // hidden
# }
# ESP_LOGW("REC:${CB_UTILITY_POWER_ON}", "Invalid ON/OFF value: %s", on_state.c_str());
# return true; // default
# - can_id: ${CB_GEYSER_TOP_TEMPERATURE}
# then:
# - lambda: |-
# id(update_temperature_display).execute(x, id(g_geyser_top_temperature), rect_gtoptemp, ind_utility_on, lbl_gtoptemp);
#
# - can_id: ${CB_GEYSER_BOTTOM_TEMPERATURE}
# then:
# - lambda: |-
# id(update_temperature_display).execute(x, id(g_geyser_bottom_temperature) , rect_gbottemp, ind_geyser_on, lbl_gbottemp);
#
# - can_id: ${CB_CANBUS_ID1}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID1}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID2}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID2}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID3}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID3}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID4}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID4}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID5}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID5}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID6}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID6}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID7}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID7}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID8}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID8}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID9}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID9}", "%s", &b[0] );
# - can_id: ${CB_CANBUS_ID10}
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# ESP_LOGI("REC:${CB_CANBUS_ID10}", "%s", &b[0] );
# - can_id: 0x402
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# //ESP_LOGI("canid 0x402", "%s", &b[0] );
# ESP_LOGI("RECEIVED: canid 0x402", "%s", b.c_str());
# - can_id: 0x400
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# //ESP_LOGI("canid 0x400", "%s", &b[0] );
# ESP_LOGI("RECEIVED: canid 0x400", "%s", b.c_str());
# - can_id: 0x401
# then:
# - lambda: |-
# std::string b(x.begin(), x.end());
# //ESP_LOGI("canid 0x401", "%s", &b[0] );
# ESP_LOGI("RECEIVED: canid 0x401", "%s", b.c_str());
#display:
# - platform: ili9xxx
# model: ili9488
# id: tft_display
# color_palette: 8BIT
# data_rate: 40MHz
# spi_id: spi_bus0
# 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
# spi_id: spi_bus0
# 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
#
#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_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:
binary_sensor:
- platform: template
id: battery_discharge_over_current
name: "Battery Discharge Over Current"
device_class: problem
- platform: template
id: battery_cell_under_temperature
name: "Battery Cell Under Temperature"
device_class: problem
- platform: template
id: battery_cell_over_temperature
name: "Battery Cell Over Temperature"
device_class: problem
- platform: template
id: battery_cell_or_module_under_voltage
name: "Battery Under Voltage"
device_class: problem
- platform: template
id: battery_cell_or_module_over_voltage
name: "Battery Over Voltage"
device_class: problem
- platform: template
id: battery_system_error
name: "Battery System Error"
device_class: problem
- platform: template
id: battery_charge_over_current
name: "Battery Charge Over Current"
device_class: problem
- platform: template
id: battery_discharge_high_current
name: "Battery Discharge High Current"
device_class: problem
- platform: template
id: battery_cell_low_temperature
name: "Battery Low Temperature"
device_class: problem
- platform: template
id: battery_cell_high_temperature
name: "Battery High Temperature"
device_class: problem
- platform: template
id: battery_cell_or_module_low_voltage
name: "Battery Low Voltage"
device_class: problem
- platform: template
id: battery_cell_or_module_high_voltage
name: "Battery High Voltage"
device_class: problem
- platform: template
id: battery_internal_communication_fail
name: "Battery Communication Fail"
device_class: problem
- platform: template
id: battery_charge_high_current
name: "Battery Charge High Current"
device_class: problem
- platform: template
id: battery_charge_enable
name: "Battery Charge Enable"
#device_class: battery_charging
- platform: template
id: battery_discharge_enable
name: "Battery Discharge Enable"
#device_class: battery_charging
- platform: template
id: battery_request_force_charge1
name: "Battery Request Force Charge 1"
# device_class: battery_charging
- platform: template
id: battery_request_force_charge2
name: "Battery Request Force Charge 2"
# device_class: battery_charging
- platform: template
id: battery_request_full_charge
name: "Battery Request Full Charge "
# device_class: battery_charging
- platform: template
id: battery_charging
name: "Battery Charging"
device_class: battery_charging
lambda: "return id(battery_system_current).state > 0;"
sensor:
- platform: template
id: battery_soc
name: "Battery SOC"
accuracy_decimals: 0
unit_of_measurement: "%"
state_class: measurement
device_class: battery
- platform: template
id: battery_soh
name: "Battery SOH"
accuracy_decimals: 0
unit_of_measurement: "%"
state_class: measurement
device_class: battery
- platform: template
id: battery_system_voltage
name: "Battery Voltage"
accuracy_decimals: 2
unit_of_measurement: "V"
state_class: measurement
device_class: voltage
- platform: template
id: battery_system_current
name: "Battery Current"
accuracy_decimals: 1
unit_of_measurement: "A"
state_class: measurement
device_class: current
- platform: template
id: battery_average_cell_temperature
name: "Battery Cell Temperature"
accuracy_decimals: 1
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
- platform: template
id: battery_charge_voltage_limit
name: "Battery Charge Voltage Limit"
accuracy_decimals: 1
unit_of_measurement: "V"
state_class: measurement
device_class: voltage
- platform: template
id: battery_charge_current_limit
name: "Battery Charge Current Limit"
accuracy_decimals: 1
unit_of_measurement: "A"
state_class: measurement
device_class: current
- platform: template
id: battery_discharge_current_limit
name: "Battery Discharge Current Limit"
accuracy_decimals: 1
unit_of_measurement: "A"
state_class: measurement
device_class: current
# - 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
- platform: template
id: battery_manufacturer
name: "Battery Manufacturer"
- platform: template
id: battery_module_numbers
name: "Battery Module Numbers"
script:
# - id: update_temperature_display
# parameters:
# x: std::vector<uint8_t>&
# globalvar: double&
# rect: lv_obj_t*
# indicator: lv_obj_t*
# label: lv_obj_t*
# then:
# - lambda: |-
# char buffer [4];
# buffer[0] = '\0';
# double value = x[3] + ((double)((x[2] << 16) + (x[1] << 8) + x[0]))/16777216;
# globalvar = value;
# 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);
# lv_label_set_text(label, buffer);
# - 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(g_geyser_heating_on);
# - 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());
# }
- id: dump_can_message
parameters:
x: std::vector<uint8_t>&
can_id: uint32_t
remote_transmission_request: bool
then:
lambda: |-
char buffer[80];
char tag[16];
// comment out following few lines if you don't want to deduplicate consecutive messages
auto& y = id(can_lastframe);
bool isduplicate = can_id == id(can_lastid);
auto j = y.begin();
if(x.size() == y.size()) {
for(auto i = x.begin(); i != x.end() && isduplicate; ++i) {
isduplicate = isduplicate && (*i == *j++);
}
}
if(isduplicate) {
return;
}
else {
y.clear();
y.insert(y.end(), x.begin(), x.end());
id(can_lastid) = can_id;
}
// end of deduplication
snprintf(tag, sizeof(tag), "CAN REC: 0x%X", can_id);
snprintf(buffer, sizeof(buffer), " %d ", remote_transmission_request);
std::string text = "";
std::string line = buffer;
std::string decoded = "";
for(auto i = x.begin(); i != x.end(); ++i) {
auto byte = *i;
snprintf(buffer, sizeof(buffer), " %02X", byte);
line += buffer;
if(byte > 31 && byte < 127) {
text += (char) byte;
}
else {
text += ".";
}
}
switch (can_id)
{
case ${CB_BATTERY_SETTING}:
{
float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V
float charge_current_limit = 0.1 * static_cast<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A
float discharge_current_limit = 0.1 * static_cast<int16_t>((x[5] << 8) + x[4]); // unit = 0.1A
snprintf(buffer, sizeof(buffer), "BATTERY SETTINGS: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit);
decoded += buffer;
}
break;
case ${CB_BATTERY_STATE}:
{
uint soc = static_cast<uint16_t>((x[1] << 8) + x[0]);
uint soh = static_cast<uint16_t>((x[3] << 8) + x[2]);
snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh);
decoded += buffer;
}
break;
case ${CB_BATTERY_STATUS}:
{
float system_voltage = 0.01 * static_cast<int16_t>((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system
float system_current = 0.1 * static_cast<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current
float average_cell_temperature = 0.1 * static_cast<int16_t>((x[5] << 8) + x[4]); // unit = 0.1°C
snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature);
decoded += buffer;
}
break;
case ${CB_BATTERY_INFO}:
{
uint8_t protection1 = x[0];
uint8_t protection2 = x[1];
uint8_t alarm1 = x[2];
uint8_t alarm2 = x[3];
uint8_t module_numbers = x[4];
char ch5 = x[5];
char ch6 = x[6];
bool discharge_over_current = protection1 & 0x80;
bool cell_under_temperature = protection1 & 0x10;
bool cell_over_temperature = protection1 & 0x08;
bool cell_or_module_under_voltage = protection1 & 0x04;
bool cell_or_module_over_voltage = protection1 & 0x02;
bool system_error = protection2 & 0x8;
bool charge_over_current = protection2 & 0x01;
bool discharge_high_current = alarm1 & 0x80;
bool cell_low_temperature = alarm1 & 0x10;
bool cell_high_temperature = alarm1 & 0x08;
bool cell_or_module_low_voltage = alarm1 & 0x04;
bool cell_or_module_high_voltage = alarm1 & 0x02;
bool internal_communication_fail = alarm2 & 0x8;
bool charge_high_current = alarm2 & 0x01;
snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6);
decoded += buffer;
}
break;
case ${CB_BATTERY_REQUEST_FLAG}:
{
uint8_t request_flag = x[0];
bool charge_enable = request_flag & 0x80;
bool discharge_enable = request_flag & 0x40;
bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it.
bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently.
bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1.
snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : "");
decoded += buffer;
}
break;
case ${CB_BATTERY_MANUFACTURER}:
{
std::string manufacturer(x.begin(), x.end());
decoded += "BATTERY OEM: " + manufacturer;
}
break;
}
ESP_LOGI(tag, "%s %s %s", line.c_str(), text.c_str(), decoded.c_str());