Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distinction between soc timestamp and soc request timestamp, update soc only if soc_timestamp is newer #2000

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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$",
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