編集履歴一覧に戻る
eucalyのアイコン画像

eucaly が 2024年08月24日20時07分54秒 に編集

コメント無し

本文の変更

こんにちは、ゆうかりです。 今回は、パナソニックのHEMSシステム、「aiseg2」のWebサーバを。 色々弄ってみた的なお話です。 *前回の記事と似てるけど、違います!。 **留意事項** **・今回のお話は、HEMSのセキュリティレベルを著しく下げます!** **・「何がキケンか分からない」ヒトは、マネしないで下さい!** **・くれぐれも、適切な運用を!** # HEMSの基本的な仕組み HEMSは、「Echonet Lite」というプロトコルでおしゃべりします。 機器構成は、こんな感じ。 ![キャプションを入力できます](https://camo.elchika.com/6dd54ab5705d02e99b3e1f0ece7e4ae92294a6a8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f30343238623737612d636332352d343732622d613932372d636130613038306363663265/) ここでのポイントがいくつか。 まず、各機材にくっついている、「EOJ」。 これは、「Echonet ObJect」の略で、「その機材が何か」を表してたりします。 例えばエアコンなら「0130」、照明器具なら「0290」て感じ。 そしてEchonet Lite自体には、「それぞれの個別機材の識別」はありません。 それぞれの機材識別は、OSI参照モデルで言うところのネットワーク層以下に任されてる感じ、つまりIPだのMACだのね。 ![キャプションを入力できます](https://camo.elchika.com/232f8e26742a3a0e913e093b8acabe3188671c32/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f64636235623933642d353030362d343532652d613166352d396430356261303030346563/) んで、多少実装が特殊なのが、上の図で言うところの「真ん中」と「右」の機材たち。 通信ノードと機材が1:1で繋がってるなら、単純に通信ノードの応答がそのまま機材応答になるのですが、通信ノードと機材が1:多の場合は、通信ノードの応答は、入れ子構造になるっぽいです。 # aiseg2でのajax実装 まず、Echonet Lite回りの実装から。 aiseg2の場合、通信ノードの識別に「nodeId」を、EOJはそのまま「eoj」を使っています。 nodeIdは、10桁の数字、EOJは後側に「01」が追加されている感じです、例えばエアコンは「0x013001」として処理されます。 次にトークン。 aiseg2は排他制御やアクセス元ブラウザの個体識別の為かな?に、トークンを使っています。 トークン自体はAjaxではなく、HTMLに埋め込まれています。 最後に、リクエストと応答。 リクエストはHTML取得時を除き、POSTしか受け付けません。 また、bodyにフォーム名「data」の中に、jsonを埋め込む必要があります。 jsonを直接送っているわけではないため、Content-typeは「application/x-www-form-urlencoded」にしないとダメです。 応答はjsonで返ってきます、が、例えばアドバンススイッチリンクモデルの無線アダプタなどは、「入れ子構造」になるため、jsonの中にjsonが埋め込まれて帰ってきます。 # aiseg2のハック さて、では早速、aiseg2をハックしていきましょう。 なお、対象機材は、「アドバンススイッチリンクモデルの無線アダプタ」と、「エアコン」になります。 ウチで使ってるやーつ、ね!。 言語はPythonと、時々Javascriptで。 ## トークンの取得 トークンを取得するには、HTMLをゲットする必要があります。 画面的にはココ。 ![キャプションを入力できます](https://camo.elchika.com/c7817b9fa66795e38a8eaba02e76f542a27267eb/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f35333135396630642d313935622d343536302d626534332d663265633865343838656666/) んで、以下がこのHTMLを表示している時のアドレスなんですけれども。 えぇ、思いっきりNodeIdとeojが分かります。 これが、「アドバンススイッチリンクモデルの無線アダプタ」の、NodeIdとeojになります。 ``` http://10.20.30.216/page/devices/device/323_adv?page=1&nodeId=1234567890&eoj=0x0f2001&type=0x2a ``` トークンは、HTMLソース内に埋め込まれています。 こんな感じで。 ![キャプションを入力できます](https://camo.elchika.com/86ff2acd491d308ae0a65e32fb1d79863e142df8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f35633165366663662d326563622d346363612d613333622d373832393238363261613836/) これをPythonでハンドリングするには、こんな感じで。

