Skip to content

Commit

Permalink
Distinction between soc timestamp and soc request timestamp, update s…
Browse files Browse the repository at this point in the history
…oc only if soc_timestamp is newer (#2000)

* Distinction between soc timestamp and soc request timestamp

* update soc only if soc_timestamp is newer
  • Loading branch information
LKuemmel authored Dec 17, 2024
1 parent 8d1351b commit 76effac
Show file tree
Hide file tree
Showing 11 changed files with 41 additions and 24 deletions.
6 changes: 4 additions & 2 deletions packages/control/ev.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions packages/control/ev_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@


@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"),
pytest.param(False, True, 100, True, id="charging, expired"),
])
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)

Expand Down
3 changes: 2 additions & 1 deletion packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)])
Expand Down
1 change: 1 addition & 0 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,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$",
Expand Down
1 change: 1 addition & 0 deletions packages/modules/common/abstract_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions packages/modules/common/component_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,18 @@ 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
soc_timestamp: timestamp of last request as unix timestamp
"""
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
Expand Down
11 changes: 8 additions & 3 deletions packages/modules/common/configurable_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
15 changes: 9 additions & 6 deletions packages/modules/update_soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions packages/modules/vehicles/tesla/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions packages/modules/vehicles/tesla/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion packages/modules/vehicles/tesla/soc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 76effac

Please sign in to comment.