Azure Storage+ Azure Functions + SignalR で定点カメラ監視

# はじめに
Azureのサービスを利用して外部からアクセス可能な定点カメラの環境を構築したため、一例として記載します。

#構築イメージ
![observation.png](https://qiita-image-store.s3.amazonaws.com/0/109302/ed708487-0f7c-6007-4c8a-aa0452dd6f9b.png)

- カメラの画像は、Webカメラ + Raspberry Pi + Windows 10 IoT Core + UWP で定期的にAzure Storageにアップロードするようにしました。
- SignalR HubをWeb Appとして用意しました。
- Azure Functionsを
    - トリガー: Azure Storageへの格納
    - アクション: Web Appに用意したSignalR Hubに接続、送信
    するように設定しました。
- カメラを監視する側はブラウザにてAzure Storageに格納してあるindex.htmlにアクセスします。SignalR Hubに接続し、画像の更新を検知し次第、Azure Storageに画像を取得しにいきます。

# Azure Storageの設定
参考
https://docs.microsoft.com/ja-jp/azure/storage/storage-dotnet-how-to-use-blobs
http://zuvuyalink.net/nrjlog/archives/1612

Azure ポータルからストレージアカウントを作成します。
Azure FunctionsのトリガーにAzure Storageを指定するには、アカウントの種類に汎用を指定する必要がありました。
構成は以下のようにしました。

|コンテナ― |BLOB      |
|:--------|:---------|
|$root    |index.html|
|images   |image.jpg |

# Web App(SignalR)の作成
参考
http://d.hatena.ne.jp/fkmt5/20141218/1418828466
http://qiita.com/tomoyukilabs/items/81698edd5812ff6acb34
https://docs.microsoft.com/en-us/aspnet/signalr/overview/deployment/using-signalr-with-azure-web-sites
https://cmatskas.com/signalr-cross-domain-with-cors/

Azure ポータルからWeb Appを作成します。
Visual StudioにてASP .NET Web Applicationプロジェクトを作成し、Azure ポータルで作成したWeb Appに公開しました。
[github](https://github.com/kwhrkzk/Observation/tree/master/ObservationHub)
にプロジェクトを公開しています。
気を付けた点はAzure StorageとWeb Appではドメインが異なるため、[CORS (Cross-Origin Resource Sharing)](http://qiita.com/tomoyukilabs/items/81698edd5812ff6acb34)の設定が必要なことです。
[具体的](https://cmatskas.com/signalr-cross-domain-with-cors/)にはNuGet package : Microsoft.Owin.Cors の追加と
owainスタートアップのConfigurationを

```csharp
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
```

から

```csharp
public void Configuration(IAppBuilder app)
  {
    app.Map("/signalr", map =>
    {
      map.UseCors(CorsOptions.AllowAll);
      var hubConfiguration = new HubConfiguration { };
      map.RunSignalR(hubConfiguration);
    });
  }
```

に変更しました。
[それから、Azure ポータルからWeb Appの設定でWebSocketをEnableにする必要があります。](https://docs.microsoft.com/en-us/aspnet/signalr/overview/deployment/using-signalr-with-azure-web-sites)

# Azure Functionsの設定
参考
http://tech.guitarrapc.com/entry/2016/04/05/043723
https://buchizo.wordpress.com/2016/12/04/azure-functions-%E3%81%AE%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%87%E3%83%90%E3%83%83%E3%82%B0/
https://github.com/Azure/Azure-Functions/issues/203

Azure ポータルからFunction Appを作成します。
Visual Studioからプロジェクトを作成して公開できるそうなんですが、
[VS2015にAzure SDK for .NET 3.0.0 を適用したらAzure Functionsプロジェクトが使用できなくなるので注意が必要です。](https://github.com/Azure/Azure-Functions/issues/203)
Azure SDK for .NET 2.9.6に戻せば使用できるようになるそうですが、私はあきらめてAzure ポータル上で直接編集しました。
やりたいことは、Azure Storageの特定のBLOBが更新されたことをトリガーにしてWeb App(SignalR)に対して送信する、です。

```json:project.json
{
 "frameworks": {
   "net46":{
     "dependencies": {
        "Microsoft.AspNet.SignalR.Client": "2.2.1"
     }
   }
 }
}
```

```json:function.json
{
  "bindings": [
    {
      "name": "image",
      "type": "blobTrigger",
      "direction": "in",
      "path": "images/image.jpg",
      "connection": "~~~~~~~~~~~~_STORAGE"
    }
  ],
  "disabled": false
}
```

```csharp:run.csx
using Microsoft.AspNet.SignalR.Client;

public static void Run(Stream image, TraceWriter log)
{
    var conn = new HubConnection("http://~~~~~~~~~~~.azurewebsites.net/signalr/");
    var proxy = conn.CreateHubProxy("myHub1");
    conn.Start().Wait();

    proxy.Invoke("SendMessage", "")
    .Wait();

    conn.Stop();
    conn.Dispose();
}
```

connection先はそれぞれAzure StorageとWeb Appのホスト名を指定する必要があります。
注意点としては[csxでNugetパッケージを使用するためにはproject.jsonを自分で追加する必要があります。](http://tech.guitarrapc.com/entry/2016/04/05/043723)
後はAzure Storageでも書きましたが、Azure FunctionsのトリガーにAzure Storageを指定するには、アカウントの種類に汎用を指定する必要があります。

# index.htmlの設定
参考
https://cmatskas.com/signalr-cross-domain-with-cors/
http://zuvuyalink.net/nrjlog/archives/1612

ブラウザからのアクセス先を用意します。
[Azure Storageの$rootコンテナに格納します。](http://zuvuyalink.net/nrjlog/archives/1612)
すると
`http://~~~~~~~~~.blob.core.windows.net/$root/index.html`
へのアクセスが
`http://~~~~~~~~~.blob.core.windows.net/index.html`
で出来るようになります。
やりたいことは画面にAzure Storageに格納してある画像を表示することで、SignalR Hubからの更新通知もしくは10秒定期で、再取得します。
[中身](https://cmatskas.com/signalr-cross-domain-with-cors/)は

```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>定点観測</title>
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>

<![endif]-->

<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/signalr.js/2.2.1/jquery.signalR.min.js"></script>
<script src="http://~~~~~~~~~~~.azurewebsites.net/signalr/hubs"></script>

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.min.js"></script>
</head>
<body>
	<script>
	$(function(){
		$('img').attr("src", "images/image.jpg");

		$.signalR.hub.url = "http://~~~~~~~~~~~.azurewebsites.net/signalr";
		var myhub = $.signalR["myHub1"];

		var subject = new Rx.Subject();

		myhub.client.recieveMessage = function(message) {
			subject.onNext(message);
		};

		Rx.Observable.merge(
			subject
			, Rx.Observable.interval(10000/* ms */)
			).throttle(5000/* ms */)
			.subscribe(function(message) {
				$('img').attr("src", "images/image.jpg?r=" + Math.random());
			});

		$.signalR.hub.start().done(function() {
		});
	});
	</script>
	<img></img>
</body>
</html>
```

です。デフォルトだとSignalR Hubのurlは現在のドメインになっているので、明示的に指定するところがポイントでした。

# 画像送信の仕組みの設定
参考
https://docs.microsoft.com/ja-jp/azure/storage/storage-dotnet-how-to-use-blobs
https://docs.microsoft.com/ja-jp/windows/uwp/audio-video-camera/basic-photo-video-and-audio-capture-with-mediacapture

Webカメラから画像を一定周期で取得してAzure Storageの特定のBLOBに上書き格納します。
Webカメラ + Raspberry Pi + Windows 10 IoT Core + UWP 
で実装しましたがAzure Storageに格納できるならば何でも良いはずです。
[github](https://github.com/kwhrkzk/Observation/tree/master/WebCamera)
に公開しています。
Azure ポータルからAzure Storageのアクセスキーを取得し、アップロードする際に入力する必要があります。

# 月額料金について
参考
http://www.buildinsider.net/pr/microsoft/azure/dictionary05
https://azure.microsoft.com/ja-jp/pricing/calculator/
https://azure.microsoft.com/ja-jp/pricing/details/storage/blobs/

[Azure料金ツール](https://azure.microsoft.com/ja-jp/pricing/calculator/)
にて概算できますし、Azure ポータルで実績がわかります。
今回の使用方法だと

- 10秒に一回70KBのファイルをAzure Storageにアップロード
- 10秒に一回Azure Function起動(一回の起動当たり、1秒未満実行)
- 1ブラウザ当たり、10秒に一回70KBをAzure Storageからダウンロード
- Web Appは無料プランを使用

で考えていて、実際にかかった料金を見てみると一日当たり4円程度かかっている感じでした。
120円/月程度なので、仮想マシンを構築したりドメインを取得したりするよりかは安いと思います。

# Notification Hubsについて
参考
https://docs.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-push-notification-overview
https://docs.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-push-notification-faq

AzureにはNotification Hubsという通知用の仕組みがあったので、これを利用できるかなと思っていたのですがどうやらiosやandroidのようなプラットフォームに対してプッシュ通知をするための仕組みであり、ブラウザに対して通知はしてくれないみたいです。
[以下、引用](https://docs.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-push-notification-faq)
>また、Notification Hubs には、すぐに使えるブラウザー内プッシュ通知の送信サービスもありません。 これは、サポートされているサーバー側プラットフォームに SignalR を使用して実装すれば使用できます。

# 構築後の感想
外部からのアクセスを考える際はアドレスを固定するために、固定IPまたはドメイン取得+DDNS登録を考える必要が出てくると思うのですが、
今回のやり方だとアドレスが
http://~~~~~~~~~.blob.core.windows.net/index.html
と固定できるのが楽だな、と思いました。(PaaSでも気にしなくて良いはずですけど)
[認証が必要な場合は素直にWeb Appを入り口にして認証の仕組みを利用すればできそうです。](http://qiita.com/dz_/items/5c1bf63fb8d221a49296)
また、[Face API](https://azure.microsoft.com/ja-jp/services/cognitive-services/face/)なるものもあるそうで、Storageにアップロードした画像を解析＋顔検知＋スマートフォンに通知、といった仕組みもできそうです。

ひとつのWeb Appにいろんな仕組みを詰め込むよりも今回のように仕組みを分けておいたほうが拡張性やメンテナンス性が上がるなと思いました。