+

 *以降のpythonスクリプトで使用するモジュールだの変数だのを含みます

```python import requests from requests.auth import HTTPDigestAuth import json import time import re import sys import os aisegip = '10.20.30.216' user = 'aiseg' password = '01204400111' targetnodeid = '1234567890' targeteoj = '0x0f2001'

+

headers = {'Content-Type': 'application/x-www-form-urlencoded'}

url = 'http://' + aisegip + '/page/devices/device/323_adv?page=1&nodeId=' + targetnodeid + '&eoj=' + targeteoj + '&type=0x2a' res = requests.get(url, auth=HTTPDigestAuth(user,password)) token = re.search(r'token=\'(\d+?)\'', res.text).group(1) ``` トークンは、大体1時間くらいで更新されるみたいです。 なのでそれなりの頻度で再取得するのがよろしいかと。 なお、トークンを取得するリクエストは、他のajax系リクエストからしばらく時間を置かないと失敗するみたいです。 10秒くらい休ませてから取得すると、よろしい感じ。 ## エアコンのNodeIdの取得 エアコンが複数あると、もちろんnodeIdも複数ある感じになります。 エアコン制御画面から、nodeId達をゲットしちゃいましょう。 ```python airconnodeids = [] url = 'http://' + aisegip + '/page/devices/device/321?page=1&individual_page=1' res = requests.get(url, auth=HTTPDigestAuth(user,password)) resarray = res.text.split('\n') for resline in resarray: if 'nodeId="' in resline: matchdata = re.search(r'nodeId="\d+',resline).group() nodedata = re.sub(r'\D','',matchdata) airconnodeids.append(nodedata) ``` ## 機器の状態取得 アドバンススイッチリンクモデルの無線アダプタ編 さてここからはPOST取得になります。 ```python url = 'http://' + aisegip + '/data/devices/device/323/get_advinfo' bodydata = 'data={"token":"' + token + '","nodeId":"' + targetnodeid + '","eoj":"' + targeteoj + '"}' res = requests.post(url, auth=HTTPDigestAuth(user,password),headers=headers,data=bodydata) ``` 応答は「入れ子JSON」になるため、解釈はちょっと工夫が必要です。 一部文字がエスケープされてるので・・・。 pythonの場合は ```python targetjson = re.search(r',"advance":"(.+)"}$', res.text).group(1) targetjson = re.sub(r'\\','', targetjson) advancejson = json.loads(targetjson) ``` javascriptの場合は ```javascript var url = 'http://10.20.30.40/ramdisk/aisegadvancestatus.json'; fetch(url) .then(function (data) { return data.json(); }) .then(function (json) { var jsonbase = json["advance"]; var jsonint = jsonbase.replace('\"','"'); var jsondata = JSON.parse(jsonint); }); ``` これで、各機材の名前や状態がオブジェクトでゲットできます。 こんな感じのやーつが!。 ```json { "type": "2", "code": "7", "parent": "1", "category": "10", "macaddr": "0xdeadbeef", "name": "寝室吹き抜け", "state": "0x31", "adjust": "0", "mode": "0", "condition": "消灯中", "switch": "点灯", "nodeId": "123456782", "eoj": "0x029107" }, { "type": "2", "code": "8", "parent": "0", "category": "2", "macaddr": "0xdeadbeef", "name": "寝室シーリング", "state": "0x30", "adjust": "1", "mode": "0", "condition": "点灯中", "switch": "消灯", "nodeId": "123456783", "eoj": "0x029108" }, ``` ## 機器の状態取得 エアコン編 リクエストは、エアコンごとにオブジェクトを定義する形のJSONを作る必要があります。 こんな感じで。 ```python url = 'http://' + aisegip + '/data/devices/device/321/auto_update' bodydata = 'data={"page":"1","individual_page":"1","list":[' comflag = 0 for airconnodeid in airconnodeids: if comflag == 0: comflag = 1 else: bodydata = bodydata + ',' bodydata = bodydata + '{"nodeId":"' + airconnodeid + '","eoj":"0x013001","type":"0x33"}' bodydata = bodydata + ']}' res = requests.post(url, auth=HTTPDigestAuth(user,password),headers=headers,data=bodydata) ``` ウチのエアコンは、パナソニックのJモデルなのですけれども。 割といろんな情報が拾えます。 上位モデルだと、「室外温度」だの「湿度」だのもゲットできそうな感じ。 ```json { "nodeId": "123456785", "eoj": "0x013001", "type": "0x33", "page": "1", "individual_page": "1", "name": "リビング・エアコン", "state": "0x31", "state_str": "停止中", "entry": "1", "mode": "0x42", "index_mode_button": "運転", "temp": "冷房<br />26℃", "inner": "31℃", "outer": "-", "humidity": "-", "timerDate": null, "makerCode": "0x00000B", "IpAdd": "-" }, { "nodeId": "123456786", "eoj": "0x013001", "type": "0x33", "page": "1", "individual_page": "1", "name": "洋室1・エアコン", "state": "0x30", "state_str": "運転中", "entry": "1", "mode": "0x42", "index_mode_button": "停止", "temp": "冷房<br />26℃", "inner": "24℃", "outer": "29℃", "humidity": "-", "timerDate": null, "makerCode": "0x00000B", "IpAdd": "-" }, ``` ## 機器の制御 これはもう、aiseg2の画面をポチポチして、開発ツールのネットワーク画面でリクエストを確認しちゃうのが早いです。 例えば、アドバンスリンクモデルの場合は ```python url = 'http://' + aisegip + '/action/devices/device/323/operation' bodydata = 'data={"objSendData":"{\\"nodeId\\":\\"' + nodedata['nodeId'] + '\\",\\"eoj\\":\\"' + nodedata['eoj'] + '\\",\\"type\\":\\"0x2a\\",\\"device\\":{\\"onoff\\":\\"0x31\\",\\"modulate\\":\\"-\\"}}","token":"' + token + '"}' res = requests.post(url, auth=HTTPDigestAuth(user,password),headers=headers,data=bodydata) ``` こんな感じになります。 「nodedata['nodeid']」「nodedata['eoj']」は、無線アダプタのヤツではなく、機器の状態取得でゲットした、個別のやーつを、ね。 上の例だと、「onoff」に「0x31」をセットするとOFFに、「0x30」をセットするとONになります。 エアコンの場合はこんな感じ。 ```python url = 'http://' + aisegip + '/action/devices/device/321/change' bodydata = 'data={"nodeId":"' + nodedata + '","eoj":"0x013001","type":"0x33","state":"' + targetstate + '","token":"' + token + '"}' res = requests.post(url, auth=HTTPDigestAuth(user,password),headers=headers,data=bodydata) ``` 「nodedata」は、エアコンのnodeIdをセットする感じで。 「targetstate」に、「0x30」をセットするとOFFに、「0x31」をセットするとONになります、何故かアドバンスリンクモデルの例と逆なの・・・。 # これでいろいろやらかせます 例えば、、、。 「照明器具の状態表示と、一括OFFスイッチ」を、玄関につけてみたり。 ![キャプションを入力できます](https://camo.elchika.com/1cd73777fa33307bde326a3b26e0a3dd6538f32a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f61623131313932372d626463632d343538632d393337312d326232353661383531313564/) ダウンライトとか点いてたらLEDが「黄色」に、消えてたら「緑」に光る感じのやーつ。 スマホでエアコンやテレビの電源を操作してみたり。 ![キャプションを入力できます](https://camo.elchika.com/acdee0e79da377d185b0d0fe18d7872e30c29148/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f37616165623861652d613737372d346334392d623536302d636662333261623062316232/) ゆめがひろがります!。 まあ、、、aiseg2自体そんな新しいシステムでもないし、クラウド系API使えばもっとラクなのかもですが。 いいのよ、楽しいから!。 以上です。 # 参考文献 電気自動車充放電器/電気自動車充電器・HEMS コントローラ間 アプリケーション通信インタフェース仕様書 - ECHONET Consortium https://echonet.jp/wp/wp-content/uploads/pdf/General/Standard/AIF/evps_evse/evps_evse_aif_ver1.40.pdf ECHONET Lite 技術解説 KAITSDKの紹介 - HEMS認証支援センター http://sh-center.org/120620/downloads/seminar_151002.pdf