Tesla API の検証環境(物理)が手に入ったので、改めて Tesla API の使い方を解説してみる
今年の1月にこんなエントリーを書いたわたくしですが、
5月に少し気を失っていたら、以下のような画面が iPad に表示され
少し気を失っていたら、注文が完了していた。
— Kazuya Sugimoto @CData Software Japan (@sugimomoto) 2020年5月12日
どうやら検証リストに新しいAPIが追加されてしまったらしい。#Tesla pic.twitter.com/rBWIg841db
9月末にとうとう Tesla API 検証環境(物理)が届いてしまいました。
きーたー! pic.twitter.com/nykb9HPEqg
— Kazuya Sugimoto @CData Software Japan (@sugimomoto) 2020年9月29日
上記エントリーでは、ダミーデータを返すモック環境のAPIだったので、常にバッテリーが減らない、いくら操作をしても壊れない夢のような(そしてちょっと寂しい)テスラだったのですが、ちゃんとバッテリーが減るAPIとして試すことができるようになりました!
やったね!!!
— Kazuya Sugimoto @CData Software Japan (@sugimomoto) 2020年9月29日
2月にチマチマ書き起こしていた テスラ Mock API 用のOpen API Specを使って、テスラ実機のデータが取得できたー!
ちゃんと今のバッテリーレベルとかが見れるぞ!
私ちゃんと作ってた! 偉い!https://t.co/qmnVR6b0rn pic.twitter.com/8YkUFXzZMy
というわけで改めてこのTesla APIの使い方を解説していきたいと思います!
- そもそも Tesla API とはなんなのか?
- Tesla API のちょっと気をつけたいこと
- Tesla API はどんなことができるの?
- Tesla API のここが面白い!
- Tesla API の使い方
- おわりに
そもそも Tesla API とはなんなのか?
前の記事でも書いていますが改めて。
Tesla API は Tesla のモバイルアプリで使用されているバックエンド用のインターフェースです。
apps.apple.com
このアプリからいろんなことができるようになっていて、今のバッテリーや室温・外気温状態から、メディアの操作、ロックの解除、テスラの位置情報まで、車両情報の取得から操作まで幅広いことを可能にしています。
【機能一覧】
- 充電状況をリアルタイムで確認し、充電を開始または停止する
- 運転前に車両の暖房/冷房を入れる (ガレージ内でも可能)
- 遠隔からロックまたはロック解除する
- 車の現在地を確認したり、動きを追跡する
- お気に入りのアプリから目的地を送信し、ナビを開始します
- 同乗者はすばやくメディアをコントロールすることができます
- 駐車場で車のライトを点滅したりホーンを鳴らしたりして車両を見つける
- パノラマ ルーフを開閉する
- 車両をガレージまたは狭い駐車スペースから呼び出す(オートパイロット搭載車用
- どこにいても車両のソフトウェアをアップデートできます
- パワーウォールへのソーラーからの充電量、家庭でのエネルギー使用量、系統への売電量をモニターする
- ソーラー発電量とバッテリー消費データをダウンロード
以下は実際の私のテスラのアプリ画面。
このそれぞれの機能がバックエンドAPIによって提供されているのですが、そのAPIリクエストをキャプチャして、リバースエンジニアリングし、非公式の API 仕様書として書き起こしているサイトがいくつかあります!
これがいわゆる今回取り上げる「Tesla API」と呼ばれるものです。いくつかサイトがありますが、以下のサイトが結構充実している感です。
ですので、実質的にモバイルアプリで提供されている機能はほぼこのAPIを通じて、直接様々なプログラムやサービスから実行することができます。
例えば、Microsoft が提供しているiPaaS「Power Automate」から実行してみた例がこちら。
おおー
— Kazuya Sugimoto @CData Software Japan (@sugimomoto) 2020年10月26日
PowerAutomate でTesla API のカスタムコネクタできたー
とりあえずライトを付けるだけだけどw
実際に動くとちょっと感動w pic.twitter.com/2ygmLfmKdG
Tesla API のちょっと気をつけたいこと
そんなTesla APIですが、具体的な内容や使い方に入る前に大事なことを書いておきます。それはこのAPIがTesla 社にとって「非公式のAPI」である、ということです。
あくまで1ユーザーがリバースエンジニアリングして、公開したAPI仕様であり、Tesla社 が公に「このAPIを自由に使っていいよ!」と言っているわけではありません。基本的にテスラ社の公式アプリから使うことを前提としています。
現在このTesla APIを利用した便利なサービス、例えばTeslaFiやTezLabといったものが公開されていますが、これらはこの非公式APIによって開発されています。
何が言いたいかと言えば、「自己責任で試す必要があるAPIである」ということです。
後述するこのAPIの認証情報・AccessTokenは「Teslaのデータの収集、そして様々な操作を可能」にします。
もし上記のサービス郡が悪意を持ったり、もしくはその認証情報が流出すれば、誰かがあなたのテスラを自由に操作し、データを盗み見ることができるようになります。GPS座標情報もありますし、キーのロック・ロック解除、電源のON/OFFもできます。何ができるようになるかは想像に難くないでしょう。
また、Tesla社がいきなりサービスを遮断する、ということも考えられます。
これは、AWSやGCPからのアクセスがシャットダウンされた事例があるようなので、そういった課題も考えられるということを認識しておきましょう。
さらに、何かこの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で以下のようにまとめてみました。
車両データの取得から、エアコンやメディアの操作まで、アプリで提供されていることがしっかり網羅されていることがわかります。
ちなみにこれらの機能や取得できるデータの項目・内容については日々アップデートが行われています。私が今年の1月時点に見た項目と実際に購入した9月時点の項目でもやはり違いがいくつかありました。(API Reference で漏れていた可能性もありますが)
Tesla API のここが面白い!
上のリストを見ていると、色んな操作ができるんだなーというのが目にとまると思うんですが、私が Tesla APIで最も注目しているポイントは操作よりもむしろ「データ」の方です。
Tesla はすべての機能がコンソールに集約されていることに伴い、それらのデータの多くをAPIを通じて取得できるようになっています。
例えば車両番号や車両の起動状況といった基本情報から、バッテリーの残容量・充電状況・充電リミッターの設定値、外気温・車内気温、エアコンの温度設定、GPSによる現在地の緯度経度、Software Updateの状態、さらにはアクセルパワー・スピード・シフトモードの情報まで取得することができます。
以下のAPI Referenceでその中身を垣間見ることができます。
{ "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ツールに流した時の状況ですが、しっかりとリアルタイムでデータを反映させることができました。
Tesla 社はAPIを通じてこれだけのビックデータを扱える環境にある、ということが垣間見えますね。
Tesla API の使い方
それでは概要を押さえたところで Tesla API の基本的な使い方を見ていきましょう。
認証方法
認証は OAuth 2.0 の PasswordGrant を利用します。
※ちなみに、Password GrantによるTokenの取得は最新のOAuth 2.0では非推奨になっていますが、おそらく内部利用のためだけなので、気にしていないのかな? と思ったりです。
必要な情報はアプリに埋め込まれている「ClientId」・「ClientSecret」、そしてアプリにログインするために利用している「User」と「Password」の情報の4種類です。
「ClientId」・「ClientSecret」は以下のWebSiteで確認することができます。
以下のように「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の環境変数に設定しています)
ちなみに 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では以下のようにリクエストします。
Tesla の起動
AccessTokenを取得し、Product Idを特定したので、早速Teslaの操作を実施してみましょう。
とはいっても、さらにその前にやることがあります。Tesla の操作をしたり、Teslaのデータを取得するにはまず Tesla 本体そのものを起動させる必要があります。
テスラアプリでアクセスした時に表示される、この「起動中」のやつですね。
この起動は「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
これでリクエストが通ると、車両が起動状態になります。
レスポンスはすぐに返ってくるんですが、実際には起動中だったりするので注意しましょう。
起動状況は「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が取得できます。
ちなみに車両が起動していない状況でこれらのリクエストを行うと、408 Request Timeout が返ってきますので注意しましょう。
{ "response": null, "error": "vehicle unavailable: {:error=>\"vehicle unavailable:\"}", "error_description": "" }
データの取得にはこの他、「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
コマンドも種類によっては、Bodyで色んな指定をする場合があるので注意しましょう。
おわりに
こういった形でAPIを組み合わせていくことで、テスラ純正アプリでは提供されていない、様々なことが実現できるようになります。
例えば、最近私が行っているのが、バッテリーの使用状況の可視化です。毎日8時にAPIを実行し、データをDBにためて、バッテリーをどのくらい使っていっているのか可視化しています。
2020/11/21 My Tesla Status :)
— Kazuya Sugimoto @CData Software Japan (@sugimomoto) 2020年11月21日
バッテリーレベル:79% (前日:89% / 前日比:-10%)
残航続距離:296.25km(前日比:-37.5km)
外気温:5.5
内気温:5.1 pic.twitter.com/FfAQI109KD
ちなみになんでこんなことをしたかと言えば、私がマンション暮らしのためどの程度の日々電池を消耗し、どのくらいの頻度で充電を行っているのかを可視化したかったからです。
テスラアプリは基本的に「今」の状況しか見れないのですが、API経由でデータを蓄積することでこのような可視化も可能になります。
この辺のアプローチはまた別な記事で紹介していく予定です。