Morning Girl

Web API, Windows, C#, .NET, Dynamics 365/CRM etc..

Tesla API の検証環境(物理)が手に入ったので、改めて Tesla API の使い方を解説してみる

今年の1月にこんなエントリーを書いたわたくしですが、

kageura.hatenadiary.jp

5月に少し気を失っていたら、以下のような画面が iPad に表示され

9月末にとうとう Tesla API 検証環境(物理)が届いてしまいました。

上記エントリーでは、ダミーデータを返すモック環境のAPIだったので、常にバッテリーが減らない、いくら操作をしても壊れない夢のような(そしてちょっと寂しい)テスラだったのですが、ちゃんとバッテリーが減るAPIとして試すことができるようになりました!

というわけで改めてこのTesla APIの使い方を解説していきたいと思います!

そもそも Tesla API とはなんなのか?

前の記事でも書いていますが改めて。

Tesla API は Tesla のモバイルアプリで使用されているバックエンド用のインターフェースです。

Tesla

Tesla

  • Tesla, Inc.
  • ライフスタイル
  • 無料
apps.apple.com

f:id:sugimomoto:20201122221615p:plain

このアプリからいろんなことができるようになっていて、今のバッテリーや室温・外気温状態から、メディアの操作、ロックの解除、テスラの位置情報まで、車両情報の取得から操作まで幅広いことを可能にしています。

