MQTTゲートウェイFujiでAWS IoTに繋いでみた


<a href="https://github.com/shiguredo/fuji">MQTTゲートウェイ Fuji</a>で証明書によるクライアント認証に対応したので、AWS IoTに繋いでみました。
＜2016/2/16:追記：Fujiが1.0.0バージョンリリースとなり、この機能が正式に取り込まれていました＞

以下の記事を大変参考にさせていただきました。ありがとうございます。

http://dev.classmethod.jp/cloud/aws/pub-sub-with-aws-iot-over-mqtt/


## まずAWS IoT側でThingを作る

以下の情報が得られます

- Thingのcertificate
- 証明書のprivate_key
- 証明書のprivate_keyに対応するpublick_key
- MQTT topic

証明書の認証のルートCAはシマンテックサイトから取得します。

```
curl -o rootCA.pem  https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem
```


## ところで、ThingsのshadowをupdateするにはどんなJSONを投げるのか？

API：http://docs.aws.amazon.com/iot/latest/developerguide//thing-shadow-mqtt.html

MQTTクライアントのmosquitto_sub, mosquitto_pubコマンドを使って動作確認してみます。

### まず、結果をモニタするためのsubscribeをしておきます

```
 mosquitto_sub -t "\$aws/things/RoomTemp/#" -h XXX.amazonaws.com -p 8883 --cafile rootCA.pem --cert UNIQUEID-certificate.pem.crt --key UNIQUEID-private.pem.key -d
 

Client mosqsub/hostname. sending SUBSCRIBE (Mid: 1, Topic: $aws/things/RoomTemp/#, QoS: 0)
```

### ではJSONデータをpublishしてみましょう。

値だけのJSONをpublishしてみた

```
Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update', ... (13 bytes))
{"value": 31}
```

すると、rejectを食らっていた

```
Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update/rejected', ... (53 bytes))
{"code":400,"message":"Missing required node: state"}
```

`desired`と`reported`だけでもダメ。

```
Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update', ... (59 bytes))
{ "desired": { "value": 30 }, "reported": { "value": 30 } }
Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update/rejected', ... (53 bytes))
{"code":400,"message":"Missing required node: state"}
```

publishするJSONは以下の形式に従う必要があります。良い子はちゃんとドキュメントを読みましょう。

http://docs.aws.amazon.com/iot/latest/developerguide//thing-shadow-document-syntax.html#thing-shadow-example-request-json

註：clientTokeとversionはオプション

```
{
    "state": {
        "desired": {
            "attribute1": integer2,
            "attribute2": "string2",
            ...
            "attributeN": boolean2
        },
        "reported": {
            "attribute1": integer1,
            "attribute2": "string1",
            ...
            "attributeN": boolean1
        }
    }
    "clientToken": "token",
    "version": version
}
```


成功例
desiredとreportedが異なる値にしてみました。

```
Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update', ... (69 bytes))
{"state":{ "desired": { "value": 30 }, "reported": { "value": 31 } }}

Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update/delta', ... (103 bytes))
{"version":4,"timestamp":1447815699,"state":{"value":30},"metadata":{"value":{"timestamp":1447815699}}}

Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update/accepted', ... (197 bytes))
{"state":{"desired":{"value":30},"reported":{"value":31}},"metadata":{"desired":{"value":{"timestamp":1447815699}},"reported":{"value":{"timestamp":1447815699}}},"version":4,"timestamp":1447815699}
```

この結果、`out of sync`の状態になります。

以下のように`desired`と`reported`が一致するとAWSのコンソールに`Shadow Status In sync`と表示されるようになります。

```
Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update', ... (69 bytes))
{"state":{ "desired": { "value": 30 }, "reported": { "value": 30 } }}

Client mosqsub/hostname. received PUBLISH (d0, q0, r0, m0, '$aws/things/RoomTemp/shadow/update/accepted', ... (197 bytes))
{"state":{"desired":{"value":30},"reported":{"value":30}},"metadata":{"desired":{"value":{"timestamp":1447815776}},"reported":{"value":{"timestamp":1447815776}}},"version":5,"timestamp":1447815776}
```

# fujiで繋げてみます

## 設定ファイル

```{awsthing.toml}
[gateway]
name = "RoomTemp"

[[broker."aws/1"]]

host = "ABCDEFG.iot.ap-northeast-1.amazonaws.com"
port = 8883
topic_prefix = "aws"

tls = true

cacert = "/AWSThing/rootCA.pem"
client_cert = "/AWSThing/XXXXX-certificate.pem.crt"
client_key = "AWSThing/XXXXX-private.pem.key"

[device."RoomTemp"]
type = "dummy"
broker = "aws"
interval = 30
qos = 0
payload = "30"
```

## 実行結果

```
$ ./fuji -c awsthing.toml -d

INFO[0000] start fuji gateway
WARN[0000] status create error, no status found
INFO[0000] broker connecting to: tcp://ABCDEF.iot.ap-northeast-1.amazonaws.com:8883
INFO[0000] start dummy device
INFO[0000] client connected
DEBU[0030] message got: {aws/RoomTemp/RoomTemp/dummy/publish}
DEBU[0030] message published: {aws/RoomTemp/RoomTemp/dummy/publish}
```


# 知見、のような物


## 証明書が無効な場合、以下のようなエラーが出るらしい

```
ERRO[0000] Failed to start MQTT client: Network Error : remote error: unknown certificate 

ERRO[0000] Failed to start MQTT client: Network Error : remote error: unknown certificate authority 
```

## どうやらwillの設定があるとうまく繋がらないらしい

もともとAWS IoTのシステムはWillをサポートせず、Shadowの状態として表現できるようなので特に問題では無いと思いますが、設定でwill_messageを指定したらつながらず、ちょっと悩みました（2015年12月下旬時点）