Node-REDでECHONET Liteの送受信nodeを作成してみた

##概要
　Node-REDにdefaultでinstallされるUDPの送信nodeと受信nodeを利用して、ECHONET Liteの送受信を容易にするnodeを作成しました。受信したUDP dataはパースされECHONET Liteで規定されるブロックに分解されます。データを送信する場合はECHONET Liteで規定される各ブロックの値を引数として渡します。ECHONET Liteはバイト単位でパケットが構成されますが、UInt8のdata typeを持たないJavaScriptでは扱いにくいため、データはIntで渡します。
　サンプルプログラムとして以下の３つを用意しました。

- Node-REDからマニュアルでエアコンと照明のON/OFF制御
- 電子錠の開閉でエアコンと照明のON/OFF制御
- スマホのブラウザーをエアコンと照明のON/OFF制御リモコンにするweb application

なお、以下の環境で動作確認しています。
Node-RED version: v0.15.1
Node.js  version: v6.9.1

全体のFLOWはこんな感じです。

![全体Flow.png](https://qiita-image-store.s3.amazonaws.com/0/73141/f1d42080-ec7e-0221-6744-bbf96ca17531.png "全体Flow.png")

##送信

まず送信側から見ていきましょう。画面上で「EL send」と表示されているUDPの送信ノードの設定はこのようになります。to portに3610を入力してください。

![udp_out.png](https://qiita-image-store.s3.amazonaws.com/0/73141/9754bb8a-c0e1-c30d-4f6d-7332f88a09a2.png)



このudp nodeの入力側に接続されている「Send EL1」というfunction nodeの中身はこのようになりますが、理解しなくても使えます。

```JavaScript
// Send ECHONET Lite packet
// Assumption: OPC = 1
//
//  Usage:
//      Input following data
//
//  msg.els.ip      // ip address (String)
//  msg.els.tid     // TID (Int)
//  msg.els.seoj    // SEOJ ([Int:device, Int:instance])
//  msg.els.deoj    // DEOJ ([Int:device, Int:instance])
//  msg.els.esv     // ESV (Int)
//  msg.els.epc     // EPC (Int)
//  msg.els.edt     // EDT ([Int])
//          // in case of no EDT data, no edt or []

const tid_buffer = Buffer.allocUnsafe(2);
let   seoj_buffer = Buffer.allocUnsafe(3);
const seoj = msg.els.seoj;
let   deoj_buffer = Buffer.allocUnsafe(3);
const deoj = msg.els.deoj;
const esv_buffer = Buffer.allocUnsafe(1);
const opc_buffer = Buffer.from([1]);
const epc_buffer = Buffer.allocUnsafe(1);
const pdc_buffer = Buffer.allocUnsafe(1);
let edt = msg.els.edt;

tid_buffer.writeUInt16BE(msg.els.tid, 0);
esv_buffer.writeUInt8(msg.els.esv, 0);
epc_buffer.writeUInt8(msg.els.epc, 0);

seoj_buffer.writeUInt16BE(seoj[0], 0);
seoj_buffer.writeUInt8(seoj[1], 2);
deoj_buffer.writeUInt16BE(deoj[0], 0);
deoj_buffer.writeUInt8(deoj[1], 2);

// edt[]からedt_bufferを作成
if ((typeof edt) == "undefined") {
    edt = [];
}

const edt_buffer = Buffer.allocUnsafe(edt.length);
for (var i = 0; i < edt.length; i++) {
    edt_buffer.writeUInt8(edt[i], i);
}

pdc_buffer.writeUInt8(edt_buffer.length, 0);

// Create ECHONET Lite packet
const el_buffer = Buffer.from([
	0x10, 0x81,
	tid_buffer[0], tid_buffer[1],
	seoj_buffer[0], seoj_buffer[1], seoj_buffer[2],
	deoj_buffer[0], deoj_buffer[1], deoj_buffer[2],
	esv_buffer[0], opc_buffer[0], epc_buffer[0], pdc_buffer[0]]);

// add EDT data
if (edt_buffer.length !== 0) {
    msg.payload = Buffer.concat([el_buffer, edt_buffer], 
        (el_buffer.length + edt_buffer.length));
} else {
    msg.payload = el_buffer;
}

msg.ip = msg.els.ip;
return msg;
```

　冒頭のコメントに記述されているように、このfunctionに以下のデータを渡してやると、byte列のECHONET Liteデータパケットに変換してudp nodeに渡します。なお、OPC=1のみ対応しています。

- msg.els.ip      // ip address (String)
- msg.els.tid     // TID (Int)
- msg.els.seoj    // SEOJ ([Int:device, Int:instance])
- msg.els.deoj    // DEOJ ([Int:device, Int:instance])
- msg.els.esv     // ESV (Int)
- msg.els.epc     // EPC (Int)
- msg.els.edt     // EDT ([Int])

具体的には、この「Send EL1」functionの入力側に接続されている「エアコン ON」functionを見てみましょう。

```JavaScript
msg.els = {
    "ip" : "224.0.23.0",
    "tid" : 5,
    "seoj" : [0x05ff,1],
    "deoj" : [0x0130,1],
    "esv" : 0x61,
    "epc" : 0x80,
    "edt" : [0x30]
};

return msg;
```

このfunction内の必要な箇所の値を変更するだけで、ECHONET Liteのデータ送信を実現できます。

##受信

続いて受信側を見ていきましょう。画面上で「EL receive」と表示されているUDPの受信ノードの設定はこのようになります。「Listen for」で「multicast message」を選択し、「Group」に224.0.23.0を「on port」に3610を入力してください。「Output」は「a Buffer」を選択してください。

![udp_in.png](https://qiita-image-store.s3.amazonaws.com/0/73141/d2e4b609-60f6-be65-6f1c-0806b356bf5b.png "udp_in.png")


このudp nodeの出力側に接続されている「Parse EL1」というfunction nodeの中身はこのようになりますが、理解しなくても使えます。

```JavaScript
// Parse ECHONET Lite packet
// Assumption: OPC = 1

//  OUTPUT:
//  msg.elr.ip      // ip address (String)
//  msg.elr.ehd     // EHD (Int)
//  msg.elr.tid     // TID (Int)
//  msg.elr.seoj    // SEOJ ([Int:device, Int:instance])
//  msg.elr.deoj    // DEOJ ([Int:device, Int:instance])
//  msg.elr.esv     // ESV (Int)
//  msg.elr.opc     // OPC (Int)
//  msg.elr.epc     // EPC (Int)
//  msg.elr.pdc     // PDC (Int)
//  msg.elr.edt     // EDT ([Int]) or [](no EDT data)

const buffer = msg.payload;
msg.elr = null;

if (buffer.length >= 14) {
  const ehd_buffer = buffer.slice(0, 2);
  const tid_buffer = buffer.slice(2, 4);
  const seoj_device_buffer = buffer.slice(4, 6);
  const seoj_instance_buffer = buffer.slice(6, 7);
  const deoj_device_buffer = buffer.slice(7, 9);
  const deoj_instance_buffer = buffer.slice(9, 10);
  const esv_buffer = buffer.slice(10, 11);
  const opc_buffer = buffer.slice(11, 12);
  const epc_buffer = buffer.slice(12, 13);
  const pdc_buffer = buffer.slice(13, 14);

  const ehd = ehd_buffer.readUInt16BE(0);
  const tid = tid_buffer.readUInt16BE(0);
  const seoj_device = seoj_device_buffer.readUInt16BE(0);
  const seoj_instance = seoj_instance_buffer.readUInt8(0);
  const seoj = [seoj_device, seoj_instance];
  const deoj_device = deoj_device_buffer.readUInt16BE(0);
  const deoj_instance = deoj_instance_buffer.readUInt8(0);
  const deoj = [deoj_device, deoj_instance];
  const esv = esv_buffer.readUInt8(0);
  const opc = opc_buffer.readUInt8(0);
  const epc = epc_buffer.readUInt8(0);
  const pdc = pdc_buffer.readUInt8(0);
  let edt = [];
  
  if (buffer.length >= 15) {
    const edt_buffer = buffer.slice(14);
    for (var i = 0; i < edt_buffer.length; i++) {
        edt[i] = edt_buffer.readUInt8(i);
    }
  }

  // ECHONET Lite header check
  if (ehd == 0x1081) {
    msg.elr = {
        "ip"            : msg.ip,
        "ehd"           : ehd,
        "tid"           : tid,
        "seoj"          : seoj,
        "deoj"          : deoj,
        "esv"           : esv,
        "opc"           : opc,
        "epc"           : epc,
        "pdc"           : pdc,
        "edt"           : edt
    };
  }
}

return msg;
```

　冒頭のコメントに記述されているように、このfunctionから以下のデータが出力されます。なお、OPC=1のみ対応しています。

- msg.elr.ip      // ip address (String)
- msg.elr.ehd     // EHD (Int)
- msg.elr.tid     // TID (Int)
- msg.elr.seoj    // SEOJ ([Int:device, Int:instance])
- msg.elr.deoj    // DEOJ ([Int:device, Int:instance])
- msg.elr.esv     // ESV (Int)
- msg.elr.opc     // OPC (Int)
- msg.elr.epc     // EPC (Int)
- msg.elr.pdc     // PDC (Int)
- msg.elr.edt     // EDT ([Int])

パースされたデータの使い方の例を、この「Parse EL1」functionの出力側に接続されている「filter keyStatus」functionで見てみましょう。受信したデータの送信元が「電子錠」で、プロパティが「開閉状態」ならば、開閉状態を表すEDTデータをmsg.payloadに格納しています。次のswitch nodeで閉(0x41)または開(0x42)で分岐し、エアコンや照明をON/OFF制御しています。

```JavaScript
// 電子錠 EOJ=0x026f, 開閉状態 EPC=0xe0
if ((msg.elr.seoj[0] == 0x026f) && (msg.elr.epc == 0xe0)) {
    msg.payload = msg.elr.edt[0];
}

return msg;
```

##Webリモコン
Node-REDはweb server機能をもっているので、web applicationを容易に作成することができます。全体Flowの下部の「ON/OFF Remote Controller」を見てください。スマホのブラウザで
> http://<Node-RED serverのip address>:1880/control

をアクセスすると、このようなシンプルなリモコン画面が現れます。このON/OFFボタンでエアコンや照明をON/OFF制御できます。

![web_remocon.PNG](https://qiita-image-store.s3.amazonaws.com/0/73141/33424a06-e626-1c69-4390-1d7dca9d6d16.png "web_remocon.PNG")

全体のFlowの左下部にある「/control」と書かれたhttp nodeでGET requestを受け付けます。このnodeはURLに「/control」と入力してあるだけです。このnodeの出力が接続されている「ON/OFF button」と書かれたtemplate nodeがHTMLを出力して、ブラウザーにボタンを表示します。template nodeのソースコードはこのようになっています。

```HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset=“UFT-8”>
    <style>
        body { background: #FAFAD2; }
        h1 {color:black; font-family:sans-serif;}
        button { width: 300px; height: 100px; font-size: 36px; border-radius: 10px; border-style: none; background-color: #aaaaaa;}
        button:hover {background-color: #dddddd;}
    </style>
    <title> ON/OFF BUTTON </title>
    <script language="javascript" type="text/javascript">
        function OnButtonClick() {
            location.href = "/on";
        }
        function OffButtonClick() {
            location.href = "/off";
        }
    </script>
</head>
<body>
	<h1>HEMS Controller</h1>
    <button type="button" name="buttonOn" onclick="OnButtonClick()">ON</button> 
    <button type="button" name="buttonOff" onclick="OffButtonClick()">OFF</button> <br/>
</body>
</html>
```

このHTMLがhttp response nodeから出力されます。


##全体のFlow

全体のFlowのJSONデータはこちらです。

```JSON
[{"id":"929982f7.1dfe88","type":"inject","z":"b25212d8.336a88","name":"Search","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":145.63636779785156,"y":159,"wires":[["48da7a43.d0e234"]]},{"id":"2ab8c00.fd8fd4","type":"link in","z":"b25212d8.336a88","name":"ON","links":["60038e3d.3ea5","60579462.f009ac","a47b7d1b.85f958"],"x":144,"y":242.18182373046875,"wires":[["8f824877.268258","30b813d1.96d6f4"]]},{"id":"7bab7379.ce52ac","type":"link in","z":"b25212d8.336a88","name":"OFF","links":["19f3c295.8e39bd","49fe4793.c299c8","d6714c57.f29c38"],"x":146,"y":329.18182373046875,"wires":[["46ac93c4.9bf35c","2bcde2cc.b0e0b6"]]},{"id":"4f1896ac.7cfcb","type":"comment","z":"b25212d8.336a88","name":"Check Key Status","info":"電子錠のOn/OFFでエアコンと照明のON/OFFを制御","x":128.22727966308594,"y":422.5454406738281,"wires":[]},{"id":"e46582fe.0e69b","type":"comment","z":"b25212d8.336a88","name":"Flow: demoRoom","info":"ECHONET Liteの送受信を実現。\nBrowserで接続するとON/OFF制御のリモコンとなる。","x":138.86363220214844,"y":114.81817626953125,"wires":[]},{"id":"6e147962.3b1508","type":"http in","z":"b25212d8.336a88","name":"/on","url":"/on","method":"get","swaggerDoc":"","x":87.5,"y":628.1818237304688,"wires":[["a47b7d1b.85f958","cd5ec784.0801d8"]]},{"id":"a47b7d1b.85f958","type":"link out","z":"b25212d8.336a88","name":"REST/on","links":["2ab8c00.fd8fd4"],"x":219.5,"y":627.1818313598633,"wires":[]},{"id":"85a91f31.a3686","type":"http in","z":"b25212d8.336a88","name":"/off","url":"/off","method":"get","swaggerDoc":"","x":88,"y":669.1818237304688,"wires":[["d6714c57.f29c38","cd5ec784.0801d8"]]},{"id":"d6714c57.f29c38","type":"link out","z":"b25212d8.336a88","name":"REST/off","links":["7bab7379.ce52ac"],"x":217.5,"y":668.1818237304688,"wires":[]},{"id":"e35592e3.6ced4","type":"http in","z":"b25212d8.336a88","name":"/control","url":"/control","method":"get","swaggerDoc":"","x":87,"y":710.1818237304688,"wires":[["387d2a5d.fa9d76","cd5ec784.0801d8"]]},{"id":"8bb17ee2.8b71c","type":"http response","z":"b25212d8.336a88","name":"","x":596.5,"y":654.1818237304688,"wires":[]},{"id":"387d2a5d.fa9d76","type":"debug","z":"b25212d8.336a88","name":"","active":true,"console":"false","complete":"true","x":371.5,"y":709.1818313598633,"wires":[]},{"id":"dfdee8d8.e1e74","type":"comment","z":"b25212d8.336a88","name":"ON/OFF Remote Controller","info":"Browserで接続すると、ON/OFF制御のボタンが表示される\n\nhttp://<Node-redのIP address>:1880/control","x":149.5,"y":588.1818237304688,"wires":[]},{"id":"baad2d25.311cc8","type":"debug","z":"b25212d8.336a88","name":"","active":true,"console":"false","complete":"true","x":601.5,"y":705.1818237304688,"wires":[]},{"id":"cd5ec784.0801d8","type":"template","z":"b25212d8.336a88","name":"ON/OFF button","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <meta charset=“UFT-8”>\n    <style>\n        body { background: #FAFAD2; }\n        h1 {color:black; font-family:sans-serif;}\n        button { width: 300px; height: 100px; font-size: 36px; border-radius: 10px; border-style: none; background-color: #aaaaaa;}\n        button:hover {background-color: #dddddd;}\n    </style>\n    <title> ON/OFF BUTTON </title>\n    <script language=\"javascript\" type=\"text/javascript\">\n        function OnButtonClick() {\n            location.href = \"/on\";\n        }\n        function OffButtonClick() {\n            location.href = \"/off\";\n        }\n    </script>\n</head>\n<body>\n\t<h1>HEMS Controller</h1>\n    <button type=\"button\" name=\"buttonOn\" onclick=\"OnButtonClick()\">ON</button> \n    <button type=\"button\" name=\"buttonOff\" onclick=\"OffButtonClick()\">OFF</button> <br/>\n</body>\n</html>","x":389.5,"y":653.1818237304688,"wires":[["8bb17ee2.8b71c","baad2d25.311cc8"]]},{"id":"2cd805cd.f6b84a","type":"function","z":"b25212d8.336a88","name":"Parse EL1","func":"// Parse ECHONET Lite packet\n// Assumption: OPC = 1\n\n//  OUTPUT:\n//  msg.elr.ip      // ip address (String)\n//  msg.elr.ehd     // EHD (Int)\n//  msg.elr.tid     // TID (Int)\n//  msg.elr.seoj    // SEOJ ([Int:device, Int:instance])\n//  msg.elr.deoj    // DEOJ ([Int:device, Int:instance])\n//  msg.elr.esv     // ESV (Int)\n//  msg.elr.opc     // OPC (Int)\n//  msg.elr.epc     // EPC (Int)\n//  msg.elr.pdc     // PDC (Int)\n//  msg.elr.edt     // EDT ([Int]) or [](no EDT data)\n\nconst buffer = msg.payload;\nmsg.elr = null;\n\nif (buffer.length >= 14) {\n  const ehd_buffer = buffer.slice(0, 2);\n  const tid_buffer = buffer.slice(2, 4);\n  const seoj_device_buffer = buffer.slice(4, 6);\n  const seoj_instance_buffer = buffer.slice(6, 7);\n  const deoj_device_buffer = buffer.slice(7, 9);\n  const deoj_instance_buffer = buffer.slice(9, 10);\n  const esv_buffer = buffer.slice(10, 11);\n  const opc_buffer = buffer.slice(11, 12);\n  const epc_buffer = buffer.slice(12, 13);\n  const pdc_buffer = buffer.slice(13, 14);\n\n  const ehd = ehd_buffer.readUInt16BE(0);\n  const tid = tid_buffer.readUInt16BE(0);\n  const seoj_device = seoj_device_buffer.readUInt16BE(0);\n  const seoj_instance = seoj_instance_buffer.readUInt8(0);\n  const seoj = [seoj_device, seoj_instance];\n  const deoj_device = deoj_device_buffer.readUInt16BE(0);\n  const deoj_instance = deoj_instance_buffer.readUInt8(0);\n  const deoj = [deoj_device, deoj_instance];\n  const esv = esv_buffer.readUInt8(0);\n  const opc = opc_buffer.readUInt8(0);\n  const epc = epc_buffer.readUInt8(0);\n  const pdc = pdc_buffer.readUInt8(0);\n  let edt = [];\n  \n  if (buffer.length >= 15) {\n    const edt_buffer = buffer.slice(14);\n    for (var i = 0; i < edt_buffer.length; i++) {\n        edt[i] = edt_buffer.readUInt8(i);\n    }\n  }\n\n  // ECHONET Lite header check\n  if (ehd == 0x1081) {\n    msg.elr = {\n        \"ip\"            : msg.ip,\n        \"ehd\"           : ehd,\n        \"tid\"           : tid,\n        \"seoj\"          : seoj,\n        \"deoj\"          : deoj,\n        \"esv\"           : esv,\n        \"opc\"           : opc,\n        \"epc\"           : epc,\n        \"pdc\"           : pdc,\n        \"edt\"           : edt\n    };\n  }\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":324,"y":458,"wires":[["f79f22e2.9b9448","c709dd31.ebd05"]]},{"id":"f79f22e2.9b9448","type":"function","z":"b25212d8.336a88","name":"filter keyStatus","func":"//  msg.elr.ip      // ip address (String)\n//  msg.elr.ehd     // EHD (Int)\n//  msg.elr.tid     // TID (Int)\n//  msg.elr.seoj    // SEOJ ([Int:device, Int:instance])\n//  msg.elr.deoj    // DEOJ ([Int:device, Int:instance])\n//  msg.elr.esv     // ESV (Int)\n//  msg.elr.opc     // OPC (Int)\n//  msg.elr.epc     // EPC (Int)\n//  msg.elr.pdc     // PDC (Int)\n//  msg.elr.edt     // EDT ([Int]) or [](no EDT data)\n\n// 電子錠 EOJ=0x026f, 開閉状態 EPC=0xe0\nif ((msg.elr.seoj[0] == 0x026f) && (msg.elr.epc == 0xe0)) {\n    msg.payload = msg.elr.edt[0];\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":519,"y":458,"wires":[["acff4917.ab63f8"]]},{"id":"acff4917.ab63f8","type":"switch","z":"b25212d8.336a88","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"0x41","vt":"num"},{"t":"eq","v":"0x42","vt":"num"},{"t":"else"}],"checkall":"true","outputs":3,"x":682.5,"y":459,"wires":[["60038e3d.3ea5"],["49fe4793.c299c8"],[]]},{"id":"60038e3d.3ea5","type":"link out","z":"b25212d8.336a88","name":"0x41","links":["2ab8c00.fd8fd4"],"x":798.5,"y":446,"wires":[]},{"id":"49fe4793.c299c8","type":"link out","z":"b25212d8.336a88","name":"0x42","links":["7bab7379.ce52ac"],"x":800.5,"y":484,"wires":[]},{"id":"42922bec.b77014","type":"udp in","z":"b25212d8.336a88","name":"EL receive","iface":"","port":"3610","ipv":"udp4","multicast":"true","group":"224.0.23.0","datatype":"buffer","x":118,"y":459,"wires":[["2cd805cd.f6b84a","c617b521.c138c8","81a3a52d.f2ecd8"]]},{"id":"c617b521.c138c8","type":"debug","z":"b25212d8.336a88","name":"","active":true,"console":"false","complete":"false","x":324.5,"y":509,"wires":[]},{"id":"c709dd31.ebd05","type":"debug","z":"b25212d8.336a88","name":"","active":true,"console":"false","complete":"elr","x":520,"y":506,"wires":[]},{"id":"41393aaf.0409c4","type":"inject","z":"b25212d8.336a88","name":"Search","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":112,"y":1251,"wires":[["5819b7f9.f2929"]]},{"id":"5819b7f9.f2929","type":"function","z":"b25212d8.336a88","name":"Search","func":"//  msg.els.ip_string   // ip address (String)\n//  msg.els.tid         // TID (Int)\n//  msg.els.seoj        // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj        // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv         // ESV (Int)\n//  msg.els.epc         // EPC (Int)\n//  msg.els.edt_int     // EDT ([Int])\n\nmsg.els = {\n    \"ip\" : \"224.0.23.0\",\n    \"tid\" : 5,\n    \"seoj\" : [0x05ff,1],\n    \"deoj\" : [0x0ef0,1],\n    \"esv\" : 0x62,\n    \"epc\" : 0xD6\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":301,"y":1260,"wires":[[]]},{"id":"d1d50d8a.9a455","type":"udp out","z":"b25212d8.336a88","name":"","addr":"","iface":"","port":"3610","ipv":"udp4","outport":"","base64":false,"multicast":"multi","x":769,"y":206,"wires":[]},{"id":"816be296.50e15","type":"inject","z":"b25212d8.336a88","name":"ON","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":146,"y":206,"wires":[["30b813d1.96d6f4","8f824877.268258"]]},{"id":"7b5baebc.d748d","type":"function","z":"b25212d8.336a88","name":"Send EL1","func":"// Send ECHONET Lite packet\n// Assumption: OPC = 1\n//\n//  Usage:\n//      Input following data\n//\n//  msg.els.ip      // ip address (String)\n//  msg.els.tid     // TID (Int)\n//  msg.els.seoj    // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj    // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv     // ESV (Int)\n//  msg.els.epc     // EPC (Int)\n//  msg.els.edt     // EDT ([Int])\n//          // in case of no EDT data, no edt or []\n\nconst tid_buffer = Buffer.allocUnsafe(2);\nlet   seoj_buffer = Buffer.allocUnsafe(3);\nconst seoj = msg.els.seoj;\nlet   deoj_buffer = Buffer.allocUnsafe(3);\nconst deoj = msg.els.deoj;\nconst esv_buffer = Buffer.allocUnsafe(1);\nconst opc_buffer = Buffer.from([1]);\nconst epc_buffer = Buffer.allocUnsafe(1);\nconst pdc_buffer = Buffer.allocUnsafe(1);\nlet edt = msg.els.edt;\n\ntid_buffer.writeUInt16BE(msg.els.tid, 0);\nesv_buffer.writeUInt8(msg.els.esv, 0);\nepc_buffer.writeUInt8(msg.els.epc, 0);\n\nseoj_buffer.writeUInt16BE(seoj[0], 0);\nseoj_buffer.writeUInt8(seoj[1], 2);\ndeoj_buffer.writeUInt16BE(deoj[0], 0);\ndeoj_buffer.writeUInt8(deoj[1], 2);\n\n// edt[]からedt_bufferを作成\nif ((typeof edt) == \"undefined\") {\n    edt = [];\n}\n\nconst edt_buffer = Buffer.allocUnsafe(edt.length);\nfor (var i = 0; i < edt.length; i++) {\n    edt_buffer.writeUInt8(edt[i], i);\n}\n\npdc_buffer.writeUInt8(edt_buffer.length, 0);\n\n// Create ECHONET Lite packet\nconst el_buffer = Buffer.from([\n\t0x10, 0x81,\n\ttid_buffer[0], tid_buffer[1],\n\tseoj_buffer[0], seoj_buffer[1], seoj_buffer[2],\n\tdeoj_buffer[0], deoj_buffer[1], deoj_buffer[2],\n\tesv_buffer[0], opc_buffer[0], epc_buffer[0], pdc_buffer[0]]);\n\n// add EDT data\nif (edt_buffer.length !== 0) {\n    msg.payload = Buffer.concat([el_buffer, edt_buffer], \n        (el_buffer.length + edt_buffer.length));\n} else {\n    msg.payload = el_buffer;\n}\n\nmsg.ip = msg.els.ip;\nreturn msg;","outputs":1,"noerr":0,"x":547.5,"y":207,"wires":[["d1d50d8a.9a455","20a78033.c64988","2ecc22f4.b79f3e"]]},{"id":"20a78033.c64988","type":"debug","z":"b25212d8.336a88","name":"","active":true,"console":"false","complete":"ip","x":760,"y":262,"wires":[]},{"id":"8f824877.268258","type":"function","z":"b25212d8.336a88","name":"エアコン　ON","func":"//  msg.els.ip_string   // ip address (String)\n//  msg.els.tid         // TID (Int)\n//  msg.els.seoj        // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj        // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv         // ESV (Int)\n//  msg.els.epc         // EPC (Int)\n//  msg.els.edt_int     // EDT ([Int])\n\nmsg.els = {\n    \"ip\" : \"224.0.23.0\",\n    \"tid\" : 5,\n    \"seoj\" : [0x05ff,1],\n    \"deoj\" : [0x0130,1],\n    \"esv\" : 0x61,\n    \"epc\" : 0x80,\n    \"edt\" : [0x30]\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":348.5,"y":206,"wires":[["7b5baebc.d748d"]]},{"id":"30b813d1.96d6f4","type":"function","z":"b25212d8.336a88","name":"ライト　ON","func":"//  msg.els.ip_string   // ip address (String)\n//  msg.els.tid         // TID (Int)\n//  msg.els.seoj        // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj        // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv         // ESV (Int)\n//  msg.els.epc         // EPC (Int)\n//  msg.els.edt_int     // EDT ([Int])\n\nmsg.els = {\n    \"ip\" : \"224.0.23.0\",\n    \"tid\" : 5,\n    \"seoj\" : [0x05ff,1],\n    \"deoj\" : [0x0290,1],\n    \"esv\" : 0x61,\n    \"epc\" : 0x80,\n    \"edt\" : [0x30]\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":338.5,"y":248,"wires":[["7b5baebc.d748d"]]},{"id":"2bcde2cc.b0e0b6","type":"function","z":"b25212d8.336a88","name":"ライト　OFF","func":"//  msg.els.ip_string   // ip address (String)\n//  msg.els.tid         // TID (Int)\n//  msg.els.seoj        // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj        // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv         // ESV (Int)\n//  msg.els.epc         // EPC (Int)\n//  msg.els.edt_int     // EDT ([Int])\n\nmsg.els = {\n    \"ip\" : \"224.0.23.0\",\n    \"tid\" : 5,\n    \"seoj\" : [0x05ff,1],\n    \"deoj\" : [0x0290,1],\n    \"esv\" : 0x61,\n    \"epc\" : 0x80,\n    \"edt\" : [0x31]\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":334.5,"y":329,"wires":[["7b5baebc.d748d"]]},{"id":"46ac93c4.9bf35c","type":"function","z":"b25212d8.336a88","name":"エアコン　OFF","func":"//  msg.els.ip_string   // ip address (String)\n//  msg.els.tid         // TID (Int)\n//  msg.els.seoj        // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj        // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv         // ESV (Int)\n//  msg.els.epc         // EPC (Int)\n//  msg.els.edt_int     // EDT ([Int])\n\nmsg.els = {\n    \"ip\" : \"224.0.23.0\",\n    \"tid\" : 5,\n    \"seoj\" : [0x05ff,1],\n    \"deoj\" : [0x0130,1],\n    \"esv\" : 0x61,\n    \"epc\" : 0x80,\n    \"edt\" : [0x31]\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":348.5,"y":289,"wires":[["7b5baebc.d748d"]]},{"id":"9231a3bb.ce8bc","type":"inject","z":"b25212d8.336a88","name":"OFF","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":149,"y":290,"wires":[["2bcde2cc.b0e0b6","46ac93c4.9bf35c"]]},{"id":"2ecc22f4.b79f3e","type":"debug","z":"b25212d8.336a88","name":"","active":true,"console":"false","complete":"false","x":780,"y":308,"wires":[]},{"id":"fad08edd.8db6c8","type":"link in","z":"b25212d8.336a88","name":"UDP send","links":[],"x":605,"y":269,"wires":[["d1d50d8a.9a455"]]},{"id":"81a3a52d.f2ecd8","type":"link out","z":"b25212d8.336a88","name":"UDP receive","links":[],"x":177.5,"y":513,"wires":[]},{"id":"48da7a43.d0e234","type":"function","z":"b25212d8.336a88","name":"Search","func":"//  msg.els.ip_string   // ip address (String)\n//  msg.els.tid         // TID (Int)\n//  msg.els.seoj        // SEOJ ([Int:device, Int:instance])\n//  msg.els.deoj        // DEOJ ([Int:device, Int:instance])\n//  msg.els.esv         // ESV (Int)\n//  msg.els.epc         // EPC (Int)\n//  msg.els.edt_int     // EDT ([Int])\n\nmsg.els = {\n    \"ip\" : \"224.0.23.0\",\n    \"tid\" : 0x30f0,\n    \"seoj\" : [0x05ff,1],\n    \"deoj\" : [0x0ef0,1],\n    \"esv\" : 0x62,\n    \"epc\" : 0xd6,\n    \"edt\" : []\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":330,"y":158,"wires":[["7b5baebc.d748d"]]},{"id":"276ccd10.744a02","type":"comment","z":"b25212d8.336a88","name":"Send EL1","info":"Description: msg.elsの値を元にECHONET Liteのバイナリーデータを作成しUDP送信nodeに値を渡す\n\nAssumption: OPC = 1\n\nUsage: 以下のデータを入力します\n\n- msg.els.ip\n    - Content: ip address\n    - Data Type: String\n    - Sample: \"224.0.23.0\"\n- msg.els.tid\n    - Content: TID\n    - Data Type: Int\n    - Sample: 0x0101\n- msg.els.seoj\n    - Content: SEOJ\n    - Data Type: [Int:device, Int:instance]\n    - device: EOJ, instance: instance number\n    - Sample: [0x05ff, 0x01]\n- msg.els.deoj\n    - Content: DEOJ\n    - Data Type: [Int:device, Int:instance]\n    - device: EOJ, instance: instance number\n    - Sample: [0x0130, 0x01]\n- msg.els.esv\n    - Content: ESV\n    - Data Type: Int\n    - Sample: 0x62\n- msg.els.epc\n    - Content: EPC\n    - Data Type: Int\n    - Sample: 0x80\n- msg.els.edt\n    - Content: EDT\n    - Data Type: [Int]\n    - Sample: [0x30]\n    - Note: in case of no EDT data, []","x":546.5,"y":168,"wires":[]},{"id":"ac7445a8.9d057","type":"comment","z":"b25212d8.336a88","name":"Parse EL1","info":"Description: UDP受信nodeから渡されたデータをパースしmsg.elrとして渡す\n\nAssumption: OPC = 1\n\nUsage: 以下のデータを出力する\n\n- msg.elr.ip\n    - Content: ip address\n    - Data Type: String\n    - Sample: \"224.0.23.0\"\n- msg.elr.ehd\n    - Content: EHD\n    - Data Type: Int\n    - Sample: 0x1081\n- msg.elr.tid\n    - Content: TID\n    - Data Type: Int\n    - Sample: 0x0101\n- msg.elr.seoj\n    - Content: SEOJ\n    - Data Type: [Int:device, Int:instance]\n    - device: EOJ, instance: instance number\n    - Sample: [0x05ff, 0x01]\n- msg.elr.deoj\n    - Content: DEOJ\n    - Data Type: [Int:device, Int:instance]\n    - device: EOJ, instance: instance number\n    - Sample: [0x0130, 0x01]\n- msg.elr.esv\n    - Content: ESV\n    - Data Type: Int\n    - Sample: 0x62\n- msg.elr.opc\n    - Content: OPC\n    - Data Type: Int\n    - Sample: 0x01\n- msg.elr.epc\n    - Content: EPC\n    - Data Type: Int\n    - Sample: 0x80\n- msg.elr.pdc\n    - Content: PDC\n    - Data Type: Int\n    - Sample: 0x01\n- msg.elr.edt\n    - Content: EDT\n    - Data Type: [Int]\n    - Sample: [0x30]\n    - Note: in case of no EDT data, []","x":313,"y":419,"wires":[]}]
```