🔌

SA-M0経由でECHONET Liteのスマートメーターにアクセスしてみた

2021/08/11に公開

2022/06/09 追記

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 の出力が理解できません。略語だらけなので。。。

一応本稿でも必要な略語は解説していますがこちらの方が正しい解説です。

https://qiita.com/miyazawa_shi/items/725bc5eb6590be72970d

WiSUN を直接喋る場合と SA-M0 を経由する場合の違い

http://route-b.iij.ad.jp/archives/128

Wi-SUN アダプタを使って直接やりとりする場合は、上記 blog のスマートメーターの探索認証・接続の処理を行う必要がありますが、SA-M0 を経由した場合、この部分はすべて SA-M0 がやってくれます。(ただし、スマホアプリで初期設定をすることが必要です)

SA-M0 からデータを取得するアプリケーションは、単純に暗号化等の事を考えずに、リクエストを送信すれば応答してくれます。楽です。

Node.js の echonet-lite ライブラリの使い方

今回必要なのは情報の取得だけですが、思いっきりドハマリしたので echonet-lite ライブラリの使い方も含めて書いていきます。

機器スキャンのサンプルプログラム(下記 URL) を題材にして説明します。

https://www.npmjs.com/package/echonet-lite

基本的な考え方

  • 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()して見つけた機器を片っ端から調べた)

https://echonet.jp/spec_object_rk/

*2 命令コード

第二部 ECHONET Lite 通信ミドルウェア仕様の 3-6 ページに記載されている、 表 3-9 要求用 ESV コード一覧に規定されている。

EL. 定数を見れば分かるかもしれない。

https://echonet.jp/spec_v113_lite/

*3 プロパティ (EPC)

APPENDIX 機器オブジェクト詳細規定に規定されている。 スマートメーターは 3.3.25 (3-290 ページ)

https://echonet.jp/spec_object_rk/

*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 コード一覧に規定されている。

https://echonet.jp/spec_v113_lite/

注意点

  • 自分が送信した命令も受信してしまうので、それは処理しないようにする必要がある。

例えば、 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 で公開しています。

https://github.com/yakumo-saki/b-route-reader

蛇足

sendOPC1 を promise でラップしたら使いやすさが一気に上がる気がする。

Discussion