From 5e21f4bb9fa5fcad51fd6c883c29e275669d2b53 Mon Sep 17 00:00:00 2001 From: rleidner Date: Sun, 22 Feb 2026 20:15:41 +0100 Subject: [PATCH 01/10] soc_calculation_threshold, range calculation --- packages/helpermodules/update_config.py | 13 +++++- packages/modules/common/abstract_vehicle.py | 2 + .../modules/common/configurable_vehicle.py | 42 ++++++++++++++----- packages/modules/update_soc.py | 4 +- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 4983f05e30..0eeaf51f9b 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 107 + DATASTORE_VERSION = 108 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -2699,3 +2699,14 @@ def upgrade(topic: str, payload) -> None: return {topic: provider} self._loop_all_received_topics(upgrade) self._append_datastore_version(107) + + def upgrade_datastore_108(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/vehicle/[0-9]+/soc_module/general_config", topic) is not None: + payload = decode_payload(payload) + # initialize request_calculation_threshold if not set + if "request_calculation_threshold" not in payload: + payload["request_calculation_threshold"] = 0 + return {topic: payload} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(108) diff --git a/packages/modules/common/abstract_vehicle.py b/packages/modules/common/abstract_vehicle.py index 1e15f42877..b942856b6e 100644 --- a/packages/modules/common/abstract_vehicle.py +++ b/packages/modules/common/abstract_vehicle.py @@ -14,6 +14,7 @@ class VehicleUpdateData: timestamp_soc_from_cp: Optional[int] = None last_soc_timestamp: Optional[int] = None last_soc: float = None + average_consump: int = None @dataclass @@ -22,6 +23,7 @@ class GeneralVehicleConfig: request_interval_charging: int = 300 request_interval_not_charging: int = 43200 request_only_plugged: bool = False + request_calculation_threshold: int = 0 @dataclass diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index 99b38d9324..25c9dfefbd 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -1,6 +1,6 @@ from enum import Enum import logging -# import time +import time from typing import Optional, TypeVar, Generic, Callable from helpermodules import timecheck @@ -125,10 +125,36 @@ def _get_carstate_source(self, vehicle_update_data: VehicleUpdateData) -> SocSou # Wenn SoC vom LP nicht mehr aktuell, dann berechnen. return SocSource.CALCULATION + def _calc_soc_range(self, vehicle_update_data: VehicleUpdateData) -> CarState: + _soc = calc_soc.calc_soc( + vehicle_update_data, + vehicle_update_data.efficiency, + self.calculated_soc_state.last_imported or vehicle_update_data.imported, + vehicle_update_data.battery_capacity) + _range = int(vehicle_update_data.battery_capacity * _soc / vehicle_update_data.average_consump) + return CarState(soc=_soc, range=_range) + def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source: SocSource) -> CarState: if source == SocSource.API: try: _carState = self.__component_updater(vehicle_update_data) + _now = int(time.time()) + _diff = 0 + if _carState.soc_timestamp: + _diff = int(_now - _carState.soc_timestamp) + if _diff and self.general_config.request_calculation_threshold and\ + self.general_config.request_calculation_threshold > 0 and\ + _diff > self.general_config.request_calculation_threshold and\ + vehicle_update_data.plug_state and\ + vehicle_update_data.last_soc and\ + vehicle_update_data.last_soc_timestamp >= vehicle_update_data.plug_time and\ + (self.calculated_soc_state.last_imported or vehicle_update_data.imported): + _age = int(self.general_config.request_calculation_threshold / 60) + _txt1 = "Ladestand und Reichweite sind berechnet, da der von der Online-Abfrage " + _txt1 = _txt1 + f"gelieferte Zeitstempel mehr als {_age} min alt ist." + self.fault_state.warning(f"{_txt1}") + self.fault_state.store_error() + _carState = self._calc_soc_range(vehicle_update_data) except Exception as e: if vehicle_update_data.plug_state and\ vehicle_update_data.last_soc and\ @@ -136,11 +162,8 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source (self.calculated_soc_state.last_imported or vehicle_update_data.imported): _txt1 = "SoC FALLBACK: SoC wird berechnet, da ein Fehler bei der Abfrage aufgetreten ist:" self.fault_state.warning(f"{_txt1} {e}") - _carState = CarState(soc=calc_soc.calc_soc( - vehicle_update_data, - vehicle_update_data.efficiency, - self.calculated_soc_state.last_imported or vehicle_update_data.imported, - vehicle_update_data.battery_capacity)) + self.fault_state.store_error() + _carState = self._calc_soc_range(vehicle_update_data) else: if not vehicle_update_data.plug_state: reason = ", weil kein Fahrzeug eingesteckt ist." @@ -156,11 +179,8 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source raise Exception(f"Der SoC kann nicht ausgelesen werden: {e}. {_txt1}{reason}") return _carState elif source == SocSource.CALCULATION: - return CarState(soc=calc_soc.calc_soc( - vehicle_update_data, - vehicle_update_data.efficiency, - self.calculated_soc_state.last_imported or vehicle_update_data.imported, - vehicle_update_data.battery_capacity)) + _carState = self._calc_soc_range(vehicle_update_data) + return _carState elif source == SocSource.CP: return CarState(soc=vehicle_update_data.soc_from_cp, soc_timestamp=vehicle_update_data.timestamp_soc_from_cp) diff --git a/packages/modules/update_soc.py b/packages/modules/update_soc.py index 474a6cb483..10221c6517 100644 --- a/packages/modules/update_soc.py +++ b/packages/modules/update_soc.py @@ -119,6 +119,7 @@ def _get_vehicle_update_data(self, ev_num: int) -> VehicleUpdateData: timestamp_soc_from_cp = None battery_capacity = ev_template.data.battery_capacity efficiency = ev_template.data.efficiency + average_consump = ev_template.data.average_consump soc_timestamp = ev.data.get.soc_timestamp return VehicleUpdateData(plug_state=plug_state, charge_state=charge_state, @@ -128,7 +129,8 @@ def _get_vehicle_update_data(self, ev_num: int) -> VehicleUpdateData: soc_from_cp=soc_from_cp, timestamp_soc_from_cp=timestamp_soc_from_cp, last_soc_timestamp=soc_timestamp, - last_soc=ev.data.get.soc if ev.data.get.soc is not None else soc_from_cp) + last_soc=ev.data.get.soc if ev.data.get.soc is not None else soc_from_cp, + average_consump=average_consump) def _filter_failed_store_threads(self, threads_store: List[Thread]) -> List[Thread]: ev_data = copy.deepcopy(subdata.SubData.ev_data) From 21ac21a7c22711b9d03aeca65a3f5ec09d425aff Mon Sep 17 00:00:00 2001 From: rleidner Date: Sun, 22 Feb 2026 20:57:22 +0100 Subject: [PATCH 02/10] fix pytest: adding average_consump --- .../common/configurable_vehicle_test.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/modules/common/configurable_vehicle_test.py b/packages/modules/common/configurable_vehicle_test.py index 90fed2a5a3..faa7f73958 100644 --- a/packages/modules/common/configurable_vehicle_test.py +++ b/packages/modules/common/configurable_vehicle_test.py @@ -204,12 +204,12 @@ def test_2(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 47 @@ -225,12 +225,12 @@ def test_3(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 44 @@ -247,11 +247,11 @@ def test_4(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[2][0][0].soc == 44 @@ -268,11 +268,11 @@ def test_5(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[2][0][0].soc == 44 @@ -289,7 +289,7 @@ def test_6(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[0][0][0].soc == 44 @@ -340,8 +340,8 @@ def test_9(monkeypatch): c = conf_vehicle_api_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[1][0][0].soc == 46 @@ -372,9 +372,9 @@ def test_11(monkeypatch): c = conf_vehicle_api_while_charging() # execution - c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True,average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, charge_state=True)) + c.update(VehicleUpdateData(plug_state=True, charge_state=True,average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[1][0][0].soc == 44 From ec82429f4e98fbb51738b1461f8f761a4edb0eb6 Mon Sep 17 00:00:00 2001 From: rleidner Date: Sun, 22 Feb 2026 21:13:11 +0100 Subject: [PATCH 03/10] fix flake8 issues --- .../common/configurable_vehicle_test.py | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/modules/common/configurable_vehicle_test.py b/packages/modules/common/configurable_vehicle_test.py index faa7f73958..75f500c1ba 100644 --- a/packages/modules/common/configurable_vehicle_test.py +++ b/packages/modules/common/configurable_vehicle_test.py @@ -204,12 +204,14 @@ def test_2(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, + average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, + average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 47 @@ -225,12 +227,15 @@ def test_3(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, + average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, + average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, + average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 44 @@ -247,11 +252,11 @@ def test_4(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[2][0][0].soc == 44 @@ -268,11 +273,12 @@ def test_5(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, + average_consump=18)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[2][0][0].soc == 44 @@ -289,7 +295,8 @@ def test_6(monkeypatch): c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, + average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[0][0][0].soc == 44 @@ -340,8 +347,10 @@ def test_9(monkeypatch): c = conf_vehicle_api_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID,average_consump=18)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, + average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, + average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[1][0][0].soc == 46 @@ -372,9 +381,9 @@ def test_11(monkeypatch): c = conf_vehicle_api_while_charging() # execution - c.update(VehicleUpdateData(plug_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, average_consump=18)) # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, charge_state=True,average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, charge_state=True, average_consump=18)) # evaluation assert mock_value_store.set.call_args_list[1][0][0].soc == 44 From 847138a2611a248bcc9ec133a874ad989bddbc9f Mon Sep 17 00:00:00 2001 From: rleidner <89418596+rleidner@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:29:35 +0100 Subject: [PATCH 04/10] Update packages/modules/common/configurable_vehicle.py Co-authored-by: Thomas Papendieck <14850347+tpd-opitz@users.noreply.github.com> --- packages/modules/common/configurable_vehicle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index 25c9dfefbd..6d589b49ad 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -179,8 +179,7 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source raise Exception(f"Der SoC kann nicht ausgelesen werden: {e}. {_txt1}{reason}") return _carState elif source == SocSource.CALCULATION: - _carState = self._calc_soc_range(vehicle_update_data) - return _carState + return self._calc_soc_range(vehicle_update_data) elif source == SocSource.CP: return CarState(soc=vehicle_update_data.soc_from_cp, soc_timestamp=vehicle_update_data.timestamp_soc_from_cp) From 42b3ef84e8a6bc599c49b627655aaf8d2939c65a Mon Sep 17 00:00:00 2001 From: rleidner Date: Mon, 9 Mar 2026 09:33:08 +0100 Subject: [PATCH 05/10] resolve conflict in update_config.py --- packages/helpermodules/update_config.py | 168 +++++++++++++++++++++++- 1 file changed, 164 insertions(+), 4 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 0eeaf51f9b..02623e2bf6 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,12 +57,25 @@ class UpdateConfig: - DATASTORE_VERSION = 108 + DATASTORE_VERSION = 113 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", - "^openWB/bat/config/configured$", + "^openWB/bat/config/bat_control_activated$", "^openWB/bat/config/power_limit_mode$", + "^openWB/bat/config/power_limit_condition$", + "^openWB/bat/config/bat_control_min_soc$", + "^openWB/bat/config/bat_control_max_soc$", + "^openWB/bat/config/manual_mode$", + "^openWB/bat/config/price_limit_activated$", + "^openWB/bat/config/price_limit$", + "^openWB/bat/config/price_charge_activated$", + "^openWB/bat/config/charge_limit$", + "^openWB/bat/[0-9]+/get/max_charge_power$", + "^openWB/bat/[0-9]+/get/max_discharge_power$", + "^openWB/bat/[0-9]+/get/state_str$", + + "^openWB/bat/config/configured$", "^openWB/bat/set/charging_power_left$", "^openWB/bat/set/regulate_up$", "^openWB/bat/get/fault_state$", @@ -200,6 +213,7 @@ class UpdateConfig: "^openWB/counter/[0-9]+/config/max_currents$", "^openWB/counter/[0-9]+/config/max_total_power$", + "^openWB/general/allow_unencrypted_access$", "^openWB/general/extern$", "^openWB/general/extern_display_mode$", "^openWB/general/charge_log_data_config$", @@ -496,6 +510,7 @@ class UpdateConfig: "^openWB/system/device/[0-9]+/component/[0-9]+/simulation/timestamp_present$", "^openWB/system/device/[0-9]+/config$", "^openWB/system/device/module_update_completed$", + "^openWB/system/hostname$", "^openWB/system/io/[0-9]+/config$", "^openWB/system/ip_address$", "^openWB/system/lastlivevaluesJson$", @@ -503,6 +518,33 @@ class UpdateConfig: "^openWB/system/mqtt/valid_partner_ids$", "^openWB/system/release_train$", "^openWB/system/secondary_auto_update$", + "^openWB/system/security/user_management_active$", + "^openWB/system/security/access_allowed$", + "^openWB/system/security/access/Settings$", + "^openWB/system/security/access/Status$", + "^openWB/system/security/access/ChargeLog$", + "^openWB/system/security/access/Chart$", + "^openWB/system/security/access/GeneralConfiguration$", + "^openWB/system/security/access/DisplayConfiguration$", + "^openWB/system/security/access/IdentificationConfiguration$", + "^openWB/system/security/access/GeneralChargeConfiguration$", + "^openWB/system/security/access/SurplusChargeConfiguration$", + "^openWB/system/security/access/ActiveBatControlConfiguration$", + "^openWB/system/security/access/HardwareInstallation$", + "^openWB/system/security/access/LoadManagementConfiguration$", + "^openWB/system/security/access/ChargePointInstallation$", + "^openWB/system/security/access/VehicleConfiguration$", + "^openWB/system/security/access/IoConfiguration$", + "^openWB/system/security/access/LegacySmartHomeConfiguration$", + "^openWB/system/security/access/InstallAssistant$", + "^openWB/system/security/access/CloudConfiguration$", + "^openWB/system/security/access/MqttBridgeConfiguration$", + "^openWB/system/security/access/DebugConfiguration$", + "^openWB/system/security/access/Support$", + "^openWB/system/security/access/DataManagement$", + "^openWB/system/security/access/SecurityConfiguration$", + "^openWB/system/security/access/SystemConfiguration$", + "^openWB/system/security/access/LegalSettings$", "^openWB/system/time$", "^openWB/system/update_in_progress$", "^openWB/system/usage_terms_acknowledged$", @@ -510,8 +552,17 @@ class UpdateConfig: ] default_topic = ( ("openWB/bat/config/bat_control_permitted", False), + ("openWB/bat/config/bat_control_activated", False), + ("openWB/bat/config/power_limit_mode", "mode_no_discharge"), + ("openWB/bat/config/power_limit_condition", "vehicle_charging"), + ("openWB/bat/config/bat_control_min_soc", 5), + ("openWB/bat/config/bat_control_max_soc", 90), + ("openWB/bat/config/manual_mode", "manual_disable"), + ("openWB/bat/config/price_limit_activated", False), + ("openWB/bat/config/price_limit$", 0.3), + ("openWB/bat/config/price_charge_activated", False), + ("openWB/bat/config/charge_limit$", 0.3), ("openWB/bat/config/configured", False), - ("openWB/bat/config/power_limit_mode", "no_limit"), ("openWB/bat/get/fault_state", 0), ("openWB/bat/get/fault_str", NO_ERROR), ("openWB/bat/get/power_limit_controllable", False), @@ -531,6 +582,7 @@ class UpdateConfig: ("openWB/vehicle/template/ev_template/0", asdict(EvTemplateData(name="Standard-Fahrzeug-Profil", min_current=10))), ("openWB/vehicle/template/charge_template/0", get_charge_template_default()), + ("openWB/general/allow_unencrypted_access", True), ("openWB/general/charge_log_data_config", get_default_charge_log_columns()), ("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value), ("openWB/general/chargemode_config/pv_charging/bat_power_discharge", 1000), @@ -597,11 +649,42 @@ class UpdateConfig: ("openWB/system/usage_terms_acknowledged", False), ("openWB/system/debug_level", 30), ("openWB/system/device/module_update_completed", True), + ("openWB/system/hostname", "unknown"), ("openWB/system/ip_address", "unknown"), ("openWB/system/mqtt/valid_partner_ids", []), ("openWB/system/release_train", "master"), ("openWB/system/secondary_auto_update", True), ("openWB/system/serial_number", get_serial_number()), + ("openWB/system/security/user_management_active", False), + # the following topics in openWB/system/security/ must default to True! + # ACLs will restrict access to this topics if user management is active so that the UI can distinguish + # between "no access" (no topic received) and "access" (topic received) + ("openWB/system/security/access_allowed", True), + ("openWB/system/security/access/Settings", True), + ("openWB/system/security/access/Status", True), + ("openWB/system/security/access/ChargeLog", True), + ("openWB/system/security/access/Chart", True), + ("openWB/system/security/access/GeneralConfiguration", True), + ("openWB/system/security/access/DisplayConfiguration", True), + ("openWB/system/security/access/IdentificationConfiguration", True), + ("openWB/system/security/access/GeneralChargeConfiguration", True), + ("openWB/system/security/access/SurplusChargeConfiguration", True), + ("openWB/system/security/access/ActiveBatControlConfiguration", True), + ("openWB/system/security/access/HardwareInstallation", True), + ("openWB/system/security/access/LoadManagementConfiguration", True), + ("openWB/system/security/access/ChargePointInstallation", True), + ("openWB/system/security/access/VehicleConfiguration", True), + ("openWB/system/security/access/IoConfiguration", True), + ("openWB/system/security/access/LegacySmartHomeConfiguration", True), + ("openWB/system/security/access/InstallAssistant", True), + ("openWB/system/security/access/CloudConfiguration", True), + ("openWB/system/security/access/MqttBridgeConfiguration", True), + ("openWB/system/security/access/DebugConfiguration", True), + ("openWB/system/security/access/Support", True), + ("openWB/system/security/access/DataManagement", True), + ("openWB/system/security/access/SecurityConfiguration", True), + ("openWB/system/security/access/SystemConfiguration", True), + ("openWB/system/security/access/LegalSettings", True), ) invalid_topic = ( # Tuple: (Regex, callable) @@ -2701,6 +2784,83 @@ def upgrade(topic: str, payload) -> None: self._append_datastore_version(107) def upgrade_datastore_108(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/chargepoint/[0-9]+/set/log$", topic) is not None: + log_data = decode_payload(payload) + if log_data.get("time_charged") is not None: + if isinstance(log_data["time_charged"], str): + log_data["time_charged"] = 0 + return {topic: log_data} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(108) + + def upgrade_datastore_109(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("^openWB/bat/[0-9]+/get/power$", topic) is not None: + index = get_index(topic) + new_topics = {} + # add new topics for battery control: + # openWB/bat/[0-9]+/get/max_charge_power => 0 + # openWB/bat/[0-9]+/get/max_discharge_power => 0 + if f"openWB/bat/{index}/get/max_charge_power" not in self.all_received_topics: + new_topics[f"openWB/bat/{index}/get/max_charge_power"] = 0 + if f"openWB/bat/{index}/get/max_discharge_power" not in self.all_received_topics: + new_topics[f"openWB/bat/{index}/get/max_discharge_power"] = 0 + return new_topics if new_topics else None + self._loop_all_received_topics(upgrade) + self._append_datastore_version(109) + + def upgrade_datastore_110(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/chargepoint/[0-9]+/config", topic) is not None: + config = decode_payload(payload) + if config.get("type") == "openwb_dc_adapter": + config["configuration"]["user"] = None + config["configuration"]["password"] = None + ip_address = config["configuration"].pop("ip_address") + config["configuration"]["url"] = f'http://{ip_address}/connect.php' + return {topic: config} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(110) + + def upgrade_datastore_111(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + # add "userManagementSupported" flag if display theme "cards" is selected + if re.search("openWB/optional/int_display/theme", topic) is not None: + configuration_payload = decode_payload(payload) + configuration_payload.update( + {"userManagementSupported": True if configuration_payload.get("type") == "cards" else False}) + return {topic: configuration_payload} + # add "userManagementSupported" flag if web theme "koala" is selected + if re.search("openWB/general/web_theme", topic) is not None: + configuration_payload = decode_payload(payload) + configuration_payload.update( + {"userManagementSupported": True if configuration_payload.get("type") == "koala" else False}) + return {topic: configuration_payload} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(111) + + def upgrade_datastore_112(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/vehicle/[0-9]+/soc_module/config", topic) is not None: + payload = decode_payload(payload) + if payload.get("type") == "bmwbc": + pub_system_message( + {}, + "Die Schnittstelle des bisherigen BMW-Moduls wurde eingestellt und in openWB entfernt. Bitte " + "beachte, dass Du ohne die Konfiguration eines anderen Fahrzeug-Moduls kein SoC-basiertes " + "Laden nutzen kannst.
Unsere Fahrzeug-Module werden von der Community entwickelt. Wenn du " + "also ein BMW-Fahrer bist und gerne ein neues BMW-Modul in openWB programmieren möchtest, " + "findest Du im Forum " + "weitere Informationen.", + MessageType.INFO, + ) + return {topic: NO_MODULE} + run_command(['pip', 'uninstall', 'bimmer_connected', '-y'], process_exception=True) + self._loop_all_received_topics(upgrade) + self._append_datastore_version(112) + + def upgrade_datastore_113(self) -> None: def upgrade(topic: str, payload) -> Optional[dict]: if re.search("openWB/vehicle/[0-9]+/soc_module/general_config", topic) is not None: payload = decode_payload(payload) @@ -2709,4 +2869,4 @@ def upgrade(topic: str, payload) -> Optional[dict]: payload["request_calculation_threshold"] = 0 return {topic: payload} self._loop_all_received_topics(upgrade) - self._append_datastore_version(108) + self._append_datastore_version(113) From dc56e0b4dac9e3aaab4589c49648aff1f7b6dedc Mon Sep 17 00:00:00 2001 From: rleidner Date: Tue, 10 Mar 2026 12:47:19 +0100 Subject: [PATCH 06/10] simplify as requested --- packages/helpermodules/update_config.py | 13 +------------ packages/modules/common/abstract_vehicle.py | 1 - packages/modules/common/configurable_vehicle.py | 7 +++---- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 02623e2bf6..29e9a48941 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 113 + DATASTORE_VERSION = 112 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -2859,14 +2859,3 @@ def upgrade(topic: str, payload) -> Optional[dict]: run_command(['pip', 'uninstall', 'bimmer_connected', '-y'], process_exception=True) self._loop_all_received_topics(upgrade) self._append_datastore_version(112) - - def upgrade_datastore_113(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - if re.search("openWB/vehicle/[0-9]+/soc_module/general_config", topic) is not None: - payload = decode_payload(payload) - # initialize request_calculation_threshold if not set - if "request_calculation_threshold" not in payload: - payload["request_calculation_threshold"] = 0 - return {topic: payload} - self._loop_all_received_topics(upgrade) - self._append_datastore_version(113) diff --git a/packages/modules/common/abstract_vehicle.py b/packages/modules/common/abstract_vehicle.py index b942856b6e..7bb91c220d 100644 --- a/packages/modules/common/abstract_vehicle.py +++ b/packages/modules/common/abstract_vehicle.py @@ -23,7 +23,6 @@ class GeneralVehicleConfig: request_interval_charging: int = 300 request_interval_not_charging: int = 43200 request_only_plugged: bool = False - request_calculation_threshold: int = 0 @dataclass diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index 6d589b49ad..ef398c887e 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -142,14 +142,13 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source _diff = 0 if _carState.soc_timestamp: _diff = int(_now - _carState.soc_timestamp) - if _diff and self.general_config.request_calculation_threshold and\ - self.general_config.request_calculation_threshold > 0 and\ - _diff > self.general_config.request_calculation_threshold and\ + if _diff and\ + _diff > self.general_config.request_interval_charging and\ vehicle_update_data.plug_state and\ vehicle_update_data.last_soc and\ vehicle_update_data.last_soc_timestamp >= vehicle_update_data.plug_time and\ (self.calculated_soc_state.last_imported or vehicle_update_data.imported): - _age = int(self.general_config.request_calculation_threshold / 60) + _age = int(self.general_config.request_interval_charging / 60) _txt1 = "Ladestand und Reichweite sind berechnet, da der von der Online-Abfrage " _txt1 = _txt1 + f"gelieferte Zeitstempel mehr als {_age} min alt ist." self.fault_state.warning(f"{_txt1}") From 50b72ceb8afb89c57decd5349dac5ec6319b73d0 Mon Sep 17 00:00:00 2001 From: rleidner Date: Tue, 10 Mar 2026 21:17:43 +0100 Subject: [PATCH 07/10] calc_soc -> calc_vehicle_data, fix pytest --- .../modules/common/configurable_vehicle.py | 33 +++--- .../common/configurable_vehicle_test.py | 105 ++++++++---------- .../vehicles/common/calc_soc/calc_soc.py | 24 ---- .../vehicles/common/calc_soc/calc_soc_test.py | 11 -- .../__init__.py | 0 .../calc_vehicle_data/calc_vehicle_data.py | 25 +++++ .../calc_vehicle_data_test.py | 14 +++ 7 files changed, 107 insertions(+), 105 deletions(-) delete mode 100644 packages/modules/vehicles/common/calc_soc/calc_soc.py delete mode 100644 packages/modules/vehicles/common/calc_soc/calc_soc_test.py rename packages/modules/vehicles/common/{calc_soc => calc_vehicle_data}/__init__.py (100%) create mode 100644 packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py create mode 100644 packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index ef398c887e..73d36d8837 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -11,7 +11,7 @@ from modules.common.component_context import SingleComponentUpdateContext from modules.common.component_state import CarState from modules.common.fault_state import ComponentInfo, FaultState -from modules.vehicles.common.calc_soc import calc_soc +from modules.vehicles.common.calc_vehicle_data import calc_vehicle_data from modules.vehicles.manual.config import ManualSoc from modules.vehicles.mqtt.config import MqttSocSetup @@ -125,19 +125,11 @@ def _get_carstate_source(self, vehicle_update_data: VehicleUpdateData) -> SocSou # Wenn SoC vom LP nicht mehr aktuell, dann berechnen. return SocSource.CALCULATION - def _calc_soc_range(self, vehicle_update_data: VehicleUpdateData) -> CarState: - _soc = calc_soc.calc_soc( - vehicle_update_data, - vehicle_update_data.efficiency, - self.calculated_soc_state.last_imported or vehicle_update_data.imported, - vehicle_update_data.battery_capacity) - _range = int(vehicle_update_data.battery_capacity * _soc / vehicle_update_data.average_consump) - return CarState(soc=_soc, range=_range) - def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source: SocSource) -> CarState: if source == SocSource.API: try: _carState = self.__component_updater(vehicle_update_data) + _odometer = _carState.odometer _now = int(time.time()) _diff = 0 if _carState.soc_timestamp: @@ -153,7 +145,13 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source _txt1 = _txt1 + f"gelieferte Zeitstempel mehr als {_age} min alt ist." self.fault_state.warning(f"{_txt1}") self.fault_state.store_error() - _carState = self._calc_soc_range(vehicle_update_data) + _soc, _range = calc_vehicle_data.calc_vehicle_data( + vehicle_update_data, + self.calculated_soc_state.last_imported or + vehicle_update_data.imported) + _carState = CarState(soc=_soc, range=_range) + _carState.odometer = _odometer + log.info(f"RLEXP: odometer: {_carState.odometer}") except Exception as e: if vehicle_update_data.plug_state and\ vehicle_update_data.last_soc and\ @@ -162,7 +160,11 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source _txt1 = "SoC FALLBACK: SoC wird berechnet, da ein Fehler bei der Abfrage aufgetreten ist:" self.fault_state.warning(f"{_txt1} {e}") self.fault_state.store_error() - _carState = self._calc_soc_range(vehicle_update_data) + _soc, _range = calc_vehicle_data.calc_vehicle_data( + vehicle_update_data, + self.calculated_soc_state.last_imported or + vehicle_update_data.imported) + _carState = CarState(soc=_soc, range=_range) else: if not vehicle_update_data.plug_state: reason = ", weil kein Fahrzeug eingesteckt ist." @@ -178,7 +180,12 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source raise Exception(f"Der SoC kann nicht ausgelesen werden: {e}. {_txt1}{reason}") return _carState elif source == SocSource.CALCULATION: - return self._calc_soc_range(vehicle_update_data) + _soc, _range = calc_vehicle_data.calc_vehicle_data( + vehicle_update_data, + self.calculated_soc_state.last_imported or + vehicle_update_data.imported) + _carState = CarState(soc=_soc, range=_range) + return _carState elif source == SocSource.CP: return CarState(soc=vehicle_update_data.soc_from_cp, soc_timestamp=vehicle_update_data.timestamp_soc_from_cp) diff --git a/packages/modules/common/configurable_vehicle_test.py b/packages/modules/common/configurable_vehicle_test.py index 75f500c1ba..c24cabb98e 100644 --- a/packages/modules/common/configurable_vehicle_test.py +++ b/packages/modules/common/configurable_vehicle_test.py @@ -6,7 +6,7 @@ from modules.common.configurable_vehicle import ConfigurableVehicle, SocSource from modules.common import store from modules.devices.tesla.tesla.config import Tesla -from modules.vehicles.common.calc_soc import calc_soc +from modules.vehicles.common.calc_vehicle_data import calc_vehicle_data from modules.vehicles.manual.config import ManualSoc from modules.vehicles.mqtt.config import MqttSocSetup @@ -141,10 +141,10 @@ def test_get_carstate_source(conf_vehicle: ConfigurableVehicle, @pytest.mark.parametrize( "vehicle_update_data, use_soc_from_cp, expected_calculated_soc_state, expected_call_count", [ - pytest.param(VehicleUpdateData(last_soc=42), False, CalculatedSocState(), 1, id="request only from api"), - pytest.param(VehicleUpdateData(imported=150, last_soc=42), True, CalculatedSocState( + pytest.param(VehicleUpdateData(last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), False, CalculatedSocState(), 1, id="request only from api"), + pytest.param(VehicleUpdateData(imported=150, last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), True, CalculatedSocState( last_imported=150), 1, id="request from api, not plugged"), - pytest.param(VehicleUpdateData(imported=200, plug_state=True, last_soc=42), True, + pytest.param(VehicleUpdateData(imported=200, plug_state=True, last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), True, CalculatedSocState(last_imported=200), 1, id="request from api, recently plugged"), ]) def test_update_api(vehicle_update_data, @@ -197,21 +197,19 @@ def test_1(monkeypatch): def test_2(monkeypatch): # Anstecken -> Eintragen -> aktueller SoC von EV -> SoC von Ev hochrechnen # setup - mock_calc_soc = Mock(name="calc_soc", return_value=47) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[47,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, last_soc_timestamp=0)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, - average_consump=18)) - # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, - average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, last_soc_timestamp=0)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, average_consump=18, last_soc_timestamp=0)) + # im nächsten Zyklus wird mit calc_vehicle_data hochgerechnet + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, average_consump=18, last_soc_timestamp=0)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 47 @@ -220,87 +218,82 @@ def test_2(monkeypatch): def test_3(monkeypatch): # Anstecken -> aktueller SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_soc = Mock(name="calc_soc", return_value=44) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, - average_consump=18)) + c.update(VehicleUpdateData(plug_state=True)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, - average_consump=18)) - # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, - average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) + # im nächsten Zyklus wird mit calc_vehicle_data hochgerechnet + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 44 - assert mock_calc_soc.call_count == 2 + assert mock_calc_vehicle_data.call_count == 2 def test_4(monkeypatch): # Anstecken -> kein SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_soc = Mock(name="calc_soc", return_value=44) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) - # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True)) + # im nächsten Zyklus wird mit calc_vehicle_data hochgerechnet + c.update(VehicleUpdateData(plug_state=True)) # evaluation assert mock_value_store.set.call_args_list[2][0][0].soc == 44 - assert mock_calc_soc.call_count == 2 + assert mock_calc_vehicle_data.call_count == 2 def test_5(monkeypatch): # Anstecken -> kein aktueller SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_soc = Mock(name="calc_soc", return_value=44) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, - average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) c.calculated_soc_state.manual_soc = 42 - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) - # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True)) + # im nächsten Zyklus wird mit calc_vehicle_data hochgerechnet + c.update(VehicleUpdateData(plug_state=True)) # evaluation assert mock_value_store.set.call_args_list[2][0][0].soc == 44 - assert mock_calc_soc.call_count == 1 + assert mock_calc_vehicle_data.call_count == 1 def test_6(monkeypatch): # Abgesteckt -> vom letzten Manuellen SoC hochrechnen # setup - mock_calc_soc = Mock(name="calc_soc", return_value=44) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) c = conf_vehicle_manual_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, - average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) # evaluation assert mock_value_store.set.call_args_list[0][0][0].soc == 44 - assert mock_calc_soc.call_count == 1 + assert mock_calc_vehicle_data.call_count == 1 def test_7(monkeypatch): @@ -342,19 +335,17 @@ def test_9(monkeypatch): # setup mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) - mock_calc_soc = Mock(name="calc_soc", return_value=46) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[46,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) c = conf_vehicle_api_from_cp() # execution - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, - average_consump=18)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, - average_consump=18)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID)) # evaluation assert mock_value_store.set.call_args_list[1][0][0].soc == 46 - assert mock_calc_soc.call_count == 1 + assert mock_calc_vehicle_data.call_count == 1 def test_10(monkeypatch): @@ -374,17 +365,17 @@ def test_10(monkeypatch): def test_11(monkeypatch): # Anstecken -> kein SoC von EV -> API -> von API hochrechnen # setup - mock_calc_soc = Mock(name="calc_soc", return_value=44) - monkeypatch.setattr(calc_soc, "calc_soc", mock_calc_soc) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) c = conf_vehicle_api_while_charging() # execution - c.update(VehicleUpdateData(plug_state=True, average_consump=18)) - # im nächsten Zyklus wird mit calc_soc hochgerechnet - c.update(VehicleUpdateData(plug_state=True, charge_state=True, average_consump=18)) + c.update(VehicleUpdateData(plug_state=True)) + # im nächsten Zyklus wird mit calc_vehicle_data hochgerechnet + c.update(VehicleUpdateData(plug_state=True, charge_state=True)) # evaluation assert mock_value_store.set.call_args_list[1][0][0].soc == 44 - assert mock_calc_soc.call_count == 1 + assert mock_calc_vehicle_data.call_count == 1 diff --git a/packages/modules/vehicles/common/calc_soc/calc_soc.py b/packages/modules/vehicles/common/calc_soc/calc_soc.py deleted file mode 100644 index be0c032be3..0000000000 --- a/packages/modules/vehicles/common/calc_soc/calc_soc.py +++ /dev/null @@ -1,24 +0,0 @@ -import logging - -from modules.common.abstract_vehicle import VehicleUpdateData - -log = logging.getLogger(__name__) - - -def calc_soc(vehicle_update_data: VehicleUpdateData, - efficiency: int, - last_imported: float, - battery_capacity: float) -> float: - imported_since_last_soc = vehicle_update_data.imported - last_imported - energy_battery_gain = imported_since_last_soc * efficiency / 100 - battery_soc_gain = (energy_battery_gain / battery_capacity) * 100 - soc = vehicle_update_data.last_soc + battery_soc_gain - log.debug( - f"SoC-Gain: (({imported_since_last_soc/1000}kWh charged * {efficiency}% efficiency) / " - f"{battery_capacity/1000}kWh battery-size) * 100 = {battery_soc_gain}%", - ) - log.debug(f"{vehicle_update_data.last_soc}% + {energy_battery_gain / 1000}kWh = {soc}%") - if soc > 100: - log.warning(f"Calculated SoC of {soc}% exceeds maximum and is limited to 100%! Check your settings!") - soc = 100 - return soc diff --git a/packages/modules/vehicles/common/calc_soc/calc_soc_test.py b/packages/modules/vehicles/common/calc_soc/calc_soc_test.py deleted file mode 100644 index e5b20d2cf8..0000000000 --- a/packages/modules/vehicles/common/calc_soc/calc_soc_test.py +++ /dev/null @@ -1,11 +0,0 @@ -from modules.common.abstract_vehicle import VehicleUpdateData -from modules.vehicles.common.calc_soc.calc_soc import calc_soc - - -def test_calc_soc(): - # setup & execution - soc = calc_soc(VehicleUpdateData(imported=10000, last_soc=12.6), - efficiency=90, last_imported=0, battery_capacity=100000) - - # evaluation - assert soc == 21.6 diff --git a/packages/modules/vehicles/common/calc_soc/__init__.py b/packages/modules/vehicles/common/calc_vehicle_data/__init__.py similarity index 100% rename from packages/modules/vehicles/common/calc_soc/__init__.py rename to packages/modules/vehicles/common/calc_vehicle_data/__init__.py diff --git a/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py new file mode 100644 index 0000000000..21d1d192cc --- /dev/null +++ b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py @@ -0,0 +1,25 @@ +import logging +from typing import Union + +from modules.common.abstract_vehicle import VehicleUpdateData + +log = logging.getLogger(__name__) + + +def calc_vehicle_data(vehicle_update_data: VehicleUpdateData, + last_imported: float) -> Union[float, float]: + imported_since_last_soc = vehicle_update_data.imported - last_imported + energy_battery_gain = imported_since_last_soc * vehicle_update_data.efficiency / 100 + battery_soc_gain = (energy_battery_gain / vehicle_update_data.battery_capacity) * 100 + _soc = vehicle_update_data.last_soc + battery_soc_gain + _eff = vehicle_update_data.efficiency + log.debug( + f"SoC-Gain: (({imported_since_last_soc/1000}kWh charged * {_eff}% _eff) / " + f"{vehicle_update_data.battery_capacity/1000}kWh battery-size) * 100 = {battery_soc_gain}%", + ) + log.debug(f"{vehicle_update_data.last_soc}% + {energy_battery_gain / 1000}kWh = {_soc}%") + if _soc > 100: + log.warning(f"Calculated SoC of {_soc}% exceeds maximum and is limited to 100%! Check your settings!") + _soc = 100 + _range = int(vehicle_update_data.battery_capacity * _soc / vehicle_update_data.average_consump) + return _soc, _range diff --git a/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py new file mode 100644 index 0000000000..d13e90934c --- /dev/null +++ b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py @@ -0,0 +1,14 @@ +from modules.common.abstract_vehicle import VehicleUpdateData +from modules.vehicles.common.calc_vehicle_data.calc_vehicle_data import calc_vehicle_data + + +def test_calc_vehicle_data(): + # setup & execution + soc, range = calc_vehicle_data(VehicleUpdateData(imported=10000, last_soc=12.6, + efficiency=90, + battery_capacity=100000, + average_consump=18), + last_imported=0) + + # evaluation + assert soc == 21.6 From bcfeb44d1566811979e21bbf8dbce82feea8d449 Mon Sep 17 00:00:00 2001 From: rleidner Date: Tue, 10 Mar 2026 21:25:43 +0100 Subject: [PATCH 08/10] fix flake8 --- .../common/configurable_vehicle_test.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/modules/common/configurable_vehicle_test.py b/packages/modules/common/configurable_vehicle_test.py index c24cabb98e..f5f603a16b 100644 --- a/packages/modules/common/configurable_vehicle_test.py +++ b/packages/modules/common/configurable_vehicle_test.py @@ -141,10 +141,15 @@ def test_get_carstate_source(conf_vehicle: ConfigurableVehicle, @pytest.mark.parametrize( "vehicle_update_data, use_soc_from_cp, expected_calculated_soc_state, expected_call_count", [ - pytest.param(VehicleUpdateData(last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), False, CalculatedSocState(), 1, id="request only from api"), - pytest.param(VehicleUpdateData(imported=150, last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), True, CalculatedSocState( + pytest.param(VehicleUpdateData(last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), + False, CalculatedSocState(), 1, id="request only from api"), + pytest.param(VehicleUpdateData(imported=150, last_soc=42, last_soc_timestamp=1, plug_time=0, + average_consump=18), + True, CalculatedSocState( last_imported=150), 1, id="request from api, not plugged"), - pytest.param(VehicleUpdateData(imported=200, plug_state=True, last_soc=42, last_soc_timestamp=1, plug_time=0, average_consump=18), True, + pytest.param(VehicleUpdateData(imported=200, plug_state=True, last_soc=42, last_soc_timestamp=1, + plug_time=0, average_consump=18), + True, CalculatedSocState(last_imported=200), 1, id="request from api, recently plugged"), ]) def test_update_api(vehicle_update_data, @@ -197,7 +202,7 @@ def test_1(monkeypatch): def test_2(monkeypatch): # Anstecken -> Eintragen -> aktueller SoC von EV -> SoC von Ev hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[47,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[47, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -207,9 +212,11 @@ def test_2(monkeypatch): c.update(VehicleUpdateData(plug_state=True, last_soc_timestamp=0)) c.calculated_soc_state.manual_soc = 42 c.update(VehicleUpdateData(plug_state=True, last_soc_timestamp=0)) - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, average_consump=18, last_soc_timestamp=0)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, + average_consump=18, last_soc_timestamp=0)) # im nächsten Zyklus wird mit calc_vehicle_data hochgerechnet - c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, average_consump=18, last_soc_timestamp=0)) + c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, + average_consump=18, last_soc_timestamp=0)) # evaluation assert mock_value_store.set.call_args_list[3][0][0].soc == 47 @@ -218,7 +225,7 @@ def test_2(monkeypatch): def test_3(monkeypatch): # Anstecken -> aktueller SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -240,7 +247,7 @@ def test_3(monkeypatch): def test_4(monkeypatch): # Anstecken -> kein SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -261,7 +268,7 @@ def test_4(monkeypatch): def test_5(monkeypatch): # Anstecken -> kein aktueller SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -282,7 +289,7 @@ def test_5(monkeypatch): def test_6(monkeypatch): # Abgesteckt -> vom letzten Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -335,7 +342,7 @@ def test_9(monkeypatch): # setup mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[46,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[46, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) c = conf_vehicle_api_from_cp() @@ -365,7 +372,7 @@ def test_10(monkeypatch): def test_11(monkeypatch): # Anstecken -> kein SoC von EV -> API -> von API hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44,250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) From dc8996a7472777da0f4dd0966bac23e6718bb032 Mon Sep 17 00:00:00 2001 From: rleidner Date: Tue, 10 Mar 2026 21:31:35 +0100 Subject: [PATCH 09/10] remove odometer --- packages/modules/common/configurable_vehicle.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index 73d36d8837..76437005a5 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -129,7 +129,6 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source if source == SocSource.API: try: _carState = self.__component_updater(vehicle_update_data) - _odometer = _carState.odometer _now = int(time.time()) _diff = 0 if _carState.soc_timestamp: @@ -150,8 +149,6 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source self.calculated_soc_state.last_imported or vehicle_update_data.imported) _carState = CarState(soc=_soc, range=_range) - _carState.odometer = _odometer - log.info(f"RLEXP: odometer: {_carState.odometer}") except Exception as e: if vehicle_update_data.plug_state and\ vehicle_update_data.last_soc and\ From 7830a889db5ec98aa90bb0f7b96d54766a7572ce Mon Sep 17 00:00:00 2001 From: rleidner Date: Wed, 11 Mar 2026 11:56:49 +0100 Subject: [PATCH 10/10] Feinschliff --- .../modules/common/configurable_vehicle.py | 27 +++++++------------ .../common/configurable_vehicle_test.py | 14 +++++----- .../calc_vehicle_data/calc_vehicle_data.py | 6 ++--- .../calc_vehicle_data_test.py | 17 +++++++----- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index 76437005a5..9ff600c015 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -133,8 +133,7 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source _diff = 0 if _carState.soc_timestamp: _diff = int(_now - _carState.soc_timestamp) - if _diff and\ - _diff > self.general_config.request_interval_charging and\ + if _diff > self.general_config.request_interval_charging and\ vehicle_update_data.plug_state and\ vehicle_update_data.last_soc and\ vehicle_update_data.last_soc_timestamp >= vehicle_update_data.plug_time and\ @@ -144,11 +143,9 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source _txt1 = _txt1 + f"gelieferte Zeitstempel mehr als {_age} min alt ist." self.fault_state.warning(f"{_txt1}") self.fault_state.store_error() - _soc, _range = calc_vehicle_data.calc_vehicle_data( - vehicle_update_data, - self.calculated_soc_state.last_imported or - vehicle_update_data.imported) - _carState = CarState(soc=_soc, range=_range) + _carState = calc_vehicle_data.calc_vehicle_data(vehicle_update_data, + self.calculated_soc_state.last_imported or + vehicle_update_data.imported) except Exception as e: if vehicle_update_data.plug_state and\ vehicle_update_data.last_soc and\ @@ -157,11 +154,9 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source _txt1 = "SoC FALLBACK: SoC wird berechnet, da ein Fehler bei der Abfrage aufgetreten ist:" self.fault_state.warning(f"{_txt1} {e}") self.fault_state.store_error() - _soc, _range = calc_vehicle_data.calc_vehicle_data( - vehicle_update_data, - self.calculated_soc_state.last_imported or - vehicle_update_data.imported) - _carState = CarState(soc=_soc, range=_range) + _carState = calc_vehicle_data.calc_vehicle_data(vehicle_update_data, + self.calculated_soc_state.last_imported or + vehicle_update_data.imported) else: if not vehicle_update_data.plug_state: reason = ", weil kein Fahrzeug eingesteckt ist." @@ -177,11 +172,9 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source raise Exception(f"Der SoC kann nicht ausgelesen werden: {e}. {_txt1}{reason}") return _carState elif source == SocSource.CALCULATION: - _soc, _range = calc_vehicle_data.calc_vehicle_data( - vehicle_update_data, - self.calculated_soc_state.last_imported or - vehicle_update_data.imported) - _carState = CarState(soc=_soc, range=_range) + _carState = calc_vehicle_data.calc_vehicle_data(vehicle_update_data, + self.calculated_soc_state.last_imported or + vehicle_update_data.imported) return _carState elif source == SocSource.CP: return CarState(soc=vehicle_update_data.soc_from_cp, diff --git a/packages/modules/common/configurable_vehicle_test.py b/packages/modules/common/configurable_vehicle_test.py index f5f603a16b..a9afd68af5 100644 --- a/packages/modules/common/configurable_vehicle_test.py +++ b/packages/modules/common/configurable_vehicle_test.py @@ -202,7 +202,7 @@ def test_1(monkeypatch): def test_2(monkeypatch): # Anstecken -> Eintragen -> aktueller SoC von EV -> SoC von Ev hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[47, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=47, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -225,7 +225,7 @@ def test_2(monkeypatch): def test_3(monkeypatch): # Anstecken -> aktueller SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=44, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -247,7 +247,7 @@ def test_3(monkeypatch): def test_4(monkeypatch): # Anstecken -> kein SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=44, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -268,7 +268,7 @@ def test_4(monkeypatch): def test_5(monkeypatch): # Anstecken -> kein aktueller SoC von EV -> Eintragen -> Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=44, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -289,7 +289,7 @@ def test_5(monkeypatch): def test_6(monkeypatch): # Abgesteckt -> vom letzten Manuellen SoC hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=44, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) @@ -342,7 +342,7 @@ def test_9(monkeypatch): # setup mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[46, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=46, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) c = conf_vehicle_api_from_cp() @@ -372,7 +372,7 @@ def test_10(monkeypatch): def test_11(monkeypatch): # Anstecken -> kein SoC von EV -> API -> von API hochrechnen # setup - mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=[44, 250]) + mock_calc_vehicle_data = Mock(name="calc_vehicle_data", return_value=CarState(soc=44, range=250)) monkeypatch.setattr(calc_vehicle_data, "calc_vehicle_data", mock_calc_vehicle_data) mock_value_store = Mock(name="value_store") monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store)) diff --git a/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py index 21d1d192cc..8c5dd2820d 100644 --- a/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py +++ b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data.py @@ -1,13 +1,13 @@ import logging -from typing import Union from modules.common.abstract_vehicle import VehicleUpdateData +from modules.common.component_state import CarState log = logging.getLogger(__name__) def calc_vehicle_data(vehicle_update_data: VehicleUpdateData, - last_imported: float) -> Union[float, float]: + last_imported: float) -> CarState: imported_since_last_soc = vehicle_update_data.imported - last_imported energy_battery_gain = imported_since_last_soc * vehicle_update_data.efficiency / 100 battery_soc_gain = (energy_battery_gain / vehicle_update_data.battery_capacity) * 100 @@ -22,4 +22,4 @@ def calc_vehicle_data(vehicle_update_data: VehicleUpdateData, log.warning(f"Calculated SoC of {_soc}% exceeds maximum and is limited to 100%! Check your settings!") _soc = 100 _range = int(vehicle_update_data.battery_capacity * _soc / vehicle_update_data.average_consump) - return _soc, _range + return CarState(soc=_soc, range=_range) diff --git a/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py index d13e90934c..92eec2b090 100644 --- a/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py +++ b/packages/modules/vehicles/common/calc_vehicle_data/calc_vehicle_data_test.py @@ -1,14 +1,19 @@ + from modules.common.abstract_vehicle import VehicleUpdateData +from modules.common.component_state import CarState from modules.vehicles.common.calc_vehicle_data.calc_vehicle_data import calc_vehicle_data def test_calc_vehicle_data(): # setup & execution - soc, range = calc_vehicle_data(VehicleUpdateData(imported=10000, last_soc=12.6, - efficiency=90, - battery_capacity=100000, - average_consump=18), - last_imported=0) + carState = calc_vehicle_data(VehicleUpdateData(imported=10000, last_soc=12.6, + efficiency=90, + battery_capacity=100000, + average_consump=18000), + last_imported=0) # evaluation - assert soc == 21.6 + # print(f"soc={carState.soc}, range={carState.range}") + assert isinstance(carState, CarState) + assert carState.soc == 21.6 + assert carState.range == 120