【機能一覧】

  • 充電状況をリアルタイムで確認し、充電を開始または停止する
  • 運転前に車両の暖房/冷房を入れる (ガレージ内でも可能)
  • 遠隔からロックまたはロック解除する
  • 車の現在地を確認したり、動きを追跡する
  • お気に入りのアプリから目的地を送信し、ナビを開始します
  • 同乗者はすばやくメディアをコントロールすることができます
  • 駐車場で車のライトを点滅したりホーンを鳴らしたりして車両を見つける
  • パノラマ ルーフを開閉する
  • 車両をガレージまたは狭い駐車スペースから呼び出す(オートパイロット搭載車用
  • どこにいても車両のソフトウェアをアップデートできます
  • パワーウォールへのソーラーからの充電量、家庭でのエネルギー使用量、系統への売電量をモニターする
  • ソーラー発電量とバッテリー消費データをダウンロード

以下は実際の私のテスラのアプリ画面。

f:id:sugimomoto:20201122221627p:plain

このそれぞれの機能がバックエンドAPIによって提供されているのですが、そのAPIリクエストをキャプチャして、リバースエンジニアリングし、非公式の API 仕様書として書き起こしているサイトがいくつかあります!

これがいわゆる今回取り上げる「Tesla API」と呼ばれるものです。いくつかサイトがありますが、以下のサイトが結構充実している感です。

https://www.teslaapi.io/

f:id:sugimomoto:20201122221639p:plain

ですので、実質的にモバイルアプリで提供されている機能はほぼこのAPIを通じて、直接様々なプログラムやサービスから実行することができます。

例えば、Microsoft が提供しているiPaaS「Power Automate」から実行してみた例がこちら。

Tesla API のちょっと気をつけたいこと

そんなTesla APIですが、具体的な内容や使い方に入る前に大事なことを書いておきます。それはこのAPIがTesla 社にとって「非公式のAPIである、ということです。

あくまで1ユーザーがリバースエンジニアリングして、公開したAPI仕様であり、Tesla社 が公に「このAPIを自由に使っていいよ!」と言っているわけではありません。基本的にテスラ社の公式アプリから使うことを前提としています。

現在このTesla APIを利用した便利なサービス、例えばTeslaFiやTezLabといったものが公開されていますが、これらはこの非公式APIによって開発されています。

teslafi.com

f:id:sugimomoto:20201122221727p:plain

https://tezlabapp.com/app

f:id:sugimomoto:20201122221735p:plain

jp.techcrunch.com

何が言いたいかと言えば、「自己責任で試す必要があるAPIである」ということです。

後述するこのAPIの認証情報・AccessTokenは「Teslaのデータの収集、そして様々な操作を可能」にします。

もし上記のサービス郡が悪意を持ったり、もしくはその認証情報が流出すれば、誰かがあなたのテスラを自由に操作し、データを盗み見ることができるようになります。GPS座標情報もありますし、キーのロック・ロック解除、電源のON/OFFもできます。何ができるようになるかは想像に難くないでしょう。

また、Tesla社がいきなりサービスを遮断する、ということも考えられます。

これは、AWSGCPからのアクセスがシャットダウンされた事例があるようなので、そういった課題も考えられるということを認識しておきましょう。

teslamotorsclub.com

さらに、何かこのAPIについて困ったからといって、Tesla社に助けを求めることはできません。

とまあ、色々と脅し文句を最初に述べましたが、もしあなたがテスラの技術要素、エンジニアリングに興味があるなら、このAPIは様々な知見、知的好奇心を満たしてくれる素晴らしいものです。またAPIを通じた自動化により日々のテスラライフを便利にもしてくれます。

これらのことを理解した上で、Tesla API を試しましょう!

Tesla API はどんなことができるの?

まず Tesla API でどんなことをできるのか? 見ていきましょう。

機能郡は大きく3種類に分けられます。

カテゴリ 機能概要 主なエンドポイント
認証操作 ID・PWを用いてAPIアクセス用のトークンの取得および取り消し /oauth/token
データの取得 テスラの現在の状態、バッテリーや設定に関するデータの取得ができます。 /vehicles・/vehicles/:id/vehicle_data
テスラの操作 ドアのロック解除やエアコン、メディアの操作ができます。 /vehicles/:id/wake_up・/vehicles/:id/command/flash_lights

またここAPI Referenceを参考に、全体の機能郡をざっくりとGoogle Spreadsheetで以下のようにまとめてみました。

車両データの取得から、エアコンやメディアの操作まで、アプリで提供されていることがしっかり網羅されていることがわかります。

docs.google.com

f:id:sugimomoto:20201122222158p:plain

ちなみにこれらの機能や取得できるデータの項目・内容については日々アップデートが行われています。私が今年の1月時点に見た項目と実際に購入した9月時点の項目でもやはり違いがいくつかありました。(API Reference で漏れていた可能性もありますが)

Tesla API のここが面白い!

上のリストを見ていると、色んな操作ができるんだなーというのが目にとまると思うんですが、私が Tesla APIで最も注目しているポイントは操作よりもむしろ「データ」の方です。

Tesla はすべての機能がコンソールに集約されていることに伴い、それらのデータの多くをAPIを通じて取得できるようになっています。

例えば車両番号や車両の起動状況といった基本情報から、バッテリーの残容量・充電状況・充電リミッターの設定値、外気温・車内気温、エアコンの温度設定、GPSによる現在地の緯度経度、Software Updateの状態、さらにはアクセルパワー・スピード・シフトモードの情報まで取得することができます。

以下のAPI Referenceでその中身を垣間見ることができます。

www.teslaapi.io

{
  "response": {
    "id": :id,
    "user_id": :user_id,
    "vehicle_id": :vehicle_id,
    "vin": ":vin",
    "display_name": ":name",
    "option_codes": "AD15,AF02,AH00,APF0,APH2,APPA,AU00,BCMB,BP00,BR00,BS00,BTX4,CC02,CDM0,CH05,COUS,CW02,DRLH,DSH7,DV4W,FG02,FR01,GLFR,HC00,HP00,IDBO,INBPB,IX01,LP01,LT3B,MDLX,ME02,MI02,PF00,PI01,PK00,PMBL,QLPB,RCX0,RENA,RFPX,S02B,SP00,SR04,ST02,SU01,TIC4,TM00,TP03,TR01,TRA1,TW01,UM01,USSB,UTAB,WT20,X001,X003,X007,X011,X014,X021,X025,X026,X028,X031,X037,X040,X042,YFFC,SC05",
    "color": null,
    "tokens": [
      ":token1",
      ":token2"
    ],
    "state": "online",
    "in_service": null,
    "id_s": ":ids",
    "calendar_enabled": true,
    "backseat_token": null,
    "backseat_token_updated_at": null,
    "gui_settings": {
      "gui_distance_units": "mi/hr",
      "gui_temperature_units": "F",
      "gui_charge_rate_units": "mi/hr",
      "gui_24_hour_time": false,
      "gui_range_display": "Rated",
      "timestamp": 1532986218050
    },
    "vehicle_state": {
      "api_version": 3,
      "autopark_state": "unavailable",
      "autopark_state_v2": "standby",
      "autopark_style": "dead_man",
      "calendar_supported": true,
      "car_version": "2018.21.9 75bdbc11",
      "center_display_state": 0,
      "df": 0,
      "dr": 0,
      "ft": 0,
      "homelink_nearby": false,
      "last_autopark_error": "no_error",
      "locked": true,
      "notifications_supported": true,
      "odometer": 14522.605889,
      "parsed_calendar_supported": true,
      "pf": 0,
      "pr": 0,
      "remote_start": false,
      "remote_start_supported": true,
      "rt": 0,
      "sun_roof_percent_open": null,
      "sun_roof_state": "unknown",
      "timestamp": 1532986223303,
      "valet_mode": false,
      "vehicle_name": ":name"
    },
    "climate_state": {
      "inside_temp": 27.0,
      "outside_temp": 24.0,
      "driver_temp_setting": 18.3,
      "passenger_temp_setting": 18.3,
      "left_temp_direction": -280,
      "right_temp_direction": -280,
      "is_front_defroster_on": false,
      "is_rear_defroster_on": false,
      "fan_status": 0,
      "is_climate_on": false,
      "min_avail_temp": 15.0,
      "max_avail_temp": 28.0,
      "seat_heater_left": false,
      "seat_heater_right": false,
      "seat_heater_rear_left": false,
      "seat_heater_rear_right": false,
      "seat_heater_rear_center": false,
      "seat_heater_rear_right_back": 0,
      "seat_heater_rear_left_back": 0,
      "battery_heater": false,
      "battery_heater_no_power": false,
      "steering_wheel_heater": false,
      "wiper_blade_heater": false,
      "side_mirror_heaters": false,
      "is_preconditioning": false,
      "smart_preconditioning": false,
      "is_auto_conditioning_on": false,
      "timestamp": 1532986223306
    },
    "charge_state": {
      "charging_state": "Complete",
      "fast_charger_type": "\u003cinvalid\u003e",
      "fast_charger_brand": "\u003cinvalid\u003e",
      "charge_limit_soc": 86,
      "charge_limit_soc_std": 90,
      "charge_limit_soc_min": 50,
      "charge_limit_soc_max": 100,
      "charge_to_max_range": false,
      "max_range_charge_counter": 0,
      "fast_charger_present": false,
      "battery_range": 212.26,
      "est_battery_range": 152.09,
      "ideal_battery_range": 271.83,
      "battery_level": 83,
      "usable_battery_level": 83,
      "charge_energy_added": 16.57,
      "charge_miles_added_rated": 51.0,
      "charge_miles_added_ideal": 65.5,
      "charger_voltage": 0,
      "charger_pilot_current": 6,
      "charger_actual_current": 0,
      "charger_power": 0,
      "time_to_full_charge": 0.0,
      "trip_charging": false,
      "charge_rate": 0.0,
      "charge_port_door_open": true,
      "conn_charge_cable": "SAE",
      "scheduled_charging_start_time": null,
      "scheduled_charging_pending": false,
      "user_charge_enable_request": null,
      "charge_enable_request": true,
      "charger_phases": null,
      "charge_port_latch": "Engaged",
      "charge_current_request": 6,
      "charge_current_request_max": 6,
      "managed_charging_active": false,
      "managed_charging_user_canceled": false,
      "managed_charging_start_time": null,
      "battery_heater_on": false,
      "not_enough_power_to_heat": false,
      "timestamp": 1532986223305
    },
    "drive_state": {
      "shift_state": null,
      "speed": null,
      "power": 0,
      "latitude": 40.459728,
      "longitude": -79.923447,
      "heading": 340,
      "gps_as_of": 1532986222,
      "native_location_supported": 1,
      "native_latitude": 40.459729,
      "native_longitude": -79.923444,
      "native_type": "wgs",
      "timestamp": 1532986223305
    },
    "vehicle_config": {
      "can_actuate_trunks": true,
      "car_special_type": "base",
      "car_type": "modelx",
      "charge_port_type": "US",
      "eu_vehicle": false,
      "exterior_color": "MetallicBlack",
      "has_ludicrous_mode": false,
      "motorized_charge_port": true,
      "perf_config": "P1",
      "plg": true,
      "rear_seat_heaters": 3,
      "rear_seat_type": 3,
      "rhd": false,
      "roof_color": "None",
      "seat_type": 0,
      "spoiler_type": "Passive",
      "sun_roof_installed": 0,
      "third_row_seats": "FuturisFoldFlat",
      "timestamp": 1532986223303,
      "trim_badging": "90d",
      "wheel_type": "AeroTurbine20"
    }
  }
}

しかも、このデータは「リアルタイム」で参照できます。

以下は1秒間隔でデータを取得して、BIツールに流した時の状況ですが、しっかりとリアルタイムでデータを反映させることができました。

f:id:sugimomoto:20201122222348p:plain

Tesla 社はAPIを通じてこれだけのビックデータを扱える環境にある、ということが垣間見えますね。

Tesla API の使い方

それでは概要を押さえたところで Tesla API の基本的な使い方を見ていきましょう。

APIの検証には Postman を使用しました。

認証方法

認証は OAuth 2.0 の PasswordGrant を利用します。

※ちなみに、Password GrantによるTokenの取得は最新のOAuth 2.0では非推奨になっていますが、おそらく内部利用のためだけなので、気にしていないのかな? と思ったりです。

必要な情報はアプリに埋め込まれている「ClientId」・「ClientSecret」、そしてアプリにログインするために利用している「User」と「Password」の情報の4種類です。

「ClientId」・「ClientSecret」は以下のWebSiteで確認することができます。

www.teslaapi.io

以下のように「POST /oauth/token」でJSONのBodyを入力して、リクエストを実施します。

POST /oauth/token?grant_type=password HTTP/1.1
Host: owner-api.teslamotors.com
Content-Type: application/json

{
    "password": "YOUR_TESLA_ACCOUNT_PASSWORD",
    "email": "YOUR_TESLA_ACCOUNT",
    "client_secret": "XXXXXXXXXX",
    "client_id": "XXXXXXX",
    "grant_type": "password"
}

これにより以下のようなレスポンスを得られます。このaccess_tokenの値を使って、実際にテスラのデータを取得したり、操作したりすることができます。

{
    "access_token": "qts-XXXXXXXXXXXX",
    "token_type": "bearer",
    "expires_in": 3888000,
    "refresh_token": "XXXXXX",
    "created_at": 1606030650
}

Postmanでリクエストする場合は以下のような設定になります。(PasswordやUserの部分はCollectionの環境変数に設定しています)

f:id:sugimomoto:20201122222450p:plain

ちなみに AccessTokenの期限は「3888000秒」つまり45日間です。一回取得してしまえば、そのままある程度利用できますね。

このAccessTokenについては色々と注意するべき点がありますが、詳しい内容は別途記事で紹介したいと思います。

Product IDの特定

Tesla API では実際にデータの取得などを行う前に、操作対象の製品のID(API上では、Idとしか書かれませんが、いろんなIDがあるので、ここでは便宜上Product Idとします)を特定する必要があります。

Product IDはテスラアカウントに関連付けられているTesla Model 3、S、Xの他、蓄電池製品である PowerWallも含まれます。(ソーラールーフとかも今後加わるのかな?)

確認するためのAPIは2種類存在します。一つは「GET /products」、もう一つは「GET /vehicles」です。

「GET /products」では、車両およびPowerWallも含めた全製品が取得でき、「GET /vehicles」では車両のみ取得できるようです。

また、ここで先程取得したAccessTokenを使います。Authorization Headerで「Bearer 取得したAccessToken」を指定してください。

GET /api/1/vehicles HTTP/1.1
Host: owner-api.teslamotors.com
Authorization: Bearer qts-XXXXXXXXXXXXX

このリクエストで以下のようなJSONを取得できます。この時「id」のプロパティがProduct Idに当たる部分です。vehicle_idと混同しやすいので注意しましょう。

{
    "response": [
        {
            "id": XXXX,
            "vehicle_id": XXXXX,
            "vin": "XXXXX",
            "display_name": "てしゅら",
            "option_codes": "AD15,MDL3,PBSB,RENA,BT37,ID3W,RF3G,S3PB,DRLH,DV2W,W39B,APF0,COUS,BC3B,CH07,PC30,FC3P,FG31,GLFR,HL31,HM31,IL31,LTPB,MR31,FM3B,RS3H,SA3P,STCP,SC04,SU3C,T3CA,TW00,TM00,UT3P,WR00,AU3P,APH3,AF00,ZCST,MI00,CDM0",
            "color": null,
            "access_type": "OWNER",
            "tokens": [
                "XXXXX",
                "XXXXX"
            ],
            "state": "online",
            "in_service": false,
            "id_s": "XXXXX",
            "calendar_enabled": true,
            "api_version": 10,
            "backseat_token": null,
            "backseat_token_updated_at": null,
            "vehicle_config": null
        }
    ],
    "count": 1
}

Postmanでは以下のようにリクエストします。

f:id:sugimomoto:20201122222504p:plain

Tesla の起動

AccessTokenを取得し、Product Idを特定したので、早速Teslaの操作を実施してみましょう。

とはいっても、さらにその前にやることがあります。Tesla の操作をしたり、Teslaのデータを取得するにはまず Tesla 本体そのものを起動させる必要があります。

テスラアプリでアクセスした時に表示される、この「起動中」のやつですね。

f:id:sugimomoto:20201122222600p:plain

この起動は「POST /vehicles/:id/wake_up」というコマンドをAPIでリクエストすることで実施できます。

その際に、事前に確認しておいたProduct Idも一緒に利用します。以下のようなリクエストで「:id」の部分を取得しておいたProduct Idに置き換えます。なお、コマンド系のAPIはすべてPOSTリクエストになります。

POST /api/1/vehicles/:id/wake_up HTTP/1.1
Host: owner-api.teslamotors.com
Authorization: Bearer XXXXXXXX

f:id:sugimomoto:20201122222610p:plain

これでリクエストが通ると、車両が起動状態になります。

レスポンスはすぐに返ってくるんですが、実際には起動中だったりするので注意しましょう。

起動状況は「GET /vehicles」で取得できるプロパティの「state」で確認できます。ここがOnlineになっていればOKです。

{
    "response": [
        {
            "id": XXXX,
            "vehicle_id": XXXXX,
            "vin": "XXXXX",
            "display_name": "てしゅら",
            "option_codes": "AD15,MDL3,PBSB,RENA,BT37,ID3W,RF3G,S3PB,DRLH,DV2W,W39B,APF0,COUS,BC3B,CH07,PC30,FC3P,FG31,GLFR,HL31,HM31,IL31,LTPB,MR31,FM3B,RS3H,SA3P,STCP,SC04,SU3C,T3CA,TW00,TM00,UT3P,WR00,AU3P,APH3,AF00,ZCST,MI00,CDM0",
            "color": null,
            "access_type": "OWNER",
            "tokens": [
                "XXXXX",
                "XXXXX"
            ],
            "state": "online",
            "in_service": false,
            "id_s": "XXXXX",
            "calendar_enabled": true,
            "api_version": 10,
            "backseat_token": null,
            "backseat_token_updated_at": null,
            "vehicle_config": null
        }
    ],
    "count": 1
}

車両のデータを取得してみる

次に車両データを取得してみましょう。

車両データの取得は「GET /vehicles/:id/vehicle_data」で行います。

GET /api/1/vehicles/:id/vehicle_data HTTP/1.1
Host: owner-api.teslamotors.com
Authorization: Bearer XXXXXX

これで先に紹介したあの大量のプロパティを持つJSONが取得できます。

f:id:sugimomoto:20201122222636p:plain

ちなみに車両が起動していない状況でこれらのリクエストを行うと、408 Request Timeout が返ってきますので注意しましょう。

{
    "response": null,
    "error": "vehicle unavailable: {:error=>\"vehicle unavailable:\"}",
    "error_description": ""
}

f:id:sugimomoto:20201122222643p:plain

データの取得にはこの他、「GET /vehicles/:id/data_request/climate_state」「GET /vehicles/:id/data_request/drive_state」などがありますが、「GET /vehicles/:id/vehicle_data」だけで網羅して取得できます。

ライトの点灯

最後にせっかくなので別なコマンドも試してみましょう。ちょうど先に紹介していた、以下の動画のAPIです。

https://twitter.com/sugimomoto/status/1320686548619198464?s=20

ここまでやっていればそんなに難しいことはないですね。WakeUpのコマンドを変えるだけなので「POST /api/1/vehicles/:id/command/flash_lights」で実行できます。

POST /api/1/vehicles/:id/command/flash_lights HTTP/1.1
Host: www.teslaapi.io
Authorization: Bearer XXXXXXX

f:id:sugimomoto:20201122222654p:plain

コマンドも種類によっては、Bodyで色んな指定をする場合があるので注意しましょう。

おわりに

こういった形でAPIを組み合わせていくことで、テスラ純正アプリでは提供されていない、様々なことが実現できるようになります。

例えば、最近私が行っているのが、バッテリーの使用状況の可視化です。毎日8時にAPIを実行し、データをDBにためて、バッテリーをどのくらい使っていっているのか可視化しています。

ちなみになんでこんなことをしたかと言えば、私がマンション暮らしのためどの程度の日々電池を消耗し、どのくらいの頻度で充電を行っているのかを可視化したかったからです。

テスラアプリは基本的に「今」の状況しか見れないのですが、API経由でデータを蓄積することでこのような可視化も可能になります。

この辺のアプローチはまた別な記事で紹介していく予定です。