From 0e8da874c229ce6a1da38a646fbc3c24729138af Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 7 Oct 2026 14:33:47 +0200 Subject: [PATCH] =?UTF-8?q?Verbraucher:=20min=20Betriebsstrom=20auf=20mini?= =?UTF-8?q?male=20Leistung=20=C3=A4ndern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/control/consumer/consumer.py | 5 ++- packages/control/consumer/consumer_data.py | 2 +- packages/control/consumer/consumer_test.py | 21 +++++++---- packages/helpermodules/update_config.py | 17 ++++++++- packages/helpermodules/update_config_test.py | 39 +++++++++++++++++++- 5 files changed, 71 insertions(+), 13 deletions(-) diff --git a/packages/control/consumer/consumer.py b/packages/control/consumer/consumer.py index def4aafc6c..35fe38de03 100644 --- a/packages/control/consumer/consumer.py +++ b/packages/control/consumer/consumer.py @@ -77,7 +77,7 @@ def update(self): def _get_min_current(self) -> float: if self.data.usage.type == ConsumerUsage.SUSPENDABLE_TUNABLE: - return self.data.config.min_current + return self._convert_power_to_current(self.data.config.min_power) else: if self.data.get.charge_state: return get_medium_charging_current(self.data.get.currents) @@ -416,7 +416,8 @@ def wait_for_start_handler( self, func: Callable[[], Tuple[float, Optional[str], Chargemode]] ) -> Tuple[float, Optional[str], Chargemode, bool]: chargemode_defined_by_wait_for_start_handler = False - standby_threshold = self.STANDBY_THRESHOLD if self.data.usage.type in ON_OFF else self.data.config.min_current + standby_threshold = (self.STANDBY_THRESHOLD if self.data.usage.type in ON_OFF + else self._convert_power_to_current(self.data.config.min_power)) if self.data.usage.wait_for_start_active: if self.data.set.wait_for_start_state == WaitForStartStates.WAIT_FOR_DEVICE_START: # mit Minimalstrom prüfen, damit Standby-Geräte nicht als laufend erkannt werden diff --git a/packages/control/consumer/consumer_data.py b/packages/control/consumer/consumer_data.py index f830321cba..99aaefa9f4 100644 --- a/packages/control/consumer/consumer_data.py +++ b/packages/control/consumer/consumer_data.py @@ -55,7 +55,7 @@ class ConsumerConfig: connected_phases: int = 1 phase_1: int = 1 max_power: float = 5000 - min_current: float = 0.5 + min_power: float = 115 min_interval: int = 60 is_home_consumption_consumer: str = "auto_home_consumption" diff --git a/packages/control/consumer/consumer_test.py b/packages/control/consumer/consumer_test.py index b64e6b6631..1ed387eeef 100644 --- a/packages/control/consumer/consumer_test.py +++ b/packages/control/consumer/consumer_test.py @@ -28,7 +28,7 @@ def mock_data() -> None: def consumer() -> Consumer: load = Consumer(1) load.data.usage.type = ConsumerUsage.CONTINUOUS - load.data.config.min_current = 6 + load.data.config.min_power = 1380 load.data.config.max_power = 2300 load.data.config.connected_phases = 1 load.data.get.voltages = [230] @@ -169,7 +169,7 @@ def test_wait_for_start_handler_standby_threshold_depends_on_usage_type( consumer.data.usage.wait_for_start_active = True consumer.data.usage.type = usage_type consumer.data.set.wait_for_start_state = WaitForStartStates.WAIT_FOR_DEVICE_START - # 0.2 A liegt über STANDBY_THRESHOLD (0.15), aber unter min_current (6 A) + # 0.2 A liegt über STANDBY_THRESHOLD (0.15), aber unter der aus min_power abgeleiteten Schwelle (~6 A) consumer.data.get.currents = [0.2, 0.2, 0.2] charging_func = Mock(return_value=(11, "ok", Chargemode.PV_CHARGING)) @@ -300,29 +300,34 @@ def test_pv_charging(consumer: Consumer): @pytest.mark.parametrize( - "usage_type, charge_state, currents, expected_min_current", + "usage_type, charge_state, connected_phases, currents, expected_min_current", [ - pytest.param(ConsumerUsage.CONTINUOUS, True, [13, 13, 13], 13, id="continuous-while-running"), - pytest.param(ConsumerUsage.CONTINUOUS, False, [13, 13, 13], 10, id="continuous-while-stopped"), - pytest.param(ConsumerUsage.SUSPENDABLE_TUNABLE, True, [13, 13, 13], 6, id="tunable-while-running"), - pytest.param(ConsumerUsage.SUSPENDABLE_TUNABLE, False, [13, 13, 13], 6, id="tunable-while-stopped"), + pytest.param(ConsumerUsage.CONTINUOUS, True, 1, [13, 13, 13], 13, id="continuous-while-running"), + pytest.param(ConsumerUsage.CONTINUOUS, False, 1, [13, 13, 13], 10, id="continuous-while-stopped"), + pytest.param(ConsumerUsage.SUSPENDABLE_TUNABLE, True, 1, [13, 13, 13], 6, + id="tunable-single-phase"), + pytest.param(ConsumerUsage.SUSPENDABLE_TUNABLE, False, 2, [13, 13, 13], 3, + id="tunable-two-phases-min-power-conversion"), ], ) def test_set_control_parameter_sets_min_current_by_usage_and_charge_state( consumer: Consumer, usage_type: ConsumerUsage, charge_state: bool, + connected_phases: int, currents: List[float], expected_min_current: float): # setup consumer.data.usage.type = usage_type consumer.data.get.charge_state = charge_state + consumer.data.config.connected_phases = connected_phases + consumer.data.get.voltages = [230]*3 consumer.data.get.currents = currents # execution consumer.set_control_parameter( required_current=11, - phases=1, + phases=connected_phases, submode=Chargemode.PV_CHARGING, mode=Chargemode.PV_CHARGING, ) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index df9bd4824a..966c65bd7a 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -63,7 +63,7 @@ class UpdateConfig: - DATASTORE_VERSION = 153 + DATASTORE_VERSION = 154 FILE_OPERATION_VERSION = 0 @@ -4103,3 +4103,18 @@ def upgrade(topic: str, payload) -> Optional[dict]: return None self._loop_all_received_topics(upgrade) self._append_datastore_version(153) + + def upgrade_datastore_154(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search(r"^openWB/consumer/[0-9]+/config$", topic) is not None: + config = decode_payload(payload) + if "min_current" in config: + if "min_power" not in config: + phases = config.get("connected_phases") or 1 + config["min_power"] = float(config["min_current"]) * phases * 230 + del config["min_current"] + return {topic: config} + return None + + self._loop_all_received_topics(upgrade) + self._append_datastore_version(154) diff --git a/packages/helpermodules/update_config_test.py b/packages/helpermodules/update_config_test.py index e05490039b..9c48ff2735 100644 --- a/packages/helpermodules/update_config_test.py +++ b/packages/helpermodules/update_config_test.py @@ -267,11 +267,48 @@ def test_upgrade_datastore_125_converts_energycharts_surcharge(mock_pub): provider = uc.all_received_topics["openWB/optional/ep/flexible_tariff/provider"] assert provider["configuration"]["surcharge"] == pytest.approx(0.0001) assert uc.all_received_topics["openWB/system/datastore_version"] == [123, 125] - updated_topics = [call.args[0] for call in mock_pub.pub.call_args_list] assert "openWB/optional/ep/flexible_tariff/provider" in updated_topics +def test_upgrade_datastore_154_converts_min_current_to_min_power_and_removes_legacy_field(): + uc = UpdateConfig() + uc.all_received_topics = { + "openWB/system/datastore_version": list(range(154)), + "openWB/consumer/1/config": { + "connected_phases": 2, + "max_power": 5000, + "min_current": 1.5, + }, + } + + uc.upgrade_datastore_154() + + migrated_config = uc.all_received_topics["openWB/consumer/1/config"] + assert migrated_config["min_power"] == pytest.approx(690.0) + assert "min_current" not in migrated_config + assert 154 in uc.all_received_topics["openWB/system/datastore_version"] + + +def test_upgrade_datastore_154_keeps_existing_min_power_and_removes_legacy_field(): + uc = UpdateConfig() + uc.all_received_topics = { + "openWB/system/datastore_version": list(range(154)), + "openWB/consumer/2/config": { + "connected_phases": 3, + "max_power": 5000, + "min_power": 1200, + "min_current": 2, + }, + } + + uc.upgrade_datastore_154() + + migrated_config = uc.all_received_topics["openWB/consumer/2/config"] + assert migrated_config["min_power"] == 1200 + assert "min_current" not in migrated_config + + def test_upgrade_datastore_125_is_idempotent_for_already_converted_values(mock_pub): uc = UpdateConfig() uc.all_received_topics = {