diff --git a/apps/predbat/execute.py b/apps/predbat/execute.py index 2076b2499..2ab45416c 100644 --- a/apps/predbat/execute.py +++ b/apps/predbat/execute.py @@ -790,25 +790,32 @@ def execute_plan(self): # Don't disable discharge during force charge/discharge slots but otherwise turn it off to prevent # from draining the battery if not isExporting: - if inverter.inv_has_timed_pause: - if resetPause: - inverter.adjust_pause_mode(pause_discharge=True) - pause_discharge_requested = True - resetPause = False + # Not while this inverter is actually charging, unless it can pause: the battery is being + # filled from the grid, so it cannot be feeding the car. A discharge rate of 0 adds nothing + # then, and some GivEnergy inverters will not grid-charge at all with it at 0 and the SoC on + # the reserve (#5419); pinning reserve just above a rising SoC costs a write for every 1% of + # the climb (#3899). Both are left to reset below for the duration, and applied once charging + # stops - which is the point the inverter returns to demand and the hold starts to mean + # something. A timed pause is one latched write that blocks only discharge, so it stays on + # through the charge, covering the moments the inverter reaches its target before Predbat + # sees it. The sibling iBoost hold below already sits out a charge for the same reason. + hold_deferred = (not inverter.inv_has_timed_pause) and status_per_inverter.get(inverter.id) == "Charging" + if hold_deferred: + self.log("Inverter {} is charging, so the hold for car {} leaves the discharge rate and reserve alone until charging stops".format(inverter.id, car_n)) else: - if discharge_rate is None: - discharge_rate = 0 - # Not while actually charging: the battery is being filled from the grid, so it - # cannot be feeding the car, and pinning reserve just above a rising SoC costs a - # write for every 1% of the climb (#3899). Left to reset below for the duration, - # and latched at the SoC reached once charging stops - which is the point the - # inverter returns to demand and the hold starts to mean something. The sibling - # iBoost hold below already sits out a charge for the same reason. - if self.set_reserve_enable and status != "Charging": - inverter.adjust_reserve(min(inverter.soc_percent + 1, 100)) - resetReserve = False + if inverter.inv_has_timed_pause: + if resetPause: + inverter.adjust_pause_mode(pause_discharge=True) + pause_discharge_requested = True + resetPause = False + else: + if discharge_rate is None: + discharge_rate = 0 + if self.set_reserve_enable: + inverter.adjust_reserve(min(inverter.soc_percent + 1, 100)) + resetReserve = False + self.log("Disabling battery discharge whilst car {} is charging".format(car_n)) carHolding = True - self.log("Disabling battery discharge whilst car {} is charging".format(car_n)) if ("Hold for car" not in status) and (status_hold_car == ""): if status == "Demand": status = "Hold for car" diff --git a/apps/predbat/tests/test_execute.py b/apps/predbat/tests/test_execute.py index 0f1b7fd46..9b9c16f7e 100644 --- a/apps/predbat/tests/test_execute.py +++ b/apps/predbat/tests/test_execute.py @@ -3707,9 +3707,32 @@ def run_execute_tests(my_predbat): assert_immediate_soc_target=100, has_timed_pause=False, assert_pause_discharge=False, - assert_discharge_rate=0, assert_reserve=0, ) + # #5419: nor may it pin the discharge rate to 0 while charging - a charging battery cannot feed the car, and + # some GivEnergy inverters will not grid-charge at all with the rate at 0 and the SoC on the reserve. The + # scenario above asserts the rate staying at its maximum. Once the battery stops charging and holds instead, + # it can feed the car again, so the rate hold must still apply there: + failed |= run_execute_test( + my_predbat, + "car_charge2_no_pause", + charge_window_best=charge_window_best, + charge_limit_best=charge_limit_best2, + soc_kw=10, + car_slot=charge_window_best_slot, + assert_charge_time_enable=False, + set_charge_window=True, + set_export_window=True, + assert_status="Hold charging, Hold for car", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=-1, + assert_immediate_soc_target=50, + assert_soc_target=100, + assert_reserve=100, + has_timed_pause=False, + assert_pause_discharge=False, + assert_discharge_rate=0, + ) failed |= run_execute_test( my_predbat, "car_charge2", diff --git a/docs/car-charging.md b/docs/car-charging.md index acbc0bf2e..cb50c0452 100644 --- a/docs/car-charging.md +++ b/docs/car-charging.md @@ -784,6 +784,7 @@ Note: [Multiple cars](car-charging.md#multiple-electric-cars) can be planned wit - **switch.predbat_car_charging_from_battery** - When set to On the car can drain the home battery, Predbat will manage the correct level of battery accordingly. When set to Off home battery discharge will be prevented when your car charges, and all load from the car and home will be from the grid. This is achieved by setting the battery discharge rate to 0 during car charging and to the maximum otherwise. +On inverters without a timed pause, the car hold does not set the discharge rate to 0 while the home battery is itself charging in a charge window, as a charging battery cannot feed the car. The home battery can still charge from the grid/solar in either case. Only use this if Predbat knows your car charging plan, e.g. you are using Intelligent Octopus or you use the car slots in Predbat to control your car charging.