From d7b1f5c9bf83935940cf0d7ef4d2cf0f04d6feda Mon Sep 17 00:00:00 2001 From: "Alan D. Tse" Date: Thu, 30 Jan 2020 22:21:40 -0800 Subject: [PATCH] feat: change to adaptive algorithm based updates Instead of polling for a set SCAN_INTERVAL, the new algorithm will determine if the car has recently parked and update normally for the IDLE_INTERVAL (600). After the idle period is complete, updates are throttled to the SLEEP_INTERVAL until the car is asleep. There is now a regular ONLINE_INTERVAL (60) check which does not query the car and will immediately detect if a car has become awake to resume updates. --- teslajsonpy/const.py | 10 +++++++++ teslajsonpy/controller.py | 44 +++++++++++++++++++++++++++++++++++++-- teslajsonpy/vehicle.py | 4 ++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 teslajsonpy/const.py diff --git a/teslajsonpy/const.py b/teslajsonpy/const.py new file mode 100644 index 00000000..eeb21783 --- /dev/null +++ b/teslajsonpy/const.py @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +""" +Python Package for controlling Tesla API. + +For more details about this api, please refer to the documentation at +https://github.com/zabuldon/teslajsonpy +""" +IDLE_INTERVAL = 600 # interval after parking to check at regular update_interval +ONLINE_INTERVAL = 60 # interval for checking online state; does not hit individual cars +SLEEP_INTERVAL = 660 # interval required to let vehicle sleep; based on testing diff --git a/teslajsonpy/controller.py b/teslajsonpy/controller.py index 0303b738..2b52b64b 100644 --- a/teslajsonpy/controller.py +++ b/teslajsonpy/controller.py @@ -20,6 +20,7 @@ from teslajsonpy.charger import ChargerSwitch, ChargingSensor, RangeSwitch from teslajsonpy.climate import Climate, TempSensor from teslajsonpy.connection import Connection +from teslajsonpy.const import IDLE_INTERVAL, ONLINE_INTERVAL, SLEEP_INTERVAL from teslajsonpy.exceptions import RetryLimitError, TeslaException from teslajsonpy.gps import GPS, Odometer from teslajsonpy.lock import ChargerLock, Lock @@ -77,6 +78,7 @@ def __init__( self.__vin_vehicle_id_map = {} self.__vehicle_id_vin_map = {} self.__websocket_listeners = [] + self.__last_parked_timestamp = {} async def connect(self, test_login=False) -> Tuple[Text, Text]: """Connect controller to Tesla.""" @@ -99,6 +101,7 @@ async def connect(self, test_login=False) -> Tuple[Text, Text]: self.__update[vin] = True self.raw_online_state[vin] = car["state"] self.car_online[vin] = car["state"] == "online" + self.__last_parked_timestamp[vin] = self._last_attempted_update_time self.__climate[vin] = {} self.__charging[vin] = {} self.__state[vin] = {} @@ -460,6 +463,7 @@ async def _wake_up(self, car_id): return self.car_online[car_vin] async def update(self, car_id=None, wake_if_asleep=False, force=False): + # pylint: disable=too-many-locals """Update all vehicle attributes in the cache. This command will connect to the Tesla API and first update the list of @@ -482,11 +486,26 @@ async def update(self, car_id=None, wake_if_asleep=False, force=False): RetryLimitError """ + + def _calculate_next_interval(vin: int) -> int: + if self.raw_online_state[vin] == "asleep" or self.__driving[vin].get( + "shift_state" + ): + self.__last_parked_timestamp[vin] = cur_time + elif (cur_time - (self.__last_parked_timestamp[vin])) > IDLE_INTERVAL: + _LOGGER.debug( + "%s trying to sleep; will ignore updates for %s seconds", + vin[-5:], + round(SLEEP_INTERVAL + self._last_update_time[vin] - time.time(), 2), + ) + return SLEEP_INTERVAL + return self.update_interval + cur_time = time.time() async with self.__controller_lock: # Update the online cars using get_vehicles() last_update = self._last_attempted_update_time - if force or cur_time - last_update > self.update_interval: + if force or cur_time - last_update > ONLINE_INTERVAL: cars = await self.get_vehicles() self.car_online = {} for car in cars: @@ -520,7 +539,7 @@ async def update(self, car_id=None, wake_if_asleep=False, force=False): or vin not in self._last_update_time or ( (cur_time - self._last_update_time[vin]) - > self.update_interval + > _calculate_next_interval(vin) ) ) ): # Only update cars with update flag on @@ -536,6 +555,18 @@ async def update(self, car_id=None, wake_if_asleep=False, force=False): self.__charging[vin] = response["charge_state"] self.__state[vin] = response["vehicle_state"] self.__config[vin] = response["vehicle_config"] + if ( + self.__driving[vin].get("shift_state") + and self.__driving[vin].get("shift_state") + != response["drive_state"]["shift_state"] + and ( + response["drive_state"]["shift_state"] is None + or response["drive_state"]["shift_state"] == "P" + ) + ): + self.__last_parked_timestamp[vin] = ( + response["drive_state"]["timestamp"] / 1000 + ) self.__driving[vin] = response["drive_state"] self.__gui[vin] = response["gui_settings"] self._last_update_time[vin] = time.time() @@ -721,6 +752,15 @@ def _process_websocket_message(self, data): self.__driving[vin]["latitude"] = update_json["est_lat"] self.__driving[vin]["longitude"] = update_json["est_lng"] self.__driving[vin]["power"] = update_json["power"] + if ( + self.__driving[vin].get("shift_state") + and self.__driving[vin].get("shift_state") != update_json["shift_state"] + and ( + update_json["shift_state"] is None + or update_json["shift_state"] == "P" + ) + ): + self.__last_parked_timestamp[vin] = update_json["timestamp"] / 1000 self.__driving[vin]["shift_state"] = update_json["shift_state"] self.__charging[vin]["battery_range"] = update_json["range"] self.__charging[vin]["est_battery_range"] = update_json["est_range"] diff --git a/teslajsonpy/vehicle.py b/teslajsonpy/vehicle.py index bcd0e7d6..12e2cd89 100644 --- a/teslajsonpy/vehicle.py +++ b/teslajsonpy/vehicle.py @@ -60,6 +60,10 @@ def id(self): """Return the id of this Vehicle.""" return self._id + def vehicle_id(self): + """Return the vehicle_id of this Vehicle.""" + return self._vehicle_id + def car_name(self): """Return the car name of this Vehicle.""" return (