From d8c39d616a871ceb64c4d77ee0d4341a99442d91 Mon Sep 17 00:00:00 2001 From: Leon Date: Thu, 5 Sep 2024 13:42:31 -0400 Subject: [PATCH] Fix access token refreshing logic --- run/tesla_api.py | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/run/tesla_api.py b/run/tesla_api.py index d46aafd2..a00c200a 100755 --- a/run/tesla_api.py +++ b/run/tesla_api.py @@ -18,7 +18,7 @@ base_url = 'https://owner-api.teslamotors.com/api/1/vehicles' SETTINGS = { 'DEBUG': False, - 'REFRESH_TOKEN': False, + 'refresh_token': False, 'tesla_email': 'dummy@local', 'tesla_password': '', 'tesla_access_token': '', @@ -62,8 +62,8 @@ def _execute_request(url=None, method=None, data=None, require_vehicle_online=Tr # Tesla REST Service sometimes misbehaves... this seems to be caused by an invalid/expired auth token # TODO: Remove auth token and retry? - if result['response'] is None: - _error("Fatal Error: Tesla REST Service returned an invalid response") + if result.get('response') is None: + _error(f"Fatal Error: Tesla REST Service returned an invalid response: {result}") sys.exit(1) vehicle_online = result['response']['state'] == "online" @@ -142,16 +142,43 @@ def _get_api_token(): # case, we can't accurately determine the age of the token, so we just # use it. Later executions of the script should run after the date has # updated correctly, at which point we can properly compare the dates. + max_retries = 24 + retry_count = 0 + retry_interval_s = 5 + cutoff_year = 2019 + # Wait for system time to be synchronized, up to max_retries times + while retry_count < max_retries: + now = datetime.now() + if now.year >= cutoff_year: # Time is correct, break the loop + break + _log( + f"System time is out of sync: {now}. Retrying in {retry_interval_s} seconds... (Attempt {retry_count + 1}/{max_retries})") + time.sleep(retry_interval_s) # Sleep for 5 seconds before checking again + retry_count += 1 + now = datetime.now() - if now.year < 2019: # This script was written in 2019. + if now.year < cutoff_year: # This script was written in 2019. + _log("Time is still wrong after waiting. Using existing access token.") + return tesla_api_json['access_token'] + + if not SETTINGS.get('refresh_token'): + _log('No refreshing token available. Using existing access token.') return tesla_api_json['access_token'] tesla = teslapy.Tesla(SETTINGS['tesla_email'], None) - if SETTINGS['REFRESH_TOKEN'] or 0 < tesla.expires_at < time.time(): - _log('Refreshing api token') + # For some reason, the `expires_at` timestamp doesn't exactly match the `exp` field in the JWT body. + # The `expires_at` is typically about 1 minute ahead of the `exp` value in the JWT payload. + # Access token usually expires after a few hours, so refresh the token if it has less than `expiration_buffer_s` seconds remaining. + expiration_buffer_s = 60 * 30 + if tesla.expires_at <= time.time() - expiration_buffer_s: + _log("Refreshing expired access token...") + tesla.token['refresh_token'] = SETTINGS.get('refresh_token') tesla.refresh_token() - tesla_api_json['access_token'] = tesla.token.get('access_token') + if tesla_api_json['access_token'] != tesla.token.get('access_token'): + _log("Syncing access token...") + tesla_api_json['access_token'] = tesla.token.get('access_token') + _write_tesla_api_json() return tesla_api_json['access_token'] # If the access token is not already stored in tesla_api_json AND @@ -531,7 +558,7 @@ def main(): args = _get_arg_parser().parse_args() SETTINGS['DEBUG'] = args.debug - SETTINGS['REFRESH_TOKEN'] = args.refresh_token + SETTINGS['refresh_token'] = args.refresh_token if args.vin: SETTINGS['tesla_vin'] = args.vin