はじめに
おうちハック Advent Calendar 2015の5日目の記事です。
こんにちは。CTOのkamocです。 前回の記事(ルンバがうちに、やってきた。)では最新モデルのルンバ980をハックしていく方向性について書きました。 今回はアプリとiRobot API間の通信をハックして行きます。
最初に
今回は図の★の部分。アプリとiRobot API間の通信をハックして行きます。
今回のゴール
今回の記事では、ルンバに「掃除しろ」と命令したらルンバが掃除を開始する Slack BOT を開発します。
iRobot アプリの掃除開始命令を解析し、Slack BOT から同命令を叩いてルンバに掃除させます。
通信内容を観察してみる
それでは早速 iRobot アプリと iRobot API の通信を解析してみましょう。 通信の解析には Charles というツールを使用しました。
iPhone のパケットキャプチャのやり方はこちらを参考にしました。
Charles を起動して iPhone のプロキシ設定を済ませた状態で、iRobotアプリを立ち上げるとこんな感じになります。
いろいろ通信してますね〜。 http://www.irobot.com と https://irobot.axeda.com のどちらかが iRobot API でしょう。この後詳しく見ていきます。 https://e.crashlytics.com はアプリのクラッシュレポートや利用状況を記録しているやつですね。いつもお世話になっています。 http://192.168.10.4 はルンバとの直接通信ですね。ルンバのIPアドレスは前回の記事で書いたとおり、iRobotアプリから確認することができます。
http://www.irobot.com との通信内容
Charles で通信内容覗いてみました。レスポンスのJSONの一例はこちら。
{
"termsUrl": "http:\/\/www.irobot.com\/roombaterms",
"privacyUrl": "",
"tosUrl": "",
"eulaUrl": "",
"version": "1.0"
}
termsUrlの値の URL をブラウザで開いてみると、Legal Informationが表示されますね。 これは iRobot アプリの このアプリについて > 利用規約および個人情報保護方針 を選択した時に表示されるページです。
どうやらこのドメインとの通信では、アプリ内のWebView等で表示するコンテンツの情報を取得しているようですね。
https://irobot.axeda.com との通信内容
というわけで、こちらのドメインがルンバのAPIである可能性が濃厚になってきました。 通信を覗いてみましょう。
同ドメインについては以下のURLに対してのみ通信を行っています。 https://irobot.axeda.com/services/v1/rest/Scripto/execute/AspenApiRequest
一番上の通信のリクエストパラメータを見てみます。
blid=xxxxxxxxxx&robotpwd=xxxxxxxxxxx&method=getStatus
※ blid と robotpwd のパスワードは値をマスクしています。
本記事では以降 blid を {{ ROOMBA_ID }}, robotpwd を {{ ROOMBA_PW }} と表記します。
どうやら、APIのURLは共通で、“method”パラメータの値で挙動を定義しているようですね。 レスポンスはこちらです。
{
"status": "OK",
"method": "getStatus",
"robotName": "kamoc",
"sku": "R980060",
"country": "JP",
"postalCode": "********(我が家の郵便番号)",
"regDate": "2015-11-05",
"manualUpdate": "",
"swUpdateAvailable": "",
"milestones": "",
"robotLanguage": "",
"tzName": "Asia/Tokyo",
"twoPass": "",
"openOnly": "",
"schedHold": "",
"carpetBoost": "",
"binPause": "",
"vacHigh": "",
"noPP": "",
"ecoCharge": "",
"mission": "{\"nMssn\":25,\"done\":\"ok\",\"flags\":0,\"sqft\":168,\"runM\":23,\"chrgM\":0,\"pauseM\":0,\"doneM\":0,\"dirt\":0,\"chrgs\":0,\"saves\":0,\"evacs\":0,\"pauseId\":0,\"wlBars\":[100,0,0,0,0]}",
"preventativeMaintenance": "[{\"partId\":\"bin\",\"date\":\"2015-11-05\",\"distance\":0,\"runtime\":0,\"months\":0,\"notified\":false},{\"partId\":\"core\",\"date\":\"2015-11-05\",\"distance\":0,\"runtime\":0,\"months\":0,\"notified\":false},{\"partId\":\"extractor\",\"date\":\"2015-11-05\",\"distance\":0,\"runtime\":0,\"months\":0,\"notified\":false}]",
"cleanSchedule": "{\"cycle\":[\"none\",\"none\",\"none\",\"none\",\"none\",\"none\",\"none\"],\"h\":[0,0,0,0,0,0,0],\"m\":[0,0,0,0,0,0,0]}",
"autoEvacCount": "",
"autoEvacFlags": "",
"wifiDiagnostics": "",
"autoEvacModel": "",
"robot_status": "{\"flags\":0,\"cycle\":\"none\",\"phase\":\"charge\",\"pos\":{\"theta\":174,\"point\":{\"x\":456,\"y\":9}},\"batPct\":100,\"expireM\":0,\"rechrgM\":0,\"error\":0,\"notReady\":0,\"mssnM\":23,\"sqft\":168}",
"softwareVersion": "v1.2.9",
"lastSwUpdate": "2015-11-09 18:32:02+0000",
"bbrun": "{\"hr\":7,\"min\":25,\"sqft\":24,\"nStuck\":2,\"nScrubs\":23,\"nPicks\":105,\"nPanics\":8,\"nCliffsF\":80,\"nCliffsR\":96,\"nMBStll\":0,\"nWStll\":1,\"nCBump\":0}",
"missing": false,
"ota": "{\"st\":0,\"err\":0,\"lbl\":\"\"}"
}
※ postalCode だけ値をマスクしています。
おぉ!いろいろと取れましたねぇ。 このAPIを定期的に呼び出せば、ルンバの現在の状態を知ることができそうですね。
次に、掃除開始命令を探してみましょう。 Charles さんに接続した状態で、iRobot アプリから掃除開始命令を出します。
はい。いましたねー。
iRobot APIの紹介
解析して明らかになった iRobot API をいくつか紹介します。
API の URL はすべて以下のURL。
https://irobot.axeda.com/services/v1/rest/Scripto/execute/AspenApiRequest
掃除開始
リクエストパラメータを以下のように設定
blid: "{{ ROOMBA_ID }}"
robotpwd: "{{ ROOOMBA_PW }}"
method: "multipleFieldSet"
value: { "remoteCommand" : "start" }
ステータス確認
リクエストパラメータを以下のように設定
blid: "{{ ROOMBA_ID }}"
robotpwd: "{{ ROOOMBA_PW }}"
method: "getStatus"
行動履歴確認
リクエストパラメータを以下のように設定
blid: "{{ ROOMBA_ID }}"
robotpwd: "{{ ROOOMBA_PW }}"
method: "missionHistory"
APIの調査が終わったところで、curlコマンドを使ってAPIを叩き、実際にルンバが動くかどうか試してみましょう。
curl コマンドを使った検証
試しに掃除開始APIを curl コマンドで叩いてみましょう。
curl https://irobot.axeda.com/services/v1/rest/Scripto/execute/AspenApiRequest -X POST -d "blid={{ROOMBA_ID}}&robotpwd={{ROOMBA_PW}}&method=multipleFieldSet&value=%7b%22remoteCommand%22%20%3a%20%22start%22%7d"
{"status":"OK","method":"multipleFieldSet"}%
valueのパラメータは JSON 文字列をURLエンコードしています。
コンソールのEnterキーをターン!と叩きます。
・・・(2,3秒間があく)
ルンバ「ててーててー♪ ぴー ぴー ぴー ぴー」==◎
ルンバが走り出しました!!
ここまできたら後は Slack BOT とつなぐだけですね。
Slack bot の作成
Slack bot の作成は以下のサイトを参考にしました。
Coffeeスクリプトはこんな感じです。
module.exports = (robot) ->
robot.respond /((掃除))/i, (msg) ->
data = 'blid={{ROOMBA_ID}}&robotpwd={{ROOMBA_PW}}&method=multipleFieldSet&value=%7b%22remoteCommand%22%20%3a%20%22start%22%7d'
msg.http('https://irobot.axeda.com/services/v1/rest/Scripto/execute/AspenApiRequest')
.header('Content-Type', 'application/x-www-form-urlencoded')
.post(data) (err, res, body) ->
json = JSON.parse body
if json.status == "OK"
msg.send "仕方ないね"
else
msg.send "ルンバに問題が発生しているよ"
Slack bot のソースコード一式はGitHubにあげてあります。
{{ROOMBA_ID}} と {{ROOMBA_PW}} だけご自身のルンバの値に書き換えてください。
社内 Slack で運用してみた
弊社 Slack にて世界中どこからでも、何時でも、kamoc宅のルンバを起動できるという状態にしてみました。
弊社社員に可愛がってもらっている様子がこちらです。
夜11:39に福島県からのリクエストで静岡県のルンバが掃除しております。
これはもう事件ですね。
セキュリティーの重要性について改めて考えさせられました。
現在1週間程運用しておりますが、深夜の掃除で目が覚めるといった自体は起きておりません。
SlackBOTをホストしているHerokuさんがスリープ状態になってしまい、呼びかけに反応しないという問題が発生するため、設定の調整が必要です。
それでは、今日はこの辺で。
ルンバかわいいよ、ルンバ。