ThingをDiscoverしてBeam

## はじめに
SORACOM Advent Calendar 2015の12/14分を担当させて頂きますソラコムの安川です。タイトルは予告通りThingをDiscoverしてBeamで参ります。

タイトルで何の話かピンとくる人がいますでしょうかね？射撃系武器で使徒を倒すシミュレーションを思い出した人は僕とシンクロ率が高そうですが、それとは関係なくて、今回の話は身の回りのデバイスを見つけてSORACOM Beamを使ってリモートアクセス出来るようにしよう！というお話です。

IoTと聞くとセンサーやアクチュエータの話が最初に出てきて、そんなデバイスなかなか手元にないなという人も多いんじゃないかと思いますが、意外と身の回りにはローカルエリア限定で「つながる」モノって結構あったりします。今日はそんな一例として、Universal Plug and Play (UPnP)デバイスを対象に、モノをDiscoverしてAWS IoTに繋いでみようと思います。

## 用意するもの
- SORACOM Air SIM Card
- 3G/LTEのインターフェースを持ったデバイス（ラズパイ＋ドングルでOK）
- おうちのLANと繋ぐインターフェース（WiFiでも有線でも）

うちの場合はラズパイを有線で家庭内LANに接続しつつ、SORACOM Air入りのL-05Aを挿しました。これでハードウェア的な準備は完了です。

