「AE2100」内蔵の920MHz帯無線「SmartHop」のモジュールを活用してみよう(3)　 ―パトランプ発報編―

# 要約
* この記事では、OKIのAIエッジコンピューター「AE2100」に内蔵されている920MHz帯マルチホップ無線「SmartHop」のモジュールを使用して、遠隔地にあるセンサーデータの閾値によって、遠隔地にある無線リモートI/Oの接点を制御する方法を紹介します。

* 第3回の「パトランプ発報編」では、SmartHopのMHモジュールを搭載したリモートI/Oに、SmartHopモジュールの通信フォーマットでバイナリ(電文)を送信することで、リモートI/Oに繋がったパトランプを点灯させる方法を解説します。



# はじめに
本記事は、「AE2100」内蔵の920MHz帯無線「SmartHop」のモジュールを活用する最後の記事「パトランプ発報編」です。

●[AE2100内蔵の920MHz帯無線モジュールを活用してみよう(1)―無線ネットワーク構築編―](https://qiita.com/shimatak/items/ae87081694d1bcbb3cf5)
●[AE2100内蔵の920MHz帯無線モジュールを活用してみよう(2)―データ収集編―](https://qiita.com/shimatak/items/d0ce8dc027e6f5d6cd5e)
●AE2100内蔵の920MHz帯無線モジュールを活用してみよう(3)―パトランプ発報編―　(本記事)

前回の記事では、SmartHopの無線ネットワーク経由でCO2濃度の収集・可視化をしました。
最後は、そのCO2濃度が異常値に達した時に、パトランプで警告する仕組みを実装します。
パトランプの点灯は接点を利用しますが、リモートI/OはSmartHopのMHモジュールを搭載したものを使用しますので、無線ネットワーク経由で遠隔にあるパトランプを点灯させることができます。
なお、今回はパトランプを使用しますが、接点で制御可能な換気扇や表示板などにも応用可能です。

では、お馴染みのNode-REDを使用して直観的かつ可能な限りローコードでアプリケーションの実装を行っていきましょう。




# 環境
実行環境は次の図のような構成を前提とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/5278408c-ec0e-23b1-fde3-c393d96dccd6.png)


[無線I/Oユニット子機](https://www3.panasonic.biz/ac/j/fasys/eco/cmd/enr1_io/index.jsp) (リモートI/O)と[パトランプ](https://www.patlite.co.jp/support/Discontinued%20Product.html)(LES-02AW)は、リード線を以下のように配線しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/04d2fdfe-72e8-183c-b256-81608c04770a.png)





その他の環境は[「AE2100」内蔵の920MHz帯無線「SmartHop」のモジュールを活用してみよう(1) ―無線ネットワーク構築編―](https://qiita.com/shimatak/items/ae87081694d1bcbb3cf5)をご参照ください。
また、過去の記事で紹介したSmartHopモジュールの運用開始コマンドの実行やコンテナの起動、CO2濃度を取得するためのノードの配置は完了している前提で話を進めていきます。
「なんのこっちゃ？」と思った方は、[前回の記事](https://qiita.com/shimatak/items/d0ce8dc027e6f5d6cd5e)をご確認ください。


# ノードの全体構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/eb026020-1254-3a8d-037d-10e6e1b47ca3.png)



全体的な流れとしては、[AE2100内蔵の920MHz帯無線モジュールを活用してみよう(2)―データ収集編―](https://qiita.com/shimatak/items/d0ce8dc027e6f5d6cd5e)で取得したCO2濃度の閾値に合わせて、無線I/OユニットのOUT0、OUT1、OUT2のどれかを「ON」にするためのバイナリ(電文)をSmartHopモジュールの通信フォーマットに従って、送信します。
次の見出しから、実装のポイントを解説します。


# SmartHopの通信フォーマットとModbus (RTU)通信と仕様
前回の記事の復習になりますが、SmartHopの通信フォーマットは、以下のようになっています。
以下、[無線通信モジュール/ホスト CPU間　API仕様書](https://www.oki.com/jp/920M/support/#tab02)　P.18、P.19の抜粋
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/ea838eab-5aed-a3ff-fd1b-faf6ef2f5356.png)
ここで、Payloadには無線I/Oユニットで接点をON、OFFするためのModbus RTUの電文を入れますが、
マニュアルを確認したところ、接点のON、OFFは以下の通信フォーマットで電文を送信することで実行できるようです。

[無線I/Oユニット(ENR1)](https://www3.panasonic.biz/ac/j/dl/manual/index.jsp?series_cd=3567)のマニュアルのP.151抜粋
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/ab02b1df-862c-c6f8-6068-dfd378a4dbe1.png)

この内容をSmartHopモジュールの通信フォーマットに当てはめると、以下の表のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/b05c3f4a-b4c0-319f-1343-8599e70cc164.png)



# 無線I/Oユニットを制御するnode.jsの実装
本記事で追加するfunctionノードは、CO2濃度が700PPM以上の場合は緑色(OUT0 ON)、700PPMより大きく1000PPM未満の場合は黄色(OUT1 ON)、1000以上の場合は赤色(OUT2 ON)にパトランプを点灯するように、SmartHopモジュールの通信フォーマットに従ったバイナリ(電文)を作成します。
node.jsで実装すると以下のようになります。
<details><summary>ここをクリックしてソースコードを表示</summary><div>

```diff_jsl
var buf = new Buffer(15); // バイナリの配列をつくる

//SmartHopのモジュールとホストCPU間APIの通信フォーマット(UARTインターフェースの通信フォーマット)に従って電文(バイナリ)を送る
buf[0]=0x7E;//SYN(0x7E固定) 1byte
buf[1]=0x11;//ProcessID：モジュールとホストCPU間の透過データ　 1byte
           //仕様書P.19の表5参照
buf[2]=0x00;//Length(payloadサイズ) 2byte
buf[3]=0x0A;//ここでは、10byteを指定

//無線I/Oユニットの接点制御するModbus電文(10byte)
buf[4]=0x02;//スレーブアドレス(局番) 1byte
buf[5]=0x0F;//機能コード(複数出力の書き込み)　1byte
buf[6]=0x00;//開始アドレス 2byte
buf[7]=0x00;
buf[8]=0x00;//データ数(接点数)　2byte
buf[9]=0x08;
buf[10]=0x01;//変更データのバイト数　1byte

//CO2濃度の値が700ppm以下の場合は、パトランプの緑色のみを光らせる(黄色、赤色はOFFにする)
if(msg.payload<=700){
buf[11]=0x01;//変更データ(OUT2:0 + OUT1:0 + OUT0:1 = 001[b] = 1[d])　1byte
buf[12]=0x7F;//エラーチェック(CRC-16)　2byte
buf[13]=0x40;
}
//CO2濃度の値が700ppmより大きく1000ppm未満の場合は、パトランプの黄色のみを光らせる(緑色、赤色はOFFにする)
else if(msg.payload>700&&msg.payload<1000){
buf[11]=0x02;//変更データ(OUT2:0 + OUT1:1 + OUT0:0 = 010[b] = 2[d])　1byte
buf[12]=0x3F;//エラーチェック(CRC-16)　2byte
buf[13]=0x41;
}
//CO2濃度の値が1000ppm以上の場合は、パトランプの赤色のみを光らせる(緑色、黄色はOFFにする)
else if(msg.payload>=1000){
buf[11]=0x04;//変更データ(OUT2:1 + OUT1:0 + OUT0:0 = 100[b] = 4[d])　1byte
buf[12]=0xBF;//エラーチェック(CRC-16)　2byte
buf[13]=0x43;
}
//無線I/Oユニットのバイナリ(10byte)終了

buf[14]=0x7E;//SYN(0x7E固定) 1byte

msg.payload = buf;
return msg;
```
</div></details>


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/e612528d-86ff-0688-ce89-048e51a415c3.png)


あとは、serial requestノードをSmartHop(UARTシリアル)の通信設定値に合わせて設定すれば、SmartHop経由でバイナリ(電文)の送付ができます。
設定値は、[前回の記事](https://qiita.com/kojiyuu/items/6c0c20ee6175f5d4e94b)と同じでOKです。


デバックエディターに、マニュアルのフォーマット通りのバイナリ(電文)が応答メッセージとして表示されていれば問題ありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/f0bb6883-09e5-2da7-8cc1-258dc5e6afd0.png)



# 出力結果の確認
実際に3密を防止するソリューションを動かしてみましょう。

//CO2濃度の値が700ppm以下の場合は、パトランプの緑色のみを光らせる(黄色、赤色はOFFにする)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/18b1b2e5-14e6-13a6-13ee-b679c7c82730.png)
//CO2濃度の値が700ppmより大きく1000ppm以下の場合は、パトランプの黄色のみを光らせる(緑色、赤色はOFFにする)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/133a4874-2633-b6e6-1520-80d6a4a77785.png)
//CO2濃度の値が1000ppm以上の場合は、パトランプの赤色のみを光らせる(緑色、黄色はOFFにする)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/d2823a70-5135-cbcc-116e-1608a43f872c.png)
CO2コントローラーを会議室などの人が密集しそうな場所に、パトランプを自席に設置して遠隔から3密を監視することで、社内の感染リスクを少しでも軽減できるように働きかけたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/7a2c17ff-1b3d-cc15-c9aa-d00bb768a680.png)

<br>
また、Node-REDの実装内容を若干変更すれば、以下のような大規模な3密を防止するソリューションに変更することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1620360/782ca465-3032-bf0f-01a4-c971953d3e3f.png)
まず、各階にCO2コントローラーと無線ユニット子機を配置します。
次に、1FのCO2濃度が1000ppmを超えた場合はパトランプを緑に点灯(OUT0をON)、2FのCO2濃度が超過した場合は黄色に(OUT1をON)、そして3FのCO2濃度が超過した場合は赤に点灯(OUT2をON)するようにAE2100へアプリケーションを実装します。
最後に、空調管理者がパトランプの点灯する色に合わせて、各階の空調を調節するという運用です。

本記事を最後までご覧いただいた閲覧者さまであれば、実装方法はお分かりいただけるかと思います。
ぜひご一緒に感染症対策を推進していきましょう！



# 参考
念のため、ノードフローのjsonファイルを公開します。この内容をコピペしたjsonファイルを作成して、Node-REDのパレットにドラック&ドロップすれば、一連の動作は問題なく動くはずです。動作しない場合は、[前回の記事](https://qiita.com/shimatak/items/d0ce8dc027e6f5d6cd5e)を再度ご確認ください。

```flows.json
[{"id":"d55d9079.29bed","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"e80027f1.e4ba88","type":"debug","z":"d55d9079.29bed","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":610,"y":100,"wires":[]},{"id":"c57639f0.91ab98","type":"debug","z":"d55d9079.29bed","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":870,"y":100,"wires":[]},{"id":"54e5e0f3.d2eb1","type":"serial request","z":"d55d9079.29bed","name":"serial request","serial":"5a7b30e9.8a0bb","x":420,"y":260,"wires":[["564d4ba0.ff0dd4","e80027f1.e4ba88"]]},{"id":"4d80c7a5.b693d8","type":"function","z":"d55d9079.29bed","name":"","func":"var buf = new Buffer(13); // バイナリの配列をつくる\n//SmartHopのモジュールとホストCPU間APIの\n//通信フォーマット(UARTインターフェースの通信フォーマット)に従って\n//電文(バイナリ)を送る\nbuf[0]=0x7E;//SYN(0x7E固定) 1byte\n\nbuf[1]=0x11;//ProcessID：モジュールとホストCPU間の透過データ　 1byte\n           //仕様書P.19の表5参照\n\nbuf[2]=0x00;//Length(payloadサイズ) 2byte\nbuf[3]=0x08;//ここでは、8byteを指定\n\n//CO2コントローラーの測定データ取得コマンド(8byte)を送る\nbuf[4]=0x01;//スレーブアドレス(局番) 1byte\n\nbuf[5]=0x03;//機能コード(0x03固定)　1byte\n\nbuf[6]=0x00;//データ項目 2byte\nbuf[7]=0x44;//ここでは、CO2濃度を指定\n\nbuf[8]=0x00;//データ数　2byte\nbuf[9]=0x01;//ここでは、１つのデータを指定\n\nbuf[10]=0xC4;//エラーチェック　2byte\nbuf[11]=0x1F;\n//CO2コントローラーの測定データ取得コマンド(8byte)終了\n\nbuf[12]=0x7E;//SYN(0x7E固定) 1byte\n\n \nmsg.payload = buf;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":260,"y":260,"wires":[["54e5e0f3.d2eb1","14414286.0ca43d"]]},{"id":"564d4ba0.ff0dd4","type":"function","z":"d55d9079.29bed","name":"","func":"var dec = new Buffer(1);//新しい配列を作成\nvar s1=msg.payload[7].toString(16);//[7]に格納されている数値を文字型に変換\nvar s2=msg.payload[8].toString(16);//[8]に格納されている数値を文字型に変換\nvar sum=s1+s2;//[7]に格納されている文字と[8]に格納されている文字を結合する\ndec= parseInt(sum,16);//16進数の文字を10進数の数字に変換\nmsg.payload = dec;//msg.payloadに数値を格納\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":260,"wires":[["c57639f0.91ab98","251df414.d0183c","fa263f4b.29865","cedac6e9.ed3048"]]},{"id":"251df414.d0183c","type":"ui_gauge","z":"d55d9079.29bed","name":"gauge","group":"ddfec04a.0cf5f","order":1,"width":6,"height":6,"gtype":"gage","title":"CO2濃度","label":"ppm","format":"{{value}}","min":0,"max":"2000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":850,"y":200,"wires":[]},{"id":"fa263f4b.29865","type":"ui_chart","z":"d55d9079.29bed","name":"chart","group":"ddfec04a.0cf5f","order":4,"width":9,"height":6,"label":"CO2濃度推移","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"400","ymax":"800","removeOlder":"10","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":850,"y":300,"wires":[[]]},{"id":"cbe762b5.1dff7","type":"inject","z":"d55d9079.29bed","name":"inject","props":[{"p":"payload"}],"repeat":"2","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":260,"wires":[["4d80c7a5.b693d8"]]},{"id":"14414286.0ca43d","type":"debug","z":"d55d9079.29bed","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":410,"y":100,"wires":[]},{"id":"3ea3a843.ee4078","type":"serial request","z":"d55d9079.29bed","name":"serial request","serial":"5a7b30e9.8a0bb","x":672,"y":420,"wires":[["656b77dd.ef4868"]]},{"id":"656b77dd.ef4868","type":"debug","z":"d55d9079.29bed","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":862,"y":420,"wires":[]},{"id":"cedac6e9.ed3048","type":"function","z":"d55d9079.29bed","name":"","func":"var buf = new Buffer(15); \nbuf[0]=0x7E;\nbuf[1]=0x11;\nbuf[2]=0x00;\nbuf[3]=0x0A;\nbuf[4]=0x02;\nbuf[5]=0x0F;\nbuf[6]=0x00;\nbuf[7]=0x00;\nbuf[8]=0x00;\nbuf[9]=0x08;\nbuf[10]=0x01;\nif(msg.payload<=700){\nbuf[11]=0x01;\nbuf[12]=0x7F;\nbuf[13]=0x40;\n}\nelse if(msg.payload>700&&msg.payload<1000){\nbuf[11]=0x02;\nbuf[12]=0x3F;\nbuf[13]=0x41;\n}\nelse if(msg.payload>=1000){\nbuf[11]=0x04;\nbuf[12]=0xBF;\nbuf[13]=0x43;\n}\nbuf[14]=0x7E;\nmsg.payload = buf;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":500,"y":420,"wires":[["3ea3a843.ee4078"]]},{"id":"5a7b30e9.8a0bb","type":"serial-port","z":"","serialport":"/dev/tty920M","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"/n","bin":"bin","out":"char","addchar":"","responsetimeout":"1000"},{"id":"ddfec04a.0cf5f","type":"ui_group","z":"","name":"ダッシュボード","tab":"d2e076eb.aafbd8","order":1,"disp":true,"width":18,"collapse":false},{"id":"d2e076eb.aafbd8","type":"ui_tab","z":"","name":"Qiitaデモ","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
```

# まとめ
今回は、AE2100内蔵の920MHz帯マルチホップ無線「SmartHop」モジュールを活用して、
SmartHop経由でのセンサーデータの収集・見える化～パトランプの発報までの「3密を防止するソリューション」を簡単に開発してみました。
本記事のセンサー部分は、CO2センサーを使用しましたが、センサー部分を変更することで、さまざまな情報データをAE2100へ収集し、解析することができます。
例えば、IPカメラをAE2100に繋げ、映像から車両の速度をAI解析します。
速度の閾値が超えていれば、SmartHop経由でパトランプを発報させるなどのユースケースが実際に社会実装されていたりします。(参考情報：[AISION 車両センシング](https://www.oki.com/jp/aision/car_sensing/))
本記事の反響が大きければ、次回は映像AIとSmartHopを組込み合わせた記事も検討できればと思いますので、ご興味のある方はぜひLGTM(いいね！)をお願いします。



前回の記事:
●[AE2100内蔵の920MHz帯無線モジュールを活用してみよう(1)―無線ネットワーク構築編―](https://qiita.com/shimatak/items/ae87081694d1bcbb3cf5)
●[AE2100内蔵の920MHz帯無線モジュールを活用してみよう(2)―データ収集編―](https://qiita.com/shimatak/items/d0ce8dc027e6f5d6cd5e)