From d8cf5f14a13c988cb0a83b7da2c2d50d2aa31105 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:06:53 +0000 Subject: [PATCH 1/7] Change method of metric keep --- apps/predbat/config.py | 12 ++ apps/predbat/fetch.py | 2 + apps/predbat/predbat.py | 1 + apps/predbat/prediction.py | 13 +- apps/predbat/unit_test.py | 325 ++++++++++++++++++---------------- apps/predbat/userinterface.py | 8 +- 6 files changed, 197 insertions(+), 164 deletions(-) diff --git a/apps/predbat/config.py b/apps/predbat/config.py index 0db15cfb..e32452c4 100644 --- a/apps/predbat/config.py +++ b/apps/predbat/config.py @@ -298,6 +298,18 @@ "icon": "mdi:battery-50", "default": 0.5, }, + { + "name": "best_soc_keep_weight", + "friendly_name": "Best SOC Keep Weighting", + "type": "input_number", + "min": 0.1, + "max": 5, + "step": 0.10, + "unit": "*", + "icon": "mdi:multiplication", + "default": 0.5, + "enable": "expert_mode", + }, { "name": "metric_min_improvement", "friendly_name": "Metric Min Improvement", diff --git a/apps/predbat/fetch.py b/apps/predbat/fetch.py index 8e346d86..ede4c222 100644 --- a/apps/predbat/fetch.py +++ b/apps/predbat/fetch.py @@ -1759,6 +1759,7 @@ def fetch_config_options(self): self.best_soc_min = self.get_arg("best_soc_min") self.best_soc_max = self.get_arg("best_soc_max") self.best_soc_keep = self.get_arg("best_soc_keep") + self.best_soc_keep_weight = self.get_arg("best_soc_keep_weight") self.set_soc_minutes = 30 self.set_window_minutes = 30 self.inverter_set_charge_before = self.get_arg("inverter_set_charge_before") @@ -1791,6 +1792,7 @@ def fetch_config_options(self): self.set_status_notify = self.get_arg("set_status_notify") self.set_inverter_notify = self.get_arg("set_inverter_notify") self.set_export_freeze_only = self.get_arg("set_export_freeze_only") + print("Getting set_export_freeze_only", self.set_export_freeze_only) self.set_discharge_during_charge = self.get_arg("set_discharge_during_charge") # Mode diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index c99d5c09..6b8587dc 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -344,6 +344,7 @@ def reset(self): self.best_soc_max = 0 self.best_soc_margin = 0 self.best_soc_keep = 0 + self.best_soc_keep_weight = 0.5 self.rate_min = 0 self.rate_min_minute = 0 self.rate_min_forward = {} diff --git a/apps/predbat/prediction.py b/apps/predbat/prediction.py index 35e4da01..06fbb184 100644 --- a/apps/predbat/prediction.py +++ b/apps/predbat/prediction.py @@ -154,6 +154,7 @@ def __init__(self, base=None, pv_forecast_minute_step=None, pv_forecast_minute10 self.battery_loss = base.battery_loss self.battery_loss_discharge = base.battery_loss_discharge self.best_soc_keep = base.best_soc_keep + self.best_soc_keep_weight = base.best_soc_keep_weight self.best_soc_min = base.best_soc_min self.car_charging_battery_size = base.car_charging_battery_size self.rate_import = base.rate_import @@ -411,9 +412,9 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi if minute < 4 * 60: keep_minute_scaling = 0 else: - keep_minute_scaling = min(((minute - 4 * 60) / (2 * 60)), 1.0) * 0.5 + keep_minute_scaling = min(((minute - 4 * 60) / (2 * 60)), 1.0) * self.best_soc_keep_weight else: - keep_minute_scaling = 0.5 + keep_minute_scaling = self.best_soc_keep_weight # Find charge & discharge windows charge_window_n = charge_window_optimised.get(minute_absolute, -1) @@ -808,12 +809,8 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi diff = get_diff(battery_draw, pv_dc, pv_ac, load_yesterday, inverter_loss) # Metric keep - pretend the battery is empty and you have to import instead of using the battery - if soc < self.best_soc_keep: - # Apply keep as a percentage of the time in the future so it gets stronger over an 4 hour period - # Weight to 50% chance of the scenario - keep_diff = max(get_diff(0, 0, pv_now, load_yesterday, inverter_loss), battery_draw) - if keep_diff > 0: - metric_keep += rate_import[minute_absolute] * keep_diff * keep_minute_scaling + if self.best_soc_keep > 0 and soc <= self.best_soc_keep: + metric_keep += (self.best_soc_keep - soc) * rate_import[minute_absolute] * keep_minute_scaling * step / 60.0 if diff > 0: # Import # All imports must go to home (no inverter loss) or to the battery (inverter loss accounted before above) diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index 29a59ff6..3d3520d1 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -69,13 +69,13 @@ def get_state(self, entity_id, default=None, attribute=None, refresh=False): if not entity_id: return {} elif entity_id in self.dummy_items: - # print("Getting state: {} {}".format(entity_id, self.dummy_items[entity_id])) + #print("Getting state: {} {}".format(entity_id, self.dummy_items[entity_id])) return self.dummy_items[entity_id] else: return None def call_service(self, service, **kwargs): - # print("Calling service: {} {}".format(service, kwargs)) + #print("Calling service: {} {}".format(service, kwargs)) if self.service_store_enable: self.service_store.append([service, kwargs]) return None @@ -91,13 +91,13 @@ def call_service(self, service, **kwargs): if not entity_id.startswith("switch."): print("Warn: Service for entity {} not a switch".format(entity_id)) elif entity_id in self.dummy_items: - self.dummy_items[entity_id] = "on" + self.dummy_items[entity_id] = 'on' elif service == "switch/turn_off": entity_id = kwargs.get("entity_id", None) if not entity_id.startswith("switch."): print("Warn: Service for entity {} not a switch".format(entity_id)) elif entity_id in self.dummy_items: - self.dummy_items[entity_id] = "off" + self.dummy_items[entity_id] = 'off' elif service == "select/select_option": entity_id = kwargs.get("entity_id", None) if not entity_id.startswith("select."): @@ -107,12 +107,12 @@ def call_service(self, service, **kwargs): return None def set_state(self, entity_id, state, attributes=None): - # print("Setting state: {} to {}".format(entity_id, state)) + #print("Setting state: {} to {}".format(entity_id, state)) self.dummy_items[entity_id] = state return None def get_history(self, entity_id, now=None, days=30): - # print("Getting history for {}".format(entity_id)) + #print("Getting history for {}".format(entity_id)) if self.history_enable: return [self.history] else: @@ -426,14 +426,12 @@ def run_compute_metric_tests(my_predbat): failed |= compute_metric_test(my_predbat, "cost_battery_cycle", cost=10.0, battery_cycle=25, metric_battery_cycle=0.1, assert_metric=10 + 25 * 0.1) return failed - def dummy_sleep(seconds): """ Dummy sleep function """ pass - class DummyRestAPI: def __init__(self): self.commands = [] @@ -443,25 +441,24 @@ def dummy_rest_postCommand(self, url, json): """ Dummy rest post command """ - # print("Dummy rest post command {} {}".format(url, json)) + #print("Dummy rest post command {} {}".format(url, json)) self.commands.append([url, json]) def dummy_rest_getData(self, url): if url == "dummy/runAll": - # print("Dummy rest get data {} returns {}".format(url, self.rest_data)) + #print("Dummy rest get data {} returns {}".format(url, self.rest_data)) return self.rest_data elif url == "dummy/readData": - # print("Dummy rest get data {} returns {}".format(url, self.rest_data)) + #print("Dummy rest get data {} returns {}".format(url, self.rest_data)) return self.rest_data else: return None - + def get_commands(self): commands = self.commands self.commands = [] return commands - def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_time, prev_charge_end_time, prev_enable_charge, charge_start_time, charge_end_time, minutes_now): """ test: @@ -473,7 +470,7 @@ def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_ inv.rest_data = None ha.dummy_items["select.charge_start_time"] = prev_charge_start_time ha.dummy_items["select.charge_end_time"] = prev_charge_end_time - ha.dummy_items["switch.scheduled_charge_enable"] = "on" if prev_enable_charge else "off" + ha.dummy_items['switch.scheduled_charge_enable'] = 'on' if prev_enable_charge else 'off' charge_start_time_tm = datetime.strptime(charge_start_time, "%H:%M:%S") charge_end_time_tm = datetime.strptime(charge_end_time, "%H:%M:%S") @@ -484,18 +481,18 @@ def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_ if ha.get_state("select.charge_end_time") != charge_end_time: print("ERROR: Charge end time should be {} got {}".format(charge_end_time, ha.get_state("select.charge_end_time"))) failed = True - if ha.get_state("switch.scheduled_charge_enable") != "on": + if ha.get_state("switch.scheduled_charge_enable") != 'on': print("ERROR: Charge enable should be on got {}".format(ha.get_state("switch.scheduled_charge_enable"))) failed = True - + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Timeslots"] = {} - inv.rest_data["Timeslots"]["Charge_start_time_slot_1"] = prev_charge_start_time - inv.rest_data["Timeslots"]["Charge_end_time_slot_1"] = prev_charge_end_time + inv.rest_data["Timeslots"]["Charge_start_time_slot_1"] = prev_charge_start_time + inv.rest_data["Timeslots"]["Charge_end_time_slot_1"] = prev_charge_end_time inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Enable_Charge_Schedule"] = "on" if prev_enable_charge else "off" + inv.rest_data["Control"]["Enable_Charge_Schedule"] = 'on' if prev_enable_charge else 'off' dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = charge_start_time dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = charge_end_time @@ -504,21 +501,20 @@ def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_ inv.adjust_charge_window(charge_start_time_tm, charge_end_time_tm, minutes_now) rest_command = dummy_rest.get_commands() if prev_charge_start_time != charge_start_time or prev_charge_end_time != charge_end_time: - expect_data = [["dummy/setChargeSlot1", {"start": charge_start_time[0:5], "finish": charge_end_time[0:5]}]] + expect_data = [['dummy/setChargeSlot1', {'start': charge_start_time[0:5], 'finish': charge_end_time[0:5]}]] else: expect_data = [] if prev_enable_charge != True: - expect_data.append(["dummy/enableChargeSchedule", {"state": "enable"}]) + expect_data.append(['dummy/enableChargeSchedule', {'state' : 'enable'}]) if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True return failed - - + def test_adjust_reserve(test_name, ha, inv, dummy_rest, prev_reserve, reserve, expect_reserve=None, reserve_min=4, reserve_max=100): """ - Test + Test inv.adjust_reserve(self, reserve): """ failed = False @@ -527,7 +523,7 @@ def test_adjust_reserve(test_name, ha, inv, dummy_rest, prev_reserve, reserve, e inv.reserve_percent = reserve_min inv.reserve_max = reserve_max - + print("Test: {}".format(test_name)) # Non-REST Mode @@ -537,28 +533,27 @@ def test_adjust_reserve(test_name, ha, inv, dummy_rest, prev_reserve, reserve, e if ha.get_state("number.reserve") != expect_reserve: print("ERROR: Reserve should be {} got {}".format(expect_reserve, ha.get_state("number.reserve"))) failed = True - + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Battery_Power_Reserve"] = prev_reserve + inv.rest_data["Control"]["Battery_Power_Reserve"] = prev_reserve dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"]["Battery_Power_Reserve"] = expect_reserve inv.adjust_reserve(reserve) rest_command = dummy_rest.get_commands() if prev_reserve != expect_reserve: - expect_data = [["dummy/setBatteryReserve", {"reservePercent": expect_reserve}]] + expect_data = [['dummy/setBatteryReserve', {'reservePercent': expect_reserve}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed - def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, expect_rate=None, discharge=False): """ Test the adjust_inverter_mode function @@ -566,7 +561,7 @@ def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, exp failed = False if expect_rate is None: expect_rate = rate - + print("Test: {}".format(test_name)) # Non-REST Mode @@ -580,13 +575,13 @@ def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, exp if ha.get_state(entity) != expect_rate: print("ERROR: Inverter rate should be {} got {}".format(expect_rate, ha.get_state(entity))) failed = True - + # REST Mode rest_entity = "Battery_Discharge_Rate" if discharge else "Battery_Charge_Rate" inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"][rest_entity] = prev_rate + inv.rest_data["Control"][rest_entity] = prev_rate dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"][rest_entity] = expect_rate @@ -598,15 +593,15 @@ def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, exp rest_command = dummy_rest.get_commands() if prev_rate != expect_rate: if discharge: - expect_data = [["dummy/setDischargeRate", {"dischargeRate": expect_rate}]] + expect_data = [['dummy/setDischargeRate', {'dischargeRate': expect_rate}]] else: - expect_data = [["dummy/setChargeRate", {"chargeRate": expect_rate}]] + expect_data = [['dummy/setChargeRate', {'chargeRate': expect_rate}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed @@ -617,7 +612,7 @@ def test_adjust_inverter_mode(test_name, ha, inv, dummy_rest, prev_mode, mode, e failed = False if expect_mode is None: expect_mode = mode - + print("Test: {}".format(test_name)) # Non-REST Mode @@ -627,28 +622,27 @@ def test_adjust_inverter_mode(test_name, ha, inv, dummy_rest, prev_mode, mode, e if ha.get_state("select.inverter_mode") != expect_mode: print("ERROR: Inverter mode should be {} got {}".format(expect_mode, ha.get_state("select.inverter_mode"))) failed = True - + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Mode"] = prev_mode + inv.rest_data["Control"]["Mode"] = prev_mode dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"]["Mode"] = expect_mode inv.adjust_inverter_mode(True if mode == "Timed Export" else False, False) rest_command = dummy_rest.get_commands() if prev_mode != expect_mode: - expect_data = [["dummy/setBatteryMode", {"mode": expect_mode}]] + expect_data = [['dummy/setBatteryMode', {'mode': expect_mode}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed - def test_adjust_battery_target(test_name, ha, inv, dummy_rest, prev_soc, soc, isCharging, isExporting, expect_soc=None): """ Test the adjust_battery_target function @@ -657,6 +651,7 @@ def test_adjust_battery_target(test_name, ha, inv, dummy_rest, prev_soc, soc, is if expect_soc is None: expect_soc = soc + print("Test: {}".format(test_name)) # Non-REST Mode @@ -667,42 +662,28 @@ def test_adjust_battery_target(test_name, ha, inv, dummy_rest, prev_soc, soc, is print("ERROR: Charge limit should be {} got {}".format(expect_soc, ha.get_state("number.charge_limit"))) failed = True + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Target_SOC"] = prev_soc + inv.rest_data["Control"]["Target_SOC"] = prev_soc dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"]["Target_SOC"] = expect_soc inv.adjust_battery_target(soc, isCharging=True, isExporting=False) rest_command = dummy_rest.get_commands() if soc != prev_soc: - expect_data = [["dummy/setChargeTarget", {"chargeToPercent": expect_soc}]] + expect_data = [['dummy/setChargeTarget', {'chargeToPercent': expect_soc}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed - -def test_inverter_update( - test_name, - my_predbat, - dummy_items, - expect_charge_start_time, - expect_charge_end_time, - expect_charge_enable, - expect_discharge_start_time, - expect_discharge_end_time, - expect_discharge_enable, - expect_battery_power, - expect_pv_power, - expect_load_power, - expect_soc_kwh, -): +def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start_time, expect_charge_end_time, expect_charge_enable, expect_discharge_start_time, expect_discharge_end_time, expect_discharge_enable, expect_battery_power, expect_pv_power, expect_load_power, expect_soc_kwh): failed = False print("**** Running Test: {} ****".format(test_name)) @@ -712,7 +693,7 @@ def test_inverter_update( discharge_start_time_minutes = (datetime.strptime(expect_discharge_start_time, "%H:%M:%S") - midnight).total_seconds() / 60 discharge_end_time_minutes = (datetime.strptime(expect_discharge_end_time, "%H:%M:%S") - midnight).total_seconds() / 60 - my_predbat.args["givtcp_rest"] = None + my_predbat.args['givtcp_rest'] = None inv = Inverter(my_predbat, 0) inv.sleep = dummy_sleep @@ -729,16 +710,16 @@ def test_inverter_update( print("Test: Update Inverter") - dummy_items["select.charge_start_time"] = expect_charge_start_time - dummy_items["select.charge_end_time"] = expect_charge_end_time - dummy_items["select.discharge_start_time"] = expect_discharge_start_time - dummy_items["select.discharge_end_time"] = expect_discharge_end_time - dummy_items["sensor.battery_power"] = expect_battery_power - dummy_items["sensor.pv_power"] = expect_pv_power - dummy_items["sensor.load_power"] = expect_load_power - dummy_items["switch.scheduled_charge_enable"] = "on" if expect_charge_enable else "off" - dummy_items["switch.scheduled_discharge_enable"] = "on" if expect_discharge_enable else "off" - dummy_items["sensor.soc_kw"] = expect_soc_kwh + dummy_items['select.charge_start_time'] = expect_charge_start_time + dummy_items['select.charge_end_time'] = expect_charge_end_time + dummy_items['select.discharge_start_time'] = expect_discharge_start_time + dummy_items['select.discharge_end_time'] = expect_discharge_end_time + dummy_items['sensor.battery_power'] = expect_battery_power + dummy_items['sensor.pv_power'] = expect_pv_power + dummy_items['sensor.load_power'] = expect_load_power + dummy_items['switch.scheduled_charge_enable'] = 'on' if expect_charge_enable else 'off' + dummy_items['switch.scheduled_discharge_enable'] = 'on' if expect_discharge_enable else 'off' + dummy_items['sensor.soc_kw'] = expect_soc_kwh inv.update_status(my_predbat.minutes_now) if not inv.inv_has_discharge_enable_time: @@ -759,7 +740,7 @@ def test_inverter_update( charge_end_time_minutes += 24 * 60 if inv.charge_start_time_minutes != charge_start_time_minutes: - print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items["select.charge_start_time"])) + print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items['select.charge_start_time'])) failed = True if inv.charge_end_time_minutes != charge_end_time_minutes: print("ERROR: Charge end time should be {} got {}".format(charge_end_time_minutes, inv.charge_end_time_minutes)) @@ -792,30 +773,30 @@ def test_inverter_update( # REST Mode dummy_rest = DummyRestAPI() - my_predbat.args["givtcp_rest"] = "dummy" + my_predbat.args['givtcp_rest'] = "dummy" inv.sleep = dummy_sleep dummy_rest.rest_data = {} dummy_rest.rest_data["Control"] = {} dummy_rest.rest_data["Control"]["Target_SOC"] = 99 - dummy_rest.rest_data["Control"]["Mode"] = "Eco" - dummy_rest.rest_data["Control"]["Battery_Power_Reserve"] = 4.0 - 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" if expect_charge_enable else "disable" - dummy_rest.rest_data["Control"]["Enable_Discharge_Schedule"] = "enable" if expect_discharge_enable else "disable" + dummy_rest.rest_data["Control"]["Mode"] = "Eco" + dummy_rest.rest_data["Control"]["Battery_Power_Reserve"] = 4.0 + 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' if expect_charge_enable else 'disable' + dummy_rest.rest_data["Control"]["Enable_Discharge_Schedule"] = 'enable' if expect_discharge_enable else 'disable' dummy_rest.rest_data["Timeslots"] = {} - dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = expect_charge_start_time - dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = expect_charge_end_time - dummy_rest.rest_data["Timeslots"]["Discharge_start_time_slot_1"] = expect_discharge_start_time - dummy_rest.rest_data["Timeslots"]["Discharge_end_time_slot_1"] = expect_discharge_end_time - dummy_rest.rest_data["Power"] = {} + dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = expect_charge_start_time + dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = expect_charge_end_time + dummy_rest.rest_data["Timeslots"]["Discharge_start_time_slot_1"] = expect_discharge_start_time + dummy_rest.rest_data["Timeslots"]["Discharge_end_time_slot_1"] = expect_discharge_end_time + dummy_rest.rest_data["Power"] = {} dummy_rest.rest_data["Power"]["Power"] = {} dummy_rest.rest_data["Power"]["Power"]["SOC_kWh"] = expect_soc_kwh dummy_rest.rest_data["Power"]["Power"]["Battery_Power"] = expect_battery_power dummy_rest.rest_data["Power"]["Power"]["PV_Power"] = expect_pv_power dummy_rest.rest_data["Power"]["Power"]["Load_Power"] = expect_load_power - dummy_items["sensor.soc_kw"] = -1 + dummy_items['sensor.soc_kw'] = -1 inv = Inverter(my_predbat, 0, rest_postCommand=dummy_rest.dummy_rest_postCommand, rest_getData=dummy_rest.dummy_rest_getData) @@ -823,7 +804,7 @@ def test_inverter_update( inv.update_status(my_predbat.minutes_now) if inv.charge_start_time_minutes != charge_start_time_minutes: - print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items["select.charge_start_time"])) + print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items['select.charge_start_time'])) failed = True if inv.charge_end_time_minutes != charge_end_time_minutes: print("ERROR: Charge end time should be {} got {}".format(charge_end_time_minutes, inv.charge_end_time_minutes)) @@ -855,7 +836,6 @@ def test_inverter_update( 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"): """ Tests; @@ -876,8 +856,8 @@ def adjust_charge_immediate(self, target_soc, freeze=False) my_predbat.args["discharge_freeze_service"] = "discharge_freeze" my_predbat.args["device_id"] = "DID0" - dummy_items["select.charge_start_time"] = charge_start_time - dummy_items["select.charge_end_time"] = charge_end_time + dummy_items['select.charge_start_time'] = charge_start_time + dummy_items['select.charge_end_time'] = charge_end_time power = int(inv.battery_rate_max_charge * MINUTE_WATT) @@ -889,14 +869,14 @@ def adjust_charge_immediate(self, target_soc, freeze=False) pass elif soc == inv.soc_percent or freeze: if stop_discharge: - expected.append(["discharge_stop", {"device_id": "DID0"}]) - expected.append(["charge_freeze", {"device_id": "DID0", "target_soc": soc, "power": power}]) + expected.append(["discharge_stop", {"device_id" : "DID0"}]) + expected.append(["charge_freeze", {"device_id" : "DID0", "target_soc": soc, "power": power}]) elif soc > 0: if stop_discharge: - expected.append(["discharge_stop", {"device_id": "DID0"}]) - expected.append(["charge_start", {"device_id": "DID0", "target_soc": soc, "power": power}]) + expected.append(["discharge_stop", {"device_id" : "DID0"}]) + expected.append(["charge_start", {"device_id" : "DID0", "target_soc": soc, "power": power}]) else: - expected.append(["charge_stop", {"device_id": "DID0"}]) + expected.append(["charge_stop", {"device_id" : "DID0"}]) if json.dumps(expected) != json.dumps(result): print("ERROR: Adjust charge immediate - charge service should be {} got {}".format(expected, result)) failed = True @@ -904,7 +884,6 @@ def adjust_charge_immediate(self, target_soc, freeze=False) ha.service_store_enable = False return failed - def test_call_adjust_export_immediate(test_name, my_predbat, ha, inv, dummy_items, soc, repeat=False, freeze=False, clear=False, charge_stop=False, discharge_start_time="00:00:00", discharge_end_time="23:55:00"): """ Tests; @@ -914,7 +893,7 @@ def adjust_export_immediate(self, target_soc, freeze=False) ha.service_store_enable = True if clear: ha.service_store = [] - + print("**** Running Test: {} ****".format(test_name)) my_predbat.args["charge_start_service"] = "charge_start" @@ -926,8 +905,8 @@ def adjust_export_immediate(self, target_soc, freeze=False) my_predbat.args["device_id"] = "DID0" power = int(inv.battery_rate_max_discharge * MINUTE_WATT) - dummy_items["select.discharge_start_time"] = discharge_start_time - dummy_items["select.discharge_end_time"] = discharge_end_time + dummy_items['select.discharge_start_time'] = discharge_start_time + dummy_items['select.discharge_end_time'] = discharge_end_time inv.adjust_export_immediate(soc, freeze=freeze) result = ha.get_service_store() @@ -937,14 +916,14 @@ def adjust_export_immediate(self, target_soc, freeze=False) pass elif freeze: if charge_stop: - expected.append(["charge_stop", {"device_id": "DID0"}]) - expected.append(["discharge_freeze", {"device_id": "DID0", "target_soc": soc, "power": power}]) + expected.append(["charge_stop", {"device_id" : "DID0"}]) + expected.append(["discharge_freeze", {"device_id" : "DID0", "target_soc": soc, "power": power}]) elif soc > 0 and soc < 100: if charge_stop: - expected.append(["charge_stop", {"device_id": "DID0"}]) - expected.append(["discharge_start", {"device_id": "DID0", "target_soc": soc, "power": power}]) + expected.append(["charge_stop", {"device_id" : "DID0"}]) + expected.append(["discharge_start", {"device_id" : "DID0", "target_soc": soc, "power": power}]) else: - expected.append(["discharge_stop", {"device_id": "DID0"}]) + expected.append(["discharge_stop", {"device_id" : "DID0"}]) if json.dumps(expected) != json.dumps(result): print("ERROR: Adjust export immediate - discharge service should be {} got {}".format(expected, result)) failed = True @@ -952,7 +931,6 @@ def adjust_export_immediate(self, target_soc, freeze=False) ha.service_store_enable = False return failed - def test_call_service_template(test_name, my_predbat, inv, service_name="test", domain="charge", data={}, extra_data={}, clear=True, repeat=False, service_template=None, expected_result=None): """ tests @@ -994,8 +972,8 @@ def call_service_template(self, service, data, domain="charge", extra_data={}) ha.service_store_enable = False return failed - def run_inverter_tests(): + """ Test the inverter functions """ @@ -1034,8 +1012,8 @@ def run_inverter_tests(): "sensor.pv_power": 1.0, "sensor.load_power": 2.0, "number.reserve": 4.0, - "switch.scheduled_charge_enable": "off", - "switch.scheduled_discharge_enable": "off", + "switch.scheduled_charge_enable": 'off', + "switch.scheduled_discharge_enable": 'off', "select.charge_start_time": "01:11:00", "select.charge_end_time": "02:22:00", "select.discharge_start_time": "03:33:00", @@ -1044,42 +1022,43 @@ def run_inverter_tests(): my_predbat.ha_interface.dummy_items = dummy_items my_predbat.args["auto_restart"] = [{"service": "switch/turn_on", "entity_id": "switch.restart"}] my_predbat.args["givtcp_rest"] = None - my_predbat.args["inverter_type"] = ["GE"] + my_predbat.args['inverter_type'] = ["GE"] for entity_id in dummy_items.keys(): arg_name = entity_id.split(".")[1] my_predbat.args[arg_name] = entity_id failed |= test_inverter_update( "update1", - my_predbat, - dummy_items, - expect_charge_start_time="01:11:00", - expect_charge_end_time="02:22:00", - expect_charge_enable=False, - expect_discharge_start_time="03:33:00", - expect_discharge_end_time="04:44:00", - expect_discharge_enable=True, - expect_battery_power=5.0, - expect_pv_power=1.0, + my_predbat, + dummy_items, + expect_charge_start_time="01:11:00", + expect_charge_end_time="02:22:00", + expect_charge_enable=False, + expect_discharge_start_time="03:33:00", + expect_discharge_end_time="04:44:00", + expect_discharge_enable=True, + expect_battery_power=5.0, + expect_pv_power=1.0, expect_load_power=2.0, expect_soc_kwh=6.0, ) failed |= test_inverter_update( "update2", - my_predbat, - dummy_items, - expect_charge_start_time="01:11:00", - expect_charge_end_time="23:22:00", - expect_charge_enable=True, - expect_discharge_start_time="03:33:00", - expect_discharge_end_time="04:44:00", - expect_discharge_enable=False, - expect_battery_power=6.0, - expect_pv_power=1.5, + my_predbat, + dummy_items, + expect_charge_start_time="01:11:00", + expect_charge_end_time="23:22:00", + expect_charge_enable=True, + expect_discharge_start_time="03:33:00", + expect_discharge_end_time="04:44:00", + expect_discharge_enable=False, + expect_battery_power=6.0, + expect_pv_power=1.5, expect_load_power=2.5, expect_soc_kwh=6.6, ) + my_predbat.args["givtcp_rest"] = None dummy_rest = DummyRestAPI() inv = Inverter(my_predbat, 0, rest_postCommand=dummy_rest.dummy_rest_postCommand, rest_getData=dummy_rest.dummy_rest_getData) @@ -1105,11 +1084,11 @@ def run_inverter_tests(): failed |= test_adjust_charge_rate("adjust_discharge_rate2", ha, inv, dummy_rest, 200, 0, 0, discharge=True) failed |= test_adjust_charge_rate("adjust_discharge_rate3", ha, inv, dummy_rest, 200, 210, 200, discharge=True) - failed |= test_adjust_reserve("adjust_reserve1", ha, inv, dummy_rest, 4, 50, reserve_max=100) - failed |= test_adjust_reserve("adjust_reserve2", ha, inv, dummy_rest, 50, 0, 4, reserve_max=100) - failed |= test_adjust_reserve("adjust_reserve3", ha, inv, dummy_rest, 20, 100, reserve_max=100) - failed |= test_adjust_reserve("adjust_reserve4", ha, inv, dummy_rest, 20, 100, 98, reserve_min=4, reserve_max=98) - failed |= test_adjust_reserve("adjust_reserve5", ha, inv, dummy_rest, 50, 0, 0, reserve_min=0, reserve_max=100) + failed |= test_adjust_reserve("adjust_reserve1", ha, inv, dummy_rest, 4, 50, reserve_max = 100) + failed |= test_adjust_reserve("adjust_reserve2", ha, inv, dummy_rest, 50, 0, 4, reserve_max = 100) + failed |= test_adjust_reserve("adjust_reserve3", ha, inv, dummy_rest, 20, 100, reserve_max = 100) + failed |= test_adjust_reserve("adjust_reserve4", ha, inv, dummy_rest, 20, 100, 98, reserve_min=4, reserve_max = 98) + failed |= test_adjust_reserve("adjust_reserve5", ha, inv, dummy_rest, 50, 0, 0, reserve_min=0, reserve_max = 100) failed |= test_adjust_charge_window("adjust_charge_window1", ha, inv, dummy_rest, "00:00:00", "00:00:00", False, "00:00:00", "00:00:00", my_predbat.minutes_now) failed |= test_adjust_charge_window("adjust_charge_window2", ha, inv, dummy_rest, "00:00:00", "00:00:00", False, "00:00:00", "23:00:00", my_predbat.minutes_now) @@ -1124,19 +1103,28 @@ def run_inverter_tests(): failed |= test_call_service_template("test_service_simple5", my_predbat, inv, service_name="test_service", domain="charge", data={"test": "data"}, extra_data={"extra": "data2"}, clear=False, repeat=False) failed |= test_call_service_template( - "test_service_complex1", - my_predbat, - inv, - service_name="complex_service", - domain="charge", - data={"test": "data"}, - extra_data={"extra": "extra_data"}, - service_template={"service": "funny", "dummy": "22", "extra": "{extra}"}, - expected_result=[["funny", {"dummy": "22", "extra": "extra_data"}]], - clear=False, + "test_service_complex1", + my_predbat, + inv, + service_name="complex_service", + domain="charge", + data={"test": "data"}, + extra_data={"extra": "extra_data"}, + service_template = {"service" : "funny", "dummy": "22", "extra": "{extra}"}, + expected_result = [['funny', {'dummy': '22', 'extra': 'extra_data'}]], + clear=False ) failed |= test_call_service_template( - "test_service_complex2", my_predbat, inv, service_name="complex_service", domain="charge", data={"test": "data"}, extra_data={"extra": "extra_data"}, service_template={"service": "funny", "dummy": "22", "extra": "{extra}"}, clear=False, repeat=True + "test_service_complex2", + my_predbat, + inv, + service_name="complex_service", + domain="charge", + data={"test": "data"}, + extra_data={"extra": "extra_data"}, + service_template = {"service" : "funny", "dummy": "22", "extra": "{extra}"}, + clear=False, + repeat=True ) inv.soc_percent = 49 failed |= test_call_adjust_charge_immediate("charge_immediate1", my_predbat, ha, inv, dummy_items, 100, clear=True, stop_discharge=True) @@ -1158,7 +1146,6 @@ def run_inverter_tests(): return failed - def simple_scenario( name, my_predbat, @@ -1196,6 +1183,7 @@ def simple_scenario( carbon=0, assert_final_carbon=0.0, keep=0.0, + keep_weight=0.5, assert_keep=0.0, save="best", quiet=False, @@ -1250,6 +1238,7 @@ def simple_scenario( my_predbat.iboost_gas_scale = gas_scale my_predbat.iboost_charging = iboost_charging my_predbat.best_soc_keep = keep + my_predbat.best_soc_keep_weight = keep_weight my_predbat.car_charging_soc[0] = 0 my_predbat.car_charging_limit[0] = 100.0 @@ -1345,7 +1334,7 @@ def simple_scenario( if abs(final_carbon_g - assert_final_carbon) >= 0.1: print("ERROR: Final Carbon {} should be {}".format(final_carbon_g, assert_final_carbon)) failed = True - if abs(metric_keep - assert_keep) >= 0.1: + if abs(metric_keep - assert_keep) >= 0.5: print("ERROR: Metric keep {} should be {}".format(metric_keep, assert_keep)) failed = True if assert_iboost_running != prediction.iboost_running: @@ -3168,6 +3157,7 @@ def run_optimise_all_windows( hybrid=False, inverter_loss=1.0, best_soc_keep=0.0, + best_soc_keep_weight=0.5, ): print("Starting optimise all windows test {}".format(name)) end_record = my_predbat.forecast_minutes @@ -3179,6 +3169,7 @@ def run_optimise_all_windows( my_predbat.inverter_hybrid = hybrid my_predbat.inverter_loss = inverter_loss my_predbat.best_soc_keep = best_soc_keep + my_predbat.best_soc_keep_weight = best_soc_keep_weight reset_rates(my_predbat, rate_import, rate_export) update_rates_import(my_predbat, charge_window_best) @@ -3276,7 +3267,7 @@ def run_optimise_all_windows_tests(my_predbat): for n in range(0, 48): price = 16 - n % 16 charge_window_best.append({"start": my_predbat.minutes_now + 30 * n, "end": my_predbat.minutes_now + 30 * (n + 1), "average": price}) - expect_charge_limit.append(100 if price <= 5.0 else 0) + expect_charge_limit.append(10 if price <= 5.0 else 0) failed |= run_optimise_all_windows( "created2", my_predbat, @@ -3286,7 +3277,26 @@ def run_optimise_all_windows_tests(my_predbat): pv_amount=0, expect_best_price=5 / 0.9, inverter_loss=0.9, - best_soc_keep=0.5, + best_soc_keep=0.0, + battery_size=10, + ) + if failed: + return failed + + # One extra charge as we will fall below keep otherwise + expect_charge_limit[10] = 10.0 + failed |= run_optimise_all_windows( + "created3", + my_predbat, + charge_window_best=charge_window_best, + expect_charge_limit=expect_charge_limit, + load_amount=0.2, + pv_amount=0, + expect_best_price=5 / 0.9, + inverter_loss=0.9, + best_soc_keep=1, + battery_soc=2, + battery_size=10, ) if failed: return failed @@ -4061,8 +4071,9 @@ def run_model_tests(my_predbat): with_battery=True, discharge=0, battery_soc=10, - assert_keep=1 * import_rate * KEEP_SCALE, + assert_keep=14 * import_rate * 0.5 + ((1 + (1/12)) * import_rate * 0.5 * 0.5), keep=1, + keep_weight=0.5, ) failed |= simple_scenario( "battery_discharge_loss", @@ -4110,8 +4121,9 @@ def run_model_tests(my_predbat): with_battery=True, discharge=0, battery_soc=10, - assert_keep=14 * import_rate * 0.5 * KEEP_SCALE + 1 * import_rate * KEEP_SCALE, + assert_keep=14 * import_rate + 1 * import_rate * 0.5, keep=1.0, + keep_weight=1.0, ) failed |= simple_scenario( "battery_discharge_load_keep_mode_test1", @@ -4123,8 +4135,9 @@ def run_model_tests(my_predbat): with_battery=True, discharge=0, battery_soc=10, - assert_keep=14 * import_rate * 0.5 * KEEP_SCALE + 1 * import_rate * KEEP_SCALE, + assert_keep=14 * import_rate * 0.8 + 1 * import_rate * 0.8 * 0.5, keep=1.0, + keep_weight=0.8, save="test", ) failed |= simple_scenario( @@ -4137,8 +4150,9 @@ def run_model_tests(my_predbat): with_battery=True, discharge=0, battery_soc=10, - assert_keep=14 * import_rate * 0.5 * KEEP_SCALE + 1 * import_rate * KEEP_SCALE, + assert_keep=14 * import_rate * 0.8 + 1 * import_rate * 0.8 * 0.5, keep=1.0, + keep_weight=0.8, save="none", ) failed |= simple_scenario( @@ -4781,8 +4795,9 @@ def run_model_tests(my_predbat): discharge=0, battery_size=10, keep=1.0, + keep_weight=1.0, assert_final_iboost=0, - assert_keep=import_rate * 14 * 0.5 * KEEP_SCALE + import_rate * 1 * KEEP_SCALE, + assert_keep=import_rate * 14 + import_rate * 1 * 0.5, ) # Alternating high/low rates @@ -4840,7 +4855,7 @@ def run_model_tests(my_predbat): my_predbat, 0, 0, - assert_final_metric=import_rate * 120 * 1.5 - 2 * import_rate * 5 * 2, + assert_final_metric=import_rate * 120 * 1.5 - 2*import_rate*5*2, assert_final_soc=0, with_battery=False, iboost_enable=True, diff --git a/apps/predbat/userinterface.py b/apps/predbat/userinterface.py index 67893d0e..c68e0d07 100644 --- a/apps/predbat/userinterface.py +++ b/apps/predbat/userinterface.py @@ -341,7 +341,13 @@ def get_ha_config(self, name, default): """ item = self.config_index.get(name) if item and item["name"] == name: - value = item.get("value", None) + enabled = self.user_config_item_enabled(item) + if name == "set_export_freeze_only": + print("Get HA config {} enabled {} item {}".format(name, enabled, item)) + if not enabled: + value = None + else: + value = item.get("value", None) if default is None: default = item.get("default", None) if value is None: From 5c83bbf0de49a67327c8ae337dc437477d0878f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:08:17 +0000 Subject: [PATCH 2/7] [pre-commit.ci lite] apply automatic fixes --- apps/predbat/prediction.py | 2 +- apps/predbat/unit_test.py | 283 +++++++++++++++++++------------------ 2 files changed, 149 insertions(+), 136 deletions(-) diff --git a/apps/predbat/prediction.py b/apps/predbat/prediction.py index 06fbb184..2e2c998f 100644 --- a/apps/predbat/prediction.py +++ b/apps/predbat/prediction.py @@ -414,7 +414,7 @@ def run_prediction(self, charge_limit, charge_window, export_window, export_limi else: keep_minute_scaling = min(((minute - 4 * 60) / (2 * 60)), 1.0) * self.best_soc_keep_weight else: - keep_minute_scaling = self.best_soc_keep_weight + keep_minute_scaling = self.best_soc_keep_weight # Find charge & discharge windows charge_window_n = charge_window_optimised.get(minute_absolute, -1) diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index 3d3520d1..93c14c7d 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -69,13 +69,13 @@ def get_state(self, entity_id, default=None, attribute=None, refresh=False): if not entity_id: return {} elif entity_id in self.dummy_items: - #print("Getting state: {} {}".format(entity_id, self.dummy_items[entity_id])) + # print("Getting state: {} {}".format(entity_id, self.dummy_items[entity_id])) return self.dummy_items[entity_id] else: return None def call_service(self, service, **kwargs): - #print("Calling service: {} {}".format(service, kwargs)) + # print("Calling service: {} {}".format(service, kwargs)) if self.service_store_enable: self.service_store.append([service, kwargs]) return None @@ -91,13 +91,13 @@ def call_service(self, service, **kwargs): if not entity_id.startswith("switch."): print("Warn: Service for entity {} not a switch".format(entity_id)) elif entity_id in self.dummy_items: - self.dummy_items[entity_id] = 'on' + self.dummy_items[entity_id] = "on" elif service == "switch/turn_off": entity_id = kwargs.get("entity_id", None) if not entity_id.startswith("switch."): print("Warn: Service for entity {} not a switch".format(entity_id)) elif entity_id in self.dummy_items: - self.dummy_items[entity_id] = 'off' + self.dummy_items[entity_id] = "off" elif service == "select/select_option": entity_id = kwargs.get("entity_id", None) if not entity_id.startswith("select."): @@ -107,12 +107,12 @@ def call_service(self, service, **kwargs): return None def set_state(self, entity_id, state, attributes=None): - #print("Setting state: {} to {}".format(entity_id, state)) + # print("Setting state: {} to {}".format(entity_id, state)) self.dummy_items[entity_id] = state return None def get_history(self, entity_id, now=None, days=30): - #print("Getting history for {}".format(entity_id)) + # print("Getting history for {}".format(entity_id)) if self.history_enable: return [self.history] else: @@ -426,12 +426,14 @@ def run_compute_metric_tests(my_predbat): failed |= compute_metric_test(my_predbat, "cost_battery_cycle", cost=10.0, battery_cycle=25, metric_battery_cycle=0.1, assert_metric=10 + 25 * 0.1) return failed + def dummy_sleep(seconds): """ Dummy sleep function """ pass + class DummyRestAPI: def __init__(self): self.commands = [] @@ -441,24 +443,25 @@ def dummy_rest_postCommand(self, url, json): """ Dummy rest post command """ - #print("Dummy rest post command {} {}".format(url, json)) + # print("Dummy rest post command {} {}".format(url, json)) self.commands.append([url, json]) def dummy_rest_getData(self, url): if url == "dummy/runAll": - #print("Dummy rest get data {} returns {}".format(url, self.rest_data)) + # print("Dummy rest get data {} returns {}".format(url, self.rest_data)) return self.rest_data elif url == "dummy/readData": - #print("Dummy rest get data {} returns {}".format(url, self.rest_data)) + # print("Dummy rest get data {} returns {}".format(url, self.rest_data)) return self.rest_data else: return None - + def get_commands(self): commands = self.commands self.commands = [] return commands + def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_time, prev_charge_end_time, prev_enable_charge, charge_start_time, charge_end_time, minutes_now): """ test: @@ -470,7 +473,7 @@ def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_ inv.rest_data = None ha.dummy_items["select.charge_start_time"] = prev_charge_start_time ha.dummy_items["select.charge_end_time"] = prev_charge_end_time - ha.dummy_items['switch.scheduled_charge_enable'] = 'on' if prev_enable_charge else 'off' + ha.dummy_items["switch.scheduled_charge_enable"] = "on" if prev_enable_charge else "off" charge_start_time_tm = datetime.strptime(charge_start_time, "%H:%M:%S") charge_end_time_tm = datetime.strptime(charge_end_time, "%H:%M:%S") @@ -481,18 +484,18 @@ def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_ if ha.get_state("select.charge_end_time") != charge_end_time: print("ERROR: Charge end time should be {} got {}".format(charge_end_time, ha.get_state("select.charge_end_time"))) failed = True - if ha.get_state("switch.scheduled_charge_enable") != 'on': + if ha.get_state("switch.scheduled_charge_enable") != "on": print("ERROR: Charge enable should be on got {}".format(ha.get_state("switch.scheduled_charge_enable"))) failed = True - + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Timeslots"] = {} - inv.rest_data["Timeslots"]["Charge_start_time_slot_1"] = prev_charge_start_time - inv.rest_data["Timeslots"]["Charge_end_time_slot_1"] = prev_charge_end_time + inv.rest_data["Timeslots"]["Charge_start_time_slot_1"] = prev_charge_start_time + inv.rest_data["Timeslots"]["Charge_end_time_slot_1"] = prev_charge_end_time inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Enable_Charge_Schedule"] = 'on' if prev_enable_charge else 'off' + inv.rest_data["Control"]["Enable_Charge_Schedule"] = "on" if prev_enable_charge else "off" dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = charge_start_time dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = charge_end_time @@ -501,20 +504,21 @@ def test_adjust_charge_window(test_name, ha, inv, dummy_rest, prev_charge_start_ inv.adjust_charge_window(charge_start_time_tm, charge_end_time_tm, minutes_now) rest_command = dummy_rest.get_commands() if prev_charge_start_time != charge_start_time or prev_charge_end_time != charge_end_time: - expect_data = [['dummy/setChargeSlot1', {'start': charge_start_time[0:5], 'finish': charge_end_time[0:5]}]] + expect_data = [["dummy/setChargeSlot1", {"start": charge_start_time[0:5], "finish": charge_end_time[0:5]}]] else: expect_data = [] if prev_enable_charge != True: - expect_data.append(['dummy/enableChargeSchedule', {'state' : 'enable'}]) + expect_data.append(["dummy/enableChargeSchedule", {"state": "enable"}]) if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True return failed - + + def test_adjust_reserve(test_name, ha, inv, dummy_rest, prev_reserve, reserve, expect_reserve=None, reserve_min=4, reserve_max=100): """ - Test + Test inv.adjust_reserve(self, reserve): """ failed = False @@ -523,7 +527,7 @@ def test_adjust_reserve(test_name, ha, inv, dummy_rest, prev_reserve, reserve, e inv.reserve_percent = reserve_min inv.reserve_max = reserve_max - + print("Test: {}".format(test_name)) # Non-REST Mode @@ -533,27 +537,28 @@ def test_adjust_reserve(test_name, ha, inv, dummy_rest, prev_reserve, reserve, e if ha.get_state("number.reserve") != expect_reserve: print("ERROR: Reserve should be {} got {}".format(expect_reserve, ha.get_state("number.reserve"))) failed = True - + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Battery_Power_Reserve"] = prev_reserve + inv.rest_data["Control"]["Battery_Power_Reserve"] = prev_reserve dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"]["Battery_Power_Reserve"] = expect_reserve inv.adjust_reserve(reserve) rest_command = dummy_rest.get_commands() if prev_reserve != expect_reserve: - expect_data = [['dummy/setBatteryReserve', {'reservePercent': expect_reserve}]] + expect_data = [["dummy/setBatteryReserve", {"reservePercent": expect_reserve}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed + def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, expect_rate=None, discharge=False): """ Test the adjust_inverter_mode function @@ -561,7 +566,7 @@ def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, exp failed = False if expect_rate is None: expect_rate = rate - + print("Test: {}".format(test_name)) # Non-REST Mode @@ -575,13 +580,13 @@ def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, exp if ha.get_state(entity) != expect_rate: print("ERROR: Inverter rate should be {} got {}".format(expect_rate, ha.get_state(entity))) failed = True - + # REST Mode rest_entity = "Battery_Discharge_Rate" if discharge else "Battery_Charge_Rate" inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"][rest_entity] = prev_rate + inv.rest_data["Control"][rest_entity] = prev_rate dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"][rest_entity] = expect_rate @@ -593,15 +598,15 @@ def test_adjust_charge_rate(test_name, ha, inv, dummy_rest, prev_rate, rate, exp rest_command = dummy_rest.get_commands() if prev_rate != expect_rate: if discharge: - expect_data = [['dummy/setDischargeRate', {'dischargeRate': expect_rate}]] + expect_data = [["dummy/setDischargeRate", {"dischargeRate": expect_rate}]] else: - expect_data = [['dummy/setChargeRate', {'chargeRate': expect_rate}]] + expect_data = [["dummy/setChargeRate", {"chargeRate": expect_rate}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed @@ -612,7 +617,7 @@ def test_adjust_inverter_mode(test_name, ha, inv, dummy_rest, prev_mode, mode, e failed = False if expect_mode is None: expect_mode = mode - + print("Test: {}".format(test_name)) # Non-REST Mode @@ -622,27 +627,28 @@ def test_adjust_inverter_mode(test_name, ha, inv, dummy_rest, prev_mode, mode, e if ha.get_state("select.inverter_mode") != expect_mode: print("ERROR: Inverter mode should be {} got {}".format(expect_mode, ha.get_state("select.inverter_mode"))) failed = True - + # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Mode"] = prev_mode + inv.rest_data["Control"]["Mode"] = prev_mode dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"]["Mode"] = expect_mode inv.adjust_inverter_mode(True if mode == "Timed Export" else False, False) rest_command = dummy_rest.get_commands() if prev_mode != expect_mode: - expect_data = [['dummy/setBatteryMode', {'mode': expect_mode}]] + expect_data = [["dummy/setBatteryMode", {"mode": expect_mode}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed + def test_adjust_battery_target(test_name, ha, inv, dummy_rest, prev_soc, soc, isCharging, isExporting, expect_soc=None): """ Test the adjust_battery_target function @@ -651,7 +657,6 @@ def test_adjust_battery_target(test_name, ha, inv, dummy_rest, prev_soc, soc, is if expect_soc is None: expect_soc = soc - print("Test: {}".format(test_name)) # Non-REST Mode @@ -662,28 +667,42 @@ def test_adjust_battery_target(test_name, ha, inv, dummy_rest, prev_soc, soc, is print("ERROR: Charge limit should be {} got {}".format(expect_soc, ha.get_state("number.charge_limit"))) failed = True - # REST Mode inv.rest_api = "dummy" inv.rest_data = {} inv.rest_data["Control"] = {} - inv.rest_data["Control"]["Target_SOC"] = prev_soc + inv.rest_data["Control"]["Target_SOC"] = prev_soc dummy_rest.rest_data = copy.deepcopy(inv.rest_data) dummy_rest.rest_data["Control"]["Target_SOC"] = expect_soc inv.adjust_battery_target(soc, isCharging=True, isExporting=False) rest_command = dummy_rest.get_commands() if soc != prev_soc: - expect_data = [['dummy/setChargeTarget', {'chargeToPercent': expect_soc}]] + expect_data = [["dummy/setChargeTarget", {"chargeToPercent": expect_soc}]] else: expect_data = [] if json.dumps(expect_data) != json.dumps(rest_command): print("ERROR: Rest command should be {} got {}".format(expect_data, rest_command)) failed = True - + return failed -def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start_time, expect_charge_end_time, expect_charge_enable, expect_discharge_start_time, expect_discharge_end_time, expect_discharge_enable, expect_battery_power, expect_pv_power, expect_load_power, expect_soc_kwh): + +def test_inverter_update( + test_name, + my_predbat, + dummy_items, + expect_charge_start_time, + expect_charge_end_time, + expect_charge_enable, + expect_discharge_start_time, + expect_discharge_end_time, + expect_discharge_enable, + expect_battery_power, + expect_pv_power, + expect_load_power, + expect_soc_kwh, +): failed = False print("**** Running Test: {} ****".format(test_name)) @@ -693,7 +712,7 @@ def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start discharge_start_time_minutes = (datetime.strptime(expect_discharge_start_time, "%H:%M:%S") - midnight).total_seconds() / 60 discharge_end_time_minutes = (datetime.strptime(expect_discharge_end_time, "%H:%M:%S") - midnight).total_seconds() / 60 - my_predbat.args['givtcp_rest'] = None + my_predbat.args["givtcp_rest"] = None inv = Inverter(my_predbat, 0) inv.sleep = dummy_sleep @@ -710,16 +729,16 @@ def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start print("Test: Update Inverter") - dummy_items['select.charge_start_time'] = expect_charge_start_time - dummy_items['select.charge_end_time'] = expect_charge_end_time - dummy_items['select.discharge_start_time'] = expect_discharge_start_time - dummy_items['select.discharge_end_time'] = expect_discharge_end_time - dummy_items['sensor.battery_power'] = expect_battery_power - dummy_items['sensor.pv_power'] = expect_pv_power - dummy_items['sensor.load_power'] = expect_load_power - dummy_items['switch.scheduled_charge_enable'] = 'on' if expect_charge_enable else 'off' - dummy_items['switch.scheduled_discharge_enable'] = 'on' if expect_discharge_enable else 'off' - dummy_items['sensor.soc_kw'] = expect_soc_kwh + dummy_items["select.charge_start_time"] = expect_charge_start_time + dummy_items["select.charge_end_time"] = expect_charge_end_time + dummy_items["select.discharge_start_time"] = expect_discharge_start_time + dummy_items["select.discharge_end_time"] = expect_discharge_end_time + dummy_items["sensor.battery_power"] = expect_battery_power + dummy_items["sensor.pv_power"] = expect_pv_power + dummy_items["sensor.load_power"] = expect_load_power + dummy_items["switch.scheduled_charge_enable"] = "on" if expect_charge_enable else "off" + dummy_items["switch.scheduled_discharge_enable"] = "on" if expect_discharge_enable else "off" + dummy_items["sensor.soc_kw"] = expect_soc_kwh inv.update_status(my_predbat.minutes_now) if not inv.inv_has_discharge_enable_time: @@ -740,7 +759,7 @@ def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start charge_end_time_minutes += 24 * 60 if inv.charge_start_time_minutes != charge_start_time_minutes: - print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items['select.charge_start_time'])) + print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items["select.charge_start_time"])) failed = True if inv.charge_end_time_minutes != charge_end_time_minutes: print("ERROR: Charge end time should be {} got {}".format(charge_end_time_minutes, inv.charge_end_time_minutes)) @@ -773,30 +792,30 @@ def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start # REST Mode dummy_rest = DummyRestAPI() - my_predbat.args['givtcp_rest'] = "dummy" + my_predbat.args["givtcp_rest"] = "dummy" inv.sleep = dummy_sleep dummy_rest.rest_data = {} dummy_rest.rest_data["Control"] = {} dummy_rest.rest_data["Control"]["Target_SOC"] = 99 - dummy_rest.rest_data["Control"]["Mode"] = "Eco" - dummy_rest.rest_data["Control"]["Battery_Power_Reserve"] = 4.0 - 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' if expect_charge_enable else 'disable' - dummy_rest.rest_data["Control"]["Enable_Discharge_Schedule"] = 'enable' if expect_discharge_enable else 'disable' + dummy_rest.rest_data["Control"]["Mode"] = "Eco" + dummy_rest.rest_data["Control"]["Battery_Power_Reserve"] = 4.0 + 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" if expect_charge_enable else "disable" + dummy_rest.rest_data["Control"]["Enable_Discharge_Schedule"] = "enable" if expect_discharge_enable else "disable" dummy_rest.rest_data["Timeslots"] = {} - dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = expect_charge_start_time - dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = expect_charge_end_time - dummy_rest.rest_data["Timeslots"]["Discharge_start_time_slot_1"] = expect_discharge_start_time - dummy_rest.rest_data["Timeslots"]["Discharge_end_time_slot_1"] = expect_discharge_end_time - dummy_rest.rest_data["Power"] = {} + dummy_rest.rest_data["Timeslots"]["Charge_start_time_slot_1"] = expect_charge_start_time + dummy_rest.rest_data["Timeslots"]["Charge_end_time_slot_1"] = expect_charge_end_time + dummy_rest.rest_data["Timeslots"]["Discharge_start_time_slot_1"] = expect_discharge_start_time + dummy_rest.rest_data["Timeslots"]["Discharge_end_time_slot_1"] = expect_discharge_end_time + dummy_rest.rest_data["Power"] = {} dummy_rest.rest_data["Power"]["Power"] = {} dummy_rest.rest_data["Power"]["Power"]["SOC_kWh"] = expect_soc_kwh dummy_rest.rest_data["Power"]["Power"]["Battery_Power"] = expect_battery_power dummy_rest.rest_data["Power"]["Power"]["PV_Power"] = expect_pv_power dummy_rest.rest_data["Power"]["Power"]["Load_Power"] = expect_load_power - dummy_items['sensor.soc_kw'] = -1 + dummy_items["sensor.soc_kw"] = -1 inv = Inverter(my_predbat, 0, rest_postCommand=dummy_rest.dummy_rest_postCommand, rest_getData=dummy_rest.dummy_rest_getData) @@ -804,7 +823,7 @@ def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start inv.update_status(my_predbat.minutes_now) if inv.charge_start_time_minutes != charge_start_time_minutes: - print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items['select.charge_start_time'])) + print("ERROR: Charge start time should be {} got {} ({})".format(charge_start_time_minutes, inv.charge_start_time_minutes, dummy_items["select.charge_start_time"])) failed = True if inv.charge_end_time_minutes != charge_end_time_minutes: print("ERROR: Charge end time should be {} got {}".format(charge_end_time_minutes, inv.charge_end_time_minutes)) @@ -836,6 +855,7 @@ def test_inverter_update(test_name, my_predbat, dummy_items, expect_charge_start 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"): """ Tests; @@ -856,8 +876,8 @@ def adjust_charge_immediate(self, target_soc, freeze=False) my_predbat.args["discharge_freeze_service"] = "discharge_freeze" my_predbat.args["device_id"] = "DID0" - dummy_items['select.charge_start_time'] = charge_start_time - dummy_items['select.charge_end_time'] = charge_end_time + dummy_items["select.charge_start_time"] = charge_start_time + dummy_items["select.charge_end_time"] = charge_end_time power = int(inv.battery_rate_max_charge * MINUTE_WATT) @@ -869,14 +889,14 @@ def adjust_charge_immediate(self, target_soc, freeze=False) pass elif soc == inv.soc_percent or freeze: if stop_discharge: - expected.append(["discharge_stop", {"device_id" : "DID0"}]) - expected.append(["charge_freeze", {"device_id" : "DID0", "target_soc": soc, "power": power}]) + expected.append(["discharge_stop", {"device_id": "DID0"}]) + expected.append(["charge_freeze", {"device_id": "DID0", "target_soc": soc, "power": power}]) elif soc > 0: if stop_discharge: - expected.append(["discharge_stop", {"device_id" : "DID0"}]) - expected.append(["charge_start", {"device_id" : "DID0", "target_soc": soc, "power": power}]) + expected.append(["discharge_stop", {"device_id": "DID0"}]) + expected.append(["charge_start", {"device_id": "DID0", "target_soc": soc, "power": power}]) else: - expected.append(["charge_stop", {"device_id" : "DID0"}]) + expected.append(["charge_stop", {"device_id": "DID0"}]) if json.dumps(expected) != json.dumps(result): print("ERROR: Adjust charge immediate - charge service should be {} got {}".format(expected, result)) failed = True @@ -884,6 +904,7 @@ def adjust_charge_immediate(self, target_soc, freeze=False) ha.service_store_enable = False return failed + def test_call_adjust_export_immediate(test_name, my_predbat, ha, inv, dummy_items, soc, repeat=False, freeze=False, clear=False, charge_stop=False, discharge_start_time="00:00:00", discharge_end_time="23:55:00"): """ Tests; @@ -893,7 +914,7 @@ def adjust_export_immediate(self, target_soc, freeze=False) ha.service_store_enable = True if clear: ha.service_store = [] - + print("**** Running Test: {} ****".format(test_name)) my_predbat.args["charge_start_service"] = "charge_start" @@ -905,8 +926,8 @@ def adjust_export_immediate(self, target_soc, freeze=False) my_predbat.args["device_id"] = "DID0" power = int(inv.battery_rate_max_discharge * MINUTE_WATT) - dummy_items['select.discharge_start_time'] = discharge_start_time - dummy_items['select.discharge_end_time'] = discharge_end_time + dummy_items["select.discharge_start_time"] = discharge_start_time + dummy_items["select.discharge_end_time"] = discharge_end_time inv.adjust_export_immediate(soc, freeze=freeze) result = ha.get_service_store() @@ -916,14 +937,14 @@ def adjust_export_immediate(self, target_soc, freeze=False) pass elif freeze: if charge_stop: - expected.append(["charge_stop", {"device_id" : "DID0"}]) - expected.append(["discharge_freeze", {"device_id" : "DID0", "target_soc": soc, "power": power}]) + expected.append(["charge_stop", {"device_id": "DID0"}]) + expected.append(["discharge_freeze", {"device_id": "DID0", "target_soc": soc, "power": power}]) elif soc > 0 and soc < 100: if charge_stop: - expected.append(["charge_stop", {"device_id" : "DID0"}]) - expected.append(["discharge_start", {"device_id" : "DID0", "target_soc": soc, "power": power}]) + expected.append(["charge_stop", {"device_id": "DID0"}]) + expected.append(["discharge_start", {"device_id": "DID0", "target_soc": soc, "power": power}]) else: - expected.append(["discharge_stop", {"device_id" : "DID0"}]) + expected.append(["discharge_stop", {"device_id": "DID0"}]) if json.dumps(expected) != json.dumps(result): print("ERROR: Adjust export immediate - discharge service should be {} got {}".format(expected, result)) failed = True @@ -931,6 +952,7 @@ def adjust_export_immediate(self, target_soc, freeze=False) ha.service_store_enable = False return failed + def test_call_service_template(test_name, my_predbat, inv, service_name="test", domain="charge", data={}, extra_data={}, clear=True, repeat=False, service_template=None, expected_result=None): """ tests @@ -972,8 +994,8 @@ def call_service_template(self, service, data, domain="charge", extra_data={}) ha.service_store_enable = False return failed -def run_inverter_tests(): +def run_inverter_tests(): """ Test the inverter functions """ @@ -1012,8 +1034,8 @@ def run_inverter_tests(): "sensor.pv_power": 1.0, "sensor.load_power": 2.0, "number.reserve": 4.0, - "switch.scheduled_charge_enable": 'off', - "switch.scheduled_discharge_enable": 'off', + "switch.scheduled_charge_enable": "off", + "switch.scheduled_discharge_enable": "off", "select.charge_start_time": "01:11:00", "select.charge_end_time": "02:22:00", "select.discharge_start_time": "03:33:00", @@ -1022,43 +1044,42 @@ def run_inverter_tests(): my_predbat.ha_interface.dummy_items = dummy_items my_predbat.args["auto_restart"] = [{"service": "switch/turn_on", "entity_id": "switch.restart"}] my_predbat.args["givtcp_rest"] = None - my_predbat.args['inverter_type'] = ["GE"] + my_predbat.args["inverter_type"] = ["GE"] for entity_id in dummy_items.keys(): arg_name = entity_id.split(".")[1] my_predbat.args[arg_name] = entity_id failed |= test_inverter_update( "update1", - my_predbat, - dummy_items, - expect_charge_start_time="01:11:00", - expect_charge_end_time="02:22:00", - expect_charge_enable=False, - expect_discharge_start_time="03:33:00", - expect_discharge_end_time="04:44:00", - expect_discharge_enable=True, - expect_battery_power=5.0, - expect_pv_power=1.0, + my_predbat, + dummy_items, + expect_charge_start_time="01:11:00", + expect_charge_end_time="02:22:00", + expect_charge_enable=False, + expect_discharge_start_time="03:33:00", + expect_discharge_end_time="04:44:00", + expect_discharge_enable=True, + expect_battery_power=5.0, + expect_pv_power=1.0, expect_load_power=2.0, expect_soc_kwh=6.0, ) failed |= test_inverter_update( "update2", - my_predbat, - dummy_items, - expect_charge_start_time="01:11:00", - expect_charge_end_time="23:22:00", - expect_charge_enable=True, - expect_discharge_start_time="03:33:00", - expect_discharge_end_time="04:44:00", - expect_discharge_enable=False, - expect_battery_power=6.0, - expect_pv_power=1.5, + my_predbat, + dummy_items, + expect_charge_start_time="01:11:00", + expect_charge_end_time="23:22:00", + expect_charge_enable=True, + expect_discharge_start_time="03:33:00", + expect_discharge_end_time="04:44:00", + expect_discharge_enable=False, + expect_battery_power=6.0, + expect_pv_power=1.5, expect_load_power=2.5, expect_soc_kwh=6.6, ) - my_predbat.args["givtcp_rest"] = None dummy_rest = DummyRestAPI() inv = Inverter(my_predbat, 0, rest_postCommand=dummy_rest.dummy_rest_postCommand, rest_getData=dummy_rest.dummy_rest_getData) @@ -1084,11 +1105,11 @@ def run_inverter_tests(): failed |= test_adjust_charge_rate("adjust_discharge_rate2", ha, inv, dummy_rest, 200, 0, 0, discharge=True) failed |= test_adjust_charge_rate("adjust_discharge_rate3", ha, inv, dummy_rest, 200, 210, 200, discharge=True) - failed |= test_adjust_reserve("adjust_reserve1", ha, inv, dummy_rest, 4, 50, reserve_max = 100) - failed |= test_adjust_reserve("adjust_reserve2", ha, inv, dummy_rest, 50, 0, 4, reserve_max = 100) - failed |= test_adjust_reserve("adjust_reserve3", ha, inv, dummy_rest, 20, 100, reserve_max = 100) - failed |= test_adjust_reserve("adjust_reserve4", ha, inv, dummy_rest, 20, 100, 98, reserve_min=4, reserve_max = 98) - failed |= test_adjust_reserve("adjust_reserve5", ha, inv, dummy_rest, 50, 0, 0, reserve_min=0, reserve_max = 100) + failed |= test_adjust_reserve("adjust_reserve1", ha, inv, dummy_rest, 4, 50, reserve_max=100) + failed |= test_adjust_reserve("adjust_reserve2", ha, inv, dummy_rest, 50, 0, 4, reserve_max=100) + failed |= test_adjust_reserve("adjust_reserve3", ha, inv, dummy_rest, 20, 100, reserve_max=100) + failed |= test_adjust_reserve("adjust_reserve4", ha, inv, dummy_rest, 20, 100, 98, reserve_min=4, reserve_max=98) + failed |= test_adjust_reserve("adjust_reserve5", ha, inv, dummy_rest, 50, 0, 0, reserve_min=0, reserve_max=100) failed |= test_adjust_charge_window("adjust_charge_window1", ha, inv, dummy_rest, "00:00:00", "00:00:00", False, "00:00:00", "00:00:00", my_predbat.minutes_now) failed |= test_adjust_charge_window("adjust_charge_window2", ha, inv, dummy_rest, "00:00:00", "00:00:00", False, "00:00:00", "23:00:00", my_predbat.minutes_now) @@ -1103,28 +1124,19 @@ def run_inverter_tests(): failed |= test_call_service_template("test_service_simple5", my_predbat, inv, service_name="test_service", domain="charge", data={"test": "data"}, extra_data={"extra": "data2"}, clear=False, repeat=False) failed |= test_call_service_template( - "test_service_complex1", - my_predbat, - inv, - service_name="complex_service", - domain="charge", - data={"test": "data"}, - extra_data={"extra": "extra_data"}, - service_template = {"service" : "funny", "dummy": "22", "extra": "{extra}"}, - expected_result = [['funny', {'dummy': '22', 'extra': 'extra_data'}]], - clear=False + "test_service_complex1", + my_predbat, + inv, + service_name="complex_service", + domain="charge", + data={"test": "data"}, + extra_data={"extra": "extra_data"}, + service_template={"service": "funny", "dummy": "22", "extra": "{extra}"}, + expected_result=[["funny", {"dummy": "22", "extra": "extra_data"}]], + clear=False, ) failed |= test_call_service_template( - "test_service_complex2", - my_predbat, - inv, - service_name="complex_service", - domain="charge", - data={"test": "data"}, - extra_data={"extra": "extra_data"}, - service_template = {"service" : "funny", "dummy": "22", "extra": "{extra}"}, - clear=False, - repeat=True + "test_service_complex2", my_predbat, inv, service_name="complex_service", domain="charge", data={"test": "data"}, extra_data={"extra": "extra_data"}, service_template={"service": "funny", "dummy": "22", "extra": "{extra}"}, clear=False, repeat=True ) inv.soc_percent = 49 failed |= test_call_adjust_charge_immediate("charge_immediate1", my_predbat, ha, inv, dummy_items, 100, clear=True, stop_discharge=True) @@ -1146,6 +1158,7 @@ def run_inverter_tests(): return failed + def simple_scenario( name, my_predbat, @@ -4071,7 +4084,7 @@ def run_model_tests(my_predbat): with_battery=True, discharge=0, battery_soc=10, - assert_keep=14 * import_rate * 0.5 + ((1 + (1/12)) * import_rate * 0.5 * 0.5), + assert_keep=14 * import_rate * 0.5 + ((1 + (1 / 12)) * import_rate * 0.5 * 0.5), keep=1, keep_weight=0.5, ) @@ -4855,7 +4868,7 @@ def run_model_tests(my_predbat): my_predbat, 0, 0, - assert_final_metric=import_rate * 120 * 1.5 - 2*import_rate*5*2, + assert_final_metric=import_rate * 120 * 1.5 - 2 * import_rate * 5 * 2, assert_final_soc=0, with_battery=False, iboost_enable=True, From 79e373372f16f60baca68ab48f492790bddbafec Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:08:22 +0000 Subject: [PATCH 3/7] Put back debug stuff --- apps/predbat/userinterface.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/predbat/userinterface.py b/apps/predbat/userinterface.py index c68e0d07..4b776a71 100644 --- a/apps/predbat/userinterface.py +++ b/apps/predbat/userinterface.py @@ -342,12 +342,7 @@ def get_ha_config(self, name, default): item = self.config_index.get(name) if item and item["name"] == name: enabled = self.user_config_item_enabled(item) - if name == "set_export_freeze_only": - print("Get HA config {} enabled {} item {}".format(name, enabled, item)) - if not enabled: - value = None - else: - value = item.get("value", None) + value = item.get("value", None) if default is None: default = item.get("default", None) if value is None: From 41fe7076f5a3147220d6e47c99355ac47f6a58b2 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:08:52 +0000 Subject: [PATCH 4/7] again --- apps/predbat/userinterface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/predbat/userinterface.py b/apps/predbat/userinterface.py index 4b776a71..67893d0e 100644 --- a/apps/predbat/userinterface.py +++ b/apps/predbat/userinterface.py @@ -341,7 +341,6 @@ def get_ha_config(self, name, default): """ item = self.config_index.get(name) if item and item["name"] == name: - enabled = self.user_config_item_enabled(item) value = item.get("value", None) if default is None: default = item.get("default", None) From aac9fddd60f9c87c368c392a1496fdb397b6525b Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:09:56 +0000 Subject: [PATCH 5/7] remove debug --- apps/predbat/fetch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/predbat/fetch.py b/apps/predbat/fetch.py index ede4c222..02589c5b 100644 --- a/apps/predbat/fetch.py +++ b/apps/predbat/fetch.py @@ -1792,7 +1792,6 @@ def fetch_config_options(self): self.set_status_notify = self.get_arg("set_status_notify") self.set_inverter_notify = self.get_arg("set_inverter_notify") self.set_export_freeze_only = self.get_arg("set_export_freeze_only") - print("Getting set_export_freeze_only", self.set_export_freeze_only) self.set_discharge_during_charge = self.get_arg("set_discharge_during_charge") # Mode From 0e7296b157d165950774855df7574527f30c0add Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:13:21 +0000 Subject: [PATCH 6/7] Update customisation.md --- docs/customisation.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/customisation.md b/docs/customisation.md index 0801273b..5232eeb3 100644 --- a/docs/customisation.md +++ b/docs/customisation.md @@ -254,9 +254,15 @@ you want to export as late in the day as you can. **input_number.predbat_best_soc_keep** is the minimum battery level in kWh that Predbat will to try to keep the battery above for the Predbat plan. This is a soft constraint only that's used for longer term planning and is ignored for the forthcoming first 4 hours of the plan. As this is not used for short-term planning it's possible for your SoC to drop below this - use **input_number.predbat_best_soc_min** -if you need a hard SoC constraint that will always be maintained. +if you want to force all charges to be above a set level. It's usually good to have best_soc_keep set to a value above 0 to allow some margin in case you use more energy than planned between charge slots. +**input_number.predbat_best_soc_keep_weight** (_expert_mode_) Is used to tune how strongly you want the keep metric to apply. +A value of 0 would essentially ignore keep while higher values will make it more important to always stay above your keep threshold even if it costs +more money to do so. + +The default is 0.5 - this is the recommended setting. + **input_number.predbat_best_soc_min** (_expert mode_) sets the minimum charge level (in kWh) for charging during each slot and the minimum force export level also (set to 0 if you want to skip some slots). If you set this to a non-zero value you will need to use the low rate threshold to control which slots you charge from or you may charge all the time. From fe0f8bff3434b281b3eb83cb0deb531a2d0302f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:13:57 +0000 Subject: [PATCH 7/7] [pre-commit.ci lite] apply automatic fixes --- docs/customisation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customisation.md b/docs/customisation.md index 5232eeb3..69c65c86 100644 --- a/docs/customisation.md +++ b/docs/customisation.md @@ -257,7 +257,7 @@ As this is not used for short-term planning it's possible for your SoC to drop b if you want to force all charges to be above a set level. It's usually good to have best_soc_keep set to a value above 0 to allow some margin in case you use more energy than planned between charge slots. -**input_number.predbat_best_soc_keep_weight** (_expert_mode_) Is used to tune how strongly you want the keep metric to apply. +**input_number.predbat_best_soc_keep_weight** (_expert_mode_) Is used to tune how strongly you want the keep metric to apply. A value of 0 would essentially ignore keep while higher values will make it more important to always stay above your keep threshold even if it costs more money to do so.