From 94f36f728c76acea4b600115dcc0d1fdfa5f8117 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:23:06 +0000 Subject: [PATCH] Max charge rate temperature curve adjustments --- apps/predbat/execute.py | 16 ++- apps/predbat/fetch.py | 7 + apps/predbat/inverter.py | 5 + apps/predbat/predbat.py | 4 +- apps/predbat/prediction.py | 43 +++--- apps/predbat/unit_test.py | 271 +++++++++++++++++++++---------------- 6 files changed, 200 insertions(+), 146 deletions(-) diff --git a/apps/predbat/execute.py b/apps/predbat/execute.py index 32b523f9..87a8890c 100644 --- a/apps/predbat/execute.py +++ b/apps/predbat/execute.py @@ -11,7 +11,7 @@ import sys from datetime import datetime, timedelta from config import MINUTE_WATT, PREDICT_STEP -from utils import dp2, dp3, calc_percent_limit, find_charge_rate +from utils import dp0, dp2, dp3, calc_percent_limit, find_charge_rate from inverter import Inverter """ @@ -101,8 +101,7 @@ def execute_plan(self): self.log("Inverter {} Charge window will be: {} - {} - current soc {} target {}".format(inverter.id, charge_start_time, charge_end_time, inverter.soc_percent, self.charge_limit_percent_best[0])) # Are we actually charging? if self.minutes_now >= minutes_start and self.minutes_now < minutes_end: - new_charge_rate = int( - find_charge_rate( + new_charge_rate, new_charge_rate_real = find_charge_rate( self.minutes_now, inverter.soc_kw, window, @@ -116,9 +115,10 @@ def execute_plan(self): self.battery_rate_max_scaling, self.battery_loss, self.log, + self.battery_temperature, + self.battery_temperature_discharge_curve ) - * MINUTE_WATT - ) + new_charge_rate = int(new_charge_rate * MINUTE_WATT) current_charge_rate = inverter.get_current_charge_rate() # Adjust charge rate if we are more than 10% out or we are going back to Max charge rate @@ -602,6 +602,7 @@ def fetch_inverter_data(self, create=True): self.discharge_rate_now = 0.0 self.pv_power = 0 self.load_power = 0 + self.battery_temperature = 0 found_first = False if create: @@ -666,6 +667,11 @@ def fetch_inverter_data(self, create=True): self.pv_power += inverter.pv_power self.load_power += inverter.load_power self.current_charge_limit = calc_percent_limit(self.current_charge_limit_kwh, self.soc_max) + self.battery_temperature += inverter.battery_temperature + + + # Work out battery temperature + self.battery_temperature = int(dp0(self.battery_temperature / self.num_inverters)) # Remove extra decimals self.soc_max = dp3(self.soc_max) diff --git a/apps/predbat/fetch.py b/apps/predbat/fetch.py index d4500909..4b8fd0fc 100644 --- a/apps/predbat/fetch.py +++ b/apps/predbat/fetch.py @@ -1768,6 +1768,13 @@ def fetch_config_options(self): self.log("Warn: battery_discharge_power_curve is incorrectly configured - ignoring") self.record_status("battery_discharge_power_curve is incorrectly configured - ignoring", had_errors=True) + # Temperature curve + self.battery_temperature_discharge_curve = self.args.get("battery_temperature_discharge_curve", {}) + if not isinstance(self.battery_temperature_discharge_curve, dict): + self.battery_temperature_discharge_curve = {} + self.log("Warn: battery_temperature_discharge_curve is incorrectly configured - ignoring") + self.record_status("battery_temperature_discharge_curve is incorrectly configured - ignoring", had_errors=True) + self.import_export_scaling = self.get_arg("import_export_scaling", 1.0) self.best_soc_margin = 0.0 self.best_soc_min = self.get_arg("best_soc_min") diff --git a/apps/predbat/inverter.py b/apps/predbat/inverter.py index a55ddb03..f543fa2d 100644 --- a/apps/predbat/inverter.py +++ b/apps/predbat/inverter.py @@ -138,6 +138,7 @@ def __init__(self, base, id=0, quiet=False, rest_postCommand=None, rest_getData= self.battery_rate_max_discharge = 0 self.battery_rate_max_charge_scaled = 0 self.battery_rate_max_discharge_scaled = 0 + self.battery_temperature = 20 self.battery_power = 0 self.battery_voltage = 52.0 self.pv_power = 0 @@ -258,6 +259,7 @@ def __init__(self, base, id=0, quiet=False, rest_postCommand=None, rest_getData= self.nominal_capacity = self.soc_max self.soc_max *= self.battery_scaling self.soc_max = dp3(self.soc_max) + self.battery_temperature = idetails.get("Battery_Temperature", 20) if self.rest_data and ("raw" in self.rest_data): raw_data = self.rest_data["raw"] @@ -270,6 +272,7 @@ def __init__(self, base, id=0, quiet=False, rest_postCommand=None, rest_getData= self.nominal_capacity = self.soc_max self.soc_max *= self.battery_scaling self.soc_max = dp3(self.soc_max) + self.battery_temperature = idetails.get("Battery_Temperature", 20) # Battery capacity nominal battery_capacity_nominal = raw_data.get("invertor", {}).get("battery_nominal_capacity", None) @@ -311,6 +314,7 @@ def __init__(self, base, id=0, quiet=False, rest_postCommand=None, rest_getData= if "Invertor_Time" in idetails: ivtime = idetails["Invertor_Time"] else: + self.battery_temperature = self.base.get_arg("battery_temperature", default=20, index=self.id) self.soc_max = self.base.get_arg("soc_max", default=10.0, index=self.id) * self.battery_scaling self.nominal_capacity = self.soc_max @@ -323,6 +327,7 @@ def __init__(self, base, id=0, quiet=False, rest_postCommand=None, rest_getData= ivtime = self.base.get_arg("inverter_time", index=self.id, default=None) + # Battery cannot be zero size if self.soc_max <= 0: self.base.log("Error: Reported battery size from REST is {}, but it must be >0".format(self.soc_max)) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index 3e98be69..5e48ea64 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -38,7 +38,7 @@ import asyncio import json -THIS_VERSION = "v8.10.2" +THIS_VERSION = "v8.11.0" # fmt: off PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "gecloud.py","utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py", "predheat.py", "futurerate.py", "octopus.py", "solcast.py","execute.py", "plan.py", "fetch.py", "output.py", "userinterface.py"] @@ -310,6 +310,7 @@ def reset(self): self.soc_kw = 0 self.soc_percent = 0 self.soc_max = 10.0 + self.battery_temperature = 20 self.end_record = 24 * 60 * 2 self.predict_soc = {} self.predict_soc_best = {} @@ -506,6 +507,7 @@ def reset(self): self.pv_today = {} self.load_minutes = {} self.load_minutes_age = 0 + self.battery_temperature_discharge_curve = {} self.config_root = "./" for root in CONFIG_ROOTS: diff --git a/apps/predbat/prediction.py b/apps/predbat/prediction.py index da43f835..a32d1941 100644 --- a/apps/predbat/prediction.py +++ b/apps/predbat/prediction.py @@ -151,6 +151,8 @@ def __init__(self, base=None, pv_forecast_minute_step=None, pv_forecast_minute10 self.battery_rate_max_discharge_scaled = base.battery_rate_max_discharge_scaled self.battery_charge_power_curve = base.battery_charge_power_curve self.battery_discharge_power_curve = base.battery_discharge_power_curve + self.battery_temperature = base.battery_temperature + self.battery_temperature_discharge_curve = base.battery_temperature_discharge_curve self.battery_rate_max_scaling = base.battery_rate_max_scaling self.battery_rate_max_scaling_discharge = base.battery_rate_max_scaling_discharge self.battery_loss = base.battery_loss @@ -644,28 +646,25 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi four_hour_rule = False elif (charge_window_n >= 0) and soc < charge_limit_n: # Charge enable - if self.set_charge_window and (save in ["best", "best10", "test"]): - # Only tune charge rate on final plan not every simulation - charge_rate_now = find_charge_rate( - minute_absolute, - soc, - charge_window[charge_window_n], - charge_limit_n, - self.battery_rate_max_charge, - self.soc_max, - self.battery_charge_power_curve, - self.set_charge_low_power, - self.charge_low_power_margin, - self.battery_rate_min, - self.battery_rate_max_scaling, - self.battery_loss, - None, - ) - else: - charge_rate_now = self.battery_rate_max_charge # Assume charge becomes enabled here - - # Apply the charging curve - charge_rate_now_curve = get_charge_rate_curve(soc, charge_rate_now, self.soc_max, self.battery_rate_max_charge, self.battery_charge_power_curve, self.battery_rate_min) * self.battery_rate_max_scaling + set_charge_low_power = self.set_charge_window and self.set_charge_low_power and (save in ["best", "best10", "test"]) + # Only tune charge rate on final plan not every simulation + charge_rate_now, charge_rate_now_curve = find_charge_rate( + minute_absolute, + soc, + charge_window[charge_window_n], + charge_limit_n, + self.battery_rate_max_charge, + self.soc_max, + self.battery_charge_power_curve, + set_charge_low_power, + self.charge_low_power_margin, + self.battery_rate_min, + self.battery_rate_max_scaling, + self.battery_loss, + None, + self.battery_temperature, + self.battery_temperature_discharge_curve, + ) battery_draw = -max(min(charge_rate_now_curve * step, charge_limit_n - soc), 0, -battery_to_max) battery_state = "f+" diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index f9ab2894..9cde521b 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -998,7 +998,6 @@ def test_inverter_update( return failed - def test_auto_restart(test_name, my_predbat, ha, inv, dummy_items, service, expected, active=False): print("**** Running Test: {} ****".format(test_name)) failed = 0 @@ -1016,14 +1015,13 @@ def test_auto_restart(test_name, my_predbat, ha, inv, dummy_items, service, expe if str(e) != "Auto-restart triggered": print("ERROR: Auto-restart should be triggered got {}".format(e)) failed = 1 - + result = ha.get_service_store() if json.dumps(expected) != json.dumps(result): print("ERROR: Auto-restart service should be {} got {}".format(expected, result)) failed = 1 return failed - def test_call_adjust_charge_immediate(test_name, my_predbat, ha, inv, dummy_items, soc, repeat=False, freeze=False, clear=False, stop_discharge=False, charge_start_time="00:00:00", charge_end_time="23:55:00", no_freeze=False): """ Tests; @@ -1237,7 +1235,6 @@ def run_car_charging_smart_tests(my_predbat): return failed - def test_inverter_self_test(test_name, my_predbat): failed = 0 @@ -1254,7 +1251,7 @@ def test_inverter_self_test(test_name, my_predbat): dummy_rest.rest_data["Control"]["Battery_Charge_Rate"] = 1100 dummy_rest.rest_data["Control"]["Battery_Discharge_Rate"] = 1500 dummy_rest.rest_data["Control"]["Enable_Charge_Schedule"] = "enable" - dummy_rest.rest_data["Control"]["Enable_Discharge_Schedule"] = "enable" + dummy_rest.rest_data["Control"]["Enable_Discharge_Schedule"] = "enable" dummy_rest.rest_data["Timeslots"] = {} dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = "00:30:00" dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = "22:00:00" @@ -1271,74 +1268,12 @@ def test_inverter_self_test(test_name, my_predbat): inv.sleep = dummy_sleep inv.self_test(my_predbat.minutes_now) rest = dummy_rest.get_commands() - expected = [ - ["dummy/setChargeTarget", {"chargeToPercent": 100}], - ["dummy/setChargeTarget", {"chargeToPercent": 100}], - ["dummy/setChargeTarget", {"chargeToPercent": 100}], - ["dummy/setChargeTarget", {"chargeToPercent": 100}], - ["dummy/setChargeTarget", {"chargeToPercent": 100}], - ["dummy/setChargeRate", {"chargeRate": 215}], - ["dummy/setChargeRate", {"chargeRate": 215}], - ["dummy/setChargeRate", {"chargeRate": 215}], - ["dummy/setChargeRate", {"chargeRate": 215}], - ["dummy/setChargeRate", {"chargeRate": 215}], - ["dummy/setChargeRate", {"chargeRate": 0}], - ["dummy/setChargeRate", {"chargeRate": 0}], - ["dummy/setChargeRate", {"chargeRate": 0}], - ["dummy/setChargeRate", {"chargeRate": 0}], - ["dummy/setChargeRate", {"chargeRate": 0}], - ["dummy/setDischargeRate", {"dischargeRate": 220}], - ["dummy/setDischargeRate", {"dischargeRate": 220}], - ["dummy/setDischargeRate", {"dischargeRate": 220}], - ["dummy/setDischargeRate", {"dischargeRate": 220}], - ["dummy/setDischargeRate", {"dischargeRate": 220}], - ["dummy/setDischargeRate", {"dischargeRate": 0}], - ["dummy/setDischargeRate", {"dischargeRate": 0}], - ["dummy/setDischargeRate", {"dischargeRate": 0}], - ["dummy/setDischargeRate", {"dischargeRate": 0}], - ["dummy/setDischargeRate", {"dischargeRate": 0}], - ["dummy/setBatteryReserve", {"reservePercent": 100}], - ["dummy/setBatteryReserve", {"reservePercent": 100}], - ["dummy/setBatteryReserve", {"reservePercent": 100}], - ["dummy/setBatteryReserve", {"reservePercent": 100}], - ["dummy/setBatteryReserve", {"reservePercent": 100}], - ["dummy/setBatteryReserve", {"reservePercent": 6}], - ["dummy/setBatteryReserve", {"reservePercent": 6}], - ["dummy/setBatteryReserve", {"reservePercent": 6}], - ["dummy/setBatteryReserve", {"reservePercent": 6}], - ["dummy/setBatteryReserve", {"reservePercent": 6}], - ["dummy/enableChargeSchedule", {"state": "disable"}], - ["dummy/enableChargeSchedule", {"state": "disable"}], - ["dummy/enableChargeSchedule", {"state": "disable"}], - ["dummy/enableChargeSchedule", {"state": "disable"}], - ["dummy/enableChargeSchedule", {"state": "disable"}], - ["dummy/setChargeSlot1", {"start": "23:01", "finish": "05:01"}], - ["dummy/setChargeSlot1", {"start": "23:01", "finish": "05:01"}], - ["dummy/setChargeSlot1", {"start": "23:01", "finish": "05:01"}], - ["dummy/setChargeSlot1", {"start": "23:01", "finish": "05:01"}], - ["dummy/setChargeSlot1", {"start": "23:01", "finish": "05:01"}], - ["dummy/setChargeSlot1", {"start": "23:00", "finish": "05:00"}], - ["dummy/setChargeSlot1", {"start": "23:00", "finish": "05:00"}], - ["dummy/setChargeSlot1", {"start": "23:00", "finish": "05:00"}], - ["dummy/setChargeSlot1", {"start": "23:00", "finish": "05:00"}], - ["dummy/setChargeSlot1", {"start": "23:00", "finish": "05:00"}], - ["dummy/setDischargeSlot1", {"start": "23:00", "finish": "23:01"}], - ["dummy/setDischargeSlot1", {"start": "23:00", "finish": "23:01"}], - ["dummy/setDischargeSlot1", {"start": "23:00", "finish": "23:01"}], - ["dummy/setDischargeSlot1", {"start": "23:00", "finish": "23:01"}], - ["dummy/setDischargeSlot1", {"start": "23:00", "finish": "23:01"}], - ["dummy/setBatteryMode", {"mode": "Timed Export"}], - ["dummy/setBatteryMode", {"mode": "Timed Export"}], - ["dummy/setBatteryMode", {"mode": "Timed Export"}], - ["dummy/setBatteryMode", {"mode": "Timed Export"}], - ["dummy/setBatteryMode", {"mode": "Timed Export"}], - ] + expected = [['dummy/setChargeTarget', {'chargeToPercent': 100}], ['dummy/setChargeTarget', {'chargeToPercent': 100}], ['dummy/setChargeTarget', {'chargeToPercent': 100}], ['dummy/setChargeTarget', {'chargeToPercent': 100}], ['dummy/setChargeTarget', {'chargeToPercent': 100}], ['dummy/setChargeRate', {'chargeRate': 215}], ['dummy/setChargeRate', {'chargeRate': 215}], ['dummy/setChargeRate', {'chargeRate': 215}], ['dummy/setChargeRate', {'chargeRate': 215}], ['dummy/setChargeRate', {'chargeRate': 215}], ['dummy/setChargeRate', {'chargeRate': 0}], ['dummy/setChargeRate', {'chargeRate': 0}], ['dummy/setChargeRate', {'chargeRate': 0}], ['dummy/setChargeRate', {'chargeRate': 0}], ['dummy/setChargeRate', {'chargeRate': 0}], ['dummy/setDischargeRate', {'dischargeRate': 220}], ['dummy/setDischargeRate', {'dischargeRate': 220}], ['dummy/setDischargeRate', {'dischargeRate': 220}], ['dummy/setDischargeRate', {'dischargeRate': 220}], ['dummy/setDischargeRate', {'dischargeRate': 220}], ['dummy/setDischargeRate', {'dischargeRate': 0}], ['dummy/setDischargeRate', {'dischargeRate': 0}], ['dummy/setDischargeRate', {'dischargeRate': 0}], ['dummy/setDischargeRate', {'dischargeRate': 0}], ['dummy/setDischargeRate', {'dischargeRate': 0}], ['dummy/setBatteryReserve', {'reservePercent': 100}], ['dummy/setBatteryReserve', {'reservePercent': 100}], ['dummy/setBatteryReserve', {'reservePercent': 100}], ['dummy/setBatteryReserve', {'reservePercent': 100}], ['dummy/setBatteryReserve', {'reservePercent': 100}], ['dummy/setBatteryReserve', {'reservePercent': 6}], ['dummy/setBatteryReserve', {'reservePercent': 6}], ['dummy/setBatteryReserve', {'reservePercent': 6}], ['dummy/setBatteryReserve', {'reservePercent': 6}], ['dummy/setBatteryReserve', {'reservePercent': 6}], ['dummy/enableChargeSchedule', {'state': 'disable'}], ['dummy/enableChargeSchedule', {'state': 'disable'}], ['dummy/enableChargeSchedule', {'state': 'disable'}], ['dummy/enableChargeSchedule', {'state': 'disable'}], ['dummy/enableChargeSchedule', {'state': 'disable'}], ['dummy/setChargeSlot1', {'start': '23:01', 'finish': '05:01'}], ['dummy/setChargeSlot1', {'start': '23:01', 'finish': '05:01'}], ['dummy/setChargeSlot1', {'start': '23:01', 'finish': '05:01'}], ['dummy/setChargeSlot1', {'start': '23:01', 'finish': '05:01'}], ['dummy/setChargeSlot1', {'start': '23:01', 'finish': '05:01'}], ['dummy/setChargeSlot1', {'start': '23:00', 'finish': '05:00'}], ['dummy/setChargeSlot1', {'start': '23:00', 'finish': '05:00'}], ['dummy/setChargeSlot1', {'start': '23:00', 'finish': '05:00'}], ['dummy/setChargeSlot1', {'start': '23:00', 'finish': '05:00'}], ['dummy/setChargeSlot1', {'start': '23:00', 'finish': '05:00'}], ['dummy/setDischargeSlot1', {'start': '23:00', 'finish': '23:01'}], ['dummy/setDischargeSlot1', {'start': '23:00', 'finish': '23:01'}], ['dummy/setDischargeSlot1', {'start': '23:00', 'finish': '23:01'}], ['dummy/setDischargeSlot1', {'start': '23:00', 'finish': '23:01'}], ['dummy/setDischargeSlot1', {'start': '23:00', 'finish': '23:01'}], ['dummy/setBatteryMode', {'mode': 'Timed Export'}], ['dummy/setBatteryMode', {'mode': 'Timed Export'}], ['dummy/setBatteryMode', {'mode': 'Timed Export'}], ['dummy/setBatteryMode', {'mode': 'Timed Export'}], ['dummy/setBatteryMode', {'mode': 'Timed Export'}]] if json.dumps(expected) != json.dumps(rest): print("ERROR: Self test should be {} got {}".format(expected, rest)) failed = True return failed - def run_inverter_tests(): """ Test the inverter functions @@ -1433,6 +1368,7 @@ def run_inverter_tests(): if failed: return failed + failed |= test_inverter_update( "update1c", my_predbat, @@ -1683,70 +1619,76 @@ def run_inverter_tests(): return failed failed |= test_auto_restart( - "auto_restart0", - my_predbat, - ha, - inv, - dummy_items, - service=None, - expected=[], + "auto_restart0", + my_predbat, + ha, + inv, + dummy_items, + service=None, + expected = [], active=True, ) if failed: return failed failed |= test_auto_restart( - "auto_restart1", - my_predbat, - ha, - inv, - dummy_items, - service={"command": "service", "service": "restart_service", "addon": "adds"}, - expected=[["restart_service", {"addon": "adds"}], ["notify/notify", {"message": "Auto-restart service restart_service called due to: Crashed"}]], + "auto_restart1", + my_predbat, + ha, + inv, + dummy_items, + service={"command" : "service", "service": "restart_service", "addon" : "adds"}, + expected = [['restart_service', {'addon': 'adds'}], ['notify/notify', {'message': 'Auto-restart service restart_service called due to: Crashed'}]] ) if failed: return failed failed |= test_auto_restart( - "auto_restart2", my_predbat, ha, inv, dummy_items, service=[{"command": "service", "service": "restart_service"}], expected=[["restart_service", {}], ["notify/notify", {"message": "Auto-restart service restart_service called due to: Crashed"}]] + "auto_restart2", + my_predbat, + ha, + inv, + dummy_items, + service=[{"command" : "service", "service": "restart_service"}], + expected = [['restart_service', {}], ['notify/notify', {'message': 'Auto-restart service restart_service called due to: Crashed'}]] ) if failed: return failed failed |= test_auto_restart( - "auto_restart3", - my_predbat, - ha, - inv, - dummy_items, - service={"command": "service", "service": "restart_service"}, - expected=[], + "auto_restart3", + my_predbat, + ha, + inv, + dummy_items, + service={"command" : "service", "service": "restart_service"}, + expected = [], active=True, ) if failed: return failed failed |= test_auto_restart( - "auto_restart4", - my_predbat, - ha, - inv, - dummy_items, - service={"command": "service", "service": "restart_service", "entity_id": "switch.restart"}, - expected=[["restart_service", {"entity_id": "switch.restart"}], ["notify/notify", {"message": "Auto-restart service restart_service called due to: Crashed"}]], + "auto_restart4", + my_predbat, + ha, + inv, + dummy_items, + service={"command" : "service", "service": "restart_service", "entity_id" : "switch.restart"}, + expected = [['restart_service', {"entity_id" : "switch.restart"}], ['notify/notify', {'message': 'Auto-restart service restart_service called due to: Crashed'}]] ) if failed: return failed os.system("touch tmp1234") failed |= test_auto_restart( - "auto_restart5", - my_predbat, - ha, - inv, - dummy_items, - service={"command": "service", "shell": "rm tmp1234"}, - expected=[], + "auto_restart5", + my_predbat, + ha, + inv, + dummy_items, + service={"command" : "service", "shell": "rm tmp1234"}, + expected = [], ) if failed: return failed @@ -1812,6 +1754,7 @@ def simple_scenario( car_limit=100, set_charge_low_power=False, set_charge_window=True, + battery_temperature=20, ): """ No PV, No Load @@ -1824,6 +1767,21 @@ def simple_scenario( my_predbat.battery_loss_discharge = battery_loss my_predbat.battery_rate_max_scaling = battery_rate my_predbat.battery_rate_max_scaling_discharge = battery_rate + my_predbat.battery_temperature = battery_temperature + my_predbat.battery_temperature_discharge_curve = { + 20: 1.0, + 10: 0.5, + 9: 0.5, + 8: 0.5, + 7: 0.5, + 6: 0.3, + 5: 0.1, + 4: 0.08, + 3: 0.07, + 2: 0.05, + 1: 0.05, + 0: 0, + } my_predbat.soc_max = battery_size my_predbat.soc_kw = battery_soc my_predbat.inverter_hybrid = hybrid @@ -2086,6 +2044,7 @@ def __init__(self, id, soc_kw, soc_max, now_utc): self.reserve_current = 0 self.reserve_percent = 0 self.reserve_percent_current = 0 + self.battery_temperature = 20 def update_status(self, minutes_now): pass @@ -2205,6 +2164,7 @@ def run_execute_test( soc_kw_array=None, reserve_max=100, car_soc=0, + battery_temperature=20, ): print("Run scenario {}".format(name)) failed = False @@ -2253,6 +2213,7 @@ def run_execute_test( inverter.reserve_percent_current = reserve_percent inverter.reserve = reserve_kwh inverter.reserve_max = reserve_max + inverter.battery_temperature = battery_temperature my_predbat.fetch_inverter_data(create=False) @@ -2335,9 +2296,7 @@ def run_execute_test( if assert_immediate_soc_target_array: assert_immediate_soc_target = assert_immediate_soc_target_array[inverter.id] - assert_soc_target_force = ( - assert_immediate_soc_target if assert_status in ["Charging", "Hold charging", "Freeze charging", "Hold charging, Hold for iBoost", "Hold charging, Hold for car", "Freeze charging, Hold for iBoost", "Hold for car", "Hold for iBoost"] else 0 - ) + assert_soc_target_force = assert_immediate_soc_target if assert_status in ["Charging", "Hold charging", "Freeze charging", "Hold charging, Hold for iBoost", "Hold charging, Hold for car", "Freeze charging, Hold for iBoost", "Hold for car", "Hold for iBoost"] else 0 if not set_charge_window: assert_soc_target_force = -1 if inverter.immediate_charge_soc_target != assert_soc_target_force: @@ -2385,9 +2344,9 @@ def run_single_debug(test_name, my_predbat, debug_file, expected_file=None): if not expected_file: my_predbat.args["plan_debug"] = True my_predbat.set_discharge_during_charge = True - # my_predbat.metric_self_sufficiency = 0 - # my_predbat.calculate_second_pass = False - # my_predbat.best_soc_keep = 1 + #my_predbat.metric_self_sufficiency = 0 + #my_predbat.calculate_second_pass = False + #my_predbat.best_soc_keep = 1 pass # my_predbat.combine_export_slots = False # my_predbat.best_soc_keep = 1.0 @@ -2921,6 +2880,82 @@ def run_execute_tests(my_predbat): if failed: return failed + my_predbat.battery_temperature_discharge_curve = { + 20: 1.0, + 10: 0.5, + 9: 0.5, + 8: 0.5, + 7: 0.5, + 6: 0.3, + 5: 0.3, + 4: 0.3, + 3: 0.262, + 2: 0.1, + 1: 0.1, + 0: 0 + } + # No impact at 10 degrees + failed |= run_execute_test( + my_predbat, + "charge_low_power_temp1", + charge_window_best=charge_window_best, + charge_limit_best=charge_limit_best, + assert_charge_time_enable=True, + soc_kw=8.0, + set_charge_window=True, + set_export_window=True, + set_charge_low_power=True, + assert_status="Charging", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=my_predbat.minutes_now + 60, + assert_charge_rate=1300, + battery_max_rate=2000, + battery_temperature=10, + ) + if failed: + return failed + + failed |= run_execute_test( + my_predbat, + "charge_low_power_temp2", + charge_window_best=charge_window_best, + charge_limit_best=charge_limit_best, + assert_charge_time_enable=True, + soc_kw=8.0, + set_charge_window=True, + set_export_window=True, + set_charge_low_power=True, + assert_status="Charging", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=my_predbat.minutes_now + 60, + assert_charge_rate=1300, + battery_max_rate=2000, + battery_temperature=3, + ) + if failed: + return failed + + + failed |= run_execute_test( + my_predbat, + "charge_low_power_temp3", + charge_window_best=charge_window_best, + charge_limit_best=charge_limit_best, + assert_charge_time_enable=True, + soc_kw=8.0, + set_charge_window=True, + set_export_window=True, + set_charge_low_power=True, + assert_status="Charging", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=my_predbat.minutes_now + 60, + assert_charge_rate=2000, + battery_max_rate=2000, + battery_temperature=1, + ) + if failed: + return failed + # Reset curve my_predbat.battery_charge_power_curve = {} @@ -4130,9 +4165,7 @@ def run_execute_tests(my_predbat): ) failed |= run_execute_test(my_predbat, "no_charge5", set_charge_window=True, set_export_window=True) failed |= run_execute_test(my_predbat, "car", car_slot=charge_window_best, set_charge_window=True, set_export_window=True, assert_status="Hold for car", assert_pause_discharge=True, assert_discharge_rate=1000, soc_kw=1, assert_immediate_soc_target=10) - failed |= run_execute_test( - my_predbat, "car2", car_slot=charge_window_best, set_charge_window=True, set_export_window=True, assert_status="Hold for car", assert_pause_discharge=False, assert_discharge_rate=0, has_timed_pause=False, soc_kw=1, assert_immediate_soc_target=10 - ) + failed |= run_execute_test(my_predbat, "car2", car_slot=charge_window_best, set_charge_window=True, set_export_window=True, assert_status="Hold for car", assert_pause_discharge=False, assert_discharge_rate=0, has_timed_pause=False, soc_kw=1, assert_immediate_soc_target=10) failed |= run_execute_test( my_predbat, "car_charge", @@ -5114,11 +5147,13 @@ def run_model_tests(my_predbat): failed |= simple_scenario("pv_only_bat_dc_export_limit_load", my_predbat, 0.5, 3, assert_final_metric=-export_rate * 24 * 0.5, assert_final_soc=24, with_battery=True, hybrid=True, export_limit=0.5) failed |= simple_scenario("battery_charge", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10) - failed |= simple_scenario("battery_charge_low_off", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=False, keep=5, assert_keep=24.59) - failed |= simple_scenario("battery_charge_low_on", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=True, keep=5, assert_keep=88.89) - failed |= simple_scenario( - "battery_charge_low_on_monitor", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=True, keep=5, assert_keep=24.59, set_charge_window=False - ) + failed |= simple_scenario("battery_charge_low_off", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=False, keep = 5, assert_keep=24.59) + failed |= simple_scenario("battery_charge_low_on", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=True, keep = 5, assert_keep=88.89) + failed |= simple_scenario("battery_charge_low_on_monitor", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=True, keep = 5, assert_keep=24.59, set_charge_window=False) + + failed |= simple_scenario("battery_charge_low_temp1", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=False, keep = 5, assert_keep=24.59, battery_temperature=20) + failed |= simple_scenario("battery_charge_low_temp2", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=False, keep = 5, assert_keep=80.00, battery_temperature=1) + failed |= simple_scenario("battery_charge_low_temp3", my_predbat, 0, 0, assert_final_metric=import_rate * 10, assert_final_soc=10, with_battery=True, charge=10, battery_size=10, set_charge_low_power=True, keep = 5, assert_keep=88.89, battery_temperature=1) if failed: return failed