Node-REDでのEnOceanのデータ読み取りをもう少し踏み込んだメモです。
電源不要で動くEnOceanスイッチをRaspberry PiのEnOcean USBゲートウェイ経由でNode-REDをつなげるメモを書きましたが、これでデータが来て法則性を見つけて、なんとなく取り出すことは出来るのですが、対象としたスイッチだけでなく、たとえばスイッチサイエンスさんにあるドア開閉センサーを対応するとなると、EnOceanのデータ仕様を把握して行う必要があるので、もう少し踏み込んでみることにしました。
まずはNode-REDにノードがないか調べてみた
すぐに使えるノードがないか調べてみました。いろいろあるにはあります。
- node-red-contrib-enocean
- industrialinternet/node-red-nodes-enocean-serial: Node-RED EnOcean In & Out nodes
ただ、EnOceanと見えつつも用途が合わなかったりシンプルに使えない様子でした。
一番いけそうだったnode-red-contrib-enoceanでも、node-enoceanを使うためなのかNode.jsがバージョン7以上だったりと使いにくい様子。(2017/09/13現在)
そもそもNode-REDのノードとして仕上げてしまうと、EnOceanの仕様に則りながら、様々なEnOcean対応機器を想定しなければならず、私のように、ちょっとずつ使える機器を把握しようとする場合には、使いづらいのかもしれません。
たとえば以下のnode-red-contrib-enoceanに内包されているnode-enoceanのソースコードでも、読み取りだけでなく、パケットタイプや機器ごとに様々な分岐があり、全体をカバーする難しさが垣間見えます。
node-enocean/telegram.js at master · node-enocean/node-enocean
ということで、一旦はスイッチとドア開閉センサーに絞り込んで対応するためfunctionノードを持ち出しつつ、EnOceanのESP3仕様を見ながら対応させていきます。
地道にやってみる
データ仕様を読んでみます。
ロッカースイッチ・シングル(ESM210R)の電文の簡単な解説 | ERMINE Corporation
こちらでシングルなので機器は違いますが実際のデータの取り出し方がわかります。
EnOcean規格・プロトコルの解説ページ | ERMINE Corporationから、ESP3の仕様の本家のPDFのリンクがあります。
ちょうどこのあたりが該当します。一旦こちらで組んでみたのがこちらです。
// データそのもの var baseData = msg.payload; // データの長さ var length = 7; // 1番目と2番目の合算らしいがうまくやる方法がわからない // PacketType var PacketType = baseData [ 4 ]; // dataLengthに基づいたデータ部分の切り出し var data = baseData .slice( 7 , 7 + length ); // payloadに格納 msg.payload = { PacketType:PacketType, data:data }
これでも、良い感じにデータを取ることが出来ますが、データの長さ dataLength が動的に取れておらず不安な面もあります。
答え合わせをしてみる
これでも動くのですが、なにぶんJavaScriptで果たして正しくコードが書かれているのかがよく分かりません。node-enoceanのソースで答え合わせおよびヒントをもらいましょう。
node-enocean/telegram.js at master · node-enocean/node-enocean
特にtelegram.jsが上記のPacket structureに対応したのソースがありました。
自分がうまく解釈できなかった部分がちゃんと変数命名にされていていいですね。データの長さ dataLength についても書かれているのでこれで行きましょう。
フローを組んでみる
ということで、早速、以前のフローを活かしながら組んでみます。USBシリアルの読み込みは一緒です。
functionノードの設定
functionノードの設定です。
こちらは、先ほどのnode-enoceanソースを参考にコードに反映します。
// データそのもの var buf = msg.payload; // データのコピー(検証用) var rawData = buf.slice( 0 , buf.length - 1 ); // 取り出しやすいようにhex変換したもの var rawByte = buf.toString( "hex" ); // ESP3仕様のdataLengthを拾う var dataLength = 255 * buf[ 1 ] + buf[ 2 ]; // ESP3仕様のoptionalLengthを拾う var optionalLength = buf[ 3 ]; // packetType var packetType = buf[ 4 ]; // headerCRC var headerCRC = buf[ 5 ]; // dataLengthに基づいたデータ部分の切り出し var rawDataByte = buf.slice( 7 , 7 + dataLength ); // payloadに格納 msg.payload = { rawData:rawData, rawByte:rawByte, dataLength:dataLength, optionalLength:optionalLength, packetType:packetType, headerCRC:headerCRC, rawDataByte:rawDataByte }
動かしてみる
実際にロッカースイッチダブルを動かしてみるとrawDataByteに切り出されたデータが入ってきます。
ロッカースイッチ・シングル(ESM210R)の電文の簡単な解説 を参考にすると、前半の0~3番目はユニークなIDで、4番目・5番目が操作時に変化するパケットです。
実際に、
// ユニークなID var id = rawDataByte.slice( 0 , 3 ).toString( "hex" );
で取り出してやると「id: “002daf”」と拾えたので、これが機器の選別に使えます。
// ユニークなID var id = rawDataByte.slice( 0 , 3 ).toString( "hex" ); if(id == "002daf"){ // 機器の状態 var state; if(rawDataByte[4] == 0 && rawDataByte[5] == 228){ state = "all off"; }else if(rawDataByte[4] == 130 && rawDataByte[5] == 99){ state = "left on"; }else if(rawDataByte[4] == 136 && rawDataByte[5] == 85){ state = "right on"; } }
4番目・5番目が操作時に変化するパケットもif文で比較してやることで、うまく状態を取り出すことが出来ました。
ドア開閉センサーについても前半の0~3番目はユニークなIDと見受けられ、4番目・5番目が操作時に変化するパケットのようです。
Node-REDフローデータ
Node-REDフローデータも置いておきます。ユニークなIDによる機器の選別と操作時に変化するパケットの分岐は機器固有のため入れていないのでご注意ください。
[ { "id": "6d969c0a.dcc224", "type": "serial in", "z": "1b0ee49a.dc829b", "name": "", "serial": "c5d2351b.ae8378", "x": 450, "y": 380, "wires": [ [ "1cfd9d3b.7e8803" ] ] }, { "id": "ddb5e219.38897", "type": "debug", "z": "1b0ee49a.dc829b", "name": "", "active": false, "console": "false", "complete": "payload", "x": 810, "y": 380, "wires": [] }, { "id": "1cfd9d3b.7e8803", "type": "function", "z": "1b0ee49a.dc829b", "name": "データ分類", "func": "// データそのもの\nvar buf = msg.payload;\n// データのコピー(検証用)\nvar rawData = buf.slice( 0 , buf.length - 1 );\n// 取り出しやすいようにhex変換したもの\nvar rawByte = buf.toString( \"hex\" );\n// ESP3仕様のdataLengthを拾う\nvar dataLength = 255 * buf[ 1 ] + buf[ 2 ];\n// ESP3仕様のoptionalLengthを拾う\nvar optionalLength = buf[ 3 ];\n// packetType\nvar packetType = buf[ 4 ];\n// headerCRC\nvar headerCRC = buf[ 5 ];\n// dataLengthに基づいたデータ部分の切り出し\nvar rawDataByte = buf.slice( 7 , 7 + dataLength );\n// payloadに格納\nmsg.payload = {\n rawData:rawData,\n rawByte:rawByte,\n dataLength:dataLength,\n optionalLength:optionalLength,\n packetType:packetType,\n headerCRC:headerCRC,\n rawDataByte:rawDataByte\n}\n\nreturn msg;", "outputs": 1, "noerr": 0, "x": 630, "y": 380, "wires": [ [ "ddb5e219.38897", "d1b63427.771808" ] ] }, { "id": "d1b63427.771808", "type": "debug", "z": "1b0ee49a.dc829b", "name": "", "active": true, "console": "false", "complete": "payload.rawDataByte", "x": 850, "y": 460, "wires": [] }, { "id": "c5d2351b.ae8378", "type": "serial-port", "z": "", "serialport": "/dev/ttyUSB0", "serialbaud": "57600", "databits": "8", "parity": "none", "stopbits": "1", "newline": "100", "bin": "bin", "out": "time", "addchar": false } ]
まとめ
ということで、EnOceanのデータ読み取りをもう少し踏み込んでみました。
前回はざっくり取得していたデータも、優秀なnode-enoceanライブラリやデータ仕様を参考にして、もう少し内容がわかり機器の選別や、細かな状態把握ができるようになってきました。
それでは、よき EnOcean & IoT & Node-RED Life を!