Skip to content

Commit

Permalink
Merge pull request #548 from RichieB2B/jwt
Browse files Browse the repository at this point in the history
Get baseURL from ou_code in bearer token
  • Loading branch information
ngardiner committed Feb 4, 2024
2 parents 81b29ea + 4b1767f commit 76d7019
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 35 deletions.
21 changes: 7 additions & 14 deletions etc/twcmanager/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
57 changes: 36 additions & 21 deletions lib/TWCManager/Vehicle/TeslaAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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 = ""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -229,12 +237,14 @@ 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(),
}
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:
Expand Down Expand Up @@ -658,7 +668,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),
Expand Down Expand Up @@ -973,6 +983,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
Expand Down Expand Up @@ -1097,6 +1110,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
Expand Down Expand Up @@ -1133,7 +1152,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)}",
Expand Down Expand Up @@ -1191,7 +1210,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:
Expand Down Expand Up @@ -1219,8 +1238,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"
Expand Down Expand Up @@ -1254,10 +1272,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"]
Expand Down Expand Up @@ -1327,7 +1342,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
)
Expand All @@ -1354,7 +1369,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:
Expand Down Expand Up @@ -1413,7 +1428,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
Expand Down Expand Up @@ -1470,7 +1485,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 = {
Expand All @@ -1482,7 +1497,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))

Expand Down

0 comments on commit 76d7019

Please sign in to comment.