![image](https://qiita-image-store.s3.amazonaws.com/0/84439/fda64ab7-63e6-3208-0a8f-2d6cb953e439.png)


ちなみに今日は長男と一緒に作業をしまして、ラズパイ側のnodeのセットアップや僕が手元で書いたコードを実際に走らせる部分を担当してもらいました :smile: 

![image](https://qiita-image-store.s3.amazonaws.com/0/84439/9ef553bc-7d74-4c19-813b-1bc85275d21e.png)

## 手順
おおまかに下記のような手順で進めます。
1. UPnP で Device Discovery
2. MQTT で Deviceの情報をAWS IoTにPublish
3. AWS IoTにメッセージを送ったらUPnPでコマンドを実行する

### まずは何はともあれDevice Discovery
まずはUPnPでのDevice Discoveryからはじめましょう。これで何も見つからなかったらつまらないので、プロトコルを変えた方が良いでしょうし。

ここではnode.jsで話を進めます。UPnPのDevice DiscoveryはSSDP（Simple Service Discovery Protocol)を使ってエンドポイントを探して、詳細を記述したDevice Description Documentを取得するという流れになります。

そこで`node-upnp-ssdp`と`upnp-device-client`というライブラリをインストールします。

```
$ npm install node-upnp-ssdp upnp-device-client
```

そしたら後は`ssdp.mSearch()`というメソッドでネットワーク内にMulticastでSearchメッセージを送り、返事が来たらそのデバイスのDevice Description Documentを取得していけばOKです。

```
var ssdp = require('node-upnp-ssdp');
var UPnPDeviceClient = require('upnp-device-client');

ssdp.on('DeviceFound', function(info){
  var client = new UPnPDeviceClient(info.location);
  client.getDeviceDescription(function(err, device){
    if (!err){
      console.log('UPnP: Device ' + device.friendlyName + ' found');
    } else {
      console.error('UPnP: ' + err);
    }
  });
});

ssdp.mSearch('upnp:rootdevice');
```

上記のコードを実行すれば家庭内のUPnP対応デバイスたちが見つかります。うちでは：
- Sony Bravia 
- nasne
- Canon MG6300
- Broadband Router
- Pogoplug

辺りが見つかりました。

### 見つかったデバイスをAWS IoTにPublishしよう
ここまででThingはいろいろ見つかりましたが、まだLANの中に閉じています。全然Internetしていませんね。そこでまず第一歩目としてAWS IoTにそのプレゼンスをPublishしてみたいと思います。

この場合、今コードを走らせているデバイスは家庭内のUPnPドメインへのゲートウェイとして動いているといえるので、ここではUPnP Gatewayと呼ぶことにして、その名称でAWS IoTにThingとして登録してみました。

![image](https://qiita-image-store.s3.amazonaws.com/0/84439/779409a2-fb51-dcaa-8e57-cb0c8da3fd88.png)


後はWizardにしたがって認証のための証明書や秘密鍵をダウンロードしておきます。

次はUPnP GatewayをAWS IoTに繋ぐ設定をします。ここでは動作確認のために直接クレデンシャルをUPnP Gatewayに設定してもよいですし、一気にSORACOM Beamの設定をしてもらってもどちらでも構いません。

SORACOM Beamの設定手順については[こちらに詳細](https://dev.soracom.io/jp/docs/aws_iot_guide/)があるのでご参照下さい。ドキュメントではCLIの場合の手順になってますが、コンソールでも設定可能です。

![image](https://qiita-image-store.s3.amazonaws.com/0/84439/97d87b55-2a39-a578-dd34-dd00452ee7a3.png)

一点注意点としては、LANとSORACOM Airと同時に複数のインターフェースが上がるので、デフォルトゲートウェイがSORACOM Airの方に向いていない場合はBeamのエンドポイントだけでもSORACOM Airに向くように設定する必要があります。

例：
```
$ sudo route add -host 100.127.127.100 dev ppp0
```

後は、先ほどコンソールログに出していたDevice Description DocumentをMQTTでAWS IoTにPublishしてあげます。

```
var ssdp = require('node-upnp-ssdp');
var UPnPDeviceClient = require('upnp-device-client');
var mqtt    = require('mqtt');

var mqttClient = mqtt.connect('mqtt://beam.soracom.io');

var publish = function(udn, device){
  var event = {
    reported: {
      devices: {}
    }
  };
  event.reported.devices[udn] = device;
  mqtt.publish('$aws/things/kenta-upnp-gateway/shadow/update', 
  	JSON.stringify(device));
};

ssdp.on('DeviceFound', function(info){
  var client = new UPnPDeviceClient(info.location);
  client.getDeviceDescription(function(err, device){
    if (!err){
      device.online = true;
      publish(device.UDN, device);
    } else {
      console.error('UPnP: ' + err);
    }
  });
});

ssdp.mSearch('upnp:rootdevice');
```

これでデバイスの情報（名称やメーカ名、型番やサポートしている機能など）がAWS IoTにあがります。

さらに、デバイスがオフラインになった時にはその旨を通知するようにしましょう。それにはSSDPのハンドラを1つ追加します。

```
ssdp.on('DeviceUnavailable:upnp:rootdevice', function(info){
  var udn = info.usn.split('::')[0];
  publish(createDevicePresense(udn, {online: false}));
);

```

短いコードですが、これだけで身の回りのデバイスの情報がAWS IoTに繋がりました。シンプルな例ですが、TVの電源をOff/Onしてみました。

![Kobito.TLshKP.png](https://qiita-image-store.s3.amazonaws.com/0/84439/b7962743-07e9-87c4-dc7f-6df12b52975c.png "Kobito.TLshKP.png")

![Kobito.7hx3o6.png](https://qiita-image-store.s3.amazonaws.com/0/84439/fc96dc67-371c-2d1e-a83a-55016d4c9643.png "Kobito.7hx3o6.png")

確かにonlineのtrue/falseが確認できます。

AWS IoTにつながってるので、例えばこの情報をDynamoDBにコピーするようにすればそれだけでデバイスの使用ログみたいのが出来ちゃいますね。同じくプリンタのOn/Offももちろん同様に取れました。

ちなみにこれを見た長男は「遅い時間にTVを見てたらみんなにバレちゃう！」と言ってました。鋭いじゃないか、そのとおりだよ :smirk: 

### さらにリモートからコマンドを送れるようにしてみる
UPnPが本領を発揮するのはもちろん、見つかったデバイスが提供するサービスを受ける時です。例えばTVなどMediaをRenderingする機能を持つデバイスはRenderingControlというサービスを提供するので、そのアクションを実行することで再生時のパラメータを変更できます。InternetGatewayデバイスならNATの穴あけを依頼することも可能です。

UPnPは通常LAN内で使用するプロトコルで、リモートから使われるようにはなっていませんが、今回のUPnP Gatewayを活用すればリモートからデバイスを操作する事ができます。例えば家のTVに外から写真を送って上げたり、プリンタで印刷したり、家のNASに保存したコンテンツをブラウズして、ルータのポートを開けてストリーミング再生するなんてことも原理的には出来ます。

早速やってみます。

先ほどのコードにMQTTでコマンドを受け付ける部分を足します。ここでは、`$aws/things/kenta-upnp-gateway/devices`というtopicの下にパスとして`<device ID>/<service ID>/<action ID>`という形式でUPnPアクションの特定に必要な情報を埋め込み、ペイロードに引数を入れたメッセージを受け取ったらそのアクションを実行するという方針で実装してみました。

```
var topicDevicesRoot = '$aws/things/kenta-upnp-gateway/devices'
mqttClient.on('connect', function () {
  mqttClient.subscribe(topicDevicesRoot + '/#');
});

mqttClient.on('message', function (topic, buffer) {
  if (topic.indexOf(topicDevicesRoot) === 0){
    executeAction(getContextFromTopic(topic),
    	JSON.parse(buffer.toString()));
  } else {
    console.error('MQTT: Unknown message received on topic ' + topic);
    console.error(buffer.toString());
  }
});
```

（上記はちょっと端折っているので全体を見たい方はこちらの[Gist](https://gist.github.com/kntyskw/40d144e47ed0067e125b)を御覧ください）

これで例えば、`$aws/things/kenta-upnp-gateway/devices/<TVのUUID>/RenderingControl/SetVolume`というtopicにMQTT Publishを送ればTVのボリュームがコントロール出来ちゃいます。

というわけで実際にもう1台SORACOM AirでつながっているデバイスをUPnP Gatewayと同じグループに設定して下記のコマンドを実行してみました。

```
$ mosquitto_pub -h beam.soracom.io -t '$aws/things/kenta-upnp-gateway/devices/<TVのUUID>/RenderingControl/SetVolume' -m '{"InstanceID": 0, "Channel": "Master", "DesiredVolume": 26}'
```

見事にVolumeがセットされました！

![image](https://qiita-image-store.s3.amazonaws.com/0/84439/9cf9a35b-aa4b-9259-9d58-7305164b205d.png)


その後長男は早速`{"DesiredVolume": 100}`とかやってくれました。TVから爆音が轟きましたが、正しい好奇心を持ってくれて父は嬉しいです。

![image](https://qiita-image-store.s3.amazonaws.com/0/70896/3cc7c5b7-d2a9-12b6-270c-c0bbfe3cf56d.png)


## SORACOM Beamでゲートウェイデバイスをつなぐ利点
ここまで読んで（もしくはもっと遥か手前で）、何で家庭のLANにもつないでいるのにSORACOM Airがいるの？と思ったかもしれません。ラズパイでSSL喋らなくていいとか、クレデンシャルファイルをラズパイ側に持たせなくていいというBeamの利点はもちろんあります。LANがインターネットに繋がってなくてもいいという利点もちょっと無理やりですがあるかもしれません。でももう一点あると思ってこのブログを書きました。

↑で別のデバイスからUPnP GatewayにアクセスしてTVをコントロールした際、さらっと「__そのSORACOM AirでつながっているデバイスをUPnP Gatewayと同じグループに設定して〜__」と書きましたが、実はこれにより、__デバイス同士のアクセスを許可__してるんですよね。デバイス側にクレデンシャルを持たせることなく、クラウド側の設定（グループに入れるかどうか）でデバイス間のやり取りが出来るかどうかを制御できて、かつそれはAPIを使っていつでも許可・拒否が設定可能といえます。

身の回りのモノへのアクセスをシンプルなID・パスワード認証でしてしまうと漏洩した時にちょっと怖いですし、第3者に一時的にアクセスさせて〜なんてユースケースも考えられるけど、それをセキュアに実現する仕組みは結構大変そうです（僕自身も昔リサーチプロジェクトでHome-to-Homeリモートアクセスとかで悩んでました）。SORACOMがそういったシステムを構築する際にもお役に立てばいいなと思っています。

## おわりに
今日は身の回りのローカルでのみ繋がる機器をDiscoverして、SORACOM BeamでAWS IoTにつなぐというテーマでお送りました。今回取り上げたのはUPnPでしたが、BonjourでDevice Discoveryしても同様のことが出来ると思いますし、IP上のプロトコルじゃなくてもBluetoothやZigBee, Z-wave, ANT+, CoAPなどなど、いろいろな応用が出来ると思います。そういった異なるプロトコルドメインをInternetするためのゲートウェイでもSORACOM Air, Beamを活用して頂ければ幸いです！