rcmdnk's blog

20210303_switchon_200_200

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で絞って見て見るようにします。

20210303_mitmproxy.jpg

試しにスイッチをON/OFFしてみると、それぞれPOSTが2回投げられ、 どうやらそれぞれの1回目がコマンドで2回目はうまく行ったかどうかの確認の感じ。

20210303_switchon.jpg

成功している場合、このResponseは

20210303_switchonok.jpg

の様な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バイト分のコードが命令信号になっている模様。

実際、スイッチを切ったりするのもこのコードが変わった形になっています。

データとして送られているdeviceIdechonetNodeechonetObjectは使っている家電に関するもの。

あとはURLの中にある文字列とCOOKIEでセットされているJSESSIONIDというのがわかればなんとかなりそう。

ということでなんどか試していると、このcURLコマンドは効かなくなりました。

もう一度スマホの方から信号を送ってみて改めて正しいコマンドをとってみると、わずかに違うものに。

違うのは

-H 'Cookie: JSESSIONID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

の部分。

他は全く一緒。

このCookieの値は必要に応じて取得して通信を有効化しているようです。

この値をとってきている部分を探してみると

20210303_getcookie.jpg

の部分。

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の値を取得している様です。

20210303_gotcookie.jpg

ここで、appSecretterminalAppIdの値がおそらく 個人IDや登録したスマホの特定に使われて通信の許可を得るためのキーになっている模様。

したがってこれらの値は自分で通信を見て取得するしかありません。

一方で、deviceIdなどはやり取りで取得することが出来ます。

複数の家電を繋げている場合には一覧を取得して選ぶ必要があると思いますが、 今はひとつなのでそれをとって使うだけ。

20210303_getbox.jpg

このコマンドのところ。

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

で。

上にも書いたように、appSecretterminalAppIdのキー部分(最後の/後の文字列)は mitmproxyなどを使って 自分で取得する必要があるので頑張って取得してください。

これらを

~/.config/cocoro/config.yml
1
2
3
---
appSecret: <*************appSecret**********************>
terminalAppIdKey: <************terminalAppIdKey*************>

といったファイルに書いて設置します。

これで、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のライブラリを適当なサーバーで動かしてどうにかするしかないかな、といった感じです。

Sponsored Links
Sponsored Links

« mitmproxyを使ってアプリの通信内容を確認する Raspberry Piからシャープの加湿空気清浄機を操作する »

}