こんにちは、ゆうかりです。
今回は、パナソニックの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
投稿者の人気記事
-
eucaly
さんが
2024/08/24
に
編集
をしました。
(メッセージ: 初版)
-
eucaly
さんが
2024/08/24
に
編集
をしました。
-
eucaly
さんが
2024/08/24
に
編集
をしました。
ログインしてコメントを投稿する