EnOceanスイッチ + Node.js + SORACOM Beam とその先

先日 ウフルさんで行われた IoTパートナーコミュニティでの総会で、EnOceanを使った投票ボタンを作ったので、そちらの報告です

## IoTパートナーコミュニティでの様子

![IMG_1747.JPG](https://qiita-image-store.s3.amazonaws.com/0/22754/6641c355-ff09-36b1-79ec-09c1c69479a0.jpeg)

## EnOceanとは？

[EnOcean](https://ja.wikipedia.org/wiki/EnOcean)とは光や温度、振動など比較的微弱なエネルギーを集めて電力に変換する環境発電技術を使った無線通信です

今回使った[オプテックス](http://www.optex.co.jp/)製 [CSW-S2-J](http://www.optex.co.jp/bems/products.php)は、ボタンを押したときの力で発電＆電波発信をするという、電池レスな製品です。しかもIPX5と、生活防水にも耐えられるので、屋外でも使えてしまう、そんな製品です

![CSW-S2-J.png](https://qiita-image-store.s3.amazonaws.com/0/22754/72e79421-dbad-8645-c9ba-b4459a034333.png)

## システム構成と解説範囲

<img src="https://docs.google.com/drawings/d/1wfa8mS8FR0YxgBN41TnCXqW817kKV9O5qsEBmJFDLJw/pub?w=1298&amp;h=276">

今回このブログで紹介するのは「責任分界点」の左側になります
右側は誰かが書いてくれることを期待しております

### 構成のポイント

* EnOceanパース＆UDP送信をしている Node.js のコードをシンプルにする
    * エッジに行けば行くほどコード変更コストが高いため。今回はエンドポイントURLが頻繁に変わるということもあり、書き換えされることが想定されていた
    * リリース時(ボタンを離したとき)のデータを上位に送らないようにしている以外のロジックを入れていません
* 3Gでの送信には UDP を利用
    * 要件的に、データ欠損が発生しても誰も死なない
        * そもそも 3G/LTE通信は下のレイヤーの信頼性が高い
    * データ量/料を削減するため
    * UDP最大ペイロードサイズ 65KB 以内に収まるデータなのでフラグメント処理不要
    * コードもシンプルにできる

## コンポーネント解説

### 3G通信モデム AK-020 ＆ Raspberry Pi 3

Raspbian上で [setup_air.sh](https://soracom-files.s3.amazonaws.com/setup_air.sh) を実行すればセットアップできます

### EnOcean受信USBモジュール USB400J

[USB400J](https://www.switch-science.com/catalog/1845/) は UARTデバイスとして `/dev/ttyUSB*` として見えます
後述する Node.js においては、このデバイスを指定するだけですので、簡単にEnOceanデバイスとの通信を実現することができます

### EnOceanパース＆UDP送信(Node.js)

`/dev/ttyUSB*` で受信したEnOceanデータを解析して、UDPで送信します

構成のポイントでも説明しましたが、リリース時(ボタンを離したとき)のデータを上位に送らないようにしている以外のロジックは入れてません

```enocean_udp_bridge.js
var host = 'beam.soracom.io';
var port = 23080;
var enoceanPort = '/dev/ttyUSB4';

var enocean = require('node-enocean-utils');
enocean.teach({
        'id'  : '00 00 00 2C BC EB',
        'eep' : 'F6-02-04',
        'name': 'CSW-S2-J',
        'manufacturer': 'OPTEX'
});


enocean.startMonitor({'path': enoceanPort});
enocean.on('data-known', (telegram) => {
        var message = telegram['message'];
        if (message['crc'] && message['value'].pressed != 0) {
                var json = JSON.stringify(message);
                console.log(json);
                var dgram = require('dgram');
                var client = dgram.createSocket("udp4");
                client.send(json, 0, json.length, port, host, function(err) {
                        client.close();
                });
        }
});
```

IDやEEPの拾い方: 製品の裏面に書いてあります

ちなみに `/dev/ttyUSB4` で狙い撃ちしているのは、後述の「ちょっと困っていること」に書いてあります

### データ転送支援サービス / SORACOM Beam

[SORACOM Beam](https://soracom.jp/services/beam/) を利用した理由は以下の通りです

#### 課題

1. enebular のエンドポイントが頻繁に変わる※  
→ **いちいち Node.js のコードは書き替えずにエンドポイントを切り替えたい**
2. 通信量&料を削減するため※にUDPを採用  
→ **UDPなデータを安全な状態 かつ enebular で受信できるプロトコルに変換したい**

これらの課題と要求を満たすために SORACOM Beam を使っています

※これは「テスト環境であるため」だからで、本番はそんなこと無いらしいです
※私個人のSIMなんで、懐具合も心配なんです
※この通信量だったら[低トラヒック向け料金「Low Data Volume」](https://blog.soracom.jp/blog/2017/05/22/how-to-buy-and-use-lowdatavolume/)で十分だった！そうすればもっと安くできたなー

#### 実際の画面

![soracom-beam.png](https://qiita-image-store.s3.amazonaws.com/0/22754/0ddbe9ab-2bed-3005-ca9a-17baee13e713.png)

SORACOM Air でつながったデバイスから `udp://beam.soracom.io:23080` に送ると `https://HOST:443/iotpc` にデータが送信されます

これならばエンドポイントが変更されてもこの画面上で変更が可能となります

SORACOM Beam から出力されるデータは下記のようなJSONとなります

```json
{
    "payload": "<BASE64 encoded 文字列>"
}
```

詳細は [SORACOM Beam](https://dev.soracom.io/jp/docs/group_configuration/#beam) をご覧ください

## 責任分界点における開発の分担

SORACOM Beam から出力される JSON を双方で共有しておけば「こういうデータが来るはずだ」という共通認識のもとで開発可能です

そのため SORACOM Beam から出てくるJSON と、Base64 decodeした後のpayloadの二つを理解しておけばOKとなります

実際に使ったドキュメントは[Google Driveで公開してます](https://docs.google.com/document/d/1F6tboC-knFEp2fGnFGtBTlnorF4p-4kfRbkcnPfLDso/edit?usp=sharing)のでご自由にご覧下さい

## ちょっと困ってること

USB400JとAK-020を同時に挿した状態で起動すると、AK-020が認識できないという現象に困っております
現在は AK-020 が認識できた後に USB400J を挿すといったworkaroundで対応してます
AK-020は `/dev/ttyUSB[0-3]` を占有するため、 USB400J が必然的に `/dev/ttyUSB4` となり、Node.jsでそれを見るようにしています
（超格好悪い）

原因は usbserial ドライバの認識順にありそうです

USB400Jは usbserial の ftdi_sio ドライバで動いているのですが、 AK-020 は usbserial の Generic なドライバで動きます。どうやら ftdi_sio のドライバが先にloadされると、Generic なほうがloadされないっぽいのです

ただ AK-020 は、最初にストレージデバイスとして認識されるタイプのデバイスであるため、それを解除→usbserialに切り替えている最中に、USB400Jが認識されてしまうという。

もう困った。

## 費用とか諸々

この構成、おおむね2日で作りました。費用については、判明している範囲で書いておきます

### 物品費用

| | 価格(税抜＆概算) |
|----|----|
| CSW-S2-J | [オプテックスさんにお問い合わせ](http://www.optex.co.jp/bems/products.php)くださいー |
| Raspberry Pi 3 Model B | 5600円 |
| USB400J | 3780円 |
| AK-020 | 4980円 |
| SORACOM Air SIMカード (日本向け/データ通信) | 1313円 |

合計: 15673円 ＋ CSW-S2-J

### SORACOM費用

| | QTY | 価格(税抜) |
|----|-----|-----------|
| SORACOM Air SIM 基本料 | 1日 | 10円 |
| SORACOM Air 通信量(上り) | 375KiB | 約0.11円 |
| 〃(下り) | 194KiB | 約0.18円 |
| SORACOM Beam(UDP受信) | 239回 | 約0.21円 |
| SORACOM Beam(HTTPS送信) | 239回 | 約0.21円 |

合計: 約11円

※Raspberry PiのNTPが結構な頻度で動いていることを忘れてしまい、下りの通信が発生してしまった。。。失敗失敗
※240回近くも押したのか。おまえら、押しすぎ

# あとがき

そのあと、飲み会参加者の人数カウントにも使われたとさ。めでたしめでたし

![19420371_10213781604360544_7838451409033050169_n.jpg](https://qiita-image-store.s3.amazonaws.com/0/22754/d33b336d-6045-46f3-56e1-fc8d19f3de1e.jpeg)