eucalyのアイコン画像
eucaly 2024年08月24日作成 (2024年08月24日更新) © MIT
セットアップや使用方法 セットアップや使用方法 閲覧数 264
eucaly 2024年08月24日作成 (2024年08月24日更新) © MIT セットアップや使用方法 セットアップや使用方法 閲覧数 264

HEMSで遊んでみる aiseg2をハック編

HEMSで遊んでみる aiseg2をハック編

こんにちは、ゆうかりです。

今回は、パナソニックのHEMSシステム、「aiseg2」のWebサーバを。
色々弄ってみた的なお話です。

*前回の記事と似てるけど、違います!。

留意事項
・今回のお話は、HEMSのセキュリティレベルを著しく下げます!
・「何がキケンか分からない」ヒトは、マネしないで下さい!
・くれぐれも、適切な運用を!

HEMSの基本的な仕組み

HEMSは、「Echonet Lite」というプロトコルでおしゃべりします。
機器構成は、こんな感じ。
キャプションを入力できます
ここでのポイントがいくつか。
まず、各機材にくっついている、「EOJ」。
これは、「Echonet ObJect」の略で、「その機材が何か」を表してたりします。
例えばエアコンなら「0130」、照明器具なら「0290」て感じ。

そしてEchonet Lite自体には、「それぞれの個別機材の識別」はありません。
それぞれの機材識別は、OSI参照モデルで言うところのネットワーク層以下に任されてる感じ、つまりIPだのMACだのね。
キャプションを入力できます

んで、多少実装が特殊なのが、上の図で言うところの「真ん中」と「右」の機材たち。
通信ノードと機材が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をゲットする必要があります。
画面的にはココ。
キャプションを入力できます

んで、以下がこのHTMLを表示している時のアドレスなんですけれども。
えぇ、思いっきりNodeIdとeojが分かります。
これが、「アドバンススイッチリンクモデルの無線アダプタ」の、NodeIdとeojになります。

http://10.20.30.216/page/devices/device/323_adv?page=1&nodeId=1234567890&eoj=0x0f2001&type=0x2a

トークンは、HTMLソース内に埋め込まれています。
こんな感じで。
キャプションを入力できます

これを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達をゲットしちゃいましょう。

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取得になります。

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の場合は

targetjson = re.search(r',"advance":"(.+)"}$', res.text).group(1) targetjson = re.sub(r'\\','', targetjson) advancejson = json.loads(targetjson)

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); });

これで、各機材の名前や状態がオブジェクトでゲットできます。

こんな感じのやーつが!。

{ "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を作る必要があります。
こんな感じで。

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モデルなのですけれども。
割といろんな情報が拾えます。
上位モデルだと、「室外温度」だの「湿度」だのもゲットできそうな感じ。

{ "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の画面をポチポチして、開発ツールのネットワーク画面でリクエストを確認しちゃうのが早いです。
例えば、アドバンスリンクモデルの場合は

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になります。

エアコンの場合はこんな感じ。

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スイッチ」を、玄関につけてみたり。
キャプションを入力できます
ダウンライトとか点いてたらLEDが「黄色」に、消えてたら「緑」に光る感じのやーつ。

スマホでエアコンやテレビの電源を操作してみたり。
キャプションを入力できます
ゆめがひろがります!。

まあ、、、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

1
eucalyのアイコン画像
いつも、てきとうです
ログインしてコメントを投稿する