From c19152fed3035f04b494ee4278a17e86219cadce Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 8 Jan 2026 21:47:23 +0200 Subject: [PATCH] Added ADS131M08 external component --- archive/sthome-ut9.yaml | 2598 ++++++++++++++++ common/wifi.yaml | 9 +- components/ads131m08/README.md | 6 + components/ads131m08/__init__.py | 24 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2232 bytes .../__pycache__/sensor.cpython-312.pyc | Bin 0 -> 1925 bytes components/ads131m08/ads131m08.cpp | 197 ++ components/ads131m08/ads131m08.h | 35 + components/ads131m08/sensor.py | 21 + device.yaml | 9 + sthome-ut1.yaml | 92 +- sthome-ut10.yaml | 41 +- sthome-ut6.yaml | 37 +- sthome-ut8.yaml | 1521 +++++----- sthome-ut9.yaml | 2682 +---------------- 15 files changed, 3961 insertions(+), 3311 deletions(-) create mode 100644 archive/sthome-ut9.yaml create mode 100644 components/ads131m08/README.md create mode 100644 components/ads131m08/__init__.py create mode 100644 components/ads131m08/__pycache__/__init__.cpython-312.pyc create mode 100644 components/ads131m08/__pycache__/sensor.cpython-312.pyc create mode 100644 components/ads131m08/ads131m08.cpp create mode 100644 components/ads131m08/ads131m08.h create mode 100644 components/ads131m08/sensor.py create mode 100644 device.yaml diff --git a/archive/sthome-ut9.yaml b/archive/sthome-ut9.yaml new file mode 100644 index 0000000..b82ab48 --- /dev/null +++ b/archive/sthome-ut9.yaml @@ -0,0 +1,2598 @@ +packages: + - !include common/wifi.yaml + - !include common/canbus.yaml + - !include common/felicityinverter.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_inv1_power_flow + type: uint16_t + restore_value: no + initial_value: '0' + - id: g_inv2_power_flow + type: uint16_t + restore_value: no + initial_value: '0' +# - 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 +# restore_value: no + +esphome: + name: "${name}" + friendly_name: "${friendly_name}" + on_boot: + - priority: 600 # This is where most sensors are set up (higher number means higher priority) + then: + - uart.write: + id: inv_uart1 + data: [0x0D, 0x0A] + - uart.write: + id: inv_uart2 + data: [0x0D, 0x0A] + +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: + +uart: + - id: inv_uart1 + rx_pin: GPIO16 + tx_pin: GPIO17 + baud_rate: 2400 + stop_bits: 1 + parity: NONE + debug: + direction: BOTH + dummy_receiver: false + after: + delimiter: "\r" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ','); + + - id: inv_uart2 + rx_pin: GPIO25 + tx_pin: GPIO26 + baud_rate: 2400 + stop_bits: 1 + parity: NONE + debug: + direction: BOTH + dummy_receiver: false + after: + delimiter: "\r" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ' '); + +sun: + id: sun_sensor + latitude: !secret latitude + longitude: !secret longitude + +time: + - platform: homeassistant + id: time_source + +switch: + - platform: restart + name: "${name} Restart" + id: "restart_switch" + +modbus: + - id: modbus1 + uart_id: inv_uart1 + send_wait_time: 1200ms #250ms + disable_crc: false + role: client + + - id: modbus2 + uart_id: inv_uart2 + send_wait_time: 1200ms #250ms + disable_crc: false + role: client + +modbus_controller: + - id: modbus_device1 + modbus_id: modbus1 + address: 0x01 + allow_duplicate_commands: False + command_throttle: 700ms #2022ms + update_interval: 60s #305s + offline_skip_updates: 2 + max_cmd_retries: 1 + setup_priority: -10 + - id: modbus_device2 + modbus_id: modbus2 + address: 0x01 + allow_duplicate_commands: False + command_throttle: 0ms + update_interval: 60s #30s + offline_skip_updates: 2 + max_cmd_retries: 0 + setup_priority: -10 + +text_sensor: +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 SerialNo" + register_type: holding + address: ${Felicity_Inv_SerialNo} # 0xF804 + response_size: 14 # should be 10, but absorbing extra four bytes + raw_encode: HEXBYTES + lambda: |- + char buffer[32]; + uint16_t sn0 = modbus_controller::word_from_hex_str(x, 0); + uint16_t sn1 = modbus_controller::word_from_hex_str(x, 2); + uint16_t sn2 = modbus_controller::word_from_hex_str(x, 4); + uint16_t sn3 = modbus_controller::word_from_hex_str(x, 6); + uint16_t sn4 = modbus_controller::word_from_hex_str(x, 8); + snprintf(buffer, sizeof(buffer), "%04d%04d%04d%04d%04d", sn0, sn1, sn2, sn3, sn4); + return std::string(buffer).substr(0, 14); + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Type" + id: inverter1_type + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_Type} # 0xF800 + response_size: 2 + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch (value) { + case 0x50: return std::string("High Frequency Inverter"); + default: return std::string("Unknown"); + } + return x; + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Sub Type" + id: inverter1_subtype + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_SubType} # 0xF801 + response_size: 6 # should be 2, but absorbing extra four bytes + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch (value) { + case 0x0204: return std::string("3024 (3000VA/24V)"); + case 0x0408: return std::string("5048 (5000VA/48V)"); + default: return std::string("Unknown"); + } + return x; + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 CPU1 F/W Version" + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_CPU1_FW_Version} # 0xF80B + response_size: 2 + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + return std::to_string(value); + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 CPU2 F/W Version" + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_CPU2_FW_Version} # 0xF80C + response_size: 2 + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + return std::to_string(value); + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Working Mode" + address: ${Felicity_Inv_WorkingMode} # 0x1101 + bitmask: 0 + register_type: holding + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch(value) { + case 0: return std::string("Power On"); + case 1: return std::string("Standby"); + case 2: return std::string("Bypass"); + case 3: return std::string("Battery"); + case 4: return std::string("Fault"); + case 5: return std::string("Line"); + case 6: return std::string("PV Charge"); + } + return std::string("Unknown"); + register_count: 1 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Charge Mode" + address: ${Felicity_Inv_BatteryChargingStage} # 0x1102 + bitmask: 0 + register_type: holding + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch(value) { + case 0: return std::string("Idle"); + case 1: return std::string("Bulk"); + case 2: return std::string("Absorption"); + case 3: return std::string("Float"); + } + return std::string("Unknown"); + register_count: 1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 SerialNo" + register_type: holding + address: ${Felicity_Inv_SerialNo} # 0xF804 + response_size: 14 # should be 10, but absorbing extra four bytes + raw_encode: HEXBYTES + lambda: |- + char buffer[32]; + uint16_t sn0 = modbus_controller::word_from_hex_str(x, 0); + uint16_t sn1 = modbus_controller::word_from_hex_str(x, 2); + uint16_t sn2 = modbus_controller::word_from_hex_str(x, 4); + uint16_t sn3 = modbus_controller::word_from_hex_str(x, 6); + uint16_t sn4 = modbus_controller::word_from_hex_str(x, 8); + snprintf(buffer, sizeof(buffer), "%04d%04d%04d%04d%04d", sn0, sn1, sn2, sn3, sn4); + return std::string(buffer).substr(0, 14); + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Type" + id: inverter2_type + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_Type} # 0xF800 + response_size: 2 + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch (value) { + case 0x50: return std::string("High Frequency Inverter"); + default: return std::string("Unknown"); + } + return x; + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Sub Type" + id: inverter2_subtype + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_SubType} # 0xF801 + response_size: 6 # should be 2, but absorbing extra four bytes + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch (value) { + case 0x0204: return std::string("3024 (3000VA/24V)"); + case 0x0408: return std::string("5048 (5000VA/48V)"); + default: return std::string("Unknown"); + } + return x; + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 CPU1 F/W Version" + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_CPU1_FW_Version} # 0xF80B + response_size: 2 + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + return std::to_string(value); + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 CPU2 F/W Version" + bitmask: 0 + register_type: holding + address: ${Felicity_Inv_CPU2_FW_Version} # 0xF80C + response_size: 2 + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + return std::to_string(value); + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Working Mode" + address: ${Felicity_Inv_WorkingMode} # 0x1101 + bitmask: 0 + register_type: holding + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch(value) { + case 0: return std::string("Power On"); + case 1: return std::string("Standby"); + case 2: return std::string("Bypass"); + case 3: return std::string("Battery"); + case 4: return std::string("Fault"); + case 5: return std::string("Line"); + case 6: return std::string("PV Charge"); + } + return std::string("Unknown"); + register_count: 1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Charge Mode" + address: ${Felicity_Inv_BatteryChargingStage} # 0x1102 + bitmask: 0 + register_type: holding + raw_encode: HEXBYTES + lambda: |- + uint16_t value = modbus_controller::word_from_hex_str(x, 0); + switch(value) { + case 0: return std::string("Idle"); + case 1: return std::string("Bulk"); + case 2: return std::string("Absorption"); + case 3: return std::string("Float"); + } + return std::string("Unknown"); + register_count: 1 + +sensor: +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 SettingDataSn" + register_type: holding + address: ${Felicity_Inv_SettingDataSn} # 0x1100 + accuracy_decimals: 0 + value_type: U_WORD + register_count: 1 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Fault Code" + register_type: holding + address: ${Felicity_Inv_FaultCode} # 0x1103 + value_type: U_WORD + register_count: 1 + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + id: inv1_power_flow_msg + register_type: holding + address: ${Felicity_Inv_PowerFlowMsg} # 0x1104 + value_type: U_WORD + register_count: 4 + lambda: |- + id(g_inv1_power_flow) = x; + return x; + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Battery Voltage" + register_type: holding + address: ${Felicity_Inv_BatteryVoltage} # 0x1108 + value_type: U_WORD + register_count: 1 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.01 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Battery Current" + register_type: holding + address: ${Felicity_Inv_BatteryCurrent} # 0x1109 + value_type: S_WORD + register_count: 1 + unit_of_measurement: "A" + device_class: current + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 BatteryPower" + register_type: holding + address: ${Felicity_Inv_BatteryPower} # 0x110A + value_type: S_WORD + register_count: 7 + unit_of_measurement: "W" + device_class: power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 AC Output Voltage" + register_type: holding + address: ${Felicity_Inv_ACOutputVoltage} # 0x1111 + value_type: U_WORD + register_count: 6 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 AC Input Voltage" + register_type: holding + address: ${Felicity_Inv_ACInputVoltage} # 0x1117 + value_type: U_WORD + register_count: 2 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 AC Input Frequency" + register_type: holding + address: ${Felicity_Inv_ACInputFrequency} # 0x1119 + value_type: U_WORD + register_count: 5 + unit_of_measurement: "Hz" + device_class: frequency + accuracy_decimals: 2 + filters: + - multiply: 0.01 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 AC Output Active Power" + register_type: holding + address: ${Felicity_Inv_ACOutputActivePower} # 0x111E + value_type: S_WORD + register_count: 1 + unit_of_measurement: "W" + device_class: power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 AC Output Apparent Power" + register_type: holding + address: ${Felicity_Inv_ACOutputApparentPower} # 0x111F + value_type: U_WORD + register_count: 1 + unit_of_measurement: "VA" + device_class: apparent_power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 Load Percentage" + register_type: holding + address: ${Felicity_Inv_LoadPercentage} # 0x1120 + value_type: U_WORD + register_count: 6 + unit_of_measurement: "%" + device_class: power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 PV Input Voltage" + register_type: holding + address: ${Felicity_Inv_PVInputVoltage} # 0x1126 + value_type: U_WORD + register_count: 4 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device1 + name: "Inv1 PV Input Power" + register_type: holding + address: ${Felicity_Inv_PVInputPower} # 0x112A + value_type: S_WORD + register_count: 1 + unit_of_measurement: "W" + device_class: power + accuracy_decimals: 0 + +############### modbus device 2 ############### +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 SettingDataSn" + register_type: holding + address: ${Felicity_Inv_SettingDataSn} # 0x1100 + accuracy_decimals: 0 + value_type: U_WORD + register_count: 1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Fault Code" + register_type: holding + address: ${Felicity_Inv_FaultCode} # 0x1103 + value_type: U_WORD + register_count: 1 + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + id: inv2_power_flow_msg + register_type: holding + address: ${Felicity_Inv_PowerFlowMsg} # 0x1104 + value_type: U_WORD + register_count: 4 + lambda: |- + id(g_inv2_power_flow) = x; + return x; + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Battery Voltage" + register_type: holding + address: ${Felicity_Inv_BatteryVoltage} # 0x1108 + value_type: U_WORD + register_count: 1 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.01 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Battery Current" + register_type: holding + address: ${Felicity_Inv_BatteryCurrent} # 0x1109 + value_type: S_WORD + register_count: 1 + unit_of_measurement: "A" + device_class: current + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 BatteryPower" + register_type: holding + address: ${Felicity_Inv_BatteryPower} # 0x110A + value_type: S_WORD + register_count: 7 + unit_of_measurement: "W" + device_class: power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 AC Output Voltage" + register_type: holding + address: ${Felicity_Inv_ACOutputVoltage} # 0x1111 + value_type: U_WORD + register_count: 6 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 AC Input Voltage" + register_type: holding + address: ${Felicity_Inv_ACInputVoltage} # 0x1117 + value_type: U_WORD + register_count: 2 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 AC Input Frequency" + register_type: holding + address: ${Felicity_Inv_ACInputFrequency} # 0x1119 + value_type: U_WORD + register_count: 5 + unit_of_measurement: "Hz" + device_class: frequency + accuracy_decimals: 2 + filters: + - multiply: 0.01 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 AC Output Active Power" + register_type: holding + address: ${Felicity_Inv_ACOutputActivePower} # 0x111E + value_type: S_WORD + register_count: 1 + unit_of_measurement: "W" + device_class: power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 AC Output Apparent Power" + register_type: holding + address: ${Felicity_Inv_ACOutputApparentPower} # 0x111F + value_type: U_WORD + register_count: 1 + unit_of_measurement: "VA" + device_class: apparent_power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 Load Percentage" + register_type: holding + address: ${Felicity_Inv_LoadPercentage} # 0x1120 + value_type: U_WORD + register_count: 6 + unit_of_measurement: "%" + device_class: power + accuracy_decimals: 0 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 PV Input Voltage" + register_type: holding + address: ${Felicity_Inv_PVInputVoltage} # 0x1126 + value_type: U_WORD + register_count: 4 + unit_of_measurement: "V" + device_class: voltage + accuracy_decimals: 1 + filters: + - multiply: 0.1 + +- platform: modbus_controller + modbus_controller_id: modbus_device2 + name: "Inv2 PV Input Power" + register_type: holding + address: ${Felicity_Inv_PVInputPower} # 0x112A + value_type: S_WORD + register_count: 1 + unit_of_measurement: "W" + device_class: power + accuracy_decimals: 0 + +binary_sensor: +- platform: template + name: "Inv1 Battery Connected" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x8000; + +- platform: template + name: "Inv1 Line Normal" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x4000; + +- platform: template + name: "Inv1 PV Input Normal" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x2000; + +- platform: template + name: "Inv1 Load Connect Allowed" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x1000; + +- platform: template + name: "Inv1 PV MPPT Working" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x0080; + +- platform: template + name: "Inv1 Load Connected" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x0040; + +- platform: template + name: "Inv1 Power Flow Version Supported" +# device_class: problem + lambda: |- + return id(g_inv1_power_flow) & 0x0001; + +- platform: template + name: "Inv1 Battery Charging" + device_class: battery_charging + lambda: |- + int battery_flow = (id(g_inv1_power_flow) >> 10) & 3; + return battery_flow & 0x01; + +- platform: template + name: "Inv1 Battery Discharging" +# device_class: battery_charging + lambda: |- + int battery_flow = (id(g_inv1_power_flow) >> 10) & 3; + return battery_flow & 0x02; + +- platform: template + name: "Inv1 Draw Power from Line" + lambda: |- + int line_flow = (id(g_inv1_power_flow) >> 8) & 3; + return line_flow & 0x01; + +- platform: template + name: "Inv1 Feed Power to Line" + lambda: |- + int line_flow = (id(g_inv1_power_flow) >> 8) & 3; + return line_flow & 0x10; + +- platform: template + name: "Inv2 Battery Connected" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x8000; + +- platform: template + name: "Inv2 Line Normal" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x4000; + +- platform: template + name: "Inv2 PV Input Normal" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x2000; + +- platform: template + name: "Inv2 Load Connect Allowed" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x1000; + +- platform: template + name: "Inv2 PV MPPT Working" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x0080; + +- platform: template + name: "Inv2 Load Connected" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x0040; + +- platform: template + name: "Inv2 Power Flow Version Supported" +# device_class: problem + lambda: |- + return id(g_inv2_power_flow) & 0x0001; + +- platform: template + name: "Inv2 Battery Charging" + device_class: battery_charging + lambda: |- + int battery_flow = (id(g_inv2_power_flow) >> 10) & 3; + return battery_flow & 0x01; + +- platform: template + name: "Inv2 Battery Discharging" +# device_class: battery_charging + lambda: |- + int battery_flow = (id(g_inv2_power_flow) >> 10) & 3; + return battery_flow & 0x02; + +- platform: template + name: "Inv2 Draw Power from Line" + lambda: |- + int line_flow = (id(g_inv2_power_flow) >> 8) & 3; + return line_flow & 0x01; + +- platform: template + name: "Inv2 Feed Power to Line" + lambda: |- + int line_flow = (id(g_inv2_power_flow) >> 8) & 3; + return line_flow & 0x10; +################################################################################################3 +#### OLD STHOME-9 CONFIG STARTS HERE +#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_ID09} +# #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((x[1] << 8) + x[0]); +# id(battery_soc).publish_state(value); +# value = static_cast((x[3] << 8) + x[2]); +# id(battery_soh).publish_state(value); +# - can_id: ${CB_BATTERY_STATUS} +# then: +# - lambda: |- +# float value = 0.01 * static_cast((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((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((x[5] << 8) + x[4]); // unit = 0.1°C +# id(battery_average_cell_temperature).publish_state(value); +# - can_id: ${CB_BATTERY_LIMITS} +# 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((x[3] << 8) + x[2]); // unit = 0.1A +# id(battery_charge_current_limit).publish_state(value); +# value = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A +# id(battery_discharge_current_limit).publish_state(value); +# - can_id: ${CB_BATTERY_FAULT} +# 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& +## 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& +# 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_LIMITS}: +# { +# float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V +# float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A +# float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A +# snprintf(buffer, sizeof(buffer), "BATTERY LIMITS: 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((x[1] << 8) + x[0]); +# uint soh = static_cast((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((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((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current +# float average_cell_temperature = 0.1 * static_cast((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_FAULT}: +# { +# 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()); \ No newline at end of file diff --git a/common/wifi.yaml b/common/wifi.yaml index 399fb61..179465d 100644 --- a/common/wifi.yaml +++ b/common/wifi.yaml @@ -1,16 +1,9 @@ wifi: domain: .sthome.org + min_auth_mode: WPA2 # requires esphome v2025.11.0 or later 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, # NB! set a static IP address in main config file diff --git a/components/ads131m08/README.md b/components/ads131m08/README.md new file mode 100644 index 0000000..f1b9673 --- /dev/null +++ b/components/ads131m08/README.md @@ -0,0 +1,6 @@ +# ESPHome config/source/custom Folder + +This folder contains Contains source code files and related resources for custom components. + + +For more information, visit the [ESPHome documentation](https://esphome.io/). \ No newline at end of file diff --git a/components/ads131m08/__init__.py b/components/ads131m08/__init__.py new file mode 100644 index 0000000..0dc14d6 --- /dev/null +++ b/components/ads131m08/__init__.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi +from esphome.const import CONF_ID + +DEPENDENCIES = ["spi"] +ads131m08_ns = cg.esphome_ns.namespace("ads131m08") +ADS131M08Hub = ads131m08_ns.class_("ADS131M08Hub", cg.Component, spi.SPIDevice) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(ADS131M08Hub), + cv.Required("drdy_pin"): pins.internal_gpio_input_pin_schema, + cv.Required("vref"): cv.float_, +}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + drdy = await cg.gpio_pin_expression(config["drdy_pin"]) + reference_voltage = config["vref"] + cg.add(var.set_reference_voltage(reference_voltage)) + cg.add(var.set_drdy_pin(drdy)) diff --git a/components/ads131m08/__pycache__/__init__.cpython-312.pyc b/components/ads131m08/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..beb6a95429a6d4aa5ad9385a52f408467735c6c2 GIT binary patch literal 2232 zcmah~O>7%Q6rS~te|FdI*0Dm91}7vfZUezhTSy}km9}=;Mpc}UR6SU#maTW5~&gwxJAl=swZaF-q_RuM%sDb zy!XD@H#6Vt{u&7IfV7hDJ$0Q0;CDK38dn3mZ7u+ofdUjJ3vx`-#Z)nygGslIU0F}g zoAlb4&H8d&lCv?)@;M>$Ktjykw**T~vvONLwkxiiPBqsHF$zt-rf;|P z^fDM0RLvlkH3}**-q?8JqBI)UA&Fy7R*aLUPv-guvHyI0iomh{!OJr@uusvIS&2|F zEb3_bDq)H#gE}M~y+5PsNJ-VSVAtj*{ay0xenGrf07H~%?!_}isV}xUVA2Zg)=qk7 z4f9L`&r?fiFmuctSgXxiO~;Qw&z_4@zGqoX-; z&l>A|@!j(-lHsBm^K8Qk4(u}%lL_wQ-P4V;6J#~@iuRu;w9h;)hwoSh%z`uE(GVW#i;mK&VIo~h<#UC+hBUJ( zoX}o?$VyUBQB5HnN*C0;tr2v)pd-Uj^BQJlMZv8GGKp}ek&d(!l8X7PDW?(kQ@5kE zUWh%Zyf&?-F-xkzkOpwDiDgjTyh7UZ<0q>5#MFv2PV5Q#ge0k&YD!YHFpIrro~%GY zIz1q|`^F>E{Q;gWfGyr%;SZPj!z%-ij;+39@vkq$Y+qNI?<%pSYZl*EfqfgWZ;KC9 z`0g^_T{==KT70wuqZ=@~6%18^$I8KDk3Ek+C?9{@3Jz6-p@sNnTjxq~^}q&f-GZKb z7Z)$yKV1=ymW87muMSwknI~|t!QW99I#%MA(ES_i*%5&Ijc1!M`fO7edTzW)3jJ^( zCNkeMF%JF^CTRU7CMLl3LtPXPFtGq!KXX1r20sZ5?E|#$?ZP553ODmP6s1c~BMp0N zK&Jx7?e#F-b*U(4RYf-Gp#`fO+vswoir8CY==8&lTk3pg`>Wyz)B~DfR+Z$Bk4%gt z;v`S(tb20W>(Y#0*eV$ShmZfasQZi`L#(h@vVshcP# zWBwA-kS?2OG>&-%5u%PHRl&Z=+Q$lasv428CTAsEq^ee!F>R?D)fqg~*}QB@*n@7H zNK^2^*!bAQcw!`RRho)j9vM52gLS1#HBx|nOh+%(GHh{u33f?gKyRbv`G#|OWhRTx z>hxbgZ?j>MZii(UW*fY`4IbWE{&x7s(9hxZuqBQ!_@5qZUve#T54cSjT)1#=Y;o-F_|o;I$>r-0 zu9t?b*8WW>691LOD@(iuJ65h%!sp82b9JxBf`?XG9=5Nx?|5N{YZt&E`YCi)0CQm1?`4kYRHlCe-8{gp|4>X{V*vPz2v&1d!NH#t0INU(5|srq6{jf^Gcp^u8JNy;vOR7$ zFq3u2&bU+N<2;sG$(D7=LR^sDaW@5M0Nt4;YlGS>anPP=uE`dHbS7xw+PscWN?N=D z`?F_SOsRfcuL_xq^{XbvHnvRr>2=G-;5$z9uwfnNfiy!SY~uP+vLDmvtmjM&g<~3L z_<;N|jjq!^@I5%bbFR}SlV&Xavtq@OUOw%?vWPcI{1+hpR-OvE_J~?6L?KRtik4GD zG_ns(#74xM*O6d=q0xa@Z1}@Hk_5~O1J@@nbzd6qx-vR9jf8}xVM*?~A}Sj49C4F| zGsI(BoSB;z)6$;Zv}feBf*3h*KUvKxIRM~rU&8(1Q@lC`DE#j9BR*4&nJ8MV0Cd*E z7cj75dqCCM5w9m&fhqtihfDZ9e}>=G*Yqm$rOk$S)t#1l#;6ZVGUxxXt({jmZbzg7 zuA=&Lp4|HZU3cztXV}vjt1jzEoCU8X67L1dip|Og1@fQ|e3ip}68E0(nR`@-j;SQP zkQ33#6ml!@o_K3Mp{5hlS*Vh_^K^xQh2I5*P=%cDQY_IoXsP zaMzac@DDg#055oTQA zoJYQzg_ThAy4*EF(RAZ(jq5=`CH1H}$bMWho6s~7an-7I4q4Ztl6pXH^F~B1IRoVc z;^2K9DiY#vKn1D^9pbNommqecLR{#`aU0&9ORG>q+?-~tIMa$QstF|pk#}ff{MJNl zIQEe^IW#&vK7ichm$`YPDjHY)EjYmY0`*O@yW;~nC(UKyHI;mdq{y@%@UYKN6tx4I zc0k(>XnhHyFG2gRuXQC{@wJwGty^t<&(8nofA0U8{!RErDEmHM@a%e9R=A3{rQ~fX zd)q4BNXZ-7W*Zg;m&X^!mnK%GR&K9OJ(?=^mtXJRW<7X)Y;kNwD6{^x{F?er{&D_k zQ#sIkf+}FD@VKx+ZQT7maCP6#M(G20Lw4-K76~v`%ApP%rxWoL!CPiIO=^h HiX-?B!(*mV literal 0 HcmV?d00001 diff --git a/components/ads131m08/ads131m08.cpp b/components/ads131m08/ads131m08.cpp new file mode 100644 index 0000000..2d403d8 --- /dev/null +++ b/components/ads131m08/ads131m08.cpp @@ -0,0 +1,197 @@ +#include "ads131m08.h" +/* +namespace esphome { +namespace ads131m08 { + +static const char *const TAG = "ads131m08"; + +ADS131M08::ADS131M08(esphome::gpio::GPIOPin *pin) +: drdy_pin_(pin) +{ +} + +void ADS131M08::set_drdy_pin(InternalGPIOPin *pin) +{ + drdy_pin_ = pin; +} + +void ADS131M08::set_sensor(int channel, sensor::Sensor *s) +{ + channels_[channel] = s; +} + +void ADS131M08::set_reference_voltage(float reference_voltage) +{ + this->reference_voltage_ = reference_voltage; +} + +float ADS131M08::get_setup_priority() const +{ + return setup_priority::HARDWARE; +} + +void ADS131M08::setup() +{ + // Initialize SPI with correct settings for ADS131M08 + // SPI mode 1: CPOL=0, CPHA=1 + //this->spi_setup(); + SPI.begin(); + SPI.setDataMode(SPI_MODE1); + SPI.setBitOrder(MSBFIRST); + SPI.setClockDivider(SPI_CLOCK_DIV8); // Adjust clock speed as needed + + // Configure DRDY pin + drdy_pin_->setup(); + drdy_pin_->pin_mode(esphome::gpio::FLAG_INPUT | esphome::gpio::FLAG_PULLUP); + + // Attach interrupt on falling edge of DRDY +// esphome::gpio::attach_interrupt(drdy_pin_, esphome::gpio::INTERRUPT_FALLING_EDGE, [this]() { +// this->data_ready_ = true; +// }); + this->drdy_pin_->attach_interrupt(&ADS131M08Component::handle_drdy, this, gpio::INTERRUPT_FALLING_EDGE); + + // Initialize ADS131M08 + initialize_ads131m08(); +} + +// interrupt handler +void ADS131M08::handle_drdy(ADS131M08Component *arg) +{ + arg->data_ready_ = true; +} + +void ADS131M08::loop() +{ + if (data_ready_) { + data_ready_ = false; + read_all_channels(); + } +} + +void ADS131M08::initialize_ads131m08() +{ + // Send RESET command + SPI.transfer(0x06); + delay(1); + + // Send UNLOCK command + SPI.transfer(0x55); + SPI.transfer(0x55); + + // Configure registers as needed (example: set gain, etc.) + // Write to MODE register for continuous conversion, disable internal reference (use external MAX6070AAUT12+T at 1.25V) + write_register(0x02, 0x0000); // MODE register, continuous mode, INTREF_EN = 0 + + // Wait for external reference to settle + delay(100); + + // Start conversions + SPI.transfer(0x08); // START command +} + +void ADS131M08::write_register(uint8_t reg, uint16_t value) +{ + SPI.transfer(0x40 | reg); // WREG command + SPI.transfer(0x00); // Number of registers - 1 (0 for one register) + SPI.transfer((value >> 8) & 0xFF); + SPI.transfer(value & 0xFF); +} + +void ADS131M08::read_all_channels() +{ + // Send RDATA command or read data directly + SPI.transfer(0x12); // RDATA command + + // Read 24 bytes (3 bytes per channel for 8 channels) + for (int i = 0; i < 24; i++) { + data_buffer_[i] = SPI.transfer(0x00); + } + + // Convert to voltages for all channels + //Sensor *channels[8] = {channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8}; + for (int ch = 0; ch < 8; ch++) { + if (this->sensors_[ch] != nullptr) { + int offset = ch * 3; + int32_t raw = (data_buffer_[offset] << 16) | (data_buffer_[offset + 1] << 8) | data_buffer_[offset + 2]; + if (raw & 0x800000) + raw |= 0xFF000000; // Sign extend + float v = (raw / 8388608.0) * this->reference_voltage_; // 2^23 = 8388608, Vref = 1.25V (MAX6070AAUT12+T) + this->sensors_[ch]->publish_state(v); + } + } +} + +float ADS131M08::read_data(uint8_t channel) +{ + // This function assumes read_all_channels() has been called and data is in data_buffer_ + int offset = channel * 3; + int32_t raw = (data_buffer_[offset] << 16) | (data_buffer_[offset + 1] << 8) | data_buffer_[offset + 2]; + if (raw & 0x800000) + raw |= 0xFF000000; // Sign extend + float v = (raw / 8388608.0) * this->reference_voltage_; // 2^23 = 8388608, Vref = 1.25V (MAX6070AAUT12+T) + return v; +} + +void ADS131M08::dump_config() { + ESP_LOGCONFIG(TAG, "ADS131M08:"); + LOG_PIN(" CS Pin:", this->cs_); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); +} + +} // namespace ads131m08 +} // namespace esphome + */ +namespace esphome { +namespace ads131m08 { + +static const char *const TAG = "ads131m08"; + +void ADS131M08Hub::setup() +{ + this->spi_setup(); + this->drdy_pin_->setup(); + this->drdy_pin_->attach_interrupt(&ADS131M08Hub::isr, this, gpio::INTERRUPT_FALLING_EDGE); +} + +void IRAM_ATTR ADS131M08Hub::isr(ADS131M08Hub *arg) +{ + arg->data_ready_ = true; +} + +void ADS131M08Hub::loop() +{ + if (this->data_ready_) { + this->data_ready_ = false; + this->read_data_(); + } +} + +void ADS131M08Hub::read_data_() +{ + this->enable(); + // ADS131M08 Frame: 1 Status Word + 8 Channel Words + 1 CRC Word (24-bit words) + uint8_t frame[30]; // 10 words * 3 bytes + this->read_array(frame, 30); + this->disable(); + + for (int i = 0; i < 8; i++) { + if (this->sensors_[i] != nullptr) { + // Convert 24-bit two's complement to float (Simplified) + int32_t raw = (frame[3+(i*3)] << 16) | (frame[4+(i*3)] << 8) | frame[5+(i*3)]; + if (raw & 0x800000) + raw |= 0xFF000000; // Sign extend + this->sensors_[i]->publish_state(raw * (this->reference_voltage_ / 8388608.0)); // 2^23 = 8388608, Vref = 1.25V ( for MAX6070AAUT12+T ) + } + } +} + +void ADS131M08Hub::dump_config() +{ + ESP_LOGCONFIG(TAG, "ADS131M08:"); + LOG_PIN(" CS Pin:", this->cs_); + LOG_PIN(" DRDY Pin:", this->drdy_pin_); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); +} + +} // namespace ads131m08 +} // namespace esphome \ No newline at end of file diff --git a/components/ads131m08/ads131m08.h b/components/ads131m08/ads131m08.h new file mode 100644 index 0000000..63f2a2b --- /dev/null +++ b/components/ads131m08/ads131m08.h @@ -0,0 +1,35 @@ +#pragma once +// using external voltage reference + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace ads131m08 { + +class ADS131M08Sensor : public sensor::Sensor, public Component +{ +}; + +class ADS131M08Hub : public Component, public spi::SPIDevice +{ + public: + void setup() override; + void loop() override; + void set_drdy_pin(InternalGPIOPin *pin) { drdy_pin_ = pin; } + void register_sensor(int channel, ADS131M08Sensor *s) { sensors_[channel] = s; } + void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } + void dump_config() override; + + protected: + float reference_voltage_; + InternalGPIOPin *drdy_pin_; + ADS131M08Sensor *sensors_[8] = {nullptr}; + volatile bool data_ready_{false}; + static void isr(ADS131M08Hub *arg); + void read_data_(); +}; + +} // namespace ads131m08 +} // namespace esphome \ No newline at end of file diff --git a/components/ads131m08/sensor.py b/components/ads131m08/sensor.py new file mode 100644 index 0000000..0481d23 --- /dev/null +++ b/components/ads131m08/sensor.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import CONF_ID, CONF_CHANNEL +from . import ADS131M08Hub, ads131m08_ns + +ADS131M08Sensor = ads131m08_ns.class_("ADS131M08Sensor", sensor.Sensor, cg.Component) + +CONFIG_SCHEMA = sensor.sensor_schema().extend({ + cv.GenerateID(): cv.declare_id(ADS131M08Sensor), + cv.Required("ads_hub_id"): cv.use_id(ADS131M08Hub), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), +}).extend(cv.COMPONENT_SCHEMA) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + hub = await cg.get_variable(config["ads_hub_id"]) + cg.add(hub.register_sensor(config[CONF_CHANNEL], var)) \ No newline at end of file diff --git a/device.yaml b/device.yaml new file mode 100644 index 0000000..3e19811 --- /dev/null +++ b/device.yaml @@ -0,0 +1,9 @@ +# this is just a simple test device setup that's used to compile and test the examples + +esphome: + name: custom_components_test + +esp32: + board: esp32dev + framework: + type: esp-idf diff --git a/sthome-ut1.yaml b/sthome-ut1.yaml index 35a9980..6684f21 100644 --- a/sthome-ut1.yaml +++ b/sthome-ut1.yaml @@ -1,15 +1,31 @@ -esphome: +packages: + - !include common/wifi.yaml +# - !include common/canbus.yaml +# - !include common/felicityinverter.yaml + +substitutions: name: sthome-ut1 - friendly_name: sthome-ut1 + friendly_name: "sthome-ut1" + +esphome: + name: "${name}" + friendly_name: "${friendly_name}" esp32: - board: esp32dev + board: nodemcu-32s + variant: esp32 framework: - type: arduino + type: arduino #esp-idf # + +debug: + update_interval: 10s # Enable logging logger: - level: INFO + level: DEBUG + logs: + sensor: INFO + text_sensor: INFO # Enable Home Assistant API api: @@ -21,36 +37,20 @@ ota: password: "37f546590fcc15e1323d273540eb623a" wifi: - #ssid: !secret wifi_ssid - #password: !secret wifi_password - # we will use local dns server for local dns resolution 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.1 - gateway: 10.0.0.2 - subnet: 255.255.240.0 - dns1: 10.0.0.1 - dns2: 10.0.0.2 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: - ssid: "sthome-ut1 Fallback Hotspot" + ssid: "${name} Fallback Hotspot" password: "7SglIlgdkpAD" captive_portal: + #preferences: # flash_write_interval: 30s @@ -62,11 +62,29 @@ sun: time: - platform: homeassistant +text_sensor: + - platform: debug + device: + name: "Device Info" + reset_reason: + name: "Reset Reason" + + # human readable update text sensor from sensor:uptime + - platform: template + name: Uptime + id: uptime_human + icon: mdi:clock-start + update_interval: 10s + #define OUTPUT_R1 16 #define OUTPUT_R2 17 #define OUTPUT_R3 18 #define OUTPUT_R4 19 switch: + - platform: restart + name: "${name} Restart" + id: "restart_switch" + - platform: gpio pin: number: GPIO16 @@ -176,3 +194,31 @@ sensor: if (id(alarm_signal).state > 1.5) { id(relay1).turn_on(); } + + # 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(); \ No newline at end of file diff --git a/sthome-ut10.yaml b/sthome-ut10.yaml index 99f6643..f7bd674 100644 --- a/sthome-ut10.yaml +++ b/sthome-ut10.yaml @@ -47,7 +47,7 @@ esphome: 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 + #- ds1307.read_time: # read the RTC time - lambda: |- id(can_msgctr) = 0; id(update_heating_display).execute(false); @@ -69,7 +69,7 @@ logger: level: very_verbose initial_level: very_verbose logs: - canbus: INFO + canbus: very_verbose touchscreen: INFO xpt2046: INFO script: INFO @@ -98,24 +98,25 @@ wifi: captive_portal: time: - - platform: ds1307 + #- platform: ds1307 # repeated synchronization is not necessary unless the external RTC # is much more accurate than the internal clock - update_interval: never + # 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");' + #- 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 @@ -211,7 +212,7 @@ interval: - interval: 30s then: - script.execute: send_info_request - - interval: 500ms + - interval: 15s # 500ms then: - script.execute: send_quick_update_request @@ -1190,7 +1191,7 @@ sensor: # return currenttime.timestamp - time_obj.timestamp; # update_interval: 60s -#text_sensor: +text_sensor: ## - platform: template ## id: module_time ## name: "Module time" @@ -1218,12 +1219,12 @@ sensor: # id: uptime_human # icon: mdi:clock-start # -# - platform: template -# name: 'Last Restart' -# id: device_last_restart -# icon: mdi:clock -# entity_category: diagnostic -# + - platform: template + name: 'Last Restart' + id: device_last_restart + icon: mdi:clock + entity_category: diagnostic + script: - id: send_quick_update_request diff --git a/sthome-ut6.yaml b/sthome-ut6.yaml index cc3be0a..c9f6a62 100644 --- a/sthome-ut6.yaml +++ b/sthome-ut6.yaml @@ -1,3 +1,6 @@ +packages: + - !include common/wifi.yaml + substitutions: name: sthome-ut6 friendly_name: "sthome-ut6" @@ -22,7 +25,8 @@ esp8266: # Enable logging logger: - + level: INFO + # Enable Home Assistant API api: encryption: @@ -33,29 +37,8 @@ ota: password: "5956a60f6cf40cf4b6b172e23f236572" wifi: - #ssid: !secret wifi_ssid - #password: !secret wifi_password - # we will use local dns server for local dns resolution - 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.6 - gateway: 10.0.0.2 - subnet: 255.255.240.0 - dns1: 10.0.0.1 - dns2: 10.0.0.2 # Enable fallback hotspot (captive portal) in case wifi connection fails ap: @@ -109,6 +92,10 @@ time: # - script.execute: heartbeat # +debug: + update_interval: 60s + + switch: # Switch to restart the ESP - platform: restart @@ -189,6 +176,12 @@ sensor: text_sensor: + - platform: debug +# device: +# name: "Device Info" + reset_reason: + name: "Reset Reason" + # Expose WiFi information as sensors - platform: wifi_info ip_address: diff --git a/sthome-ut8.yaml b/sthome-ut8.yaml index 02e8eb4..8420d4f 100644 --- a/sthome-ut8.yaml +++ b/sthome-ut8.yaml @@ -1,8 +1,15 @@ +external_components: + - source: + type: local + path: components # Path relative to this YAML file + components: [ ads131m08 ] + packages: - !include common/wifi.yaml - !include common/canbus.yaml - !include common/geyser.yaml - - !include common/felicityinverter.yaml + - !include common/felicityinverter.yaml +# - device: !include device.yaml substitutions: name: sthome-ut8 @@ -28,7 +35,7 @@ esphome: 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 + - ds1307.read_time: # read the RTC time - lambda: |- id(geyser_relay).turn_off(); id(pool_relay).turn_off(); @@ -45,14 +52,16 @@ esphome: - uart.write: id: inv_uart2 data: [0x0D, 0x0A] -# - priority: 200 # Network connections like MQTT/native API are set up at this priority. -# then: -# - lambda: |- -# - priority: 400 -# then: -# - switch.turn_on: level_shifter_output_enable globals: + - id: gb_geyser_disable + type: bool + restore_value: true + initial_value: 'false' + - id: gb_temp_top + type: double + - id: gb_temp_bottom + type: double - id: g_inv1_power_flow type: uint16_t restore_value: no @@ -206,10 +215,17 @@ logger: initial_level: INFO logs: canbus: INFO + i2c: INFO + i2c.idf: INFO uart: INFO + light: INFO sensor: INFO + ds1307: INFO + text_sensor: INFO ads1115.sensor: INFO modbus: INFO + modbus_controller: INFO + modbus_controller.sensor: INFO # Enable Home Assistant API api: @@ -244,7 +260,7 @@ i2c: scan: true id: bus_a frequency: 10kHz - + ads1115: - address: 0x48 id: ads1115_48 @@ -255,8 +271,6 @@ ads1115: - address: 0x4A id: ads1115_4A continuous_mode: true - # - address: 0x4B - # id: ads1115_4B spi: - id: spi_bus0 @@ -277,13 +291,13 @@ uart: baud_rate: 2400 stop_bits: 1 parity: NONE - debug: - direction: BOTH - dummy_receiver: false - after: - delimiter: "\r" - sequence: - - lambda: UARTDebug::log_hex(direction, bytes, ','); +# debug: +# direction: BOTH +# dummy_receiver: false +# after: +# delimiter: "\r" +# sequence: +# - lambda: UARTDebug::log_hex(direction, bytes, ','); - id: inv_uart2 tx_pin: GPIO33 # Tx2 @@ -296,24 +310,24 @@ uart: baud_rate: 2400 stop_bits: 1 parity: NONE - debug: - direction: BOTH - dummy_receiver: false - after: - delimiter: "\r" - sequence: - - lambda: UARTDebug::log_hex(direction, bytes, ' '); -# +# debug: +# direction: BOTH +# dummy_receiver: false +# after: +# delimiter: "\r" +# sequence: +# - lambda: UARTDebug::log_hex(direction, bytes, ' '); + sun: id: sun_sensor latitude: !secret latitude longitude: !secret longitude time: -# #- platform: ds1307 + - platform: ds1307 # # repeated synchronization is not necessary unless the external RTC # # is much more accurate than the internal clock -# # update_interval: never + update_interval: never # # - platform: sntp # timezone: Africa/Johannesburg @@ -324,7 +338,7 @@ time: - platform: homeassistant id: time_source on_time_sync: - #- ds1307.write_time: + - ds1307.write_time: - lambda: |- id(time_synched) = true; id(init_holidays).execute(); // we need valid time to calculate holidays @@ -346,8 +360,7 @@ time: # - sensor.integration.reset: yearly_generated_energy # - sensor.integration.reset: yearly_house_energy_usage # - sensor.integration.reset: yearly_energy_loss - - # do every first day of month at one second after midnight + # do every first day of month at one second after midnight - seconds: 1 minutes: 0 hours: 0 @@ -360,7 +373,7 @@ time: - sensor.integration.reset: monthly_generated_energy - sensor.integration.reset: monthly_house_energy_usage - sensor.integration.reset: monthly_energy_loss - + # # do every day at one second after midnight # - seconds: 1 # minutes: 0 @@ -381,13 +394,14 @@ time: minutes: '*' then: - lambda: |- - // id(level_shifter_output_enable).turn_on(); + const char tag[] = "ADC\0"; // id(get_ha_settings).execute(); //id(update_power_counters).execute(); - - //ESP_LOGI("info", "Mains Voltage: %f", id(mains_voltage_adc).state); - //ESP_LOGI("info", "AMP: Ge %.4f, Li: %.4f, Ma %.4f, Pl:%.4f, VOLT: Ma: %.4f, Pl %.4f, A2: %.4f, A3 %.4f, TEMP: %.4f", id(geyser_current).state, id(lights_current).state, id(mains_current).state, id(power_outlets_current).state, id(mains_voltage_adc).state, id(inverter_output_voltage_adc).state, id(adc4A_A2).state, id(adc4A_A3).state, id(geyser_top_temperature).state); - //ESP_LOGI("info", "AMP: Ge %.4f, Li: %.4f, Ma %.4f, Pl:%.4f, VOLT: Ma: %.8f, Pl %.8f, TEMP: %.4f", id(geyser_current).state, id(lights_current).state, id(mains_current).state, id(power_outlets_current).state, id(mains_voltage_adc).state, id(inverter_output_voltage_adc).state, id(geyser_top_temperature).state); + //ESP_LOGI(tag, "Mains: %f", id(mains_voltage_adc).state); + //ESP_LOGI(tag, "Inverter Out: %f", id(inverter_output_voltage_adc).state); + //ESP_LOGI(tag, "Inverter Out: %f, Mains: %f, Spare1: %f, Spare2: %f,", id(inverter_output_voltage_adc).state, id(mains_voltage_adc).state, id(spare1_voltage_adc).state, id(spare2_voltage_adc).state); + //ESP_LOGI(tag, "AMP: Ge %.4f, Li: %.4f, Ma %.4f, Pl:%.4f, VOLT: Ma: %.4f, Pl %.4f, A2: %.4f, A3 %.4f, TEMP: %.4f", id(geyser_current).state, id(lights_current).state, id(mains_current).state, id(power_outlets_current).state, id(mains_voltage_adc).state, id(inverter_output_voltage_adc).state, id(adc4A_A2).state, id(adc4A_A3).state, top_temp); + //ESP_LOGI(tag, "AMP: Ge %.4f, Li: %.4f, Ma %.4f, Pl:%.4f, VOLT: Ma: %.8f, Pl %.8f, TEMP: %.4f", id(geyser_current).state, id(lights_current).state, id(mains_current).state, id(power_outlets_current).state, id(mains_voltage_adc).state, id(inverter_output_voltage_adc).state, top_temp); - text_sensor.template.publish: id: heating_time_text @@ -444,7 +458,10 @@ interval: - script.execute: set_active_schedule - script.execute: set_active_heating_timers - script.execute: reset_geyser_relay - - script.execute: set_heat_indicators + - lambda: |- + id(set_heat_indicators).execute(id(geyser_bottom_temperature).state, id(geyser_top_temperature).state); + // for debugging + // id(set_heat_indicators).execute(id(geyser_bottom_temp).state, id(geyser_top_temp).state); - interval: 5s then: @@ -507,9 +524,9 @@ interval: case cbf_sthome::CB_ENERGY_GEYSER: id(canbus_send_energy_geyser).execute(); break; - //case cbf_sthome::CB_ENERGY_POOL: - // id(canbus_send_energy_pool).execute(); - // break; + case cbf_sthome::CB_ENERGY_POOL: + //id(canbus_send_energy_pool).execute(); + break; case cbf_sthome::CB_ENERGY_PLUGS: id(canbus_send_energy_plugs).execute(); break; @@ -557,7 +574,7 @@ interval: for(auto& can_id : unhandled_set) { switch(can_id) { case cbf_sthome::CB_CONTROLLER_STATES: - id(canbus_send_controller_states).execute(); + // id(canbus_send_controller_states).execute(); break; case cbf_sthome::CB_GEYSER_HEATING: id(canbus_send_geyser_heating).execute(); @@ -679,7 +696,7 @@ canbus: cs_pin: GPIO14 # CB2CS spi_id: spi_bus0 id: canbus_solarbattery - mode: NORMAL #LISTENONLY + mode: NORMAL can_id: ${CB_CANBUS_ID08} bit_rate: 500KBPS on_frame: @@ -830,19 +847,42 @@ canbus: } switch: + # if geyser is disabled, it will not be energised + - platform: template + device_class: switch + id: geyser_disable + name: "Geyser Disable" + icon: "mdi:water-boiler" + lambda: |- + return id(gb_geyser_disable); + turn_on_action: + - lambda: |- + id(gb_geyser_disable) = true; + ESP_LOGI("geyser", "Disabling geyser."); + + turn_off_action: + - lambda: |- + id(gb_geyser_disable) = false; + ESP_LOGI("geyser", "Enabling geyser."); + optimistic: False + + # in economy mode, geyser is only switched on when it can be powered by solar only, i.e. without using mains + - platform: gpio + pin: + number: GPIO36 + inverted: true + mode: + input: true + pullup: false # external pullup +# filters: +# - delayed_off: 100ms + id: economy_mode # mode_select_switch + name: "Economy Mode" # "Mode Select" + icon: "mdi:beach" + - platform: restart name: "${name} Restart" id: "restart_switch" - -# - platform: gpio -# id: level_shifter_output_enable -# pin: -# number: GPIO12 -# inverted: false -# mode: -# output: true -# pullup: true -# restore_mode: ALWAYS_OFF - platform: gpio id: reset_energy_counters @@ -857,13 +897,13 @@ switch: restore_mode: RESTORE_DEFAULT_OFF on_turn_on: then: - - sensor.integration.reset: geyser_energy - - sensor.integration.reset: plugs_energy - - sensor.integration.reset: mains_energy - - sensor.integration.reset: lights_energy - - sensor.integration.reset: generated_energy - - sensor.integration.reset: house_energy_usage - - sensor.integration.reset: energy_loss +# - sensor.integration.reset: geyser_energy +# - sensor.integration.reset: plugs_energy +# - sensor.integration.reset: mains_energy +# - sensor.integration.reset: lights_energy +# - sensor.integration.reset: generated_energy +# - sensor.integration.reset: house_energy_usage +# - sensor.integration.reset: energy_loss - lambda: |- auto currenttime = id(time_source).now(); if(currenttime.is_valid()) { @@ -884,11 +924,11 @@ switch: restore_mode: ALWAYS_OFF on_turn_on: - lambda: |- + ESP_LOGI("geyser", "Geyser Relay turned on. Relay status of flag: %s set to on", id(geyser_relay_status) ? "on" : "off" ); id(geyser_relay_status) = true; // only set to false by other sensor / script to include hysteresis and thus avoid relay chattering - ESP_LOGI("info", "Geyser Relay turned on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Relay turned off"); + ESP_LOGI("geyser", "Geyser Relay turned off"); - platform: gpio pin: @@ -903,138 +943,192 @@ switch: - delay: 30s # rapid on and off states can burn-out motor - lambda: |- //id(pool_relay_status) = true; // only set to false by other sensor / script to include hysteresis and thus avoid relay chattering - ESP_LOGI("info", "Pool Relay turned on"); + ESP_LOGI("pool", "Pool Relay turned on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Pool Relay turned off"); -# -##tlc59208f: -## address: 0x40 -## id: tlc59208f_1 -# -output: - - platform: ledc - pin: - number: GPIO27 - inverted: true - id: led_geyser_temp_blue - - platform: ledc - pin: - number: GPIO26 - inverted: true - id: led_geyser_temp_green - - platform: ledc + ESP_LOGI("pool", "Pool Relay turned off"); + + - platform: gpio pin: number: GPIO25 inverted: true - id: led_geyser_temp_yellow - - platform: ledc - pin: - number: GPIO15 - inverted: true - id: led_geyser_temp_orange - - platform: ledc - pin: - number: GPIO1 - inverted: true - id: led_geyser_temp_red + mode: output + id: led_reset + name: "LED Reset" + icon: "mdi:pool" + restore_mode: ALWAYS_OFF + on_turn_on: + - lambda: |- + ESP_LOGI("led", "LED drive reset"); + - delay: 10ms + - switch.turn_off: led_reset + +tlc59208f: + address: 0x20 + id: tlc59208f_1 + i2c_id: bus_a + +output: +# - platform: ledc +# pin: +# number: GPIO27 +# inverted: true +# id: led0 +# - platform: ledc +# pin: +# number: GPIO26 +# inverted: true +# id: led1 +# - platform: ledc +# pin: +# number: GPIO25 +# inverted: true +# id: led2 +# - platform: ledc +# pin: +# number: GPIO15 +# inverted: true +# id: led3 +# - platform: ledc +# pin: +# number: GPIO1 +# inverted: true +# id: led4 - platform: ledc pin: number: GPIO12 #GPIO26 # LED_LOW_BAT inverted: false #true id: led_inverter_battery_low -## - platform: tlc59208f -## channel: 0 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_temp_blue -## -## - platform: tlc59208f -## channel: 1 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_temp_green -## -## - platform: tlc59208f -## channel: 2 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_temp_yellow -## -## - platform: tlc59208f -## channel: 3 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_temp_orange -## -## - platform: tlc59208f -## channel: 4 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_temp_red -## -## - platform: tlc59208f -## channel: 5 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_mode_0 -## -## - platform: tlc59208f -## channel: 6 -## tlc59208f_id: 'tlc59208f_1' -## id: led_geyser_mode_1 -## -## - platform: tlc59208f -## channel: 7 -## tlc59208f_id: 'tlc59208f_1' -## id: led_inverter_battery_low -# + - platform: tlc59208f + channel: 0 + tlc59208f_id: 'tlc59208f_1' + id: led0 + + - platform: tlc59208f + channel: 1 + tlc59208f_id: 'tlc59208f_1' + id: led1 + + - platform: tlc59208f + channel: 2 + tlc59208f_id: 'tlc59208f_1' + id: led2 + + - platform: tlc59208f + channel: 3 + tlc59208f_id: 'tlc59208f_1' + id: led3 + + - platform: tlc59208f + channel: 4 + tlc59208f_id: 'tlc59208f_1' + id: led4 + + - platform: tlc59208f + channel: 5 + tlc59208f_id: 'tlc59208f_1' + id: led5 + + - platform: tlc59208f + channel: 6 + tlc59208f_id: 'tlc59208f_1' + id: led6 + + - platform: tlc59208f + channel: 7 + tlc59208f_id: 'tlc59208f_1' + id: led7 + light: - platform: monochromatic - output: led_geyser_temp_blue - name: "LED Geyser Temperature Blue" + output: led0 + name: "LED Geyser Temperature 0" + id: led_geyser_temp0 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED BLUE on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED BLUE off"); + - platform: monochromatic + output: led1 + name: "LED Geyser Temperature 1" id: led_geyser_temp1 + default_transition_length: 20ms on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED BLUE on"); + ESP_LOGV("geyser", "Geyser Temperature LED GREEN on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED BLUE off"); + ESP_LOGV("geyser", "Geyser Temperature LED GREEN off"); - platform: monochromatic - output: led_geyser_temp_green - name: "LED Geyser Temperature Green" + output: led2 + name: "LED Geyser Temperature 2" id: led_geyser_temp2 + default_transition_length: 20ms on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED GREEN on"); + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED GREEN off"); + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW off"); - platform: monochromatic - output: led_geyser_temp_yellow - name: "LED Geyser Temperature Yellow" + output: led3 + name: "LED Geyser Temperature 3" id: led_geyser_temp3 + default_transition_length: 20ms on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED YELLOW on"); + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW2 on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED YELLOW off"); + ESP_LOGV("geyser", "Geyser Temperature LED YELLOW2 off"); - platform: monochromatic - output: led_geyser_temp_orange - name: "LED Geyser Temperature Orange" + output: led4 + name: "LED Geyser Temperature 4" id: led_geyser_temp4 + default_transition_length: 20ms on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED ORANGE on"); + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED ORANGE off"); + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE off"); - platform: monochromatic - output: led_geyser_temp_red - name: "LED Geyser Temperature Red" + output: led5 + name: "LED Geyser Temperature 5" id: led_geyser_temp5 + default_transition_length: 20ms on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED RED on"); + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE2 on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED RED off"); + ESP_LOGV("geyser", "Geyser Temperature LED ORANGE2 off"); + - platform: monochromatic + output: led6 + name: "LED Geyser Temperature 6" + id: led_geyser_temp6 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED off"); + - platform: monochromatic + output: led7 + name: "LED Geyser Temperature 7" + id: led_geyser_temp7 + default_transition_length: 20ms + on_turn_on: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED2 on"); + on_turn_off: + - lambda: |- + ESP_LOGV("geyser", "Geyser Temperature LED RED2 off"); - platform: monochromatic output: led_inverter_battery_low name: "LED Inverter Battery Low" @@ -1042,10 +1136,10 @@ light: default_transition_length: 20ms on_turn_on: - lambda: |- - ESP_LOGI("info", "Battery Low"); + ESP_LOGI("battery", "Battery Low"); # on_turn_off: # - lambda: |- -# ESP_LOGI("info", "Battery OK"); +# ESP_LOGI("battery", "Battery OK"); binary_sensor: - platform: status @@ -1065,29 +1159,15 @@ binary_sensor: id: inverter_battery_charge_state name: "Inverter Battery Charge" device_class: battery - on_press: - then: - - light.turn_on: - id: light_inverter_battery_low - brightness: 100% - on_release: - then: - - light.turn_off: - id: light_inverter_battery_low - - # in economy mode, geyser is only switched on when it can be powered by solar only, i.e. without using mains - - platform: gpio - pin: - number: GPIO36 - inverted: true - mode: - input: true - pullup: false # external pullup -# filters: -# - delayed_off: 100ms - id: economy_mode # mode_select_switch - name: "Economy Mode" # "Mode Select" - icon: "mdi:beach" + # on_press: + # then: + # - light.turn_on: + # id: light_inverter_battery_low + # brightness: 100% + # on_release: + # then: + # - light.turn_off: + # id: light_inverter_battery_low - platform: template id: geyser_heating @@ -1100,7 +1180,7 @@ binary_sensor: id: mains_supply name: "Mains Supply" lambda: |- - return id(mains_voltage_adc).state > 180 || id(inv1_ac_input_voltage).state > 200 || id(inv2_ac_input_voltage).state > 200; // minimum acceptable voltage is 200; inverters more accurate for now + return /* id(mains_voltage_adc).state > 180 || */ id(inv1_ac_input_voltage).state > 200 || id(inv2_ac_input_voltage).state > 200; // minimum acceptable voltage is 200; inverters more accurate for now device_class: power - platform: analog_threshold @@ -1115,12 +1195,12 @@ binary_sensor: on_state: then: - lambda: |- - ESP_LOGI("info", "Inverter 1 & 2 are being overloaded. Heavy loads should be switched off."); + ESP_LOGI("inverter", "Inverter 1 & 2 are being overloaded. Heavy loads should be switched off."); # - switch.turn_off: geyser_relay on_release: then: - lambda: |- - ESP_LOGI("info", "Overload is cleared."); + ESP_LOGI("inverter", "Overload is cleared."); - platform: template id: is_public_holiday @@ -1132,10 +1212,10 @@ binary_sensor: int day_of_month = time_obj.day_of_month; int i = 0; while(i < 12 && (id(public_holidays)[i][0] != month || id(public_holidays)[i][1] != day_of_month)) { - // ESP_LOGI("info", "%d ########### holiday check!: %d/%d ###########", i, id(holidays)[i][0], id(holidays)[i][1]); + // ESP_LOGI("geyser", "%d ########### holiday check!: %d/%d ###########", i, id(holidays)[i][0], id(holidays)[i][1]); i++; } - // ESP_LOGI("info", "%d ########### Holiday = %d: %d/%d ###########", i, i < 12, id(holidays)[i][0], id(holidays)[i][1]); + // ESP_LOGI("geyser", "%d ########### Holiday = %d: %d/%d ###########", i, i < 12, id(holidays)[i][0], id(holidays)[i][1]); return (i < 12); } return false; @@ -1391,13 +1471,63 @@ binary_sensor: float battery_level = id(battery_soc).state; bool sun_high_enough = sun_elevation >= id(sun_elevation_minimum); bool battery_full = battery_level == 100; - bool battery_getting_full = battery_level >= 95 && id(battery_charging).state; - bool pv_adequate = id(battery_charging_rate).state > 70.0; // id(pv_input_power).state > id(house_power_draw).state; - double surplus = sun_high_enough && (battery_full || battery_getting_full || pv_adequate); + bool battery_getting_full = battery_level >= 96 && id(battery_charging).state; + bool surplus = sun_high_enough && (battery_full || battery_getting_full); //ESP_LOGI("solar", "Solar Power surplus? : %s", surplus ? "Yes" : "No"); return surplus; + - platform: template + name: "Heating Enabled" + id: heating_enabled + lambda: |- + double sun_elevation = id(sun_sensor).elevation(); + float battery_level = id(battery_soc).state; + bool sun_high_enough = sun_elevation >= id(sun_elevation_minimum); + bool battery_ok = battery_level >= 95 && id(battery_system_current).state > -20; // if battery level >= 95% we can live with 20A (~1.1kW) being used from battery (cloudy conditions) + bool enabled = sun_high_enough && battery_ok; + //ESP_LOGI("solar", "Heating enabled? : %s", enabled ? "Yes" : "No"); + return enabled; + +ads131m08: + id: highres_adc + cs_pin: GPIO5 + drdy_pin: GPIO10 + vref: 1.25 + sensor: + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 0 + name: "ADS Channel 0 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 1 + name: "ADS Channel 1 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 2 + name: "ADS Channel 2 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 3 + name: "ADS Channel 3 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 4 + name: "ADS Channel 4 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 5 + name: "ADS Channel 5 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 6 + name: "ADS Channel 6 Voltage" + - platform: ads131m08 + ads_hub_id: highres_adc + channel: 7 + name: "ADS Channel 7 Voltage" + - platform: debug free: name: "Heap Free" @@ -1406,7 +1536,8 @@ sensor: loop_time: name: "Loop Time" cpu_frequency: - name: "CPU Frequency" + name: "CPU Frequency" + # NB! Keep all ads1115 sample rates the same. Update intervals should be more than or equal to 1/sample_rate # ads1115_48 - platform: ads1115 @@ -1470,7 +1601,7 @@ sensor: if(abs(x) < 0.1) return 0.0; return x; - # mod end ####################### +# # mod end ######################## # ads1115_49 - platform: ads1115 @@ -1498,7 +1629,7 @@ sensor: send_first_at: 104 #208 #416 - lambda: return sqrt(x); - multiply: 169.4 #91.1 #88.44 - - offset: -0.2 + - offset: 0.0 #-0.2 - lambda: |- if(abs(x) < 0.1) return 0.0; @@ -1507,13 +1638,12 @@ sensor: - below: 5.0 then: - lambda: |- - ESP_LOGI("info", "No geyser current detected. Geyser not heating."); + ESP_LOGI("geyser", "No geyser current detected. Geyser not heating."); - above: 0.5 then: - lambda: |- - ESP_LOGI("info", "Geyser current detected. Geyser was energised."); - # mod end ####################### - + ESP_LOGI("geyser", "Geyser current detected. Geyser was energised."); + # mod end ####################### - platform: ads1115 multiplexer: A2_A3 gain: 2.048 # 4.096 @@ -1539,119 +1669,178 @@ sensor: send_first_at: 104 #208 #416 - lambda: return sqrt(x); - multiply: 92.1 #88.44 - - offset: -0.2 + - offset: 0.0 #-0.2 - lambda: |- if(abs(x) < 0.1) return 0.0; return x; # mod end ####################### - - # ads1115_4A - # Mains voltage sensor - - platform: ads1115 - ads1115_id: ads1115_4A - sample_rate: 128 #860 - name: "Mains Voltage ADC" - id: mains_voltage_adc - unit_of_measurement: "V" - accuracy_decimals: 8 - icon: "mdi:flash" - multiplexer: A0_A1 - gain: 2.048 # 4.096 - update_interval: 8ms #5ms #23ms - device_class: voltage - state_class: measurement - filters: - - offset: 0.0065 - - lambda: return x * x; - - sliding_window_moving_average: - window_size: 625 #1250 - send_every: 104 - send_first_at: 104 #416 - - lambda: return sqrt(x); - - multiply: 930 #650 - - lambda: |- - if(abs(x) < 20) - return 0; - return x; - +# # ads1115_4A # Inverter voltage sensor - platform: ads1115 ads1115_id: ads1115_4A - sample_rate: 128 #860 - name: "Inverter Output Voltage ADC" - id: inverter_output_voltage_adc + sample_rate: 475 #860 + name: "Mains Voltage ADC" + id: mains_voltage_adc unit_of_measurement: "V" accuracy_decimals: 8 icon: "mdi:flash" - multiplexer: A2_A3 + multiplexer: A0_GND gain: 2.048 # 4.096 update_interval: 8ms #5ms #23ms device_class: voltage state_class: measurement filters: - - offset: 0.0131 + - offset: -2.048 #-2.04794027 # 0.0131 - lambda: return x * x; - sliding_window_moving_average: window_size: 625 #1250 send_every: 104 send_first_at: 104 - lambda: return sqrt(x); - - multiply: 930 #650 - - lambda: |- - if(abs(x) < 10) - return 0; - return x; + - multiply: 766.6670 # 930 #650 + #- lambda: |- + # if(abs(x) < 10) + # return 0; + # return x;# + # # ads1115_4A -## # Inverter voltage sensor -## - platform: ads1115 -## ads1115_id: ads1115_4A -## sample_rate: 860 -## name: "ADS1115 4A A2" -## id: adc4A_A2 -## unit_of_measurement: "V" -## accuracy_decimals: 8 -## icon: "mdi:flash" -## multiplexer: A2_GND -## gain: 4.096 -## update_interval: 23ms -## device_class: voltage -## state_class: measurement -## filters: -## - offset: -1.6249 # -1.266 -## - lambda: return x * x; -## - sliding_window_moving_average: -## window_size: 1250 -## send_every: 104 -## send_first_at: 104 -## - lambda: return sqrt(x); -## - multiply: 10000 -## -## # ads1115_4A -## # Inverter voltage sensor -## - platform: ads1115 -## ads1115_id: ads1115_4A -## sample_rate: 860 -## name: "ADS1115 4A A3" -## id: adc4A_A3 -## unit_of_measurement: "V" -## accuracy_decimals: 8 -## icon: "mdi:flash" -## multiplexer: A3_GND -## gain: 4.096 -## update_interval: 23ms -## device_class: voltage -## state_class: measurement -## filters: -## - offset: -1.6249 # -1.266 -## - lambda: return x * x; -## - sliding_window_moving_average: -## window_size: 1250 -## send_every: 104 -## send_first_at: 104 -## - lambda: return sqrt(x); -## - multiply: 10000 +# # Mains voltage sensor +# - platform: ads1115 +# ads1115_id: ads1115_4A +# sample_rate: 128 #860 +# name: "Spare1 ADC" +# id: spare1_voltage_adc +# unit_of_measurement: "V" +# accuracy_decimals: 8 +# icon: "mdi:flash" +# multiplexer: A1_GND +# gain: 2.048 # 4.096 +# update_interval: 8ms #5ms #23ms +# device_class: voltage +# state_class: measurement +# filters: +# - offset: 0.0 #4.096 #0.0065 +# - lambda: return x * x; +# - sliding_window_moving_average: +# window_size: 312 #625 #1250 +# send_every: 52 #104 +# send_first_at: 52 #104 #416 +# - lambda: return sqrt(x); +# - multiply: 100 # 930 #650 +# #- lambda: |- +# # if(abs(x) < 20) +# # return 0; +# # return x; + + # ads1115_4A + # Mains voltage sensor + - platform: ads1115 + ads1115_id: ads1115_4A + sample_rate: 475 #860 + name: "Inverter Output Voltage ADC" + id: inverter_output_voltage_adc + unit_of_measurement: "V" + accuracy_decimals: 8 + icon: "mdi:flash" + multiplexer: A2_GND + gain: 2.048 # 4.096 + update_interval: 8ms #5ms #23ms + device_class: voltage + state_class: measurement + filters: + - offset: -2.048 #-2.0491 #4.096 #0.0065 + - lambda: return x * x; + - sliding_window_moving_average: + window_size: 312 #625 #1250 + send_every: 52 #104 + send_first_at: 52 #104 #416 + - lambda: return sqrt(x); + - multiply: 100 # 930 #650 + #- lambda: |- + # if(abs(x) < 20) + # return 0; + # return x; + +# # ads1115_4A +# # Mains voltage sensor +# - platform: ads1115 +# ads1115_id: ads1115_4A +# sample_rate: 128 #860 +# name: "Spare2 Voltage ADC" +# id: spare2_voltage_adc +# unit_of_measurement: "V" +# accuracy_decimals: 8 +# icon: "mdi:flash" +# multiplexer: A3_GND +# gain: 2.048 # 4.096 +# update_interval: 8ms #5ms #23ms +# device_class: voltage +# state_class: measurement +# filters: +# - offset: 0.0 #4.096 #0.0065 +# - lambda: return x * x; +# - sliding_window_moving_average: +# window_size: 312 #625 #1250 +# send_every: 52 #104 +# send_first_at: 52 #104 #416 +# - lambda: return sqrt(x); +# - multiply: 100 # 930 #650 +# #- lambda: |- +# # if(abs(x) < 20) +# # return 0; +# # return x; + + # ads1115_4A +# # Inverter voltage sensor +# - platform: ads1115 +# ads1115_id: ads1115_4A +# sample_rate: 860 +# name: "ADS1115 4A A2" +# id: adc4A_A2 +# unit_of_measurement: "V" +# accuracy_decimals: 8 +# icon: "mdi:flash" +# multiplexer: A2_GND +# gain: 4.096 +# update_interval: 23ms +# device_class: voltage +# state_class: measurement +# filters: +# - offset: -1.6249 # -1.266 +# - lambda: return x * x; +# - sliding_window_moving_average: +# window_size: 1250 +# send_every: 104 +# send_first_at: 104 +# - lambda: return sqrt(x); +# - multiply: 10000 +# +# # ads1115_4A +# # Inverter voltage sensor +# - platform: ads1115 +# ads1115_id: ads1115_4A +# sample_rate: 860 +# name: "ADS1115 4A A3" +# id: adc4A_A3 +# unit_of_measurement: "V" +# accuracy_decimals: 8 +# icon: "mdi:flash" +# multiplexer: A3_GND +# gain: 4.096 +# update_interval: 23ms +# device_class: voltage +# state_class: measurement +# filters: +# - offset: -1.6249 # -1.266 +# - lambda: return x * x; +# - sliding_window_moving_average: +# window_size: 1250 +# send_every: 104 +# send_first_at: 104 +# - lambda: return sqrt(x); +# - multiply: 10000 # ## # 30A clamp ## - platform: ct_clamp @@ -1674,11 +1863,11 @@ sensor: ## - below: 0.5 ## then: ## - lambda: |- -## ESP_LOGI("info", "Geyser lost power."); +## ESP_LOGI("geyser", "Geyser lost power."); ## - above: 0.5 ## then: ## - lambda: |- -## ESP_LOGI("info", "Geyser was energised."); +## ESP_LOGI("geyser", "Geyser was energised."); # ## # 30A clamp ## - platform: ct_clamp @@ -1734,65 +1923,65 @@ sensor: ## return 0.0; ## return x; # -## - platform: template -## id: calibrate_lights -## name: "AAA Lights A" -## lambda: |- -## return id(lights_current).state; -## state_class: measurement -## device_class: current -## accuracy_decimals: 8 -## update_interval: 1s -## -## - platform: template -## id: calibrate_mains -## name: "AAA Mains A" -## lambda: |- -## return id(mains_current).state; -## state_class: measurement -## device_class: current -## accuracy_decimals: 8 -## update_interval: 1s -## -## - platform: template -## id: calibrate_mains_v -## name: "AAA Mains V" -## lambda: |- -## return id(mains_voltage_adc).state; -## state_class: measurement -## device_class: voltage -## accuracy_decimals: 8 -## update_interval: 1s -## -## - platform: template -## id: calibrate_plugs -## name: "AAA Plugs A" -## lambda: |- -## return id(power_outlets_current).state; -## state_class: measurement -## device_class: current -## accuracy_decimals: 8 -## update_interval: 1s -## -## - platform: template -## id: calibrate_plugs_V -## name: "AAA Plugs V" -## lambda: |- -## return id(inverter_output_voltage_adc).state; -## state_class: measurement -## device_class: voltage -## accuracy_decimals: 8 -## update_interval: 1s -## -## - platform: template -## id: calibrate_geyser -## name: "AAA Geyser A" -## lambda: |- -## return id(geyser_current).state; -## state_class: measurement -## device_class: current -## accuracy_decimals: 8 -## update_interval: 1s +# - platform: template +# id: calibrate_lights +# name: "AAA Lights A" +# lambda: |- +# return id(lights_current).state; +# state_class: measurement +# device_class: current +# accuracy_decimals: 8 +# update_interval: 1s +# +# - platform: template +# id: calibrate_mains +# name: "AAA Mains A" +# lambda: |- +# return id(mains_current).state; +# state_class: measurement +# device_class: current +# accuracy_decimals: 8 +# update_interval: 1s +# +# - platform: template +# id: calibrate_mains_v +# name: "AAA Mains V" +# lambda: |- +# return id(mains_voltage_adc).state; +# state_class: measurement +# device_class: voltage +# accuracy_decimals: 8 +# update_interval: 1s +# +# - platform: template +# id: calibrate_plugs +# name: "AAA Plugs A" +# lambda: |- +# return id(power_outlets_current).state; +# state_class: measurement +# device_class: current +# accuracy_decimals: 8 +# update_interval: 1s +# +# - platform: template +# id: calibrate_plugs_V +# name: "AAA Plugs V" +# lambda: |- +# return id(inverter_output_voltage_adc).state; +# state_class: measurement +# device_class: voltage +# accuracy_decimals: 8 +# update_interval: 1s +# +# - platform: template +# id: calibrate_geyser +# name: "AAA Geyser A" +# lambda: |- +# return id(geyser_current).state; +# state_class: measurement +# device_class: current +# accuracy_decimals: 8 +# update_interval: 1s # # for now we use a template until we get a voltage sensor - platform: template @@ -1854,7 +2043,7 @@ sensor: update_interval: 2s device_class: current state_class: measurement - +# - platform: template id: inverter1_2_output_power name: "Inverter 1 & 2 Output Power" @@ -1960,10 +2149,20 @@ sensor: update_interval: 2s device_class: power state_class: measurement +# +# - platform: homeassistant +# entity_id: input_number.geyser_target_temp +# id: geyser_target_temp - platform: homeassistant - entity_id: input_number.geyser_target_temp - id: geyser_target_temp + entity_id: input_number.bottom_temperature + id: geyser_bottom_temp + + - platform: homeassistant + entity_id: input_number.top_temperature + id: geyser_top_temp + +###################################################################### - platform: total_daily_energy name: 'Daily Geyser Energy' @@ -1974,6 +2173,81 @@ sensor: device_class: energy accuracy_decimals: 3 + - platform: total_daily_energy + name: 'Daily Plugs Energy' + id: daily_plugs_energy + power_id: power_outlets_power + unit_of_measurement: 'kWh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + + - platform: total_daily_energy + name: 'Daily Mains Energy' + id: daily_mains_energy + power_id: mains_power + unit_of_measurement: 'kWh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + + - platform: total_daily_energy + name: 'Daily Lights Energy' + id: daily_lights_energy + power_id: lights_power + unit_of_measurement: 'kWh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + + - platform: total_daily_energy + name: 'Daily Generated Energy' + id: daily_generated_energy + power_id: generated_power + unit_of_measurement: 'kWh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + + - platform: total_daily_energy + name: "Daily House Energy Usage" + id: daily_house_energy_usage + power_id: total_inverter_output + unit_of_measurement: 'kWh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + + - platform: total_daily_energy + name: "Daily Energy Loss" + id: daily_energy_loss + power_id: power_loss + unit_of_measurement: 'kWh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + +# NB! this is in Watt-hours + - platform: total_daily_energy + name: 'Daily Battery Energy In' + id: daily_battery_energy_in + power_id: battery_power_in + unit_of_measurement: 'Wh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + + - platform: total_daily_energy + name: 'Daily Battery Energy Out' + id: daily_battery_energy_out + power_id: battery_power_out + unit_of_measurement: 'Wh' + state_class: total_increasing + device_class: energy + accuracy_decimals: 3 + +###################################################################### + # monthly integration sensor - platform: integration name: 'Monthly Geyser Energy' @@ -1986,39 +2260,6 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly Geyser Energy' - id: yearly_geyser_energy - sensor: geyser_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - -# lifetime integration sensor - - platform: integration - name: 'Geyser Energy' - id: geyser_energy - sensor: geyser_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - - - platform: total_daily_energy - name: 'Daily Plugs Energy' - id: daily_plugs_energy - power_id: power_outlets_power - unit_of_measurement: 'kWh' - state_class: total_increasing - device_class: energy - accuracy_decimals: 3 - # monthly integration sensor - platform: integration name: 'Monthly Plugs Energy' @@ -2031,39 +2272,6 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly Plugs Energy' - id: yearly_plugs_energy - sensor: power_outlets_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - -# lifetime integration sensor - - platform: integration - name: 'Plugs Energy' - id: plugs_energy - sensor: power_outlets_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - - - platform: total_daily_energy - name: 'Daily Mains Energy' - id: daily_mains_energy - power_id: mains_power - unit_of_measurement: 'kWh' - state_class: total_increasing - device_class: energy - accuracy_decimals: 3 - # monthly integration sensor - platform: integration name: 'Monthly Mains Energy' @@ -2076,39 +2284,6 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly Mains Energy' - id: yearly_mains_energy - sensor: mains_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - -# lifetime integration sensor - - platform: integration - name: 'Mains Energy' - id: mains_energy - sensor: mains_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - - - platform: total_daily_energy - name: 'Daily Lights Energy' - id: daily_lights_energy - power_id: lights_power - unit_of_measurement: 'kWh' - state_class: total_increasing - device_class: energy - accuracy_decimals: 3 - # monthly integration sensor - platform: integration name: 'Monthly Lights Energy' @@ -2121,39 +2296,6 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly Lights Energy' - id: yearly_lights_energy - sensor: lights_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - -# lifetime integration sensor - - platform: integration - name: 'Lights Energy' - id: lights_energy - sensor: lights_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - - - platform: total_daily_energy - name: 'Daily Generated Energy' - id: daily_generated_energy - power_id: generated_power - unit_of_measurement: 'kWh' - state_class: total_increasing - device_class: energy - accuracy_decimals: 3 - # monthly integration sensor - platform: integration name: 'Monthly Generated Energy' @@ -2166,39 +2308,6 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly Generated Energy' - id: yearly_generated_energy - sensor: generated_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - -# lifetime integration sensor - - platform: integration - name: 'Generated Energy' - id: generated_energy - sensor: generated_power - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - - - platform: total_daily_energy - name: "Daily House Energy Usage" - id: daily_house_energy_usage - power_id: total_inverter_output - unit_of_measurement: 'kWh' - state_class: total_increasing - device_class: energy - accuracy_decimals: 3 - # monthly integration sensor - platform: integration name: 'Monthly House Energy Usage' @@ -2211,39 +2320,6 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly House Energy Usage' - id: yearly_house_energy_usage - sensor: total_inverter_output - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - -# lifetime integration sensor - - platform: integration - name: 'House Energy Usage' - id: house_energy_usage - sensor: total_inverter_output - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 - - - platform: total_daily_energy - name: "Daily Energy Loss" - id: daily_energy_loss - power_id: power_loss - unit_of_measurement: 'kWh' - state_class: total_increasing - device_class: energy - accuracy_decimals: 3 - # monthly integration sensor - platform: integration name: 'Monthly Energy Loss' @@ -2256,28 +2332,7 @@ sensor: unit_of_measurement: 'kWh' accuracy_decimals: 3 -# yearly integration sensor - - platform: integration - name: 'Yearly Energy Loss' - id: yearly_energy_loss - sensor: power_loss - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 # -# lifetime integration sensor - - platform: integration - name: 'Energy Loss' - id: energy_loss - sensor: power_loss - time_unit: h - restore: true - state_class: total_increasing - device_class: energy - unit_of_measurement: 'kWh' - accuracy_decimals: 3 +###################################################################### - platform: template id: heating_loss @@ -2486,11 +2541,46 @@ sensor: lambda: |- float rate = 0.0; float current = x; + // set charging rate float current_limit = id(battery_charge_current_limit).state; if(current > 0 && current_limit > 0) { rate = 100 * current / current_limit; } id(battery_charging_rate).publish_state(rate); + + - platform: template + name: "Battery Power" + id: battery_power + unit_of_measurement: "W" + device_class: power + lambda: |- + double current = id(battery_system_current).state; + double power = current * id(battery_system_voltage).state; + // set battery power indicaters + if(current < 0) { + id(battery_power_out).publish_state(-power); + id(battery_power_in).publish_state(0); + } + else { + id(battery_power_in).publish_state(power); + id(battery_power_out).publish_state(0); + } + return power; + + - platform: template + id: battery_power_in + name: "Battery Power In" + accuracy_decimals: 2 + unit_of_measurement: "W" + device_class: power + state_class: measurement + - platform: template + id: battery_power_out + name: "Battery Power Out" + accuracy_decimals: 2 + unit_of_measurement: "W" + device_class: power + state_class: measurement - platform: template id: battery_average_cell_temperature name: "Battery Cell Temperature" @@ -2536,7 +2626,7 @@ sensor: accuracy_decimals: 1 unit_of_measurement: "%" state_class: measurement - device_class: current + device_class: battery #lambda: |- # if(id(battery_system_current).state < 0) { # return 0.0; @@ -2683,7 +2773,7 @@ sensor: value_type: U_WORD register_count: 6 unit_of_measurement: "%" - device_class: power + device_class: battery accuracy_decimals: 0 - platform: modbus_controller @@ -2851,7 +2941,7 @@ sensor: value_type: U_WORD register_count: 6 unit_of_measurement: "%" - device_class: power + device_class: battery accuracy_decimals: 0 - platform: modbus_controller @@ -2888,8 +2978,8 @@ sensor: return id(inv1_pv_input_power).state + id(inv2_pv_input_power).state; - platform: template - name: "Battery Power" - id: battery_power + name: "Battery Power Inv" + id: battery_power_inv unit_of_measurement: "W" device_class: power lambda: |- @@ -3277,7 +3367,7 @@ script: // auto t_obj = ESPTime::from_epoch_local(day_seconds); // t_obj.recalc_timestamp_local(); // auto date = t_obj.strftime("%Y-%m-%d"); - // ESP_LOGI("info", "date: %s", date.c_str()); + // ESP_LOGI("geyser", "date: %s", date.c_str()); // // debug end int blk = 0; do { @@ -3302,9 +3392,9 @@ script: id(active_schedule_temperature) = static_cast(day_schedule[active_blk][0]) / ${HEATING_TEMP_SCALE}; id(active_schedule_period)[0] = day_schedule[active_blk][1] + today_seconds; id(active_schedule_period)[1] = day_schedule[active_blk][2] + today_seconds; - //ESP_LOGI("info", "3. day:%d, block:%d, schedule: %d / %d / %d (%d)", active_idx, active_blk, day_schedule[active_blk][0], day_schedule[active_blk][1], day_schedule[active_blk][2], today_seconds); + //ESP_LOGI("geyser", "3. day:%d, block:%d, schedule: %d / %d / %d (%d)", active_idx, active_blk, day_schedule[active_blk][0], day_schedule[active_blk][1], day_schedule[active_blk][2], today_seconds); //for(int i = 0; i < 12; i++) { - // ESP_LOGI("info", "holiday: {%d, %d}", id(holidays)[i][0], id(holidays)[i][1]); + // ESP_LOGI("geyser", "holiday: {%d, %d}", id(holidays)[i][0], id(holidays)[i][1]); //} - id: get_geyser_mode @@ -3341,100 +3431,101 @@ script: } - id: set_heat_indicators + parameters: + temp_bottom: double + temp_top: double then: - lambda: |- - double temp_top = id(geyser_top_temperature).state; - double temp_bottom = id(geyser_bottom_temperature).state; - float led_blue = 0; - float led_green = 0; - float led_yellow = 0; - float led_orange = 0; - float led_red = 0; - float led_on = 0.75; - float brightness = 0; - float temp1 = 30; - float temp2 = 40; - float temp3 = 50; - float temp4 = 60; - if(temp_bottom < temp1) { - brightness = 0.1*(temp1 - temp_bottom); - led_blue = brightness > 1 ? 1 : brightness; // blue - if(temp_top >= temp4) { - brightness = 0.1*(temp_top - temp4); - led_red = brightness > 1 ? 1 : brightness; // red - led_orange = 1; - led_yellow = 1; - led_green = 1; - } else if(temp_top >= temp3) { - led_orange = 0.1*(temp_top - temp3); // orange - led_yellow = 1; - led_green = 1; - } else if(temp_top >= temp2) { - led_yellow = 0.1*(temp_top - temp2); // yellow - led_green = 1; - } else if(temp_top >= temp1) { - led_green = 0.1*(temp_top - temp1); // green - } + double led_on = 1.0; + int led_count = 8; + double min_temp = 30.0; + double max_temp = 70.0; + double step_size = (max_temp - min_temp) / led_count; + double bottom_point = (temp_bottom - min_temp) / step_size; + int led_bot = static_cast(std::trunc(bottom_point)); + double led_bot_int = 1.0 + led_bot - bottom_point; + double top_point = (temp_top - min_temp) / step_size; + int led_top = static_cast(std::trunc(top_point)); + double led_top_int = top_point - led_top; + double led_0 = 0; + double led_1 = 0; + double led_2 = 0; + double led_3 = 0; + double led_4 = 0; + double led_5 = 0; + double led_6 = 0; + double led_7 = 0; + if (led_top == 0) led_0 = led_top_int * led_on; + if (led_top == 1) led_1 = led_top_int * led_on; + if (led_top == 2) led_2 = led_top_int * led_on; + if (led_top == 3) led_3 = led_top_int * led_on; + if (led_top == 4) led_4 = led_top_int * led_on; + if (led_top == 5) led_5 = led_top_int * led_on; + if (led_top == 6) led_6 = led_top_int * led_on; + if (led_top == 7) led_7 = led_top_int * led_on; + if (led_bot == 0) { + led_0 = led_bot_int * led_on; + if (led_top > 1) led_1 = led_on; + if (led_top > 2) led_2 = led_on; + if (led_top > 3) led_3 = led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; } - else if(temp_bottom >= temp1 && temp_bottom < temp2) { - led_green = 0.1*(temp2 - temp_bottom); // green - if(temp_top >= temp4) { - brightness = 0.1*(temp_top - temp4); - led_red = brightness > 1 ? 1 : brightness; // red - led_orange = 1; - led_yellow = 1; - } else if(temp_top >= temp3) { - led_orange = 0.1*(temp_top - temp3); // orange - led_yellow = 1; - } else if(temp_top >= temp2) { - led_yellow = 0.1*(temp_top - temp2); // yellow - } + if (led_bot == 1) { + led_1 = led_bot_int * led_on; + if (led_top > 2) led_2 = led_on; + if (led_top > 3) led_3 = led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; } - // new - else if(temp_bottom >= temp2 && temp_bottom < temp3) { - led_yellow = 0.1*(temp3 - temp_bottom); // yellow - if(temp_top >= temp4) { - brightness = 0.1*(temp_top - temp4); - led_red = brightness > 1 ? 1 : brightness; // red - led_orange = 1; - } else if(temp_top >= temp3) { - brightness = 0.1*(temp_top - temp3); - led_orange = brightness > 1 ? 1 : brightness; // orange - } else if(temp_top >= temp2) { - led_yellow = 0.1*(temp_top - temp2); // yellow - } + if (led_bot == 2) { + led_2 = led_bot_int * led_on; + if (led_top > 3) led_3 = led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; } - // end of new - else if(temp_bottom >= temp3 && temp_bottom < temp4) { - led_orange = 0.1*(temp3 - temp_bottom); // orange - if(temp_top >= temp4) { - brightness = 0.1*(temp_top - temp4); - led_red = brightness > 1 ? 1 : brightness; // red - } else if(temp_top >= temp3) { - led_orange = 0.1*(temp_top - temp3); // orange - } - } - else if(temp_bottom > temp4) { - double max_temp = (temp_top > temp_bottom) ? temp_top : temp_bottom; // in case there is something wrong with top temp sensor - brightness = 0.1*(max_temp - temp4); // red - led_red = brightness > 1 ? 1 : brightness; // red + if (led_bot == 3) { + led_3 = led_bot_int * led_on; + if (led_top > 4) led_4 = led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; } - if(temp_top >= temp4) { - double max_temp = (temp_top > temp_bottom) ? temp_top : temp_bottom; // in case there is something wrong with top temp sensor - brightness = 0.1*(max_temp - temp4); // red - led_red = brightness > 1 ? 1 : brightness; // red + if (led_bot == 4) { + led_4 = led_bot_int * led_on; + if (led_top > 5) led_5 = led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; } - led_blue *= led_on; - led_green *= led_on; - led_yellow *= led_on; - led_orange *= led_on; - led_red *= led_on; - id(led_geyser_temp_blue).set_level(led_blue); - id(led_geyser_temp_green).set_level(led_green); - id(led_geyser_temp_yellow).set_level(led_yellow); - id(led_geyser_temp_orange).set_level(led_orange); - id(led_geyser_temp_red).set_level(led_red); - //ESP_LOGI("info", "top: %f, bot: %f, Brightness: led_blue %f, led_green: %f, led_yellow, %f, led_orange, %f, led_red %f", temp_top, temp_bottom, led_blue, led_green, led_yellow, led_orange, led_red); + if (led_bot == 5) { + led_5 = led_bot_int * led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 6) { + led_6 = led_bot_int * led_on; + if (led_top > 6) led_6 = led_on; + if (led_top > 7) led_7 = led_on; + } + if (led_bot == 7) { + led_7 = led_bot_int * led_on; + if (led_top > 7) led_7 = 1; + } + id(led_geyser_temp0).turn_on().set_brightness(led_0).perform(); + id(led_geyser_temp1).turn_on().set_brightness(led_1).perform(); + id(led_geyser_temp2).turn_on().set_brightness(led_2).perform(); + id(led_geyser_temp3).turn_on().set_brightness(led_3).perform(); + id(led_geyser_temp4).turn_on().set_brightness(led_4).perform(); + id(led_geyser_temp5).turn_on().set_brightness(led_5).perform(); + id(led_geyser_temp6).turn_on().set_brightness(led_6).perform(); + id(led_geyser_temp7).turn_on().set_brightness(led_7).perform(); + //ESP_LOGI("geyser","bot: %f, top: %f 0: %0.2f 1: %0.2f 2: %0.2f 3: %0.2f 4: %0.2f 5: %0.2f 6: %0.2f 7: %0.2f ", temp_bottom, temp_top, led_0, led_1, led_2, led_3, led_4, led_5, led_6, led_7); # do at 1 second intervals - check if geyser is on, and switch off if required - id: reset_geyser_relay @@ -3450,20 +3541,27 @@ script: - light.turn_off: id: light_inverter_battery_low - lambda: |- + const char tag[] = "geyser"; + if(id(gb_geyser_disable)) { + //ESP_LOGI(tag, "Geyser disabled. Switching it off."); + id(geyser_relay).turn_off(); + return; + } bool battery_low = id(inverter_battery_charge_state).state || id(battery_soc).state < 60; double sun_elevation = id(sun_sensor).elevation(); bool sun_high_enough = sun_elevation >= id(sun_elevation_minimum); + double top_temp = id(geyser_top_temperature).state; auto currenttime = id(time_source).now(); if(currenttime.is_valid()) { time_t now = currenttime.timestamp; bool relay_on = id(geyser_relay).state; - ESP_LOGD("geyser", "Geyser heating is turned %s.", relay_on ? "on" : "off"); + //ESP_LOGD(tag, "Geyser heating is %s.", relay_on ? "on" : "off"); if(relay_on) { // GEYSER IS ENERGISED // =================== - // if we have a surplus of solar power, geyser will remain on regardless - if(id(solar_surplus).state) { - ESP_LOGD("info", "----------- Geyser remained on at %f °C due to surplus solar power. Battery charge: %0.1f%%", id(geyser_top_temperature).state, id(battery_soc).state); + // if we have solar power and battery is almost full, geyser will remain on regardless + if(id(heating_enabled).state) { + ESP_LOGD(tag, "Geyser remained on at %f °C due battery charge and solar power. Battery charge: %0.1f%% Battery current: %0.1f%A" , top_temp, id(battery_soc).state, id(battery_system_current).state); id(geyser_relay_status) = true; } else { @@ -3471,36 +3569,37 @@ script: // past the scheduled heating end id(geyser_relay_status) = false; id(geyser_relay).turn_off(); - ESP_LOGI("info", "----------- Past the scheduled heating end at %f °C. Heating start: %s, end: %s, time: %d", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); + ESP_LOGI(tag, "Past the scheduled heating end at %f °C. Heating start: %s, end: %s, time: %d", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); } // we will do nothing if water has heated a bit faster than calculated, unless the water is more than 'temp_overshoot_allowed' (0.25) degrees hotter than target temperature if(id(estimated_heating_overshoot_time) <= 0) { // we turn geyser off to save energy id(geyser_relay_status) = false; - ESP_LOGI("info", "----------- Heating done"); + ESP_LOGI(tag, "Water temperature (%f) is at or above target of %f °C. of Heating done", top_temp, id(active_schedule_temperature)); } if(id(inverter1_2_overload).state) { id(geyser_relay_status) = false; - ESP_LOGI("info", "----------- Overload condition. Temperature: %f °C. Heating start: %s, end: %s, time: %d", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); + ESP_LOGI(tag, "Overload condition. Temperature: %f °C. Heating start: %s, end: %s, time: %d", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); } if(battery_low && !id(mains_supply).state) { // inverter battery is low id(geyser_relay_status) = false; - ESP_LOGI("info", "----------- Low inverter battery voltage. Temperature: %f °C. Heating start: %s, end: %s, time: %d", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); + ESP_LOGI(tag, "Low inverter battery voltage. Temperature: %f °C. Heating start: %s, end: %s, time: %d", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); } if(!id(mains_supply).state && !sun_high_enough) { // sun is not high enough above horizon and mains supply is off id(geyser_relay_status) = false; - ESP_LOGI("info", "----------- No mains and inadequate solar power. Temperature: %f °C. Heating start: %s, end: %s, time: %d, Sun: %f ° elevation", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), sun_elevation); + ESP_LOGI(tag, "No mains and inadequate solar power. Temperature: %f °C. Heating start: %s, end: %s, time: %d, Sun: %f ° elevation", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), sun_elevation); } + // NB! we only call economy procedure if flag is set to leave geyser on if(id(geyser_relay_status)) { - // switch off geyser if economy mode requires it to be switched off + // set flag to switch off geyser if economy mode requires it to be switched off id(economy_mode_set_geyser_relay).execute(relay_on, sun_high_enough); } } if(!id(geyser_relay_status)) { id(geyser_relay).turn_off(); - ESP_LOGI("geyser", "Geyser was turned off at %f °C.", id(geyser_top_temperature).state); + ESP_LOGI(tag, "Geyser was turned off at %f °C.", top_temp); } } } @@ -3519,21 +3618,28 @@ script: - light.turn_off: id: light_inverter_battery_low - lambda: |- + const char tag[] = "geyser"; + if(id(gb_geyser_disable)) { + ESP_LOGI(tag, "Geyser disabled. Switching it off."); + id(geyser_relay).turn_off(); + return; + } bool battery_low = id(inverter_battery_charge_state).state || id(battery_soc).state < 60; double sun_elevation = id(sun_sensor).elevation(); bool sun_high_enough = sun_elevation >= id(sun_elevation_minimum); + double top_temp = id(geyser_top_temperature).state; auto currenttime = id(time_source).now(); if(currenttime.is_valid()) { time_t now = currenttime.timestamp; bool relay_on = id(geyser_relay).state; - ESP_LOGD("geyser", "Geyser heating is turned %s.", (relay_on) ? "on" : "off"); + //ESP_LOGD(tag, "Geyser heating is turned %s.", (relay_on) ? "on" : "off"); if(!relay_on) { id(geyser_relay_status) = false; // GEYSER IS NOT ENERGISED // ======================= // if we have a surplus of solar power, we will turn geyser on regardless if(id(solar_surplus).state) { - ESP_LOGI("geyser", "Geyser was turned on at %f °C due to surplus solar power. Battery charge: %0.1f%%", id(geyser_top_temperature).state, id(battery_soc).state); + ESP_LOGI(tag, "Geyser was turned on at %f °C due to surplus solar power. Battery charge: %0.1f%%", top_temp, id(battery_soc).state); id(geyser_relay_status) = true; id(geyser_relay).turn_on(); } @@ -3547,26 +3653,27 @@ script: // we are at or past the scheduled start time for heating // we will do a few checks to see if it is ok to turn the geyser on if(id(inverter1_2_overload).state) { - ESP_LOGI("geyser", "Geyser not turned on due to overload condition. Temperature: %f °C. Heating start: %s, end: %s, time: %d", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); + ESP_LOGI(tag, "Geyser not turned on due to overload condition. Temperature: %f °C. Heating start: %s, end: %s, time: %d", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); } else if(battery_low && !id(mains_supply).state) { // inverter battery is low - ESP_LOGI("geyser", "Geyser not turned on due to low inverter battery voltage. Temperature: %f °C. Heating start: %s, end: %s, time: %d", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); + ESP_LOGI(tag, "Geyser not turned on due to low inverter battery voltage. Temperature: %f °C. Heating start: %s, end: %s, time: %d", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); } else if((!id(mains_supply).state) && !sun_high_enough) { // sun is not high enough above horizon and mains supply is off - ESP_LOGI("geyser", "Geyser not turned on due to no mains and inadequate solar power. Temperature: %f °C. Heating start: %s, end: %s, time: %d, Sun: %f° elevation", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), sun_elevation); + ESP_LOGI(tag, "Geyser not turned on due to no mains and inadequate solar power. Temperature: %f °C. Heating start: %s, end: %s, time: %d, Sun: %f° elevation", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), sun_elevation); } else { id(geyser_relay_status) = true; } - if(id(geyser_relay_status)) { + // NB! we only call economy procedure if flag is set to switch geyser on + if(id(geyser_relay_status)) { // leave geyser switched off if economy mode requires it to be switched off id(economy_mode_set_geyser_relay).execute(relay_on, sun_high_enough); } if(id(geyser_relay_status)) { id(geyser_relay).turn_on(); - ESP_LOGV("geyser", "Geyser is turned on at %f °C. Heating start: %s, end: %s, time: %d", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); + ESP_LOGI(tag, "Geyser is turned on at %f °C. Heating start: %s, end: %s, time: %d", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time)); } } } @@ -3582,18 +3689,20 @@ script: - lambda: |- if(id(economy_mode).state) { // only set/reset geyser_relay_status here if economy mode is active + double top_temp = id(geyser_top_temperature).state; + const char tag[] = "geyser"; if(relay_on) { - double solar_power = id(generated_power).state; + double available_solar_power = id(local_generated_power).state; double geyser_power = id(geyser_element_power).state; // GEYSER IS ENERGISED // =================== if(!sun_high_enough) { id(geyser_relay_status) = false; - ESP_LOGV("geyser", "economy mode: sun not high enough, geyser to be turned off at %f °C. Heating start: %s, end: %s, time: %d, solar: %f kW", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), solar_power); + ESP_LOGI(tag, "Economy mode: sun not high enough, geyser to be turned off at %f °C. Heating start: %s, end: %s, time: %d, solar: %f kW", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), available_solar_power); } - else if(solar_power < geyser_power) { + else if(available_solar_power < geyser_power) { id(geyser_relay_status) = false; - ESP_LOGV("geyser", "economy mode: not enough solar energy, geyser turned to be off at %f °C. Heating start: %s, end: %s, time: %d, solar: %f kW", id(geyser_top_temperature).state, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), solar_power); + ESP_LOGI(tag, "Economy mode: not enough solar energy, geyser turned to be off at %f °C. Heating start: %s, end: %s, time: %d, solar: %f kW", top_temp, ESPTime::from_epoch_local(id(active_heating_start)).strftime("%Y-%m-%d %H:%M:%S").c_str(), ESPTime::from_epoch_local(id(active_heating_end)).strftime("%Y-%m-%d %H:%M:%S").c_str(), id(active_heating_time), available_solar_power); } } else { @@ -3611,16 +3720,17 @@ script: temperature_target: double then: - lambda: |- + double top_temp = id(geyser_top_temperature).state; // estimate expected heat loss - double heat_loss = id(thermal_transmittance) * id(geyser_surface_area) * (id(geyser_top_temperature).state - id(ambient_temperature).state); // in Watts + double heat_loss = id(thermal_transmittance) * id(geyser_surface_area) * (top_temp - id(ambient_temperature).state); // in Watts double heating_power = id(geyser_element_power).state - heat_loss; id(g_heat_loss) = heat_loss; id(geyser_effective_power) = (heating_power > 0.0001) ? heating_power : 0.0001; // this is to avoid dividing by zero // use specific_heat_capacity = 4184 J/kg°C to calculate heating factor, i.e. the number of seconds it will take to heat water by 1 degree double heating_factor = id(watermass) * 4184 / id(geyser_effective_power); // set estimated heat required - double geyser_temp_diff = id(geyser_top_temperature).state - id(geyser_bottom_temperature).state - id(geyser_top_bottom_constraint); - double geyser_effective_temperature = (geyser_temp_diff > 0) ? id(geyser_top_temperature).state - geyser_temp_diff : id(geyser_top_temperature).state; + double geyser_temp_diff = top_temp - id(geyser_bottom_temperature).state - id(geyser_top_bottom_constraint); + double geyser_effective_temperature = (geyser_temp_diff > 0) ? top_temp - geyser_temp_diff : top_temp; double temperature_diff = temperature_target - geyser_effective_temperature; // set estimated heating time double heating_time = heating_factor * temperature_diff; // in Joules @@ -3631,15 +3741,17 @@ script: - id: record_heat_gained then: - lambda: |- - //ESP_LOGI("info", "Recording heat lost/gained."); + const char tag[] = "geyser"; + //ESP_LOGI(tag, "Recording heat lost/gained."); auto currenttime = id(time_source).now(); id(heat_monitor_end) = currenttime.timestamp; time_t start_time = id(heat_monitor_start); time_t time_elapsed = id(heat_monitor_end) - start_time; + double top_temp = id(geyser_top_temperature).state; if(time_elapsed > 0) { // heat gained measurement if(start_time > 0) { - double water_temp = id(geyser_top_temperature).state; + double water_temp = top_temp; double ambient_temp = id(ambient_temperature).state; double previous_temp = id(last_geyser_top_temperature); if(isnan(water_temp)) { @@ -3654,7 +3766,7 @@ script: double heat_energy_gained = id(watermass) * 4184 * dtemp; // joules double heat_gain = heat_energy_gained / time_elapsed; // watts id(g_heat_gained) = heat_gain; - ESP_LOGI("info", "Geyser temperature loss/gain: %.2f°C, time elapsed %d, heat energy gained: %.0fJ, heat gain: %.0fW", dtemp, time_elapsed, heat_energy_gained, heat_gain); + ESP_LOGI(tag, "Geyser temperature loss/gain: %.2f°C, time elapsed %d, heat energy gained: %.0fJ, heat gain: %.0fW", dtemp, time_elapsed, heat_energy_gained, heat_gain); id(start_heat_monitor).execute(water_temp, ambient_temp); } } @@ -3666,7 +3778,8 @@ script: ambient_temp: double then: - lambda: |- - //ESP_LOGI("info", "Starting heat loss/gain measurement. A: %.2f, T: %.2f", ambient_temp, water_temp); + double top_temp = id(geyser_top_temperature).state; + //ESP_LOGI(tag, "Starting heat loss/gain measurement. A: %.2f, T: %.2f", ambient_temp, water_temp); auto currenttime = id(time_source).now(); id(heat_monitor_start) = currenttime.timestamp; if(isnan(water_temp)) { @@ -3682,7 +3795,7 @@ script: else { id(last_temp_diff) = water_temp - ambient_temp; } - //ESP_LOGI("info", "Start monitor @ Geyser top temperature: %.2f°C, geyser vs outside: %.2f°C", id(last_geyser_top_temperature), id(last_temp_diff)); + //ESP_LOGI(tag, "Start monitor @ Geyser top temperature: %.2f°C, geyser vs outside: %.2f°C", id(last_geyser_top_temperature), id(last_temp_diff)); } - id: init_fixed_public_holidays @@ -3820,12 +3933,12 @@ script: int i = 0; id(public_holidays)[i][0] = time_obj.month; id(public_holidays)[i][1] = time_obj.day_of_month; - //ESP_LOGI("info", "======== Set holiday h_idx:%d, %d-%d-%d [%d]", i, time_obj.year, time_obj.month, time_obj.day_of_month, time_obj.day_of_week); + //ESP_LOGI(tag, "======== Set holiday h_idx:%d, %d-%d-%d [%d]", i, time_obj.year, time_obj.month, time_obj.day_of_month, time_obj.day_of_week); time_obj = ESPTime::from_epoch_local(today.timestamp + 86400); // Easter Monday i++; id(public_holidays)[i][0] = time_obj.month; id(public_holidays)[i][1] = time_obj.day_of_month; - //ESP_LOGI("info", "======== Set holiday h_idx:%d, %d-%d-%d [%d]", i, time_obj.year, time_obj.month, time_obj.day_of_month, time_obj.day_of_week); + //ESP_LOGI(tag, "======== Set holiday h_idx:%d, %d-%d-%d [%d]", i, time_obj.year, time_obj.month, time_obj.day_of_month, time_obj.day_of_week); // do rest of public holidays int j = 0; // fixed_public_holidays array index while(j < 10) { @@ -3839,20 +3952,20 @@ script: if(holiday.day_of_week == 1) { // if Sunday holiday.increment_day(); // then Monday is also public holiday holiday.recalc_timestamp_local(); - //ESP_LOGI("info", "======== Monday is also public holiday if public holiday falls on a Sunday. h_idx:%d, fh_idx:%d, %d-%d-%d [%d]", i, j, holiday.year, holiday.month, holiday.day_of_month, holiday.day_of_week); + //ESP_LOGI(tag, "======== Monday is also public holiday if public holiday falls on a Sunday. h_idx:%d, fh_idx:%d, %d-%d-%d [%d]", i, j, holiday.year, holiday.month, holiday.day_of_month, holiday.day_of_week); } else { if(isBoxingDay && holiday.day_of_week == 2) { holiday.increment_day(); // then if President so decides, Tuesday is usually also public holiday holiday.recalc_timestamp_local(); - //ESP_LOGI("info", "======== Boxing Day falls on a Monday so Tuesday is also public holiday. h_idx:%d, fh_idx:%d, %d-%d-%d [%d]", i, j, holiday.year, holiday.month, holiday.day_of_month, holiday.day_of_week); + //ESP_LOGI(tag, "======== Boxing Day falls on a Monday so Tuesday is also public holiday. h_idx:%d, fh_idx:%d, %d-%d-%d [%d]", i, j, holiday.year, holiday.month, holiday.day_of_month, holiday.day_of_week); } } holiday.recalc_timestamp_local(); id(public_holidays)[i][0] = holiday.month; id(public_holidays)[i][1] = holiday.day_of_month; holiday.recalc_timestamp_local(); - //ESP_LOGI("info", "======== Set holiday h_idx:%d, fh_idx:%d, %d-%d-%d [%d]", i, j, holiday.year, holiday.month, holiday.day_of_month, holiday.day_of_week); + //ESP_LOGI(tag, "======== Set holiday h_idx:%d, fh_idx:%d, %d-%d-%d [%d]", i, j, holiday.year, holiday.month, holiday.day_of_month, holiday.day_of_week); j++; } @@ -3971,45 +4084,39 @@ script: then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(mains_power).state, 2048, id(mains_voltage_adc).state, 128, id(mains_current).state, 512); + auto x = 0; //cb_frame::get_byte_stream(id(mains_power).state, 2048, id(mains_voltage_adc).state, 128, id(mains_current).state, 512); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_POWER_MAINS, x); - - id: canbus_send_power_inverter then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(total_inverter_output).state, 2048, id(inverter_output_voltage_adc).state, 128, id(inverter1_2_output_current).state, 512); + auto x = 0;// cb_frame::get_byte_stream(id(total_inverter_output).state, 2048, id(inverter_output_voltage_adc).state, 128, id(inverter1_2_output_current).state, 512); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_POWER_INVERTER, x); - - id: canbus_send_power_plugs then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(power_outlets_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(power_outlets_current).state, 512); + auto x = 0;// cb_frame::get_byte_stream(id(power_outlets_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(power_outlets_current).state, 512); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_POWER_PLUGS, x); - - id: canbus_send_power_lights then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(lights_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(lights_current).state, 512); + auto x = 0;//cb_frame::get_byte_stream(id(lights_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(lights_current).state, 512); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_POWER_LIGHTS, x); - - id: canbus_send_power_geyser then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(geyser_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(geyser_current).state, 512); + auto x = 0;//cb_frame::get_byte_stream(id(geyser_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(geyser_current).state, 512); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_POWER_GEYSER, x); - # - id: canbus_send_power_pool # then: # lambda: |- # using namespace solar; # auto x = cb_frame::get_byte_stream(id(pool_power).state, 2048, id(inverter_output_voltage_adc).state, 128, id(pool_current).state, 512); # id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_POWER_POOL, x); - - id: canbus_send_power_generated then: lambda: |- @@ -4329,7 +4436,7 @@ script: # double energy = time_elapsed * power; # id(generated_energy_daily)[day_of_week-1] += energy; # id(generated_energy) += energy; -# //ESP_LOGI("info", "Generated energy: %f kWs. Total: %f kWh", energy, id(generated_energy)/3600.0); +# //ESP_LOGI(tag, "Generated energy: %f kWs. Total: %f kWh", energy, id(generated_energy)/3600.0); # } # power = id(power_loss).state; # if(isnan(power)) { diff --git a/sthome-ut9.yaml b/sthome-ut9.yaml index b82ab48..aac8c70 100644 --- a/sthome-ut9.yaml +++ b/sthome-ut9.yaml @@ -6,76 +6,20 @@ packages: 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_inv1_power_flow - type: uint16_t - restore_value: no - initial_value: '0' - - id: g_inv2_power_flow - type: uint16_t - restore_value: no - initial_value: '0' -# - 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 -# restore_value: no - + esphome: name: "${name}" friendly_name: "${friendly_name}" - on_boot: - - priority: 600 # This is where most sensors are set up (higher number means higher priority) - then: - - uart.write: - id: inv_uart1 - data: [0x0D, 0x0A] - - uart.write: - id: inv_uart2 - data: [0x0D, 0x0A] esp32: # board: nodemcu-32s board: esp32dev framework: - type: arduino - #type: esp-idf + #type: arduino + type: esp-idf -#debug: -# update_interval: 5s +debug: + update_interval: 5s # Enable logging logger: @@ -103,34 +47,6 @@ wifi: captive_portal: -uart: - - id: inv_uart1 - rx_pin: GPIO16 - tx_pin: GPIO17 - baud_rate: 2400 - stop_bits: 1 - parity: NONE - debug: - direction: BOTH - dummy_receiver: false - after: - delimiter: "\r" - sequence: - - lambda: UARTDebug::log_hex(direction, bytes, ','); - - - id: inv_uart2 - rx_pin: GPIO25 - tx_pin: GPIO26 - baud_rate: 2400 - stop_bits: 1 - parity: NONE - debug: - direction: BOTH - dummy_receiver: false - after: - delimiter: "\r" - sequence: - - lambda: UARTDebug::log_hex(direction, bytes, ' '); sun: id: sun_sensor @@ -139,2460 +55,164 @@ sun: time: - platform: homeassistant - id: time_source +text_sensor: + - platform: debug + device: + name: "Device Info" + reset_reason: + name: "Reset Reason" + + # human readable update text sensor from sensor:uptime + - platform: template + name: Uptime + id: uptime_human + icon: mdi:clock-start + + +#define OUTPUT_R1 16 +#define OUTPUT_R2 17 +#define OUTPUT_R3 18 +#define OUTPUT_R4 19 switch: - platform: restart name: "${name} Restart" id: "restart_switch" -modbus: - - id: modbus1 - uart_id: inv_uart1 - send_wait_time: 1200ms #250ms - disable_crc: false - role: client + - platform: gpio + pin: + number: GPIO16 + inverted: true + id: relay1 + name: "Floodlights Backyard" + icon: "mdi:light-flood-down" + restore_mode: RESTORE_DEFAULT_OFF + # the backyard floodlight auto turns off in 4min 14 sec. So we need to switch relay off just before or at this time + # TODO: remove or extend auto turn off to >= 10min + on_turn_on: + - delay: 250s + - switch.turn_off: relay1 - - id: modbus2 - uart_id: inv_uart2 - send_wait_time: 1200ms #250ms - disable_crc: false - role: client + - platform: gpio + pin: + number: GPIO17 + inverted: true + id: relay2 + name: "Relay 2" + icon: "mdi:run-fast" + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + - delay: 1000ms + - switch.turn_off: relay2 -modbus_controller: - - id: modbus_device1 - modbus_id: modbus1 - address: 0x01 - allow_duplicate_commands: False - command_throttle: 700ms #2022ms - update_interval: 60s #305s - offline_skip_updates: 2 - max_cmd_retries: 1 - setup_priority: -10 - - id: modbus_device2 - modbus_id: modbus2 - address: 0x01 - allow_duplicate_commands: False - command_throttle: 0ms - update_interval: 60s #30s - offline_skip_updates: 2 - max_cmd_retries: 0 - setup_priority: -10 - -text_sensor: -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 SerialNo" - register_type: holding - address: ${Felicity_Inv_SerialNo} # 0xF804 - response_size: 14 # should be 10, but absorbing extra four bytes - raw_encode: HEXBYTES - lambda: |- - char buffer[32]; - uint16_t sn0 = modbus_controller::word_from_hex_str(x, 0); - uint16_t sn1 = modbus_controller::word_from_hex_str(x, 2); - uint16_t sn2 = modbus_controller::word_from_hex_str(x, 4); - uint16_t sn3 = modbus_controller::word_from_hex_str(x, 6); - uint16_t sn4 = modbus_controller::word_from_hex_str(x, 8); - snprintf(buffer, sizeof(buffer), "%04d%04d%04d%04d%04d", sn0, sn1, sn2, sn3, sn4); - return std::string(buffer).substr(0, 14); + - platform: gpio + pin: + number: GPIO18 + inverted: true + id: relay3 + name: "Relay 3" + icon: "mdi:run-fast" + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + - delay: 1000ms + - switch.turn_off: relay3 + + - platform: gpio + pin: + number: GPIO19 + inverted: true + id: relay4 + name: "Alarm Zone 4" + icon: "mdi:alarm-light-outline" + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + - if: + condition: + - lambda: |- + double sun_elevation = id(sun_sensor).elevation(); + return (sun_elevation <= -6); // -6° = civil twilight, -12° = nautical twilight, -18° = astronomical twilight + #- sun.is_below_horizon: + then: + - switch.turn_on: relay1 + - if: + condition: + - binary_sensor.is_on: floodlight_test + then: + - switch.turn_on: relay1 + - delay: 30s + - switch.turn_off: relay4 -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Type" - id: inverter1_type - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_Type} # 0xF800 - response_size: 2 - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch (value) { - case 0x50: return std::string("High Frequency Inverter"); - default: return std::string("Unknown"); - } - return x; - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Sub Type" - id: inverter1_subtype - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_SubType} # 0xF801 - response_size: 6 # should be 2, but absorbing extra four bytes - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch (value) { - case 0x0204: return std::string("3024 (3000VA/24V)"); - case 0x0408: return std::string("5048 (5000VA/48V)"); - default: return std::string("Unknown"); - } - return x; - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 CPU1 F/W Version" - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_CPU1_FW_Version} # 0xF80B - response_size: 2 - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - return std::to_string(value); - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 CPU2 F/W Version" - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_CPU2_FW_Version} # 0xF80C - response_size: 2 - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - return std::to_string(value); - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Working Mode" - address: ${Felicity_Inv_WorkingMode} # 0x1101 - bitmask: 0 - register_type: holding - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch(value) { - case 0: return std::string("Power On"); - case 1: return std::string("Standby"); - case 2: return std::string("Bypass"); - case 3: return std::string("Battery"); - case 4: return std::string("Fault"); - case 5: return std::string("Line"); - case 6: return std::string("PV Charge"); - } - return std::string("Unknown"); - register_count: 1 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Charge Mode" - address: ${Felicity_Inv_BatteryChargingStage} # 0x1102 - bitmask: 0 - register_type: holding - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch(value) { - case 0: return std::string("Idle"); - case 1: return std::string("Bulk"); - case 2: return std::string("Absorption"); - case 3: return std::string("Float"); - } - return std::string("Unknown"); - register_count: 1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 SerialNo" - register_type: holding - address: ${Felicity_Inv_SerialNo} # 0xF804 - response_size: 14 # should be 10, but absorbing extra four bytes - raw_encode: HEXBYTES - lambda: |- - char buffer[32]; - uint16_t sn0 = modbus_controller::word_from_hex_str(x, 0); - uint16_t sn1 = modbus_controller::word_from_hex_str(x, 2); - uint16_t sn2 = modbus_controller::word_from_hex_str(x, 4); - uint16_t sn3 = modbus_controller::word_from_hex_str(x, 6); - uint16_t sn4 = modbus_controller::word_from_hex_str(x, 8); - snprintf(buffer, sizeof(buffer), "%04d%04d%04d%04d%04d", sn0, sn1, sn2, sn3, sn4); - return std::string(buffer).substr(0, 14); - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Type" - id: inverter2_type - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_Type} # 0xF800 - response_size: 2 - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch (value) { - case 0x50: return std::string("High Frequency Inverter"); - default: return std::string("Unknown"); - } - return x; - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Sub Type" - id: inverter2_subtype - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_SubType} # 0xF801 - response_size: 6 # should be 2, but absorbing extra four bytes - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch (value) { - case 0x0204: return std::string("3024 (3000VA/24V)"); - case 0x0408: return std::string("5048 (5000VA/48V)"); - default: return std::string("Unknown"); - } - return x; - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 CPU1 F/W Version" - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_CPU1_FW_Version} # 0xF80B - response_size: 2 - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - return std::to_string(value); - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 CPU2 F/W Version" - bitmask: 0 - register_type: holding - address: ${Felicity_Inv_CPU2_FW_Version} # 0xF80C - response_size: 2 - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - return std::to_string(value); - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Working Mode" - address: ${Felicity_Inv_WorkingMode} # 0x1101 - bitmask: 0 - register_type: holding - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch(value) { - case 0: return std::string("Power On"); - case 1: return std::string("Standby"); - case 2: return std::string("Bypass"); - case 3: return std::string("Battery"); - case 4: return std::string("Fault"); - case 5: return std::string("Line"); - case 6: return std::string("PV Charge"); - } - return std::string("Unknown"); - register_count: 1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Charge Mode" - address: ${Felicity_Inv_BatteryChargingStage} # 0x1102 - bitmask: 0 - register_type: holding - raw_encode: HEXBYTES - lambda: |- - uint16_t value = modbus_controller::word_from_hex_str(x, 0); - switch(value) { - case 0: return std::string("Idle"); - case 1: return std::string("Bulk"); - case 2: return std::string("Absorption"); - case 3: return std::string("Float"); - } - return std::string("Unknown"); - register_count: 1 - -sensor: -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 SettingDataSn" - register_type: holding - address: ${Felicity_Inv_SettingDataSn} # 0x1100 - accuracy_decimals: 0 - value_type: U_WORD - register_count: 1 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Fault Code" - register_type: holding - address: ${Felicity_Inv_FaultCode} # 0x1103 - value_type: U_WORD - register_count: 1 - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - id: inv1_power_flow_msg - register_type: holding - address: ${Felicity_Inv_PowerFlowMsg} # 0x1104 - value_type: U_WORD - register_count: 4 - lambda: |- - id(g_inv1_power_flow) = x; - return x; - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Battery Voltage" - register_type: holding - address: ${Felicity_Inv_BatteryVoltage} # 0x1108 - value_type: U_WORD - register_count: 1 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.01 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Battery Current" - register_type: holding - address: ${Felicity_Inv_BatteryCurrent} # 0x1109 - value_type: S_WORD - register_count: 1 - unit_of_measurement: "A" - device_class: current - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 BatteryPower" - register_type: holding - address: ${Felicity_Inv_BatteryPower} # 0x110A - value_type: S_WORD - register_count: 7 - unit_of_measurement: "W" - device_class: power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 AC Output Voltage" - register_type: holding - address: ${Felicity_Inv_ACOutputVoltage} # 0x1111 - value_type: U_WORD - register_count: 6 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 AC Input Voltage" - register_type: holding - address: ${Felicity_Inv_ACInputVoltage} # 0x1117 - value_type: U_WORD - register_count: 2 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 AC Input Frequency" - register_type: holding - address: ${Felicity_Inv_ACInputFrequency} # 0x1119 - value_type: U_WORD - register_count: 5 - unit_of_measurement: "Hz" - device_class: frequency - accuracy_decimals: 2 - filters: - - multiply: 0.01 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 AC Output Active Power" - register_type: holding - address: ${Felicity_Inv_ACOutputActivePower} # 0x111E - value_type: S_WORD - register_count: 1 - unit_of_measurement: "W" - device_class: power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 AC Output Apparent Power" - register_type: holding - address: ${Felicity_Inv_ACOutputApparentPower} # 0x111F - value_type: U_WORD - register_count: 1 - unit_of_measurement: "VA" - device_class: apparent_power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 Load Percentage" - register_type: holding - address: ${Felicity_Inv_LoadPercentage} # 0x1120 - value_type: U_WORD - register_count: 6 - unit_of_measurement: "%" - device_class: power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 PV Input Voltage" - register_type: holding - address: ${Felicity_Inv_PVInputVoltage} # 0x1126 - value_type: U_WORD - register_count: 4 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device1 - name: "Inv1 PV Input Power" - register_type: holding - address: ${Felicity_Inv_PVInputPower} # 0x112A - value_type: S_WORD - register_count: 1 - unit_of_measurement: "W" - device_class: power - accuracy_decimals: 0 - -############### modbus device 2 ############### -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 SettingDataSn" - register_type: holding - address: ${Felicity_Inv_SettingDataSn} # 0x1100 - accuracy_decimals: 0 - value_type: U_WORD - register_count: 1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Fault Code" - register_type: holding - address: ${Felicity_Inv_FaultCode} # 0x1103 - value_type: U_WORD - register_count: 1 - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - id: inv2_power_flow_msg - register_type: holding - address: ${Felicity_Inv_PowerFlowMsg} # 0x1104 - value_type: U_WORD - register_count: 4 - lambda: |- - id(g_inv2_power_flow) = x; - return x; - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Battery Voltage" - register_type: holding - address: ${Felicity_Inv_BatteryVoltage} # 0x1108 - value_type: U_WORD - register_count: 1 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.01 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Battery Current" - register_type: holding - address: ${Felicity_Inv_BatteryCurrent} # 0x1109 - value_type: S_WORD - register_count: 1 - unit_of_measurement: "A" - device_class: current - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 BatteryPower" - register_type: holding - address: ${Felicity_Inv_BatteryPower} # 0x110A - value_type: S_WORD - register_count: 7 - unit_of_measurement: "W" - device_class: power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 AC Output Voltage" - register_type: holding - address: ${Felicity_Inv_ACOutputVoltage} # 0x1111 - value_type: U_WORD - register_count: 6 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 AC Input Voltage" - register_type: holding - address: ${Felicity_Inv_ACInputVoltage} # 0x1117 - value_type: U_WORD - register_count: 2 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 AC Input Frequency" - register_type: holding - address: ${Felicity_Inv_ACInputFrequency} # 0x1119 - value_type: U_WORD - register_count: 5 - unit_of_measurement: "Hz" - device_class: frequency - accuracy_decimals: 2 - filters: - - multiply: 0.01 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 AC Output Active Power" - register_type: holding - address: ${Felicity_Inv_ACOutputActivePower} # 0x111E - value_type: S_WORD - register_count: 1 - unit_of_measurement: "W" - device_class: power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 AC Output Apparent Power" - register_type: holding - address: ${Felicity_Inv_ACOutputApparentPower} # 0x111F - value_type: U_WORD - register_count: 1 - unit_of_measurement: "VA" - device_class: apparent_power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 Load Percentage" - register_type: holding - address: ${Felicity_Inv_LoadPercentage} # 0x1120 - value_type: U_WORD - register_count: 6 - unit_of_measurement: "%" - device_class: power - accuracy_decimals: 0 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 PV Input Voltage" - register_type: holding - address: ${Felicity_Inv_PVInputVoltage} # 0x1126 - value_type: U_WORD - register_count: 4 - unit_of_measurement: "V" - device_class: voltage - accuracy_decimals: 1 - filters: - - multiply: 0.1 - -- platform: modbus_controller - modbus_controller_id: modbus_device2 - name: "Inv2 PV Input Power" - register_type: holding - address: ${Felicity_Inv_PVInputPower} # 0x112A - value_type: S_WORD - register_count: 1 - unit_of_measurement: "W" - device_class: power - accuracy_decimals: 0 +# define DIGITAL_D1 04 binary_sensor: -- platform: template - name: "Inv1 Battery Connected" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x8000; + - platform: gpio + # device_class: light + id: floodlight_test + pin: + number: GPIO04 + mode: + input: true + pullup: true + filters: + - delayed_off: 100ms + name: "Floodlights Test Mode" + icon: "mdi:lightbulb-on-outline" -- platform: template - name: "Inv1 Line Normal" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x4000; - -- platform: template - name: "Inv1 PV Input Normal" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x2000; - -- platform: template - name: "Inv1 Load Connect Allowed" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x1000; - -- platform: template - name: "Inv1 PV MPPT Working" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x0080; - -- platform: template - name: "Inv1 Load Connected" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x0040; - -- platform: template - name: "Inv1 Power Flow Version Supported" -# device_class: problem - lambda: |- - return id(g_inv1_power_flow) & 0x0001; - -- platform: template - name: "Inv1 Battery Charging" - device_class: battery_charging - lambda: |- - int battery_flow = (id(g_inv1_power_flow) >> 10) & 3; - return battery_flow & 0x01; - -- platform: template - name: "Inv1 Battery Discharging" -# device_class: battery_charging - lambda: |- - int battery_flow = (id(g_inv1_power_flow) >> 10) & 3; - return battery_flow & 0x02; - -- platform: template - name: "Inv1 Draw Power from Line" - lambda: |- - int line_flow = (id(g_inv1_power_flow) >> 8) & 3; - return line_flow & 0x01; - -- platform: template - name: "Inv1 Feed Power to Line" - lambda: |- - int line_flow = (id(g_inv1_power_flow) >> 8) & 3; - return line_flow & 0x10; - -- platform: template - name: "Inv2 Battery Connected" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x8000; - -- platform: template - name: "Inv2 Line Normal" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x4000; - -- platform: template - name: "Inv2 PV Input Normal" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x2000; - -- platform: template - name: "Inv2 Load Connect Allowed" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x1000; - -- platform: template - name: "Inv2 PV MPPT Working" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x0080; - -- platform: template - name: "Inv2 Load Connected" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x0040; - -- platform: template - name: "Inv2 Power Flow Version Supported" -# device_class: problem - lambda: |- - return id(g_inv2_power_flow) & 0x0001; - -- platform: template - name: "Inv2 Battery Charging" - device_class: battery_charging - lambda: |- - int battery_flow = (id(g_inv2_power_flow) >> 10) & 3; - return battery_flow & 0x01; - -- platform: template - name: "Inv2 Battery Discharging" -# device_class: battery_charging - lambda: |- - int battery_flow = (id(g_inv2_power_flow) >> 10) & 3; - return battery_flow & 0x02; - -- platform: template - name: "Inv2 Draw Power from Line" - lambda: |- - int line_flow = (id(g_inv2_power_flow) >> 8) & 3; - return line_flow & 0x01; - -- platform: template - name: "Inv2 Feed Power to Line" - lambda: |- - int line_flow = (id(g_inv2_power_flow) >> 8) & 3; - return line_flow & 0x10; -################################################################################################3 -#### OLD STHOME-9 CONFIG STARTS HERE -#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_ID09} -# #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((x[1] << 8) + x[0]); -# id(battery_soc).publish_state(value); -# value = static_cast((x[3] << 8) + x[2]); -# id(battery_soh).publish_state(value); -# - can_id: ${CB_BATTERY_STATUS} -# then: -# - lambda: |- -# float value = 0.01 * static_cast((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((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((x[5] << 8) + x[4]); // unit = 0.1°C -# id(battery_average_cell_temperature).publish_state(value); -# - can_id: ${CB_BATTERY_LIMITS} -# 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((x[3] << 8) + x[2]); // unit = 0.1A -# id(battery_charge_current_limit).publish_state(value); -# value = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A -# id(battery_discharge_current_limit).publish_state(value); -# - can_id: ${CB_BATTERY_FAULT} -# 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 +#define ANALOG_A1 33 +#define ANALOG_A2 32 +#define ANALOG_A3 35 +#define ANALOG_A4 34 +#define ANALOG_A5 39 +#define ANALOG_A6 36 +sensor: +# - platform: adc +# pin: 35 +# name: "Alarm Signal" +# id: alarm_signal +# update_interval: 2000ms +# attenuation: 12db +# sampling_mode: avg # 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& -## 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& -# 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_LIMITS}: -# { -# float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V -# float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A -# float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A -# snprintf(buffer, sizeof(buffer), "BATTERY LIMITS: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit); -# decoded += buffer; +# - lambda: +# if (x >= 3.11) { +# return x * 1.60256; +# } else if (x <= 0.25) { +# return 0; +# } else { +# return x * 1.51; # } -# break; -# case ${CB_BATTERY_STATE}: -# { -# uint soc = static_cast((x[1] << 8) + x[0]); -# uint soh = static_cast((x[3] << 8) + x[2]); -# snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh); -# decoded += buffer; +# on_value: +# then: +# # switch on floodlights +# lambda: |- +# if (id(alarm_signal).state > 1.5) { +# id(relay1).turn_on(); # } -# break; -# case ${CB_BATTERY_STATUS}: -# { -# float system_voltage = 0.01 * static_cast((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((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current -# float average_cell_temperature = 0.1 * static_cast((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_FAULT}: -# { -# 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()); \ No newline at end of file + + # 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(); \ No newline at end of file