SA-M0経由でECHONET Liteのスマートメーターにアクセスしてみた
2022/06/09 追記
- SA-M0 の設定を行うためのスマホアプリ
B-route Monitor
がiOS版もAndroid版も削除されたようです。 - Android版 https://play.google.com/store/apps/details?id=jp.ad.iij.broutemonitor
- iOS版 https://apps.apple.com/jp/app/id1195552686
IIJの公式にあったブログも、SA-M0に関する記述がほぼ削除されました。
正直なところ、終息になるのは仕方ないにしても設定プロトコルくらいは公開してくれても良かったのではないかと思いますし、わざわざアプリ削除までしなくても…というのが素直なところです。
軽くググった範囲ではAPKファイルも手に入らなそうな感じです。
では、ゴミなのか?
いや、ちょっと待ってください。SA-M0の中には ROHM BP35A1 が入っています。
ちょっと分解すればすぐに取り出せるので、これに ROHM BP35A7A と BP35A7-accessories を購入してやれば、Raspberry Piであるとか、Arduino機器で使用することができます。
上記は、Chip1Stopで1000円もせずに購入可能です。BP35A1は5000円以上する高級なものなのでそのままジャンク品としてオークション等に出品しても良いでしょう
(品番 C1S625901561294 C1S625901542509)
概要
nodejs を使って SA-M0 を経由してスマートメーターの値を取得します。
SA-M0 を経由することで、Wi-SUN アダプタの事を考えずに、LAN 上にスマートメーターが存在しているように見えます。
まず最初に
ゼロスタートだったので、基礎知識を身につける為に以下を読みました。
ECHONET 電文の作り方
これを読まないと echonet-lite
の出力が理解できません。略語だらけなので。。。
一応本稿でも必要な略語は解説していますがこちらの方が正しい解説です。
WiSUN を直接喋る場合と SA-M0 を経由する場合の違い
Wi-SUN アダプタを使って直接やりとりする場合は、上記 blog のスマートメーターの探索認証・接続の処理を行う必要がありますが、SA-M0 を経由した場合、この部分はすべて SA-M0 がやってくれます。(ただし、スマホアプリで初期設定をすることが必要です)
SA-M0 からデータを取得するアプリケーションは、単純に暗号化等の事を考えずに、リクエストを送信すれば応答してくれます。楽です。
Node.js の echonet-lite ライブラリの使い方
今回必要なのは情報の取得だけですが、思いっきりドハマリしたので echonet-lite
ライブラリの使い方も含めて書いていきます。
機器スキャンのサンプルプログラム(下記 URL) を題材にして説明します。
基本的な考え方
- ECHONET 機器(SA-M0)と通信を行う為には、自分自身も ECHONET 機器になる必要があります。
- ECHONET の通信対象の区別は、
(IPを使う場合) IPアドレス + 機器ID
で行われます。 -
echonet-lite
的に命令の送信とその応答は非同期で扱う必要があります。
ECHONET 機器になる部分は、echonet-lite
が受け持ってくれるので難しいことはありません。
今回であれば、自分自身はコントローラ (05ff01) になれば良いので、サンプルをそのまま使うことができます。
# 機器 ID はなんでもよさそうですが、下手な機器を設定すると他の ECHONET 機器から発見されて状態取得系の命令が飛んでくるかもしれません。
やりとりは、命令を送信すると、それに対する応答が返ってくる。という単純なものです。(命令によっては、PUSH 通知的な動きもありそうですが未確認)
落とし穴なのは、データ送信をした結果の応答は EL.initialize
の第三引数の function に返ってくるという点です。
データの送信
EL.sendOPC1 = function( ip, seoj, deoj, esv, epc, edt)
これを使えばよい。 大事なことなので強調しますが、応答はEL.initializeの第三引数のfunctionで受け取ります
引数 | 内容 | 設定値 |
---|---|---|
ip | 要求先IPアドレス | 192.168.1.25 (EL.search()して見つけておく) |
seoj | 要求元機器ID | 05ff01 (コントローラー) *1 |
deoj | 要求先機器ID | 028801 (低圧スマート電力メーター) *1 |
esv | 命令コード | EL.GET (0x62: Get) *2 |
epc | 引数1(プロパティを指定する) | 0xE0 (積算電力量) *3 *4 |
edt | 引数2(Set系命令の値) | 空文字列(Get系命令であれば無視される為) *4 |
*1 機器 IDについて
APPENDIX 機器オブジェクト詳細規定に規定されている。 (今回は、EL.search()して見つけた機器を片っ端から調べた)
*2 命令コード
第二部 ECHONET Lite 通信ミドルウェア仕様の 3-6 ページに記載されている、 表 3-9 要求用 ESV コード一覧に規定されている。
EL. 定数を見れば分かるかもしれない。
*3 プロパティ (EPC)
APPENDIX 機器オブジェクト詳細規定に規定されている。 スマートメーターは 3.3.25 (3-290 ページ)
*4 引数
規格上は、複数のプロパティを同時に要求できるが、 echonet-lite
ではその機能は実装されていない。
# 送ろうと思えば送れそうな関数はあるが、一個ずつ要求しても良いだろうという考えだと思われる。
データの受信
データの受信は、 EL.initialize
の第三引数の関数にて行われます。
第三引数の関数は function( rinfo, els, err )
となっており、それぞれ
引数 | 内容 | 設定値 |
---|---|---|
rinfo | 送信元情報 | IPアドレス以外は使わないかも |
els | 受信情報 | 主に扱う部分 |
err | エラー情報 | err != undefined ならエラーなので無視するなりなんなりする必要あり |
rinfo サンプルデータ
<code class="language-javascript">{address: '10.1.0.1xx', family: 'IPv4', port: 35110, size: 18 }
こんな感じなので、IP アドレス以外はつかわなそうです。
els サンプルデータ
els サンプルデータは、下記のコマンドを実行した際の応答例である。
EL.sendOPC1('10.1.0.100', '05ff01', '028801', EL.GET, "e8", "");
{ EHD: '1081',
TID: '0000',
SEOJ: '028801',
DEOJ: '05ff01',
EDATA: '7201e804001e0096',
ESV: '72',
OPC: '01',
DETAIL: 'e804001e0096',
DETAILs: { e8: '001e0096' } }
引数 | 内容 | 備考 |
---|---|---|
EHD | ECHONETバージョン | 1081固定 |
TID | トランザクションID |
echonet-lite を使う限りは 0000固定 |
SEOJ | 送信元機器ID | 要求のDEOJと等しいはず |
DEOJ | 送信先機器ID | 自分自身の機器ID |
EDATA | 生データ | データ部を解釈しないでそのまま格納したもの |
ESV | 応答・通知用ESVコード | 72は GETに対する応答を表す *1 |
OPC | プロパティ数 | 回答データ数。echonet-lite を使う限りは 01 固定 |
DETAIL | データ詳細 | 解釈前データ |
DETAILs | データ詳細 | 解釈後データ。要求プロパティがキーになっている。この例だと e8を要求したのでe8だけが含まれる |
*1 ESV コード
第二部 ECHONET Lite 通信ミドルウェア仕様の 3-6 ページに記載されている、 表 3-10 応答・通知用 ESV コード一覧に規定されている。
注意点
- 自分が送信した命令も受信してしまうので、それは処理しないようにする必要がある。
例えば、 rinfo が自分自身の IP アドレスである場合は無視するとか、SEOJ が自分の機器 ID だったら無視するとか
- 数値は 16 進数なので注意。
parseInt(value, 16)
で 10 進に変換可能。
プログラムの終了
echonet-lite
のサンプルプログラムを動作させると、いつまでまっても終了しない。これは、ソケットの受信待ちがずっと行われている為である。
プログラムを終了する為には以下の用にすれば良い。
(EL.initialize が内部で使用する dgram.socket
が返ってくるので、これをクローズすればイベントハンドラが終了できる)
var elsocket = EL.initialize( objList, function( rinfo, els, err ) {
(略)
if (データ受信完了) {
global.complete_flag = true; // グローバル領域にフラグを立てる
}
}
setTimeout(function(sock) {
if (global.complete_flag) { // データ受信完了なら
sock.close(); // sock = elsocket
}
, 10000, elsocket); // 関数に EL.initializeの返り値を渡す
雑なプログラムですが、こんな感じでフラグを監視するようにすれば OK です。
まとめ
出来たものは、下記 URL で公開しています。
蛇足
sendOPC1 を promise でラップしたら使いやすさが一気に上がる気がする。
Discussion