From 697748c3ef70de6e2b83dfccb4cacba2c72c974c Mon Sep 17 00:00:00 2001 From: Richard van den Berg Date: Tue, 23 Jan 2024 13:48:02 +0100 Subject: [PATCH 1/2] Get list of vehicles using products endpoint for Owner API --- lib/TWCManager/Vehicle/TeslaAPI.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/TWCManager/Vehicle/TeslaAPI.py b/lib/TWCManager/Vehicle/TeslaAPI.py index a18ab055..079499ee 100644 --- a/lib/TWCManager/Vehicle/TeslaAPI.py +++ b/lib/TWCManager/Vehicle/TeslaAPI.py @@ -229,6 +229,8 @@ def car_api_available( if self.getCarApiBearerToken() != "": if self.getVehicleCount() < 1: url = self.baseURL + if 'owner-api' in url: + url = url.replace('vehicles', 'products') headers = { "accept": "application/json", "Authorization": "Bearer " + self.getCarApiBearerToken(), From 8740c6551e99fd352deae528143bb6a330169b66 Mon Sep 17 00:00:00 2001 From: Richard van den Berg Date: Thu, 25 Jan 2024 10:30:38 +0100 Subject: [PATCH 2/2] Get baseURL from ou_code in bearer token --- etc/twcmanager/config.json | 21 ++++-------- lib/TWCManager/Vehicle/TeslaAPI.py | 55 ++++++++++++++++++------------ 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/etc/twcmanager/config.json b/etc/twcmanager/config.json index 5df942c5..7d97caf1 100644 --- a/etc/twcmanager/config.json +++ b/etc/twcmanager/config.json @@ -131,17 +131,6 @@ # you will need to un-comment it to have it take effect. #"minChargeLevel": 10, - # The Tesla API URL is base URL for all REST API calls. This should - # include the "/api/1/vehicles" path. - # Known values are: - # - # OwnerAPI (legacy): https://owner-api.teslamotors.com/api/1/vehicles - # FleetAPI North America: https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/vehicles - # FleetAPI Europe: https://fleet-api.prd.eu.vn.cloud.tesla.com/api/1/vehicles - # FleetAPI China: https://fleet-api.prd.cn.vn.cloud.tesla.cn/api/1/vehicles - # FleetAPI http proxy: https://localhost:4443/api/1/vehicles - "teslaApiUrl": "https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/vehicles", - # The client_id of the app registered with Tesla. Registration is # required to use the FleetAPI and Vehicle Command protocol. # See https://developer.tesla.com/docs/fleet-api#authentication @@ -155,11 +144,15 @@ # Instead it relies on the Tesla Vehicle Command proxy which translates # the FleetAPI REST calls if the destination vehicle supports the # new protocol. This requires the URL of the proxy to be set as - # "teslaApiUrl" above and the proxy certificate for validation here. + # "teslaProxy" and the proxy certificate for validation as + # "teslaProxyCert". # See https://github.com/teslamotors/vehicle-command # - # Only set "httpProxyCert" when setting "teslaApiUrl" to a local proxy. - #"httpProxyCert": "/path/to/public_key.pem", + # The URL of the Tesla HTTP Proxy running locally + # "teslaProxy": "https://localhost:4443" + # + # Only set "teslaProxyCert" when "teslaProxy" is set. + #"teslaProxyCert": "/path/to/public_key.pem", # The cloudUpdateInterval determines how often to poll certain # data retrieved from the Tesla API to evaluate policy. diff --git a/lib/TWCManager/Vehicle/TeslaAPI.py b/lib/TWCManager/Vehicle/TeslaAPI.py index a18ab055..0f4e38d1 100644 --- a/lib/TWCManager/Vehicle/TeslaAPI.py +++ b/lib/TWCManager/Vehicle/TeslaAPI.py @@ -8,6 +8,8 @@ from threading import Thread import time from urllib.parse import parse_qs +import jwt +import datetime logger = logging.getLogger("\U0001F697 TeslaAPI") @@ -18,9 +20,15 @@ class TeslaAPI: __apiState = None __authURL = "https://auth.tesla.com/oauth2/v3/token" __callbackURL = "https://auth.tesla.com/void/callback" - baseURL = "https://owner-api.teslamotors.com/api/1/vehicles" + baseURL = "" + regionURL = { + 'OwnerAPI': 'https://owner-api.teslamotors.com/api/1/vehicles', + 'NA': 'https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/vehicles', + 'EU': 'https://fleet-api.prd.eu.vn.cloud.tesla.com/api/1/vehicles', + 'CN': 'https://fleet-api.prd.cn.vn.cloud.tesla.cn/api/1/vehicles', + } refreshClientID = "ownerapi" - proxyCert = None + verifyCert = True carApiLastErrorTime = 0 carApiBearerToken = "" carApiRefreshToken = "" @@ -64,13 +72,13 @@ def __init__(self, master): self.master = master try: self.config = master.config - self.baseURL = self.config["config"].get( - "teslaApiUrl", "https://owner-api.teslamotors.com/api/1/vehicles" - ) + proxyURL = self.config["config"].get("teslaProxy", "") + if proxyURL: + self.baseURL = proxyURL + "/api/1/vehicles" + self.verifyCert = self.config["config"].get("teslaProxyCert", True) self.refreshClientID = self.config["config"].get( "teslaApiClientID", "ownerapi" ) - self.proxyCert = self.config["config"].get("httpProxyCert", True) self.minChargeLevel = self.config["config"].get("minChargeLevel", -1) self.chargeUpdateInterval = self.config["config"].get( "cloudUpdateInterval", 1800 @@ -234,7 +242,7 @@ def car_api_available( "Authorization": "Bearer " + self.getCarApiBearerToken(), } try: - req = requests.get(url, headers=headers, verify=self.proxyCert) + req = requests.get(url, headers=headers, verify=self.verifyCert) logger.log(logging.INFO8, "Car API cmd vehicles " + str(req)) apiResponseDict = json.loads(req.text) except requests.exceptions.RequestException: @@ -658,7 +666,7 @@ def car_api_charge(self, charge): # Retry up to 3 times on certain errors. for _ in range(0, 3): try: - req = requests.post(url, headers=headers, verify=self.proxyCert) + req = requests.post(url, headers=headers, verify=self.verifyCert) logger.log( logging.INFO8, "Car API cmd charge_" + startOrStop + " " + str(req), @@ -973,6 +981,9 @@ def getCarApiLastErrorTime(self): def getCarApiRefreshToken(self): return self.carApiRefreshToken + def getCarApiBaseURL(self): + return self.baseURL + def getCarApiRetryRemaining(self, vehicle=None): # Calculate the amount of time remaining until the API can be queried # again. This is the api backoff time minus the difference between now @@ -1097,6 +1108,12 @@ def setCarApiBearerToken(self, token=None): return False else: self.carApiBearerToken = token + if not self.baseURL: + decoded = jwt.decode(token, options={"verify_signature": False, "verify_aud": False}, leeway=datetime.timedelta(days=300)) + if 'owner-api' in ''.join(decoded.get('aud', '')): + self.baseURL = self.regionURL['OwnerAPI'] + elif decoded.get('ou_code', '') in self.regionURL: + self.baseURL = self.regionURL[decoded['ou_code']] return True else: return False @@ -1133,7 +1150,7 @@ def setChargeRate(self, charge_rate, vehicle=None, set_again=False): body = {"charging_amps": charge_rate} try: - req = requests.post(url, headers=headers, json=body, verify=self.proxyCert) + req = requests.post(url, headers=headers, json=body, verify=self.verifyCert) logger.log( logging.INFO8, f"Car API cmd set_charging_amps {charge_rate}A {str(req)}", @@ -1191,7 +1208,7 @@ def wakeVehicle(self, vehicle): "Authorization": "Bearer " + self.getCarApiBearerToken(), } try: - req = requests.post(url, headers=headers, verify=self.proxyCert) + req = requests.post(url, headers=headers, verify=self.verifyCert) logger.log(logging.INFO8, "Car API cmd wake_up" + str(req)) apiResponseDict = json.loads(req.text) except requests.exceptions.RequestException: @@ -1219,8 +1236,7 @@ class CarApiVehicle: carapi = None __config = None debuglevel = 0 - baseURL = "https://owner-api.teslamotors.com/api/1/vehicles" - proxyCert = None + verifyCert = None ID = None name = "" syncSource = "TeslaAPI" @@ -1254,10 +1270,7 @@ class CarApiVehicle: def __init__(self, json, carapi, config): self.carapi = carapi self.__config = config - self.baseURL = config["config"].get( - "teslaApiUrl", "https://owner-api.teslamotors.com/api/1/vehicles" - ) - self.proxyCert = config["config"].get("httpProxyCert", None) + self.verifyCert = config["config"].get("teslaProxyCert", True) self.ID = json["id"] self.VIN = json["vin"] self.name = json["display_name"] @@ -1327,7 +1340,7 @@ def ready(self): # Permits opportunistic API requests def is_awake(self): if self.syncSource == "TeslaAPI": - url = self.baseURL + "/" + str(self.VIN) + url = self.carapi.getCarApiBaseURL() + "/" + str(self.VIN) (result, response) = self.get_car_api( url, checkReady=False, provesOnline=False ) @@ -1354,7 +1367,7 @@ def get_car_api(self, url, checkReady=True, provesOnline=True): # Retry up to 3 times on certain errors. for _ in range(0, 3): try: - req = requests.get(url, headers=headers, verify=self.proxyCert) + req = requests.get(url, headers=headers, verify=self.verifyCert) logger.log(logging.INFO8, "Car API cmd " + url + " " + str(req)) apiResponseDict = json.loads(req.text) # This error can happen here as well: @@ -1413,7 +1426,7 @@ def update_location(self, cacheTime=60): return True def update_vehicle_data(self, cacheTime=60): - url = self.baseURL + "/" + url = self.carapi.getCarApiBaseURL() + "/" url = url + str(self.VIN) + "/vehicle_data" url = ( url @@ -1470,7 +1483,7 @@ def apply_charge_limit(self, limit): self.lastLimitAttemptTime = now - url = self.baseURL + "/" + url = self.carapi.getCarApiBaseURL() + "/" url = url + str(self.VIN) + "/command/set_charge_limit" headers = { @@ -1482,7 +1495,7 @@ def apply_charge_limit(self, limit): for _ in range(0, 3): try: req = requests.post( - url, headers=headers, json=body, verify=self.proxyCert + url, headers=headers, json=body, verify=self.verifyCert ) logger.log(logging.INFO8, "Car API cmd set_charge_limit " + str(req))