Package: cartson
carsonOverviewStates And CommandsConfigurationStreamingPythonic FeaturesOverviewcarsonis 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 histesla-apiproject as well as Mark Seminatore andTeslaJS.  So, thanks to them for the head start.Among the goals for this project is to have anasynciobased
library.  As a result, Python 2 is not supported.  In fact, it seems like it has been a decade since theprovisionaltag was removed from theasynciolibrary because it has evolved so much.  There are many guides, articles, and posts
based on early features ofasyncio.  The best way to stay up to date is by starting with Python's documentation athttps://docs.python.org/library/asyncio.html.  This project uses theasync/awaitsyntax introduced inPEP-492.  As of this writing, the
latest version of Python is 3.8.1.DependenciesThere is one dependency for basic usage —aiohttp.AuthenticationTesla has made authentication a moving target.  It's a bit like playing whack-a-mole.  Understandably so because
security should always be job zero in any computing effort.  And all 3rd party libraries not developed by Tesla are
unofficial.  Perhaps this recent variability is a sign of an impending App Store?  I don't know... just speculating.
Butauthenticationis not a goal of this library.Below are a few mechanisms to try to successfully authenticate.  Your results may vary depending on a number of factors.
Like most network service based apps, Tesla employ's DNS traffic policies with weighted and geo values (among others)
which means you might or might not be presented with captcha during password authentication.BYOTThe easiest and most reliable method is to bring your own access token.  Both the command line utility andSessionclass accept an access token instead of email/password.  If you want to use environment variables or a configuration
file to supply your access token, see theconfigurationsection below for more information.Email & PasswordBoth the command line utility andSessionclass will acceptemailandpasswordarguments.  They will be used to
generate an access token.  Examples include:> python -m carson --email nikola@tesla.com --password electricity --listCar #1 Vehicle('Antares' state='asleep' id=123..789)Car #2 Vehicle('Dark Nebula' state='asleep' id=321..987)A better practice to keep from leaking your password into your shell history is to leave--passwordoff and let the
command line utility securely prompt you for it.  Example:> python -m carson --email nikola@tesla.com --listPassword for nikola@tesla.com:Car #1 Vehicle('Antares' state='asleep' id=123..789)Car #2 Vehicle('Dark Nebula' state='asleep' id=321..987)Generate Your OwnThe command line utility can be used to generate an access token that you can then save securely for future use.  Add
the--token-onlyoption as show below:> python -m carson --email nikola@tesla.com --token-onlyPassword for nikola@tesla.com:{"access_token": "qts-0a1b2c...5f0a1b","token_type": "bearer","expires_in": 3888000,"refresh_token": "b1a0f5...c2b1a0","created_at": 1626183175}your own Credentials can be provided andcarsonwill attempt to create an access token for you.If you already have (or know how to generate) anaccess_tokenor simply do not want to provide your Tesla account's
email and password, you can instead provide your token.  Simply replace the argumentsemailandpasswordpassed to
theSessionconstructor with the argumentaccess_token(or--access-tokenif using the command line).  Your token
should resemble a long list of characters similar toqts-0a1b2c...5f0a1b.Example:...asyncwithSession(access_token='qts-0a1b2c...5f0a1b')assession:...States And CommandsWith its most basic usage, you can usecarsonto get the current state of car with the following code:>>>importasyncio>>>fromcarsonimportSession>>>asyncdefmain():...name='Dark Nebula'...asyncwithSession(email='nikola@tesla.com',password='electricity')assession:...car=awaitsession.vehicles(name)...print(f'{name}is{car.state!r}')>>>asyncio.run(main())DarkNebulais'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.8100312020-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.7128682020-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 anoauthtoken which is required for all subsequent requests.Get a list of vehicles associated with the credentials provided.ConfigurationCredentials can also be stored in configuration.carsonlooks for credentials in the following order:The argumentsemailandpasswordpassed to theSessionconstructor.The argumentaccess_tokenpassed to theSessionconstructor.The environment variablesCARSON_EMAIL,CARSON_PASSWORD,CARSON_ACCESS_TOKEN.An.inistyle config file named.carsonorcarson.iniin the user's home directory.Regarding credentials:Always use care when storing credentials.  Sometimesbad thingscan happen and often time will.Credential PrecedenceCredentials (passwordandaccess_token) are used in the following order of precedence.  When reading'if'and'if not', think Python boolean operations (e.g.'',None,0are allFalse).Ifpasswordis given to theSessionconstructor, it will always be used to generate a new access token.  Even if
a validaccess_tokenis given to theSessionconstructor at the same time.  This means a value foremailmust
also be given (or implied from config).Ifpasswordis not given to theSessionconstructor, the value used foraccess_tokenis used.  Or, if not
given, implied from config.If neitherpasswordnoraccess_tokenare given to theSessionconstructor, but bothpasswordandaccess_tokenare defined in config, theaccess_tokenfrom config will be used.StreamingTesla provides awebsocketendpoint 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 --streamcarsonwill attempt towake-upthe 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.4355162020-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.6148052020-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.3010212020-01-01 14:09:37,920 I carson  Streaming iteration=12020-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=02020-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 FeaturesPython is a fantastic language.  One of my favorite features is its ability to customize attribute access.  That ability
allows aVehicleclass 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 theStates And Commandssection.  But I wanted to
put this above the fold to call out the Pythonic features ofcarson- both in programmability and general use on the
command line.Recursive Dot-NotationConsider this JSON response from Tesla when getting making a call tovehicle_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,...Withcarson, 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 theVehicleusing standard Python dot-notation like this:car=awaitmy_session.vehicles('Dark Nebula')json_response=awaitcar.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 attributeperc=car.vehicle_state.software_update.download_percif0<perc<1:print(f'Downloading:{perc:.2%}complete.')else:print('Download complete'ifperc==1else'N/A')Endpoint Commands Asawaitable AttributesSimilarly, commands can that are mapped to an endpoint accessed via Python's class instance attribute mechanism will
return anawaitable coroutine.  For example, theVehicleclass incarsondoes not have an attribute namedstart_charge.  The endpoints mapping, however, does mapSTART_CHARGEto a POST request to the URIapi/1/vehicles/{vehicle_id}/command/charge_start.  This makes it possible to start charging your Tesla with either
this code:car=awaitmy_session.vehicles('Dark Nebula')awaitcar.start_charge()or this command> python -m carson -v --command start_charge2020-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.4600192020-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.4030622020-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}