IoT家電、ということで買ったシャープの加湿空気清浄機KI-JS50ですが、 アプリを使えばスマホから操作できるものの、 Google Homeとの連携などはそのままでは出来ません。
そこでAPIを解析してアプリ以外から操作できるようにして、 最終的にRaspberry PiとかGoogle Homeから操作できるようにしたいと思います。
シャープ加湿空気清浄機KI-JS50
シャープの最近の空気清浄機系の家電は COCORO AIR というクラウドサービスを通じて家電の操作をスマホから行ったり、 スマホで家電の状況を調べたりすることが出来るようになっています。
家電によっては、所持者が家にいるときと外出時をスマホの位置情報で把握して それによって運転状況をAIによる判断で変えてくれたりします。
まあ、AIによる判断で外出中はうるさくしても良いから激しく清浄したりするんですけど、 そのあたりを設定することは出来ず、思い通りにどうこうすることは出来ません。
一方で操作するためのAPIなんかを公開してくれてると良いのですが、 今の所COOCRO AIRに関するAPIは公開されていません。
せっかくのIoT家電なのに自由度が低いと残念な気持ちになってきます。
mitmproxyで通信を見てみる
前回やったmitmproxyを使ってCOCORO AIRへの通信を観測して 使えないか見てみます。
設定してCOCORO AIRのアプリを立ち上げて色々やっていると
https://hms.cloudlabs.sharp.co.jp
といったURLに対して色々通信していることが分かります。
なので、とりあえずf
でfilterを起動してsharp
で絞って見て見るようにします。
試しにスイッチをON/OFFしてみると、それぞれPOSTが2回投げられ、 どうやらそれぞれの1回目がコマンドで2回目はうまく行ったかどうかの確認の感じ。
成功している場合、このResponseは
の様なerrorCode: null
が返ってきているはずです。
この画面で: export.clip curl @focus
として
スイッチを入れるcURLコマンドをとってみるとこんな感じのPOST。
$ curl -H 'Accept: application/json' \
-H 'Content-Type: application/json; charset=utf-8' \
-H 'Proxy-Connection: close' \
-H 'Cookie: JSESSIONID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
-H 'User-Agent: smartlink_v200i Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148' \
--compressed \
-H 'Connection: close' \
-X POST \
'https://hms.cloudlabs.sharp.co.jp/hems/pfApi/ta/control/deviceControl?boxId=https://db.cloudlabs.sharp.co.jp/clpf/key/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&appSecret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
-d '{"controlList":[{"status":[{"valueSingle":{"code":"30"},"statusCode":"80","valueType":"valueSingle"},{"valueBinary":{"code":"00030000000000000000000000FF00000000000000000000000000"},"statusCode":"F3","valueType":"valueBinary"}],"deviceId":"XXXXXX","echonetNode":"XX-XX-XX-XX-XX-XX","echonetObject":"XXXXXX"}]}'
これを取り出してすぐにKI-JS50のスイッチを一旦消して、 これをすぐにコマンドラインから打ってみるとたしかにスイッチON出来ました。
ここで、スイッチを入れる信号はPOSTしているデータ部分の
"status":[{
"valueSingle": {"code":"30"},
"statusCode": "80",
"valueType": "valueSingle"
},{
"valueBinary":{
"code": "00030000000000000000000000FF00000000000000000000000000"
},
"statusCode": "F3",
"valueType": "valueBinary"
}]
の部分。
特にcode
で表されている27バイト分のコードが命令信号になっている模様。
実際、スイッチを切ったりするのもこのコードが変わった形になっています。
データとして送られているdeviceId
、echonetNode
、echonetObject
は使っている家電に関するもの。
あとはURLの中にある文字列とCOOKIEでセットされているJSESSIONID
というのがわかればなんとかなりそう。
ということでなんどか試していると、このcURLコマンドは効かなくなりました。
もう一度スマホの方から信号を送ってみて改めて正しいコマンドをとってみると、わずかに違うものに。
違うのは
-H 'Cookie: JSESSIONID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
の部分。
他は全く一緒。
このCookieの値は必要に応じて取得して通信を有効化しているようです。
この値をとってきている部分を探してみると
の部分。
https://hms.cloudlabs.sharp.co.jp/hems/pfApi/ta/setting/login/?appSecret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&serviceName=iClub
といったURLに
{"terminalAppId":"https:\/\/db.cloudlabs.sharp.co.jp\/clpf\/key\/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}'
といったデータを送ってPOSTをしています。
そのResponseとしてSet-Cookie
でCookieの値を取得している様です。
ここで、appSecret
とterminalAppId
の値がおそらく
個人IDや登録したスマホの特定に使われて通信の許可を得るためのキーになっている模様。
したがってこれらの値は自分で通信を見て取得するしかありません。
一方で、deviceId
などはやり取りで取得することが出来ます。
複数の家電を繋げている場合には一覧を取得して選ぶ必要があると思いますが、 今はひとつなのでそれをとって使うだけ。
このコマンドのところ。
https://hms.cloudlabs.sharp.co.jp/hems/pfApi/ta/setting/boxInfo/?appSecret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&mode=other'
といった感じのURLにGETする通信で取得していることが分かります。
cocoro: Python製COCORO AIR API
というわけで、上記を参考に簡単にコマンドラインからシャープ製加湿空気清浄機を操作する ツールを作ってみました。
インストールは
$ pip install cocoro
で。
上にも書いたように、appSecret
とterminalAppId
のキー部分(最後の/
後の文字列)は
mitmproxyなどを使って
自分で取得する必要があるので頑張って取得してください。
これらを
1 2 3 |
|
といったファイルに書いて設置します。
これで、cocoro
コマンドを使っていろいろ出来ます。
使っている家電の情報の取得:
$ cocoro info
[INFO][Cocoro] Device information
{'id': 123456, 'place': 'リビング', 'name': 'my js50', 'deviceType': 'AIR_CLEANER', 'zipCd': '7890123', 'yomi': '', 'lSubInfo': '{"room_data":{"size": 10.0, "struct": "prefab", "unit": "tatami"}}'}
$ cocoro info model --log_level=warning
KIJS50
$ cocoro info maker --log_level=warning
SHARP
こんな感じで取得。
操作はスイッチのON/OFF:
$ cocoro switch on
[INFO][Cocoro] Succeeded to control js50: switch on
$ cocoro switch off
[INFO][Cocoro] Succeeded to control js50: switch off
加湿のON/OFF:
$ cocoro humi on
[INFO][Cocoro] Succeeded to control js50: humidification on
$ cocoro humi off
[INFO][Cocoro] Succeeded to control js50: humidification off
モード切替:
$ cocoro mode auto
[INFO][Cocoro] Succeeded to control js50: mode auto
モード切替(mode
)ではいかのモードが使えます:
auto
: 自動sleep
: おやすみpollen
: 花粉quiet
: 静音medium
: 中recommendation
: おまかせeffective
: 効果実感
あとはこれを自由に使って適当なところから司令を出せるようにすれば自由な操作が手に入ります。
Google Apps Script (GAS)では出来ない?
上の方法で適当なサーバーを立てておけば色々できるんですが、 その代わりにGASでなんとか出来ないかな、と思って GASからPOSTしてみようといろいろ頑張ったのですがどうしても出来ず。
何がいけないんだろうと思って探ってみると、 GASでHTTPリクエストをするUrlFetchAppというツールが ユーザーエージェントを指定しても別途GASのユーザーエージェントを追加してしまう、 というのが問題でした。
google apps script - UrlFetch with custom user-agent string? - Stack Overflow
google apps script - UrlFetchApp.fetch() error, doesn’t seem to be using headers - Stack Overflow
これはGASで自由なユーザーエージェントを指定できてしまうと、 スパム的な使い方をされるのが困る、ということでそれを避けてるとか避けてないとか。
少なくとも今のところはユーザーエージェントを指定しないといけない様な通信にはUrlFetchAppは使えないようです。
上のCOCORO Airの通信もユーザーエージェントがきちんと特定のものでないと appSecretやCookieの設定が正しくても403 Forbiddenが返ってきてしまいます。
ので、現状では上のPythonのライブラリを適当なサーバーで動かしてどうにかするしかないかな、といった感じです。