carson
Overview
carson
is a simple Python interface for Tesla's unofficial JSON API and includes some utilities to work with data it
generates. Lots of work to discover and document the API was done by Tim Dorr and dozens of contributors to his
tesla-api project. So, thanks to them for the head start.
Among the goals for this project is to have an asyncio
based
library. As a result, Python 2 is not supported. In fact, it seems like it has been a decade since the provisional
tag was removed from the asyncio
library because it has evolved so much. There are many guides, articles, and posts
based on early features of asyncio
. The best way to stay up to date is by starting with Python's documentation at
https://docs.python.org/library/asyncio.html. This project uses the
async
/await
syntax introduced in PEP-492. As of this writing, the
latest version of Python is 3.8.1.
Dependencies
There is one dependency for basic usage — aiohttp
.
States And Commands
With its most basic usage, you can use carson
to get the current state of car with the following code:
>>>import asyncio
>>>from carson import Session
>>>async def main():
... name = 'Dark Nebula'
... async with Session(email='nikola@tesla.com', password='electricity') as session:
... car = await session.vehicles(name)
... print(f'{name} is {car.state!r}')
>>>asyncio.run(main())
Dark Nebula is 'asleep'
Or you can run it from the command line in a similar fashion:
> python -m carson -v --email nikola@tesla.com --password electricity --display-name "Dark Nebula"
To get a sense of what is happening, you can add verbose and see the requests being made.
> python -m carson -v --email nikola@tesla.com --password electricity --display-name "Dark Nebula"
2020-01-01 10:45:59,418 D carson Performing OAuth password grant for email='nikola@tesla.com'
2020-01-01 10:46:00,229 D carson Req# 1: Method=POST url='https://owner-api.teslamotors.com/oauth/token?grant_type=password' status=200 duration=0:00:00.810031
2020-01-01 10:46:00,943 D carson Req# 2: Method=GET url='https://owner-api.teslamotors.com/api/1/vehicles' status=200 duration=0:00:00.712868
2020-01-01 10:46:00,944 I carson Vehicle('Dark Nebula' state='asleep')
You can see that two requests are made:
- First is to generate an
oauth
token which is required for all subsequent requests. - Get a list of vehicles associated with the credentials provided.
OAuth
If you already have (or know how to generate) an auth_token
or simply do not want to provide your Tesla account's
email and password, you can instead provide your token. Simply replace the arguments email
and password
passed to
the Session
constructor with the argument access_token
(or --auth-token
if using the command line). Your auth
token should resemble a long list of characters similar to the following:
0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b
Example:
> python -m carson --auth-token 0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b
Example:
...
async with Session(auth_token='0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d4e5f0a1b') as session:
...
Configuration
Credentials can also be stored in configuration. carson
looks for credentials in the following order:
- The arguments
email
andpassword
passed to theSession
constructor. - The argument
access_token
passed to theSession
constructor. - The environment variables
CARSON_EMAIL
,CARSON_PASSWORD
,CARSON_ACCESS_TOKEN
. - An
.ini
style config file named.carson
orcarson.ini
in the user's home directory.
Regarding credentials: Always use care when storing credentials. Sometimes bad things can happen and often time will.
Credential Precedence
Credentials (password
and auth_token
) are used in the following order of precedence. When reading 'if' and
'if not', think Python boolean operations (e.g. ''
, None
, 0
are all False
).
- If
password
is given to theSession
constructor, it will always be used to generate a new oauth token. Even if a validauth_token
is given to theSession
constructor at the same time. This means a value foremail
must also be given (or implied from config). - If
password
is not given to theSession
constructor, the value used foraccess_token
is used. Or, if not given, implied from config. - If neither
password
noraccess_token
are given to theSession
constructor, but bothpassword
andaccess_token
are defined in config, theaccess_token
from config will be used.
Streaming
Tesla provides a websocket
endpoint from which telemetry data can be streamed and stored. To begin streaming this
telemetry, issue the following command.
> python -m carson -v --display-name YOUR_CAR_NAME --stream
carson
will attempt to wake-up the car and initiate the streaming telemetry. By default, the telemetry simply
outputs the data to log. A sample of that output is below.
2020-01-01 14:09:30,129 D carson Req# 1: Method=GET url='https://owner-api.teslamotors.com/api/1/vehicles' status=200 duration=0:00:00.435516
2020-01-01 14:09:30,752 D carson Req# 2: Method=POST url='https://owner-api.teslamotors.com/api/1/vehicles/01234567890123456/wake_up' status=200 duration=0:00:00.614805
2020-01-01 14:09:30,752 D carson Waiting for car to wake up.
2020-01-01 14:09:37,920 D carson Req# 9: Method=GET url='https://owner-api.teslamotors.com/api/1/vehicles/01234567890123456/vehicle_data' status=200 duration=0:00:00.301021
2020-01-01 14:09:37,920 I carson Streaming iteration=1
2020-01-01 14:09:37,961 I carson car=Vehicle('Dark Nebula' state='online' miles=18,421 software='2019.40.50.5' battery_level=81) iteration=1 client_errors=0 vehicle_disconnects=0
2020-01-01 14:09:38,412 D carson {"msg_type":"data:subscribe","token":"bWlj********NmY1","value":"speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power,shift_state,range,est_range,heading","tag":"0123456789"}
2020-01-01 14:09:38,412 D carson msg_count=1 msg={'msg_type': 'control:hello', 'connection_timeout': 0}
2020-01-01 14:09:39,474 D carson msg_count=2 msg={'msg_type': 'data:update', 'tag': '0123456789', 'value': '1577909378751,,18421.1,81,232,182,40.778955,-73.968583,0,,242,223,8'}
2020-01-01 14:09:49,479 D carson Timeout waiting for next message.
2020-01-01 14:09:49,495 D carson msg_count=3 msg={'msg_type': 'data:error', 'tag': '0123456789', 'value': 'disconnected', 'error_type': 'vehicle_disconnected'}
2020-01-01 14:09:49,566 I carson Streamer task ending due to shift state=''.
Pythonic Features
Python is a fantastic language. One of my favorite features is its ability to customize attribute access. That ability allows a Vehcile class instance to basically act like a chameleon. As Tesla changes its data structure and command interface for its cars, it's pretty easy for a Python class to essentially keep itself up to date.
This section would normally be placed after the States And Commands section. But I wanted to
put this above the fold to call out the Pythonic features of carson
- both in programmability and general use on the
command line.
Recursive Dot-Notation
Consider this JSON response from Tesla when getting making a call to vehicle_data
:
{
"response": {
"id": 98765432109876543,
"vehicle_id": 1234567890,
"display_name": "Dark Nebula",
"state": "online",
...
"vehicle_state": {
"api_version": 7,
...
"sentry_mode": false,
"sentry_mode_available": true,
"smart_summon_available": true,
"software_update": {
"download_perc": 0.85279,
"expected_duration_sec": 2700,
"install_perc": 0,
...
With carson
, after you make the call to get the vehicle data, you can access the JSON response that is returned, or
simply reference its associated JSON path on the instance of the Vehicle
using standard Python dot-notation like this:
car = await my_session.vehicles('Dark Nebula')
json_response = await car.vehicle_data()
# I have options here. I can access the JSON data as a normal Python `dict`
perc = json_response['vehicle_state']['software_update']['download_perc']
# Or as a Python attribute
perc = car.vehicle_state.software_update.download_perc
if 0 < perc < 1:
print(f'Downloading: {perc:.2%} complete.')
else:
print('Download complete' if perc == 1 else 'N/A')
await
able Attributes
Endpoint Commands As Similarly, commands can that are mapped to an endpoint accessed via Python's class instance attribute mechanism will
return an await
able coroutine. For example, the Vehicle
class in carson
does not have an attribute named
start_charge
. The endpoints mapping, however, does map START_CHARGE
to a POST request to the URI
api/1/vehicles/{vehicle_id}/command/charge_start
. This makes it possible to start charging your Tesla with either
this code:
car = await my_session.vehicles('Dark Nebula')
await car.start_charge()
or this command
> python -m carson -v --command start_charge
2020-01-01 11:51:44,349 D carson Req# 1: Method=GET url='https://owner-api.teslamotors.com/api/1/vehicles' status=200 duration=0:00:02.460019
2020-01-01 11:51:44,350 I carson Vehicle('Dark Nebula' state='online')
2020-01-01 11:51:44,350 I carson Performing 'start_charge'...
2020-01-01 11:51:44,753 D carson Req# 2: Method=POST url='https://owner-api.teslamotors.com/api/1/vehicles/01234567890123456/command/charge_start' status=200 duration=0:00:00.403062
2020-01-01 11:51:44,754 I carson Result=
{'carsonRequest': {'method': 'POST',
'url': 'https://owner-api.teslamotors.com/api/1/vehicles/01234567890123456/command/charge_start'},
'carsonTimestamp': '2020-01-01T17:51:44.350726',
'error': None,
'error_description': '',
'response': {'reason': 'complete', 'result': True},
'status': 200}