Azure Iot Hubのファイルアップロード(REST API)を使って情報蓄積

# はじめに
参考
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-file-upload
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-sdks

Azure Iot Hubのファイルアップロード機能をMicrosoft Azure IoT device SDKを使用せずにREST API経由で実装したため、一例として記載します。

# 構築イメージ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109302/15268948-3b65-cdae-7975-4c77c3307bcb.png)

1. 通電してネットに繋がると、IotHubに対して自身を登録します。
2. Azure Storageの格納先をIotHubに問い合わせます。
3. Azure Storageの格納先に対してファイルをアップロードします。
4. IotHubに対してアップロード完了の通知をします。

# サンプル
[ここ](https://github.com/kwhrkzk/IoTHubDevice/blob/master/IoTHubDevice/MainPage.xaml.cs)に格納しています。
注意点としてはWebカメラの画像をアップロードしているので、Webカメラが挿さっていないと正常に動作しません。

# Iot Hubの設定
参考
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-csharp-csharp-file-upload

ファイルアップロード機能を使用するためには、Iot HubとStorageアカウントの関連付けを行う必要があります。
詳細は[ここ](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-csharp-csharp-file-upload#a-nameassociate-an-azure-storage-account-to-iot-hubaiot-hub-への-azure-storage-アカウントの関連付け)に書いていますが、Azure Portalからポチポチ設定するだけです。

# デバイス登録
参考
https://docs.microsoft.com/ja-jp/rest/api/iothub/deviceapi

Azure PortalからIot Hubにデバイスを新規に用意するのではなく、デバイス自身がREST APIにてデバイスを登録します。
REST APIは[ここ](https://docs.microsoft.com/ja-jp/rest/api/iothub/deviceapi#DeviceApi_PutDevice)に記載されています。

やり取りの例を記載します。

```text:request
Method: PUT
RequestUri: 'https://~~~~.azure-devices.net/devices/76ebd05c-e4fb-48d9-8667-d4fdf7400294?api-version=2016-11-14'
Headers:
{
  Authorization: SharedAccessSignature sr=~~~~.azure-devices.net&sig=1XPkjbRVEbf0cze9NKV9othtByyqvKw3eszWlP0nzJY%3D&se=1491361099&skn=iothubowner
  Content-Type: application/json; charset=utf-8
  Content-Length: 51
}
body:
{
 "deviceId":"76ebd05c-e4fb-48d9-8667-d4fdf7400294"
}
```

```text:response
StatusCode:OK(200),
body:
{
 "deviceId":"76ebd05c-e4fb-48d9-8667-d4fdf7400294",
 "generationId":"636268715098301906",
 "etag":"MA==",
 "connectionState":"Disconnected",
 "status":"enabled",
 "statusReason":null,
 "connectionStateUpdatedTime":"0001-01-01T00:00:00",
 "statusUpdatedTime":"0001-01-01T00:00:00",
 "lastActivityTime":"0001-01-01T00:00:00",
 "cloudToDeviceMessageCount":0,
 "authentication":{
  "symmetricKey":{
   "primaryKey":"v5kJkq1fdf0RCLC9/FJIZz0flRPFZSDT5sSjBfD9guQ=",
   "secondaryKey":"omtCS503tHiJVGjcAVRegY5TRJ+e2nKl50ElYzHdXe0="
  },
  "x509Thumbprint":{
   "primaryThumbprint":null,
   "secondaryThumbprint":null
  }
 }
}
```

76ebd05c-e4fb-48d9-8667-d4fdf7400294はデバイスIDで任意の文字列が指定可能です。今回はguidを設定しています。
リクエストのbodyはdeviceIdだけ必須です。Uriで指定するデバイスIDと同じにする必要があります。

注意点がヘッダーのAuthorizationで、この値はIoT Hubの共有アクセスポリシーの値を使用してShared Access Signatures(SAS)を生成する必要があります。

# Shared Access Signatures(SAS)の生成
参考
https://docs.microsoft.com/ja-jp/azure/storage/storage-dotnet-shared-access-signature-part-1
https://github.com/Azure/azure-iot-sdk-csharp/blob/master/service/Microsoft.Azure.Devices/Common/Security/SharedAccessSignatureBuilder.cs
http://blog.qaramell.com/papemk2/16484

SASを作成するために[SharedAccessSignatureBuilder](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/service/Microsoft.Azure.Devices/Common/Security/SharedAccessSignatureBuilder.cs)を使用します。
NugetにてMicrosoft.Azure.Devicesを取得する必要があります。

与える引数はAzure Portalから取得します。
![a.png](https://qiita-image-store.s3.amazonaws.com/0/109302/2803512c-c5b1-13ab-f12d-501921bcc73e.png)

Key -> 主キーまたはセカンダリキー
KeyName -> アクセスポリシー名
Target -> IoT Hubのホスト名(~~~~.azure-devices.net)
TimeToLive -> 有効期間(日単位)

ポリシーはデフォルトの他に追加可能でアクセス許可を細かく設定可能です。
今回はデバイス登録も行うのでiothubownerを使用しています。

この値はIot Hubとやり取りする際には常に設定する必要があります。

SharedAccessSignatureBuilderを使用せずにSASを生成する方法を探してみたのですが、
私が調べた範囲だとSDK等を使った方法しか見つけられませんでした。
(SASというものがあんまり良くわかっていません)

# ファイルアップロード先取得
参考
https://docs.microsoft.com/ja-jp/rest/api/iothub/httpruntime
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-file-upload#a-nameinitialize-a-file-uploadaファイルのアップロードの初期化

REST APIは[ここ](https://docs.microsoft.com/ja-jp/rest/api/iothub/httpruntime#HttpRuntime_CreateFileUploadSasUri)に記載されています。

やり取りの例を記載します。

```text:request
 Method: POST,
 RequestUri: 'https://~~~~.azure-devices.net/devices/76ebd05c-e4fb-48d9-8667-d4fdf7400294/files?api-version=2016-11-14',
 Headers:
 {
  Authorization: SharedAccessSignature sr=~~~~.azure-devices.net&sig=j7JtHiwPHOpoeX74o%2BoykzCC7sS1ZTsPzL3SzalEH2M%3D&se=1491366804&skn=iothubowner
  Content-Type: application/json; charset=utf-8
  Content-Length: 24
 },
 body:
 {
  "blobName":"image.jpg"
 }
```

```text:response
StatusCode:OK(200),
body:
{
 "correlationId":"MjAxNzA0MDQwNTQzXzUzMTBjNzk5LWI2ZGEtNDQzYy05NTc4LTQ3ODg0MzdjN2MyY19pbWFnZS5qcGc=",
 "hostName":"~~~.blob.core.windows.net",
 "containerName":"images",
 "blobName":"76ebd05c-e4fb-48d9-8667-d4fdf7400294/image.jpg",
 "sasToken":"?sv=2015-07-08&sr=b&sig=%2F71QLEaWg6dsYl0UfnwYUCPGbALcqxZUlEP%2F2IND2XE%3D&se=2017-04-04T05%3A33%3A21Z&sp=rw"
}
```

correlationIdは④のファイルアップロード完了通知で使用します。
HostName及びcontainerNameは事前にIot HubとStorageを関連付けた情報になります。
注意点としてはblobNameがimege.jpgからデバイスID/image.jpgになっているところです。
自分が調査した限りでは、指定したコンテナ直下にblobを格納する方法は見つけられませんでした。
(blobNameをいじってPUTしても認証エラーになった)

StorageへのファイルアップロードUriはレスポンスの情報を組み合わせて用意します。

```text:Uri
https://{hostName}/{containerName}/{blobName}{sasToken}
```

# ファイルアップロード
参考
https://docs.microsoft.com/ja-jp/rest/api/storageservices/fileservices/put-blob

REST APIは[ここ](https://docs.microsoft.com/ja-jp/rest/api/storageservices/fileservices/put-blob)に記載されています。

やり取りの例を記載します。

```text:request
Method: PUT
RequestUri: 'https://~~~.blob.core.windows.net/images/76ebd05c-e4fb-48d9-8667-d4fdf7400294/image.jpg?sv=2015-07-08&sr=b&sig=%2F71QLEaWg6dsYl0UfnwYUCPGbALcqxZUlEP%2F2IND2XE%3D&se=2017-04-04T05:33:21Z&sp=rw'
Headers:
{
  x-ms-version: 2016-05-31
  x-ms-date: 2017/04/04 4:48:04
  x-ms-blob-type: BlockBlob
  Content-Length: 57863
}
body: byteArray
```

```text:response
StatusCode:Created(201)
Headers: {
 Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
 Transfer-Encoding: chunked
 Date: Tue, 04 Apr 2017 04:48:02 GMT
 x-ms-version: 2016-05-31
 ETag: "0x8D47B15C72C57CE"
 x-ms-request-server-encrypted: false
 x-ms-request-id: b01cbeb3-0001-0015-53fe-ac70ef000000
}
body: None
```

注意点として今回のREST APIはStorage認証用のsasTokenをクエリとして付加しているので、ヘッダーにAuthorizationを指定する必要はありません。
後はblobをPUTするために必須なヘッダーがいくつかありました。(x-ms-~~~)
それから、StatusCodeはファイルアップロード完了通知のパラメータとして使用します。(オプション)

# アップロード完了通知
参考
https://docs.microsoft.com/ja-jp/rest/api/iothub/httpruntime#HttpRuntime_UpdateFileUploadStatus
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-file-upload#a-namenotify-iot-hub-of-a-completed-file-uploadaiot-hub-へのファイルのアップロード完了の通知

REST APIは[ここ](https://docs.microsoft.com/ja-jp/rest/api/iothub/httpruntime#HttpRuntime_UpdateFileUploadStatus)に記載されています。

やり取りの例を記載します。

```text:request
Method: POST
RequestUri: 'https://~~~~.azure-devices.net/devices/76ebd05c-e4fb-48d9-8667-d4fdf7400294/files/notifications?api-version=2016-11-14'
Headers:
{
  Authorization: SharedAccessSignature sr=~~~.azure-devices.net&sig=M8AK7NbpN455aBaY7grGUZUnwlunkmB1HulZhtLvusA%3D&se=1491368616&skn=iothubowner
  Content-Type: application/json; charset=utf-8
  Content-Length: 164
}
body:
{
 "correlationId":"MjAxNzA0MDQwNTQzXzUzMTBjNzk5LWI2ZGEtNDQzYy05NTc4LTQ3ODg0MzdjN2MyY19pbWFnZS5qcGc=",
 "isSuccess":true,
 "statusCode":201,
 "statusDescription":"Created"
}
```

```text:response
StatusCode:NoContent(204),
body: None
```

必須パラメータはcorrelationIdだけですが、ファイルアップロードの通知を受け取る側に成功/失敗を伝えるためにオプションのパラメータも設定しています。

# まとめ
[Microsoft Azure IoT device SDK](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-sdks)はC、.NET、Java、Node.js、Pythonといろいろな言語や環境向けに公開されています。普通に実装する分にはこれらを使用するほうが圧倒的に楽に実装できると思います。
今回は学習のためにREST APIを使用して実装してみました。
ただREST APIの使用方法さえ分かればSDKがなくてもなんとかなりそうな気がしました。

後はSASの生成方法だけ自前で実装できればどの言語でも実装できそうです。