diff --git a/sthome-ut8_bak.yml b/archive/sthome-ut8_bak.yml similarity index 99% rename from sthome-ut8_bak.yml rename to archive/sthome-ut8_bak.yml index 68ce80e..4c9cc7c 100644 --- a/sthome-ut8_bak.yml +++ b/archive/sthome-ut8_bak.yml @@ -174,7 +174,6 @@ globals: type: time_t initial_value: '0' restore_value: yes -# CAN BUS - id: can1_msgctr type: int restore_value: no @@ -188,7 +187,6 @@ globals: type: std::queue< std::set > restore_value: no - esp32: board: esp32dev framework: @@ -2461,6 +2459,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 BatteryPower" + id: inv1_batterypower register_type: holding address: ${Felicity_Inv_BatteryPower} # 0x110A value_type: S_WORD @@ -2511,6 +2510,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 AC Output Active Power" + id: inv1_ac_output_active_power register_type: holding address: ${Felicity_Inv_ACOutputActivePower} # 0x111E value_type: S_WORD @@ -2557,6 +2557,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 PV Input Power" + id: inv1_pv_input_power register_type: holding address: ${Felicity_Inv_PVInputPower} # 0x112A value_type: S_WORD @@ -2624,6 +2625,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 BatteryPower" + id: inv2_batterypower register_type: holding address: ${Felicity_Inv_BatteryPower} # 0x110A value_type: S_WORD @@ -2674,6 +2676,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 AC Output Active Power" + id: inv2_ac_output_active_power register_type: holding address: ${Felicity_Inv_ACOutputActivePower} # 0x111E value_type: S_WORD @@ -2720,6 +2723,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 PV Input Power" + id: inv2_pv_input_power register_type: holding address: ${Felicity_Inv_PVInputPower} # 0x112A value_type: S_WORD @@ -3119,7 +3123,7 @@ script: index = id(is_school_holiday).state ? ${GM_SCHOOL_HOLIDAY} : id(is_public_holiday).state ? ${GM_PUBLIC_HOLIDAY} : (dayofweek == 1) ? ${GM_SUNDAY} : (dayofweek == 7) ? ${GM_SATURDAY} : ${GM_WORKDAY}; } else { - index = 0; // default + index = ${GM_WORKDAY}; // default } - id: set_active_heating_timers diff --git a/common/geyser.yaml b/common/geyser.yaml index 00b19a7..30504cf 100644 --- a/common/geyser.yaml +++ b/common/geyser.yaml @@ -5,10 +5,12 @@ substitutions: HEATING_HOT: 70 HEATING_TEMP_SCALE: 1000.0 HEATING_DAY_BLOCKS: 6 + GEYSER_MODES: 5 GM_SUNDAY: 0 GM_WORKDAY: 1 GM_SATURDAY: 2 GM_PUBLIC_HOLIDAY: 3 GM_SCHOOL_HOLIDAY: 4 + LATE_MORNING_END: 10 #10AM, in winter it should be 11AM diff --git a/common/time.yaml b/common/time.yaml new file mode 100644 index 0000000..00b9f07 --- /dev/null +++ b/common/time.yaml @@ -0,0 +1,102 @@ +# https://community.home-assistant.io/t/display-manually-set-rtc-ds3231-time-in-esphome-sntp-sync-every-12-hours-need-help-with-web-interface/868801 +# miloit +time: + - platform: sntp + id: sntp_time + + +web_server: + port: 80 + +custom_component: + - lambda: |- + class DS3231Component : public PollingComponent { + public: + DS3231Component(esphome::time::RealTimeClock *rtc) : PollingComponent(10000), rtc_(rtc) {} + + void setup() override { + if (!Wire.requestFrom(0x68, 1)) { + ESP_LOGE("DS3231", "RTC not found at I2C address 0x68"); + return; + } + ESP_LOGI("DS3231", "RTC found at I2C address 0x68"); + + auto now = this->rtc_->now(); + if (now.is_valid()) { + set_time(now.year, now.month, now.day_of_month, now.hour, now.minute, now.second, now.day_of_week); + ESP_LOGI("DS3231", "RTC time set to %04d-%02d-%02d %02d:%02d:%02d", + now.year, now.month, now.day_of_month, now.hour, now.minute, now.second); + } else { + ESP_LOGE("DS3231", "SNTP time is not valid; cannot set RTC"); + } + } + + void update() override { + auto time = get_time(); + if (time.year > 0) { + ESP_LOGI("DS3231", "Current RTC Time: %04d-%02d-%02d %02d:%02d:%02d", + time.year, time.month, time.day, time.hour, time.minute, time.second); + } else { + ESP_LOGE("DS3231", "Failed to read time from RTC"); + } + } + + void set_time(int year, int month, int day, int hour, int minute, int second, int day_of_week) { + Wire.beginTransmission(0x68); + Wire.write(0); // Start at register 0 + Wire.write(dec_to_bcd(second)); // Seconds + Wire.write(dec_to_bcd(minute)); // Minutes + Wire.write(dec_to_bcd(hour)); // Hours + Wire.write(dec_to_bcd(day_of_week)); // Day of the week + Wire.write(dec_to_bcd(day)); // Day of the month + Wire.write(dec_to_bcd(month)); // Month + Wire.write(dec_to_bcd(year - 2000)); // Year + Wire.endTransmission(); + } + + struct Time { + int year; + int month; + int day; + int hour; + int minute; + int second; + int day_of_week; + }; + + Time get_time() { + Wire.beginTransmission(0x68); + Wire.write(0); // Start at register 0 + Wire.endTransmission(); + + if (Wire.requestFrom(0x68, 7) != 7) { + ESP_LOGE("DS3231", "Failed to read time registers"); + return Time{0, 0, 0, 0, 0, 0, 0}; + } + + uint8_t second = bcd_to_dec(Wire.read()); + uint8_t minute = bcd_to_dec(Wire.read()); + uint8_t hour = bcd_to_dec(Wire.read()); + uint8_t day_of_week = bcd_to_dec(Wire.read()); + uint8_t day = bcd_to_dec(Wire.read()); + uint8_t month = bcd_to_dec(Wire.read()); + uint16_t year = bcd_to_dec(Wire.read()) + 2000; + + return Time{year, month, day, hour, minute, second, day_of_week}; + } + + private: + esphome::time::RealTimeClock *rtc_; + + uint8_t dec_to_bcd(int val) { + return ((val / 10 * 16) + (val % 10)); + } + + int bcd_to_dec(uint8_t val) { + return ((val / 16 * 10) + (val % 16)); + } + }; + + auto my_rtc = new DS3231Component(id(sntp_time)); + App.register_component(my_rtc); + return {}; \ No newline at end of file diff --git a/sthome-ut8.yaml b/sthome-ut8.yaml index 68ce80e..02e8eb4 100644 --- a/sthome-ut8.yaml +++ b/sthome-ut8.yaml @@ -8,13 +8,14 @@ substitutions: name: sthome-ut8 friendly_name: "sthome-ut8" MAX_SCHOOL_HOLIDAY_PERIODS: 12 + BATTERY_INFO_TIMEOUT: 30 # 30 sec timeout esphome: name: "${name}" friendly_name: "${friendly_name}" - platformio_options: - build_flags: -fexceptions - build_unflags: -fno-exceptions +# platformio_options: +# build_flags: -fexceptions +# build_unflags: -fno-exceptions includes: - source # copies folder with files to relevant to be included in esphome compile - # angle brackets ensure file is included above globals in main.cpp. Make sure to use include GUARDS in the file to prevent double inclusion @@ -27,6 +28,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 - lambda: |- id(geyser_relay).turn_off(); id(pool_relay).turn_off(); @@ -43,13 +45,12 @@ 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 +# - priority: 400 +# then: +# - switch.turn_on: level_shifter_output_enable globals: - id: g_inv1_power_flow @@ -174,35 +175,41 @@ globals: type: time_t initial_value: '0' restore_value: yes -# CAN BUS + - id: can1_msgctr type: int restore_value: no - id: can2_msgctr type: int - restore_value: no + restore_value: no + - id: last_battery_message_time + type: time_t + restore_value: no - id: g_cb_cache # the cache is used to only accept a frame after it has been received a specified number of times within a specified period. this hopefully will iron out spurious corrupted frames type: solar::cbf_cache restore_value: no - id: g_cb_request_queue type: std::queue< std::set > restore_value: no - - +# esp32: board: esp32dev framework: - type: esp-idf + type: esp-idf #arduino # esp-idf + +debug: + update_interval: 15s # Enable logging logger: - level: VERBOSE + level: INFO initial_level: INFO logs: canbus: INFO - uart: DEBUG + uart: INFO sensor: INFO - ads1115: INFO + ads1115.sensor: INFO + modbus: INFO # Enable Home Assistant API api: @@ -260,8 +267,13 @@ spi: uart: - id: inv_uart1 - rx_pin: GPIO16 - tx_pin: GPIO17 + tx_pin: GPIO32 # Tx1 + rx_pin: + number: GPIO34 # Rx1 + inverted: false + mode: + input: true + pullup: false # external pullup baud_rate: 2400 stop_bits: 1 parity: NONE @@ -274,8 +286,13 @@ uart: - lambda: UARTDebug::log_hex(direction, bytes, ','); - id: inv_uart2 - rx_pin: GPIO0 - tx_pin: GPIO1 + tx_pin: GPIO33 # Tx2 + rx_pin: + number: GPIO35 # Rx2 + inverted: false + mode: + input: true + pullup: false # external pullup baud_rate: 2400 stop_bits: 1 parity: NONE @@ -286,13 +303,18 @@ uart: delimiter: "\r" sequence: - lambda: UARTDebug::log_hex(direction, bytes, ' '); - +# sun: id: sun_sensor latitude: !secret latitude longitude: !secret longitude time: +# #- platform: ds1307 +# # repeated synchronization is not necessary unless the external RTC +# # is much more accurate than the internal clock +# # update_interval: never +# # - platform: sntp # timezone: Africa/Johannesburg # servers: @@ -302,27 +324,28 @@ time: - platform: homeassistant id: time_source on_time_sync: + #- ds1307.write_time: - lambda: |- id(time_synched) = true; id(init_holidays).execute(); // we need valid time to calculate holidays - // id(show_schedule).execute(); // for debugging +# // id(show_schedule).execute(); // for debugging - logger.log: "Synchronized system clock" on_time: - # do every year on the first day of the first month at one second after midnight - - seconds: 1 - minutes: 0 - hours: 0 - days_of_month: 1 - months: 1 - then: - - sensor.integration.reset: yearly_geyser_energy - - sensor.integration.reset: yearly_plugs_energy - - sensor.integration.reset: yearly_mains_energy - - sensor.integration.reset: yearly_lights_energy - - sensor.integration.reset: yearly_generated_energy - - sensor.integration.reset: yearly_house_energy_usage - - sensor.integration.reset: yearly_energy_loss +# # do every year on the first day of the first month at one second after midnight +# - seconds: 1 +# minutes: 0 +# hours: 0 +# days_of_month: 1 +# months: 1 +# then: +# - sensor.integration.reset: yearly_geyser_energy +# - sensor.integration.reset: yearly_plugs_energy +# - sensor.integration.reset: yearly_mains_energy +# - sensor.integration.reset: yearly_lights_energy +# - 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 - seconds: 1 @@ -353,18 +376,15 @@ time: - lambda: |- id(record_heat_gained).execute(); - # do every second +# # do every second - seconds: '*' minutes: '*' then: - lambda: |- - id(level_shifter_output_enable).turn_on(); + // id(level_shifter_output_enable).turn_on(); // id(get_ha_settings).execute(); - // id(update_power_counters).execute(); - id(set_active_schedule).execute(); - id(set_active_heating_timers).execute(); - id(set_geyser_relay).execute(); - id(set_heat_indicators).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); @@ -413,8 +433,23 @@ time: state: !lambda |- auto time_obj = ESPTime::from_epoch_local(id(active_schedule_period)[1]); return time_obj.strftime("%Y-%m-%d %H:%M:%S"); - +# interval: + - interval: 30s + then: + - script.execute: set_geyser_relay + + - interval: 1s + then: + - script.execute: set_active_schedule + - script.execute: set_active_heating_timers + - script.execute: reset_geyser_relay + - script.execute: set_heat_indicators + + - interval: 5s + then: + - script.execute: send_battery_info_request + - interval: 100ms then: lambda: |- @@ -560,7 +595,7 @@ interval: if(store.getpublish()) { canid_set.insert(store.can_id); if(i == ${CB_MAX_RETRANSMISSIONS} - 1) { - ESP_LOGI(store.tag().c_str(), "%s", store.to_string().c_str()); // we display once, using opportunity at end of sequence (when publish flag is reset) + ESP_LOGV(store.tag().c_str(), "%s", store.to_string().c_str()); // we display once, using opportunity at end of sequence (when publish flag is reset) store.setpublish(false); } } @@ -580,7 +615,7 @@ modbus: uart_id: inv_uart2 send_wait_time: 1200ms #250ms disable_crc: false - role: server + role: client modbus_controller: - id: modbus_device1 @@ -588,7 +623,7 @@ modbus_controller: address: 0x01 allow_duplicate_commands: False command_throttle: 700ms #2022ms - update_interval: 60s #305s + update_interval: 30s #305s offline_skip_updates: 2 max_cmd_retries: 1 setup_priority: -10 @@ -597,14 +632,14 @@ modbus_controller: address: 0x01 allow_duplicate_commands: False command_throttle: 0ms - update_interval: 60s #30s + update_interval: 30s #30s offline_skip_updates: 2 - max_cmd_retries: 0 + max_cmd_retries: 1 setup_priority: -10 canbus: - platform: mcp2515 - cs_pin: GPIO15 + cs_pin: GPIO13 # CB1CS spi_id: spi_bus0 id: canbus_sthome mode: NORMAL @@ -621,28 +656,30 @@ canbus: if(time_obj.is_valid()) { if(can_id >= 0x350 && can_id < 0x380) { auto cbitem = cbf_store_pylon(id(can2_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); bool publish = id(g_cb_cache).additem(cbitem); - //if(publish) { - // ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); - //} + if(publish) { + ESP_LOGV(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } } else if(can_id >= 0x400 && can_id <= 0x580) { auto cbitem = cbf_store_sthome(id(can2_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); bool publish = id(g_cb_cache).additem(cbitem); - //if(publish) { - // ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); - //} + if(publish) { + ESP_LOGV(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); + } } else { - ESP_LOGI("WARN", "Request within unhandled range CAN_ID: 0x%X. Request ignored!", can_id); + ESP_LOGW("canbus", "Request within unhandled range CAN_ID: 0x%X. Request ignored!", can_id); } } - platform: mcp2515 - cs_pin: GPIO5 + cs_pin: GPIO14 # CB2CS spi_id: spi_bus0 id: canbus_solarbattery - mode: LISTENONLY + mode: NORMAL #LISTENONLY can_id: ${CB_CANBUS_ID08} bit_rate: 500KBPS on_frame: @@ -651,6 +688,8 @@ canbus: then: - lambda: |- id(can1_msgctr)++; + auto time_obj = id(time_source).now(); + id(last_battery_message_time) = time_obj.timestamp; //id(canbus_sthome)->send_data(can_id, false, x); //ESP_LOGI("SND_BAT", "0x%X", can_id); @@ -661,6 +700,7 @@ canbus: auto time_obj = id(time_source).now(); if(time_obj.is_valid()) { auto cbitem = cbf_store_pylon(id(can1_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); bool publish = id(g_cb_cache).additem(cbitem); if(publish) { float value = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V @@ -679,6 +719,7 @@ canbus: auto time_obj = id(time_source).now(); if(time_obj.is_valid()) { auto cbitem = cbf_store_pylon(id(can1_msgctr), can_id, x, remote_transmission_request, time_obj.timestamp); + //ESP_LOGI(cbitem.tag().c_str(), "%s", cbitem.to_string().c_str()); bool publish = id(g_cb_cache).additem(cbitem); if(publish) { auto value = static_cast((x[1] << 8) + x[0]); @@ -793,24 +834,24 @@ switch: 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: level_shifter_output_enable +# pin: +# number: GPIO12 +# inverted: false +# mode: +# output: true +# pullup: true +# restore_mode: ALWAYS_OFF - platform: gpio id: reset_energy_counters pin: - number: GPIO34 + number: GPIO2 inverted: true mode: input: true - pullup: false # external pullup + pullup: true name: "Reset Energy Counters" disabled_by_default: True restore_mode: RESTORE_DEFAULT_OFF @@ -834,29 +875,7 @@ switch: - platform: gpio pin: - number: GPIO36 - inverted: true - mode: - input: true - pullup: false # external pullup - id: vacation_mode_switch - name: "Vacation Mode" - restore_mode: RESTORE_DEFAULT_OFF - - - platform: gpio - pin: - number: GPIO35 - inverted: true - mode: - input: true - pullup: false # external pullup - id: school_holiday_mode_switch - name: "School Holiday Mode" - restore_mode: RESTORE_DEFAULT_OFF - - - platform: gpio - pin: - number: GPIO14 + number: GPIO16 inverted: false mode: output id: geyser_relay @@ -866,15 +885,15 @@ switch: on_turn_on: - lambda: |- id(geyser_relay_status) = true; // only set to false by other sensor / script to include hysteresis and thus avoid relay chattering - ESP_LOGI("info", "************* 1: Geyser Relay turned on"); + ESP_LOGI("info", "Geyser Relay turned on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "************* 2: Geyser Relay turned off"); + ESP_LOGI("info", "Geyser Relay turned off"); - platform: gpio pin: - number: GPIO13 - inverted: false + number: GPIO17 + inverted: true mode: output id: pool_relay name: "Pool Relay" @@ -884,75 +903,138 @@ 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", "************* 1: Pool Relay turned on"); + ESP_LOGI("info", "Pool Relay turned on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "************* 2: Pool Relay turned off"); - + ESP_LOGI("info", "Pool Relay turned off"); +# +##tlc59208f: +## address: 0x40 +## id: tlc59208f_1 +# output: - platform: ledc pin: - number: GPIO26 - inverted: false + number: GPIO27 + inverted: true id: led_geyser_temp_blue - platform: ledc pin: - number: GPIO25 - inverted: false + number: GPIO26 + inverted: true id: led_geyser_temp_green - platform: ledc pin: - number: GPIO33 - inverted: false + number: GPIO25 + inverted: true id: led_geyser_temp_yellow - platform: ledc pin: - number: GPIO32 - inverted: false + number: GPIO15 + inverted: true + id: led_geyser_temp_orange + - platform: ledc + pin: + number: GPIO1 + inverted: true id: led_geyser_temp_red - platform: ledc pin: - number: GPIO27 - inverted: false + 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 +# light: - platform: monochromatic output: led_geyser_temp_blue + name: "LED Geyser Temperature Blue" id: led_geyser_temp1 on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 1 on"); + ESP_LOGI("info", "Geyser Temperature LED BLUE on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 1 off"); + ESP_LOGI("info", "Geyser Temperature LED BLUE off"); - platform: monochromatic output: led_geyser_temp_green + name: "LED Geyser Temperature Green" id: led_geyser_temp2 on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 2 on"); + ESP_LOGI("info", "Geyser Temperature LED GREEN on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 2 off"); + ESP_LOGI("info", "Geyser Temperature LED GREEN off"); - platform: monochromatic output: led_geyser_temp_yellow + name: "LED Geyser Temperature Yellow" id: led_geyser_temp3 on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 3 on"); + ESP_LOGI("info", "Geyser Temperature LED YELLOW on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 3 off"); + ESP_LOGI("info", "Geyser Temperature LED YELLOW off"); - platform: monochromatic - output: led_geyser_temp_red + output: led_geyser_temp_orange + name: "LED Geyser Temperature Orange" id: led_geyser_temp4 on_turn_on: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 4 on"); + ESP_LOGI("info", "Geyser Temperature LED ORANGE on"); on_turn_off: - lambda: |- - ESP_LOGI("info", "Geyser Temperature LED 4 off"); + ESP_LOGI("info", "Geyser Temperature LED ORANGE off"); + - platform: monochromatic + output: led_geyser_temp_red + name: "LED Geyser Temperature Red" + id: led_geyser_temp5 + on_turn_on: + - lambda: |- + ESP_LOGI("info", "Geyser Temperature LED RED on"); + on_turn_off: + - lambda: |- + ESP_LOGI("info", "Geyser Temperature LED RED off"); - platform: monochromatic output: led_inverter_battery_low name: "LED Inverter Battery Low" @@ -971,7 +1053,13 @@ binary_sensor: name: "Status" device_class: connectivity - - platform: template + - platform: gpio + pin: + number: GPIO39 + inverted: false + mode: + input: true + pullup: false # external pullup filters: - delayed_off: 50ms id: inverter_battery_charge_state @@ -987,6 +1075,20 @@ binary_sensor: - 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" + - platform: template id: geyser_heating name: "Geyser Heating" @@ -998,7 +1100,7 @@ binary_sensor: id: mains_supply name: "Mains Supply" lambda: |- - return id(mains_voltage_adc).state > 180; // minimum acceptable voltage + 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 @@ -1013,7 +1115,7 @@ binary_sensor: on_state: then: - lambda: |- - ESP_LOGI("info", "Inverter 1 & 2 are being overloaded. Turning geyser off."); + ESP_LOGI("info", "Inverter 1 & 2 are being overloaded. Heavy loads should be switched off."); # - switch.turn_off: geyser_relay on_release: then: @@ -1059,22 +1161,6 @@ binary_sensor: } } return false; - - # in vacation mode, geyser is only switched on when it can be powered by solar only, i.e. without using mains - - platform: template #gpio - id: vacation_mode - #pin: - # number: GPIO04 - # mode: - # input: true - # pullup: true - #filters: - # - delayed_off: 100ms - name: "Vacation Mode" - icon: "mdi:beach" - # remove lambda if controlled by external switch - lambda: |- - return id(vacation_mode_switch).state; # SOLAR BATTERY - platform: template @@ -1158,8 +1244,8 @@ binary_sensor: name: "Battery Charging" device_class: battery_charging lambda: "return id(battery_system_current).state > 0;" - - ## inverters +# +# Inverter 1 - platform: template name: "Inv1 Battery Connected" # device_class: problem @@ -1228,6 +1314,7 @@ binary_sensor: int line_flow = (id(g_inv1_power_flow) >> 8) & 3; return line_flow & 0x10; +# Inverter 2 - platform: template name: "Inv2 Battery Connected" # device_class: problem @@ -1295,9 +1382,31 @@ binary_sensor: lambda: |- int line_flow = (id(g_inv2_power_flow) >> 8) & 3; return line_flow & 0x10; - + + - platform: template + name: "Solar Surplus" + id: solar_surplus + 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_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); + //ESP_LOGI("solar", "Solar Power surplus? : %s", surplus ? "Yes" : "No"); + return surplus; sensor: + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + 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 @@ -1331,7 +1440,6 @@ sensor: return 0.0; return x; # mod end ####################### - - platform: ads1115 multiplexer: 'A2_A3' gain: 2.048 # 4.096 @@ -1399,11 +1507,11 @@ sensor: - below: 5.0 then: - lambda: |- - ESP_LOGI("info", "Geyser lost power."); + ESP_LOGI("info", "No geyser current detected. Geyser not heating."); - above: 0.5 then: - lambda: |- - ESP_LOGI("info", "Geyser was energised."); + ESP_LOGI("info", "Geyser current detected. Geyser was energised."); # mod end ####################### - platform: ads1115 @@ -1495,197 +1603,197 @@ sensor: 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 - -# # 30A clamp -# - platform: ct_clamp -# sensor: geyser_current_adc -# id: geyser_current -# name: "Geyser Current" -# update_interval: 2s -# sample_duration: 2000ms #15000ms -# state_class: measurement -# device_class: current -# filters: -# # burden resistor is 62Ω in parallel with 33Ω = 21.54Ω -# # multiplier should be 1860/21.54 = x86.35 -# - multiply: 88.51 # real world -# - lambda: |- -# if(x < 0.25) -# return 0.0; -# return x; -# on_value_range: -# - below: 0.5 -# then: -# - lambda: |- -# ESP_LOGI("info", "Geyser lost power."); -# - above: 0.5 -# then: -# - lambda: |- -# ESP_LOGI("info", "Geyser was energised."); - -# # 30A clamp -# - platform: ct_clamp -# sensor: lights_current_adc -# id: lights_current -# name: "Lights Current" -# update_interval: 1s -# sample_duration: 1s #15000ms -# state_class: measurement -# device_class: current -# filters: -# # burden resistor is 62Ω in parallel with 33Ω = 21.54Ω -# # multiplier should be 1860/21.54 = x86.35 -# - multiply: 88.44 # real world -# - lambda: |- -# if(x < 0.25) -# return 0.0; -# return x; - -# # 100A clamp -# - platform: ct_clamp -# sensor: mains_current_adc -# id: mains_current -# name: "Mains Current" -# update_interval: 1s -# sample_duration: 1s #15000ms -# state_class: measurement -# device_class: current -# filters: -# # burden resistor is 22Ω -# # multiplier should be 2000/22 = x90.9 -# - multiply: 90.25 # real world -# - lambda: |- -# if(x < 0.25) -# return 0.0; -# return x; +## # 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 +## sensor: geyser_current_adc +## id: geyser_current +## name: "Geyser Current" +## update_interval: 2s +## sample_duration: 2000ms #15000ms +## state_class: measurement +## device_class: current +## filters: +## # burden resistor is 62Ω in parallel with 33Ω = 21.54Ω +## # multiplier should be 1860/21.54 = x86.35 +## - multiply: 88.51 # real world +## - lambda: |- +## if(x < 0.25) +## return 0.0; +## return x; +## on_value_range: +## - below: 0.5 +## then: +## - lambda: |- +## ESP_LOGI("info", "Geyser lost power."); +## - above: 0.5 +## then: +## - lambda: |- +## ESP_LOGI("info", "Geyser was energised."); +# +## # 30A clamp +## - platform: ct_clamp +## sensor: lights_current_adc +## id: lights_current +## name: "Lights Current" +## update_interval: 1s +## sample_duration: 1s #15000ms +## state_class: measurement +## device_class: current +## filters: +## # burden resistor is 62Ω in parallel with 33Ω = 21.54Ω +## # multiplier should be 1860/21.54 = x86.35 +## - multiply: 88.44 # real world +## - lambda: |- +## if(x < 0.25) +## return 0.0; +## return x; +# +## # 100A clamp +## - platform: ct_clamp +## sensor: mains_current_adc +## id: mains_current +## name: "Mains Current" +## update_interval: 1s +## sample_duration: 1s #15000ms +## state_class: measurement +## device_class: current +## filters: +## # burden resistor is 22Ω +## # multiplier should be 2000/22 = x90.9 +## - multiply: 90.25 # real world +## - lambda: |- +## if(x < 0.25) +## return 0.0; +## return x; +## +## # 100A clamp +## - platform: ct_clamp +## sensor: power_outlets_current_adc +## id: power_outlets_current +## name: "Plugs Supply Current" +## update_interval: 1s +## sample_duration: 1s #15000ms +## state_class: measurement +## device_class: current +## filters: +## # burden resistor is 22Ω +## # multiplier should be 2000/22 = x90.9 +## - multiply: 91.14 # real world +## - lambda: |- +## if(x < 0.25) +## 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 # -# # 100A clamp -# - platform: ct_clamp -# sensor: power_outlets_current_adc -# id: power_outlets_current -# name: "Plugs Supply Current" -# update_interval: 1s -# sample_duration: 1s #15000ms -# state_class: measurement -# device_class: current -# filters: -# # burden resistor is 22Ω -# # multiplier should be 2000/22 = x90.9 -# - multiply: 91.14 # real world -# - lambda: |- -# if(x < 0.25) -# 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 - # for now we use a template until we get a voltage sensor - platform: template id: mains_voltage @@ -1726,7 +1834,7 @@ sensor: state_class: measurement - platform: template - # if no current is flowing to estimate heating time +# # if no current is flowing to estimate heating time id: geyser_element_power unit_of_measurement: "W" name: "Geyser Element Power" @@ -2158,8 +2266,7 @@ sensor: state_class: total_increasing device_class: energy unit_of_measurement: 'kWh' - accuracy_decimals: 3 - + accuracy_decimals: 3 # # lifetime integration sensor - platform: integration name: 'Energy Loss' @@ -2373,7 +2480,17 @@ sensor: accuracy_decimals: 1 unit_of_measurement: "A" state_class: measurement - device_class: current + device_class: current + on_value: + then: + lambda: |- + float rate = 0.0; + float current = x; + 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 id: battery_average_cell_temperature name: "Battery Cell Temperature" @@ -2394,15 +2511,39 @@ sensor: accuracy_decimals: 1 unit_of_measurement: "A" state_class: measurement - device_class: current + device_class: current + on_value: + then: + lambda: |- + float rate = 0.0; + float current = id(battery_system_current).state; + float current_limit = x; + if(current > 0 && current_limit > 0) { + rate = 100 * current / current_limit; + } + id(battery_charging_rate).publish_state(rate); - 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 -## inverters + device_class: current + # charging rate as a percentage of potential charging rate + - platform: template + name: "Battery Charging Rate" + id: battery_charging_rate + accuracy_decimals: 1 + unit_of_measurement: "%" + state_class: measurement + device_class: current + #lambda: |- + # if(id(battery_system_current).state < 0) { + # return 0.0; + # } + # return 100 * id(battery_system_current).state / id(battery_charge_current_limit).state; +# +# Inverter 1 - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 SettingDataSn" @@ -2461,6 +2602,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 BatteryPower" + id: inv1_batterypower register_type: holding address: ${Felicity_Inv_BatteryPower} # 0x110A value_type: S_WORD @@ -2485,6 +2627,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 AC Input Voltage" + id: inv1_ac_input_voltage register_type: holding address: ${Felicity_Inv_ACInputVoltage} # 0x1117 value_type: U_WORD @@ -2498,6 +2641,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 AC Input Frequency" + id: inv1_ac_input_frequency register_type: holding address: ${Felicity_Inv_ACInputFrequency} # 0x1119 value_type: U_WORD @@ -2511,6 +2655,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 AC Output Active Power" + id: inv1_ac_output_active_power register_type: holding address: ${Felicity_Inv_ACOutputActivePower} # 0x111E value_type: S_WORD @@ -2544,6 +2689,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 PV Input Voltage" + id: inv1_pv_input_voltage register_type: holding address: ${Felicity_Inv_PVInputVoltage} # 0x1126 value_type: U_WORD @@ -2557,6 +2703,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 PV Input Power" + id: inv1_pv_input_power register_type: holding address: ${Felicity_Inv_PVInputPower} # 0x112A value_type: S_WORD @@ -2624,6 +2771,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 BatteryPower" + id: inv2_batterypower register_type: holding address: ${Felicity_Inv_BatteryPower} # 0x110A value_type: S_WORD @@ -2648,6 +2796,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 AC Input Voltage" + id: inv2_ac_input_voltage register_type: holding address: ${Felicity_Inv_ACInputVoltage} # 0x1117 value_type: U_WORD @@ -2674,6 +2823,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 AC Output Active Power" + id: inv2_ac_output_active_power register_type: holding address: ${Felicity_Inv_ACOutputActivePower} # 0x111E value_type: S_WORD @@ -2720,6 +2870,7 @@ sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 PV Input Power" + id: inv2_pv_input_power register_type: holding address: ${Felicity_Inv_PVInputPower} # 0x112A value_type: S_WORD @@ -2728,7 +2879,53 @@ sensor: device_class: power accuracy_decimals: 0 + - platform: template + name: "PV Input Power" + id: pv_input_power + unit_of_measurement: "W" + device_class: power + lambda: |- + return id(inv1_pv_input_power).state + id(inv2_pv_input_power).state; + + - platform: template + name: "Battery Power" + id: battery_power + unit_of_measurement: "W" + device_class: power + lambda: |- + return id(inv1_batterypower).state + id(inv2_batterypower).state; + + - platform: template + name: "Local Generated Power" + id: local_generated_power + unit_of_measurement: "W" + device_class: power + lambda: |- + return id(inv1_pv_input_power).state + id(inv2_pv_input_power).state - id(inv1_batterypower).state - id(inv2_batterypower).state; + + - platform: template + name: "House Power Draw" + id: house_power_draw + unit_of_measurement: "W" + device_class: power + lambda: |- + return id(inv1_ac_output_active_power).state + id(inv2_ac_output_active_power).state; + + - platform: template + name: "Grid Power Draw" + id: grid_power_draw + unit_of_measurement: "W" + device_class: power + lambda: |- + return id(inv1_ac_output_active_power).state + id(inv2_ac_output_active_power).state + id(inv1_batterypower).state + id(inv2_batterypower).state - id(inv1_pv_input_power).state - id(inv2_pv_input_power).state; + text_sensor: + - platform: debug + device: + name: "Device Info" + reset_reason: + name: "Reset Reason" + - platform: template id: calculated_heat_loss_text name: "Heat loss (est)" @@ -2822,6 +3019,7 @@ text_sensor: - platform: modbus_controller modbus_controller_id: modbus_device1 name: "Inv1 SerialNo" + id: inv1_serialno register_type: holding address: ${Felicity_Inv_SerialNo} # 0xF804 response_size: 14 # should be 10, but absorbing extra four bytes @@ -2937,6 +3135,7 @@ text_sensor: - platform: modbus_controller modbus_controller_id: modbus_device2 name: "Inv2 SerialNo" + id: inv2_serialno register_type: holding address: ${Felicity_Inv_SerialNo} # 0xF804 response_size: 14 # should be 10, but absorbing extra four bytes @@ -3119,7 +3318,7 @@ script: index = id(is_school_holiday).state ? ${GM_SCHOOL_HOLIDAY} : id(is_public_holiday).state ? ${GM_PUBLIC_HOLIDAY} : (dayofweek == 1) ? ${GM_SUNDAY} : (dayofweek == 7) ? ${GM_SATURDAY} : ${GM_WORKDAY}; } else { - index = 0; // default + index = ${GM_WORKDAY}; // default } - id: set_active_heating_timers @@ -3149,61 +3348,164 @@ script: 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; - if(temp_bottom < 40) { - brightness = 0.1*(40 - temp_bottom); + 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 >= 60) { - brightness = 0.1*(temp_top - 60); + 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 >= 50) { - led_yellow = 0.1*(temp_top - 50); // yellow - led_green = 1; - } else if(temp_top >= 40) { - led_green = 0.1*(temp_top - 40); // green - } - } - else if(temp_bottom >= 40 && temp_bottom < 50) { - led_green = 0.1*(50 - temp_bottom); // green - if(temp_top >= 60) { - brightness = 0.1*(temp_top - 60); - led_red = brightness > 1 ? 1 : brightness; // red + } else if(temp_top >= temp3) { + led_orange = 0.1*(temp_top - temp3); // orange led_yellow = 1; - } else if(temp_top >= 50) { - led_yellow = 0.1*(temp_top - 50); // yellow + 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 } } - else if(temp_bottom >= 50 && temp_bottom < 60) { - led_yellow = 0.1*(60 - temp_bottom); // yellow - if(temp_top >= 60) { - brightness = 0.1*(temp_top - 60); + 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 + } + } + // 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 + } + } + // 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 > 60) { + 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 - 60); // red + brightness = 0.1*(max_temp - temp4); // red led_red = brightness > 1 ? 1 : brightness; // red } - if(temp_top >= 60) { + 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 - 60); // red + brightness = 0.1*(max_temp - temp4); // red led_red = brightness > 1 ? 1 : brightness; // red } 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_red %f", temp_top, temp_bottom, led_blue, led_green, led_yellow, 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); + + # do at 1 second intervals - check if geyser is on, and switch off if required + - id: reset_geyser_relay + then: + - if: + condition: + lambda: "return id(inverter_battery_charge_state).state || id(battery_soc).state < 60 ;" + then: + - light.turn_on: + id: light_inverter_battery_low + brightness: 100% + else: + - light.turn_off: + id: light_inverter_battery_low + - lambda: |- + 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); + 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"); + 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); + id(geyser_relay_status) = true; + } + else { + if(now > id(active_heating_end)) { + // 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)); + } + // 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"); + } + 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)); + } + 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)); + } + 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); + } + if(id(geyser_relay_status)) { + // 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); + } + } + } + + # do at 10 second intervals or longer - check if geyser is off and switch on if required - id: set_geyser_relay then: - if: @@ -3218,56 +3520,24 @@ script: id: light_inverter_battery_low - lambda: |- bool battery_low = id(inverter_battery_charge_state).state || id(battery_soc).state < 60; - ESP_LOGV("info", "----------- inverter_battery_charge_state %d, battery_low %d, battery soc %f, mains_supply %d, mains voltage %f", id(inverter_battery_charge_state).state, battery_low, id(battery_soc).state, id(mains_supply).state, id(mains_voltage_adc).state); double sun_elevation = id(sun_sensor).elevation(); bool sun_high_enough = sun_elevation >= id(sun_elevation_minimum); auto currenttime = id(time_source).now(); if(currenttime.is_valid()) { time_t now = currenttime.timestamp; bool relay_on = id(geyser_relay).state; - ESP_LOGV("info", "Geyser heating is turned %s.", (relay_on) ? "on" : "off"); - if(relay_on) { - // GEYSER IS ENERGISED - // =================== - if(now > id(active_heating_end)) { - // 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)); - } - // 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"); - } - 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)); - } - 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)); - } - 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); - } - if(id(geyser_relay_status)) { - // switch off geyser if vacation mode requires it to be switched off - id(vacation_mode_set_geyser_relay).execute(relay_on, sun_high_enough); - } - if(!id(geyser_relay_status)) { - id(geyser_relay).turn_off(); - ESP_LOGI("info", "----------- Geyser was turned off at %f °C.", id(geyser_top_temperature).state); - } - } - else { + ESP_LOGD("geyser", "Geyser heating is turned %s.", (relay_on) ? "on" : "off"); + if(!relay_on) { + id(geyser_relay_status) = false; // GEYSER IS NOT ENERGISED // ======================= - if(id(active_heating_time) <= 0 || now > id(active_heating_end)) { + // 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); + id(geyser_relay_status) = true; + id(geyser_relay).turn_on(); + } + else if(id(active_heating_time) <= 0 || now > id(active_heating_end)) { // no more heat required OR we are past the scheduled heating end id(geyser_relay_status) = false; // ensure geyser saved state is set to 'off' } @@ -3277,41 +3547,41 @@ 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("info", "+++++++++++ 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("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)); } else if(battery_low && !id(mains_supply).state) { // inverter battery is low - ESP_LOGI("info", "+++++++++++ 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("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)); } else if((!id(mains_supply).state) && !sun_high_enough) { // sun is not high enough above horizon and mains supply is off - ESP_LOGI("info", "+++++++++++ 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("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); } else { id(geyser_relay_status) = true; } if(id(geyser_relay_status)) { - // leave geyser switched off if vacation mode requires it to be switched off - id(vacation_mode_set_geyser_relay).execute(relay_on, sun_high_enough); + // 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_LOGI("info", "+++++++++++ 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_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)); } } } } } - # set/reset geyser_relay_status variable if vacation mode requires it - - id: vacation_mode_set_geyser_relay + # set/reset geyser_relay_status variable if economy mode requires it + - id: economy_mode_set_geyser_relay parameters: relay_on: bool sun_high_enough: bool then: - lambda: |- - if(id(vacation_mode).state) { - // only set/reset geyser_relay_status here if vacation mode is active + if(id(economy_mode).state) { + // only set/reset geyser_relay_status here if economy mode is active if(relay_on) { double solar_power = id(generated_power).state; double geyser_power = id(geyser_element_power).state; @@ -3319,11 +3589,11 @@ script: // =================== if(!sun_high_enough) { id(geyser_relay_status) = false; - ESP_LOGI("info", "+++++++++++ Vacation 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_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); } else if(solar_power < geyser_power) { id(geyser_relay_status) = false; - ESP_LOGI("info", "+++++++++++ Vacation 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_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); } } else { @@ -3623,6 +3893,16 @@ script: // insert newset at the back of the queue with can_id request counts < max_requests id(g_cb_request_queue).push(newset); } + - id: send_battery_info_request + then: + lambda: |- + auto time_obj = id(time_source).now(); + auto elapsedtime = time_obj.timestamp - id(last_battery_message_time); + if(elapsedtime >= ${BATTERY_INFO_TIMEOUT}) { + // for now we just requesting BATTERY_STATE + ESP_LOGI("battery info timeout", "sending request for CAN_ID 0x%X", solar::cbf_pylon::CB_BATTERY_STATE); + id(g_cb_cache).send_request(id(canbus_solarbattery), solar::cbf_pylon::CB_BATTERY_STATE); + } - id: canbus_send_heartbeat then: @@ -3750,21 +4030,21 @@ script: id(get_geyser_mode).execute(geysermode); alarms = ((id(inverter_battery_charge_state).state) ? 0x80 : 0) | ((id(inverter1_2_overload).state) ? 0x08 : 0); states = ((id(geyser_heating).state) ? 0x80 : 0) | ((id(geyser_relay).state) ? 0x40 : 0) | ((id(mains_supply).state) ? 0x20 : 0) | ((id(battery_charging).state) ? 0x08 : 0); - modes = ((id(vacation_mode).state) ? 0x80 : 0) | ((geysermode << 4) & 0x70); + modes = ((id(economy_mode).state) ? 0x80 : 0) | ((geysermode << 4) & 0x70); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_CONTROLLER_STATES, byte_stream); - id: canbus_send_energy_mains then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_mains_energy).state, 512, id(monthly_mains_energy).state, 32, id(yearly_mains_energy).state, 2, id(mains_energy).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_mains_energy).state, 512, id(monthly_mains_energy).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_mains_energy).state, 2, id(mains_energy).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_MAINS, x); - id: canbus_send_energy_geyser then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_geyser_energy).state, 512, id(monthly_geyser_energy).state, 32, id(yearly_geyser_energy).state, 2, id(geyser_energy).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_geyser_energy).state, 512, id(monthly_geyser_energy).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_geyser_energy).state, 2, id(geyser_energy).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_GEYSER, x); # - id: canbus_send_energy_pool @@ -3778,35 +4058,35 @@ script: then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_plugs_energy).state, 512, id(monthly_plugs_energy).state, 32, id(yearly_plugs_energy).state, 2, id(plugs_energy).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_plugs_energy).state, 512, id(monthly_plugs_energy).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_plugs_energy).state, 2, id(plugs_energy).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_PLUGS, x); - id: canbus_send_energy_lights then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_lights_energy).state, 512, id(monthly_lights_energy).state, 32, id(yearly_lights_energy).state, 2, id(lights_energy).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_lights_energy).state, 512, id(monthly_lights_energy).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_lights_energy).state, 2, id(lights_energy).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_LIGHTS, x); - id: canbus_send_energy_house then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_house_energy_usage).state, 512, id(monthly_house_energy_usage).state, 32, id(yearly_house_energy_usage).state, 2, id(house_energy_usage).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_house_energy_usage).state, 512, id(monthly_house_energy_usage).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_house_energy_usage).state, 2, id(house_energy_usage).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_HOUSE, x); - id: canbus_send_energy_generated then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_generated_energy).state, 512, id(monthly_generated_energy).state, 32, id(yearly_generated_energy).state, 2, id(generated_energy).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_generated_energy).state, 512, id(monthly_generated_energy).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_generated_energy).state, 2, id(generated_energy).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_GENERATED, x); - id: canbus_send_energy_loss then: lambda: |- using namespace solar; - auto x = cb_frame::get_byte_stream(id(daily_energy_loss).state, 512, id(monthly_energy_loss).state, 32, id(yearly_energy_loss).state, 2, id(energy_loss).state, 0.1); + auto x = cb_frame::get_byte_stream(id(daily_energy_loss).state, 512, id(monthly_energy_loss).state, 32, 0.0, 2, 0.0, 0.1); // id(yearly_energy_loss).state, 2, id(energy_loss).state, 0.1); id(g_cb_cache).send_frame(id(canbus_sthome), cbf_sthome::CB_ENERGY_LOSS, x); - id: canbus_send_battery_limits @@ -3845,6 +4125,8 @@ script: using namespace solar; id(g_cb_cache).send_frame(id(canbus_sthome), cbf_pylon::CB_BATTERY_MANUFACTURER); +#################################################################################################################################### +#################################################################################################################################### # Geyser HEATING Calculations # HEAT LOSS # The primary formula for calculating heat loss in a water heater is Q = U x A x ΔT, where: