Azure IoT Hub に Mosquitto™ から MQTT なげてみる

# はじめに

Azure IoT Hub に MQTT クライアントを接続して、データを集めて見ようと思ったが、Azureの公式ドキュメントがいまいちわかりにくい。ちょっと時間かかったので、やったことを書いておく。

先人のエントリ[^1]を大いに参考にさせていただいた。多謝

[^1]: @ekzemplaro さんのこのエントリをかなり参考にさせていただきました → [Azure IoT Hub に mosquitto で接続](https://qiita.com/ekzemplaro/items/629054841750dd91271b)  


# Azure 側

## IoT Hub を作成する

おもむろに IoT Hub を作成する（作成には数分かかる）

```shell

$ az iot hub create -g MyResourceGroup -n HubTK101
``` 

できたか？

```shell

$ az iot hub list -o table
Location    Name      Resourcegroup    Subscriptionid
----------  --------  ---------------  ------------------------------------
westus2     HubTK101  MyResourceGroup  cf74fdf1-c705-4256-b20a-5b4a8e821537
```

## Device を作る

Device がクライアントに対応する。これで、`d0` という名前のクライアントからの接続（ `pub` or `sub` ができるようになる）

```shell

$ az iot hub device-identity create -n HubTK101 -d d0 
```

10個くらい作っておくか

```shell:create_devices.sh
for i in {1..10} ; do
  az iot hub device-identity create -n HubTK101 -d dev$i
done
```

できたか？

```shell

$ az iot hub device-identity list -n HubTK101 -o table
AuthenticationType    CloudToDeviceMessageCount    ConnectionState    DeviceEtag        DeviceId    LastActivityTime              Status    StatusUpdateTime      Version
--------------------  ---------------------------  -----------------  ----------------  ----------  ----------------------------  --------  --------------------  ---------
sas                   0                            Disconnected       NzI1MjQwNDAy      dev9        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       NzkyNzkwMDQw      dev3        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       MTAzNDM5MDE2Mw==  dev6        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       MTIwNDk2MDc3      dev8        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       MTI5NTY3NDI5      dev5        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       MzQ2OTY3MzYx      dev2        2020-04-22T09:14:55.2145856Z  enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       NzQxNTU2Mjgz      dev1        2020-04-22T04:47:08.1200577Z  enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       NzA4MzgxNDg2      dev4        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       Mjc3MzEwNzc=      dev7        0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
sas                   0                            Disconnected       MTc4ODcwMTI4      dev10       0001-01-01T00:00:00Z          enabled   0001-01-01T00:00:00Z  2
```


## Azure の Firewall で ポート `8883` を開けておく[^2]

外部からのアクセスするので、ポートを開けます。セキュリティには十分気をつけましょう。

[^2]: これいらないかも。なくても動いているように思える。IoT Hubは作った時にデフォルトでポートがあいてるかも...

## 認証

ここがちょっとめんどくさい。IoT Hub の認証方法は以下の二種類があるようだ

* SASトークン（共通鍵）による認証
* X.509 証明書による認証

今回は SASトークン を使って認証する。少し前までは、SASトークンの生成にスクリプトをかいたりしているみたいだが、[今は `az` コマンドで生成できる](https://docs.microsoft.com/en-us/cli/azure/ext/azure-cli-iot-ext/iot/hub?view=azure-cli-latest#ext-azure-cli-iot-ext-az-iot-hub-generate-sas-token)ようだ

```shell:トークン発行

az iot hub generate-sas-token -n HubTK101
{
 "sas": "SharedAccessSignature sr=HubTK101.azure-devices.net&sig=sIiyFfC7Lx2GhKhjU4KZHu3Kuz4q98eOTj%2BCTJGYyWc%3D&se=1587273711&skn=iothubowner"
}
```

* これをクライアントから送信することで、クライアントが認証される。認証の 粒度は `Hub 単位`、`クライアント単位` などいくつかの粒度で生成することができる
* ポリシーを使って認可される範囲を制御できる。この例の `iothubowner` ではフルアクセスが認可される。[その他のポリシーの詳細はこちら](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-security#access-control-and-permissions)
* トークン内の `sig` `se` `skn` `sr` の[意味はこちら](https://docs.microsoft.com/ja-jp/azure/event-hubs/authenticate-shared-access-signature#generate-a-shared-access-signature-token)

## クライアント 側

### Publish

```shell:pub.sh

mosquitto_pub -d -q 1 \
  --capath /etc/ssl/certs/ \
  -V mqttv311 \
  -p 8883 \
  -h HubTK101.azure-devices.net \
  -i dev2 \
  -u "HubTK101.azure-devices.net/dev2/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=HubTK101.azure-devices.net&sig=fbOALIqK3QUVm2hcp7ngIL%2ByeXdmSl3H8N4SeW1piZs%3D&se=1587532158&skn=iothubowner" \
  -t "devices/dev2/messages/events/" \
  -m '{"v":"hi"}'
```

こんな感じになれば動いていると思います

```
./pub.sh 
Client dev2 sending CONNECT
Client dev2 received CONNACK
Client dev2 sending PUBLISH (d0, q1, r0, m1, 'devices/dev2/messages/events/', ... (10 bytes))
Client dev2 received PUBACK (Mid: 1)
Client dev2 sending DISCONNECT
```

### Subscribe

```shell:sub.sh

mosquitto_sub -d -q 1 \
  --capath /etc/ssl/certs/ \
  -V mqttv311 \
  -p 8883 \
  -h HubTK101.azure-devices.net \
  -i dev1\
  -u "HubTK101.azure-devices.net/dev1/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=HubTK101.azure-devices.net&sig=fbOALIqK3QUVm2hcp7ngIL%2ByeXdmSl3U8N3SeW1piZs%3D&se=1587532158&skn=iothubowner" \
  -t "devices/dev1/messages/events/"
```

こんな感じになれば動いていると思います

```
./sub.sh 
Client dev1 sending CONNECT
Client dev1 received CONNACK
Client dev1 sending SUBSCRIBE (Mid: 1, Topic: devices/dev1/messages/events/, QoS: 1)
Client dev1 received SUBACK
Subscribed (mid: 1): 1
```

### IoT Hub 側でのモニタ

メッセージが流れてくるのがみえます

```shell

$ az iot hub monitor-events --hub-name HubTK101 
```

```shell

$ az iot hub monitor-events --hub-name HubTK101 --device-id dev2
```

###おまけ

10個まとめて接続テストしておく

```shell

for i in {1..10} ; do

mosquitto_pub -d -q 1 \
  --capath /etc/ssl/certs/ \
  -V mqttv311 \
  -p 8883 \
  -h HubTK101.azure-devices.net \
  -i "dev$i" \
  -u "HubTK101.azure-devices.net/dev$i/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=HubTK101.azure-devices.net&sig=YCeatCNmuqnXJ%2F0%2FOES5kI5FvHad5fdRV2f2HQC1HDk%3D&se=1587560026&skn=iothubowner" \
  -t "devices/dev$i/messages/events/" \
  -m '{"msg":"hi from dev$i"}'

done
```

### 10個のデバイス（クライアント）からしばらくの間 `publish` してみる

これは、メッセージ送るたびにコネクションをはるのであまり効率的ではないですが... `dev1` は 1分おきに、`dev2` は 2分おきに...としばらくデータを `publish` しつづけます


```shell:10pubs.sh

for i in {1..10} ; do
	
  echo "Hi, I'm dev$i - the pid is $!"

  while true; do
    interval=$(($i * 60))
    sleep $interval

    mosquitto_pub -d -q 1 \
      --capath /etc/ssl/certs/ \
      -V mqttv311 \
      -p 8883 \
      -h HubTK101.azure-devices.net \
      -i "dev$i" \
      -u "HubTK101.azure-devices.net/dev$i/api-version=2016-11-14" \
      -P "SharedAccessSignature sr=HubTK101.azure-devices.net&sig=GcFNWwkqUzslCfqn5kMiNwzEBFKFV6Cgx7lXtfQv3CE%3D&se=1587790230&skn=iothubowner" \
      -t "devices/dev$i/messages/events/" \
      -m '{"msg":"Hello"}'

  done &

done
```

`ssh` でログインしている場合で、ターミナル閉じても動かし続けたいときは `nohup` しておくといい

```shell

$ nohup ./10pubs.sh &
```

SASトークンは1時間で切れるみたいなので、定期的にリフレッシュしたい。`cron` かなにかで定期的に以下のようにして、`10pubs.sh` は SASトークン を環境変数から取得すればいいかもしれない。この場合はクライアントにも `az` コマンド入れないと

```shell:SASトークンを環境変数に設定

export SAS_TOKEN=$(az iot hub generate-sas-token -n HubTK101 | jq -r .sas)
```

# メトリック

ポータルでみることができる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32540/5d8dff27-b481-4976-d204-ba4ce622f963.png)


# あなたがみるかもしれないエラー

#### エラー1

```
Client dev2 sending CONNECT
Error: A TLS error occurred.
```

原因： `--capath` の部分の指定が間違えていた。OSのデフォルトの証明書置き場にしておくと吉


#### エラー2

```
Client dev2 sending CONNECT
Client dev2 received CONNACK
Connection Refused: not authorised.
Error: The connection was refused.
```

原因：おそらくSASトークンの有効期限がきれていた。再発行したら動いた[^3]

[^3]: トークンの `se=` の部分が有効期限（エポック表記）、ざっと計算したたところデフォルトだと発行から 60分 が有効期限。そら遊んでいると無効になるわ。 トークンを発行するコマンド（いろんな言語の関数ある）を定期的に叩くの前提と考える

#### エラー3

```
az iot hub: 'generate-sas-token' is not in the 'az iot hub' command group. See 'az iot hub --help'. If the command is from an extension, please make sure the corresponding extension is installed. To learn more about extensions, please visit https://docs.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview
```

原因：`az` コマンドに IoT Hub のエクステンションが入っていない。`az extension add --name azure-iot` しておく

# シリーズ

- [DockerとMosquitto™でMQTTに触れる](https://qiita.com/narutaro/items/e48def9dca38036f01f9)
- [MQTTを調べている](https://qiita.com/narutaro/items/1837e392ef3255c9d6b2)
- [MQTTの通信を可視化する - shiftr.io](https://qiita.com/narutaro/items/5a9102f4a2e29e4525db)
- [Azure IoT Hub に Mosquitto™ から MQTT なげてみる](https://qiita.com/narutaro/items/1a16ff1321f5044afaf0)
- [MQTT.js でブラウザから MQTTブローカ に Pub/Sub する](https://qiita.com/narutaro/items/2819ce79eb82a87357eb)