diff --git a/packages/control/ev.py b/packages/control/ev.py index 7a87206e46..dce798043a 100644 --- a/packages/control/ev.py +++ b/packages/control/ev.py @@ -193,6 +193,8 @@ class Get: soc: Optional[int] = field(default=None, metadata={"topic": "get/soc"}) soc_timestamp: Optional[float] = field( default=None, metadata={"topic": "get/soc_timestamp"}) + soc_request_timestamp: Optional[float] = field( + default=None, metadata={"topic": "get/soc_request_timestamp"}) force_soc_update: bool = field(default=False, metadata={ "topic": "get/force_soc_update"}) range: Optional[float] = field(default=None, metadata={"topic": "get/range"}) @@ -233,7 +235,7 @@ def __init__(self, index: int): def soc_interval_expired(self, vehicle_update_data: VehicleUpdateData) -> bool: request_soc = False - if self.data.get.soc_timestamp is None: + if self.data.get.soc_request_timestamp is None: # Initiale Abfrage request_soc = True else: @@ -244,7 +246,7 @@ def soc_interval_expired(self, vehicle_update_data: VehicleUpdateData) -> bool: else: interval = self.soc_module.general_config.request_interval_not_charging # Zeitstempel prüfen, ob wieder abgefragt werden muss. - if timecheck.check_timestamp(self.data.get.soc_timestamp, interval-5) is False: + if timecheck.check_timestamp(self.data.get.soc_request_timestamp, interval-5) is False: # Zeit ist abgelaufen request_soc = True return request_soc diff --git a/packages/control/ev_test.py b/packages/control/ev_test.py index 1f3078165f..bcc2152042 100644 --- a/packages/control/ev_test.py +++ b/packages/control/ev_test.py @@ -11,8 +11,8 @@ @pytest.mark.parametrize( - "check_timestamp, charge_state, soc_timestamp, expected_request_soc", - [pytest.param(False, False, None, True, id="no soc_timestamp"), + "check_timestamp, charge_state, soc_request_timestamp, expected_request_soc", + [pytest.param(False, False, None, True, id="no soc_request_timestamp"), pytest.param(True, False, 100, False, id="not charging, not expired"), pytest.param(False, False, 100, True, id="not charging, expired"), pytest.param(True, True, 100, False, id="charging, not expired"), @@ -20,13 +20,13 @@ ]) def test_soc_interval_expired(check_timestamp: bool, charge_state: bool, - soc_timestamp: Optional[float], + soc_request_timestamp: Optional[float], expected_request_soc: bool, monkeypatch): # setup ev = Ev(0) ev.soc_module = create_vehicle(MqttSocSetup(), 0) - ev.data.get.soc_timestamp = soc_timestamp + ev.data.get.soc_request_timestamp = soc_request_timestamp check_timestamp_mock = Mock(return_value=check_timestamp) monkeypatch.setattr(timecheck, "check_timestamp", check_timestamp_mock) diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index ee9831b709..584ef0d234 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -423,7 +423,8 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage): elif ("/charge_template" in msg.topic or "/ev_template" in msg.topic): self._validate_value(msg, int, [(0, float("inf"))]) - elif "/get/soc_timestamp" in msg.topic: + elif ("/get/soc_request_timestamp" in msg.topic or + "/get/soc_timestamp" in msg.topic): self._validate_value(msg, float) elif "/get/soc" in msg.topic: self._validate_value(msg, float, [(0, 100)]) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 7e2b99b84d..e31fc1d510 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -300,6 +300,7 @@ class UpdateConfig: "^openWB/vehicle/[0-9]+/get/force_soc_update$", "^openWB/vehicle/[0-9]+/get/range$", "^openWB/vehicle/[0-9]+/get/soc$", + "^openWB/vehicle/[0-9]+/get/soc_request_timestamp$", "^openWB/vehicle/[0-9]+/get/soc_timestamp$", "^openWB/vehicle/[0-9]+/match_ev/selected$", "^openWB/vehicle/[0-9]+/match_ev/tag_id$", diff --git a/packages/modules/common/abstract_vehicle.py b/packages/modules/common/abstract_vehicle.py index 206f7a52ff..3772b4c63d 100644 --- a/packages/modules/common/abstract_vehicle.py +++ b/packages/modules/common/abstract_vehicle.py @@ -11,6 +11,7 @@ class VehicleUpdateData: efficiency: float = 90 soc_from_cp: Optional[float] = None timestamp_soc_from_cp: Optional[int] = None + soc_timestamp: Optional[int] = None @dataclass diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index fa2a904658..6788472f6a 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -130,7 +130,7 @@ def __init__( @auto_str class CarState: - def __init__(self, soc: float, range: Optional[float] = None, soc_timestamp: float = 0): + def __init__(self, soc: float, range: Optional[float] = None, soc_timestamp: Optional[float] = None): """Args: soc: actual state of charge in percent range: actual range in km @@ -138,7 +138,10 @@ def __init__(self, soc: float, range: Optional[float] = None, soc_timestamp: flo """ self.soc = soc self.range = range - self.soc_timestamp = soc_timestamp + if soc_timestamp is None: + self.soc_timestamp = timecheck.create_timestamp() + else: + self.soc_timestamp = soc_timestamp @auto_str diff --git a/packages/modules/common/configurable_vehicle.py b/packages/modules/common/configurable_vehicle.py index 23282f503c..c30f280faa 100644 --- a/packages/modules/common/configurable_vehicle.py +++ b/packages/modules/common/configurable_vehicle.py @@ -73,7 +73,11 @@ def update(self, vehicle_update_data: VehicleUpdateData): self.calculated_soc_state.soc_start = car_state.soc Pub().pub(f"openWB/set/vehicle/{self.vehicle}/soc_module/calculated_soc_state", asdict(self.calculated_soc_state)) - self.store.set(car_state) + if vehicle_update_data.soc_timestamp is None or vehicle_update_data.soc_timestamp < car_state.soc_timestamp: + # Nur wenn der SoC neuer ist als der bisherige, diesen setzen. + self.store.set(car_state) + else: + log.debug("Not updating SoC, because timestamp is older.") def _get_carstate_source(self, vehicle_update_data: VehicleUpdateData) -> SocSource: if isinstance(self.vehicle_config, MqttSocSetup): @@ -105,7 +109,7 @@ def _get_carstate_source(self, vehicle_update_data: VehicleUpdateData) -> SocSou # Wenn SoC vom LP nicht mehr aktuell, dann berechnen. return SocSource.CALCULATION - def _get_carstate_by_source(self, vehicle_update_data, source): + def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source: SocSource) -> CarState: if source == SocSource.API: return self.__component_updater(vehicle_update_data) elif source == SocSource.CALCULATION: @@ -116,7 +120,8 @@ def _get_carstate_by_source(self, vehicle_update_data, source): self.calculated_soc_state.soc_start, vehicle_update_data.battery_capacity)) elif source == SocSource.CP: - return CarState(vehicle_update_data.soc_from_cp) + return CarState(soc=vehicle_update_data.soc_from_cp, + soc_timestamp=vehicle_update_data.timestamp_soc_from_cp) elif source == SocSource.MANUAL: soc = self.calculated_soc_state.manual_soc or self.calculated_soc_state.soc_start self.calculated_soc_state.manual_soc = None diff --git a/packages/modules/update_soc.py b/packages/modules/update_soc.py index 68dda5ce6b..ce8e87de50 100644 --- a/packages/modules/update_soc.py +++ b/packages/modules/update_soc.py @@ -62,7 +62,8 @@ def _get_threads(self) -> Tuple[List[Thread], List[Thread]]: Pub().pub(f"openWB/set/vehicle/{ev.num}/get/range", 0) # Es wird ein Zeitstempel gesetzt, unabhängig ob die Abfrage erfolgreich war, da einige # Hersteller bei zu häufigen Abfragen Accounts sperren. - Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc_timestamp", timecheck.create_timestamp()) + Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc_request_timestamp", + timecheck.create_timestamp()) threads_update.append(Thread(target=ev.soc_module.update, args=(vehicle_update_data,), name=f"fetch soc_ev{ev.num}")) if hasattr(ev.soc_module, "store"): @@ -78,6 +79,8 @@ def _get_threads(self) -> Tuple[List[Thread], List[Thread]]: Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc", None) if ev.data.get.soc_timestamp is not None: Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc_timestamp", None) + if ev.data.get.soc_request_timestamp is not None: + Pub().pub(f"openWB/set/vehicle/{ev.num}/get/soc_request_timestamp", None) if ev.data.get.range is not None: Pub().pub(f"openWB/set/vehicle/{ev.num}/get/range", None) except Exception: @@ -98,8 +101,6 @@ def _get_vehicle_update_data(self, ev_num: int) -> VehicleUpdateData: plug_state = cp.data.get.plug_state charge_state = cp.data.get.charge_state imported = cp.data.get.imported - battery_capacity = ev_template.data.battery_capacity - efficiency = ev_template.data.efficiency if ev.soc_module.general_config.use_soc_from_cp: soc_from_cp = cp.data.get.soc timestamp_soc_from_cp = cp.data.get.soc_timestamp @@ -111,17 +112,19 @@ def _get_vehicle_update_data(self, ev_num: int) -> VehicleUpdateData: plug_state = False charge_state = False imported = None - battery_capacity = ev_template.data.battery_capacity - efficiency = ev_template.data.efficiency soc_from_cp = None timestamp_soc_from_cp = None + battery_capacity = ev_template.data.battery_capacity + efficiency = ev_template.data.efficiency + soc_timestamp = ev.data.get.soc_timestamp return VehicleUpdateData(plug_state=plug_state, charge_state=charge_state, efficiency=efficiency, imported=imported, battery_capacity=battery_capacity, soc_from_cp=soc_from_cp, - timestamp_soc_from_cp=timestamp_soc_from_cp) + timestamp_soc_from_cp=timestamp_soc_from_cp, + soc_timestamp=soc_timestamp) def _filter_failed_store_threads(self, threads_store: List[Thread]) -> List[Thread]: ev_data = copy.deepcopy(subdata.SubData.ev_data) diff --git a/packages/modules/vehicles/tesla/api.py b/packages/modules/vehicles/tesla/api.py index 965b7e2fd4..802577771a 100644 --- a/packages/modules/vehicles/tesla/api.py +++ b/packages/modules/vehicles/tesla/api.py @@ -39,15 +39,16 @@ def post_wake_up_command(vehicle: int, token: TeslaSocToken) -> str: return response["response"]["state"] -def request_soc_range(vehicle: int, token: TeslaSocToken) -> Tuple[float, float]: +def request_soc_range(vehicle: int, token: TeslaSocToken) -> Tuple[float, float, float]: vehicle_id = __get_vehicle_id(vehicle, token) data_part = "vehicles/"+str(vehicle_id)+"/vehicle_data" response = __request_data(data_part, token) response = json.loads(response) - soc = response["response"]["charge_state"]["battery_level"] + soc = float(response["response"]["charge_state"]["battery_level"]) # convert miles to km range = float(response["response"]["charge_state"]["battery_range"]) * 1.60934 - return float(soc), range + soc_timestamp = float(response["response"]["charge_state"]["timestamp"]) + return soc, range, soc_timestamp def validate_token(token: TeslaSocToken) -> TeslaSocToken: diff --git a/packages/modules/vehicles/tesla/soc.py b/packages/modules/vehicles/tesla/soc.py index 59cc7f47d4..dcb9f41c31 100644 --- a/packages/modules/vehicles/tesla/soc.py +++ b/packages/modules/vehicles/tesla/soc.py @@ -20,9 +20,9 @@ def fetch(vehicle_config: TeslaSoc, vehicle_update_data: VehicleUpdateData) -> C vehicle_config.configuration.token = api.validate_token(vehicle_config.configuration.token) if vehicle_update_data.charge_state is False: _wake_up_car(vehicle_config) - soc, range = api.request_soc_range( + soc, range, soc_timestamp = api.request_soc_range( vehicle=vehicle_config.configuration.tesla_ev_num, token=vehicle_config.configuration.token) - return CarState(soc, range) + return CarState(soc=soc, range=range, soc_timestamp=soc_timestamp) def _wake_up_car(vehicle_config: TeslaSoc): diff --git a/packages/modules/vehicles/tesla/soc_test.py b/packages/modules/vehicles/tesla/soc_test.py index 8e8e375666..815c9c7339 100644 --- a/packages/modules/vehicles/tesla/soc_test.py +++ b/packages/modules/vehicles/tesla/soc_test.py @@ -17,7 +17,7 @@ def set_up(self, monkeypatch): self.mock_context_exit = Mock(return_value=True) self.mock_validate_token = Mock(name="validate_token", return_value=self.token) self.mock_post_wake_up_command = Mock(name="post_wake_up_command", return_value="online") - self.mock_request_soc_range = Mock(name="request_soc_range", return_value=(42.5, 438.2)) + self.mock_request_soc_range = Mock(name="request_soc_range", return_value=(42.5, 438.2, 1652683252)) self.mock_value_store = Mock(name="value_store") monkeypatch.setattr(api, "validate_token", self.mock_validate_token) monkeypatch.setattr(api, "post_wake_up_command", self.mock_post_wake_up_command)