Azure の詳細
IoT デバイスをクラウドに接続する
前回は、だれかが玄関のドアベルを鳴らしたときに、プッシュ通知をモバイル デバイスに送信するために、モノのインターネット (IoT) デバイス (Raspberry Pi) をクラウド (Microsoft Azure) に接続するというアイデアについて説明しました。このアイデアは、モバイル デバイスを使用して、だれが玄関前にいるかをどこからでも確認できるというものです。
2014 年 9 月の「何から何まで: ハードウェア部品をクラウド対応のデバイスへ」(msdn.microsoft.com/magazine/dn781356) では、Microsoft Azure Mobile Services を使用してデバイスをストレージ ブロブに統合し、共有アクセス署名 (SAS: Shared Access Signature) を取得するプロセスのチュートリアルを紹介しました。このプロセスにより、デバイスは、サービス層をまったく用意しないで、ファイルをストレージ コンテナーに直接アップロードできます。これで写真をクラウドにアップロードできるようになりますが、通知をデバイスに送信するなど、まだやることは少し残っています。
このプロジェクトでは、これまで Microsoft Azure Mobile Services と Microsoft Azure Storage を利用してきました。Raspberry Pi デバイスからモバイル デバイスにメッセージを送信するには、サービス バスのキューや定期タスクといったサービスも利用する必要があります。この 2 つのサービスを Microsoft Azure Mobile Services に統合して、サービス バスのキューからメッセージを受信してデータベースに記録します。今回は、オープン ソースの新世代ソリューションの精神に倣い、MongoLab がホスト、管理する MongoDB データベースを使います。MongoDB データベースは、Azure のアドオンとして無料で追加できます。
分散メッセージング向けサービス バス
IoT コンピューティングのような状況では、サービス バスのキュー機能により強力な抽象化が実現されます。サービス バスのキューは、エンドポイント間の非同期メッセージングをサポートし、事実上すべてのワークロードをサポートするように拡張できます。また、ロジスティックやセキュリティの問題を招くおそれのある仮想ネットワークを必要としないで、クラウドとオンプレミス間のアプリ通信を実現できます。
仮想ネットワークの問題の一部は、2014 年 2 月のコラム「Windows Azure サービス バスとモノのインターネット」(msdn.microsoft.com/magazine/dn574801) で取り上げました。Microsoft Azure では、ストレージ キューという別のキュー サービスも提供されています。ストレージ キューでも、サービス バス キューと同様の機能を利用できますが、いくつか主要な方法に違いがあります。サービス バス キューを選択したのは、パブリッシャー/サブスクライバーの機能を利用でき、メッセージング処理のスケーラビリティに優れ、他のサービス バスのサービスに簡単に変換できるためです。ストレージ キューの詳細については、https://azure.microsoft.com/ja-jp/documentation/articles/storage-dotnet-how-to-use-queues/ を参照してください。
サービス バス キューは標準の RESTful API を公開し、ロング ポーリングを行ってメッセージを受信する機能をサポートします。ロング ポーリングは、一定期間 HTTP 接続を開いておくテクニックです。ロング ポーリングはタイムアウトをサポートするため、IoT コンピューティングのシナリオには優れたテクニックです。タイムアウトがあることで、デバイスでは次回のロング ポーリングまで接続を閉じ、電力消費を抑え、ネットワーク リソースを解放できるようになります。
今回は、複数の SmartDoor ドアベル デバイスを 1 つのモバイル デバイスに結び付ける、多対一のリレーションシップを利用します。具体的には、今回のパブリッシャー/サブスクライバーの形式で、メッセージ パブリッシャーを複数用意し、1 つのメッセージ サブスクライバーを結び付けます。この場合、サブスクライバーがキューから読み取りを行うモバイル サービスで、パブリッシャーが複数台の SmartDoor デバイスです。デバイスにメッセージを返信する場合は、逆のリレーションシップを構築することもできます。
IoT のシナリオでする代表的な通信パターンは 4 つあります。図 1 に示すように、1 台以上の Raspberry Pi デバイスがサービス バス キューにメッセージを送信 (パブリッシュ) します。Microsoft Azure Mobile Services がサービス バス キューの唯一のサブスクライバーになります。
図 1 Microsoft Azure Mobile Services で Microsoft Azure Service Bus から読み取りを行うアーキテクチャ図
Microsoft Azure Mobile Services には、一定間隔でタスクのスケジュールを設定できるタスク スケジューラーが統合されています。今回は 60 秒間隔でサービス バス キューから読み取りを行います。モバイル サービスをサービス バス キューに接続することについては、https://msdn.microsoft.com/ja-jp/magazine/dn574801.aspx を参照してください。doorBellListener (図 2 参照) という今回の定期タスクでは、接続文字列を指定して Node.js Azure SDK のサービス バス API を呼び出すことでキューから読み取りを行います。
図 2 Microsoft Azure Mobile Services の定期タスク (doorbellListener)
// Get a reference to the azure module
var azure = require('azure');
// Get our service bus connection string
var connectionString = process.env.ServiceBusConnectionString;
// This task will run for 60 seconds so the initial timeout should be 60
var c_Timeout = 60;
function doorbellListener() {
//Get the current unix time in seconds
var date = new Date();
var time = date.getTime();
var startSeconds = time / 1000;
var sb = azure.createServiceBusService(connectionString);
listenForMessages(c_Timeout);
// Define a function that will listen for messages
// on the queue for number of seconds
function listenForMessages(seconds) {
console.log('Doorbell Listener Started for timeout: ' + seconds);
// Long poll the service bus for seconds
sb.receiveQueueMessage("smartdoor", { timeoutIntervalInS: seconds },
function(err, data) {
if(err){
// This path will get hit if we didn't get any messages
console.log(err);
}
else{
// We have received a message from a device
var ringNotification = JSON.parse(data.body);
console.log('recieved message from doorbell ' +
ringNotification.doorbellId + '
with image link ' + ringNotification.imagePointer)
function continueListeningForMessages(){
// Go back and listen for more messages for the duration of this task
var currentDate = new Date();
var currentSeconds = currentDate.getTime() / 1000;
console.log('currentSeconds ' + currentSeconds);
console.log('startSeconds ' + startSeconds);
// Compute the seconds between when we started this scheduled task and now
// This is the time that we will long-poll the service bus
var newTimeout = Math.round((c_Timeout - (currentSeconds - startSeconds)));
if(newTimeout > 0){
// Note: the receiveQueueMessage function takes ints no decimals!!
listenForMessages(newTimeout);
}
}
Node.js の非同期性と、ロング ポーリングを使ってメッセージをサービス バスに送信する動作により、recieveQueueMessage に渡すタイムアウト値を、定期タスクの recieveQueueMessage のインスタンスの実行時間に応じて計算する必要があります。その結果、タスクのインスタンスが複数同時に実行されないようになります。
サービス バスが優れている点は、RESTful API を公開していることです。これにより、どのようなデバイスにもメッセージを送信できます。Microsoft Azure には、Python、Ruby、Node.js、C# などの主要言語用の SDK が用意されています。コードをあらゆるプラットフォーム用に変換したいので、この RESTful API を直接使用します。
メッセージを送信するには、サービス バス キュー向けのポリシーを作成する必要があります。このポリシーが、サービス バスでの特定の操作を許可するキーになります。すべてのキューには一定数のポリシーがあります。独自の対象領域ではこのことを考慮に入れます。
今回は、ユーザーがサービス バス キューにメッセージを送信することだけを許可する、DevicePolicy というポリシーを生成します (図 3 参照)。
これにより、キーが他のユーザーの手に渡ったとしても、サービス バスでメッセージをリッスンすることはできません。
図 3 メッセージをサービス バス キューに送信する DevicePolicy
メッセージをサービス バス キューに送信するための Raspberry Pi のデバイス コードを 図 4 に示します。
図 4 写真のアップロードに成功したことを示すメッセージの送信
Console.WriteLine("Sending notification to service bus queue");
WebRequest sbRequest = WebRequest.Create(
"https://smartdoordemo.servicebus.Windows.net/smartdoorqueue/messages");
var headers = sbRequest.Headers;
sbRequest.Method = "POST";
using (var sbMessageStream = sbRequest.GetRequestStream())
{
string body = JsonConvert.SerializeObject(new DoorBellNotification()
{
doorBellID = deviceID,
imageUrl = photoResp.photoId
});
var bytes = Encoding.UTF8.GetBytes(body);
sbMessageStream.Write(bytes, 0, bytes.Length);
headers.Add("Authorization", createToken(
"https://smartdoordemo.servicebus.Windows.net/smartdoorqueue/
messages", "DevicePolicy",
ConfigurationManager.AppSettings["ServiceBusSharedAccessKey"]));
}
try
{
Console.WriteLine("Sending door bell notification for " + deviceID);
using (var response = sbRequest.GetResponse())
{
Console.WriteLine("Sucessfully Sent");
}
}
catch (Exception e)
{
Console.WriteLine("Couldn't post to service bus -" + e);
}
program.cs のコード全体を確認するには、bit.ly/1qFYAF2 (英語) を参照してください。このコードでは、サービス バス キューの RESTful API で POST 要求を行い、ブロブ ストレージ用にモバイル サービスから受け取る署名と同様の SAS を提供します。この SAS は、SHA-256 アルゴリズムを使用する暗号化キーから構成されます。SAS では、アクセス可能なリソースと SAS の有効期限を定義します。createToken メソッドは、DevicePolicy ポリシーの共有アクセス キーに基づいて SAS を構築するシンプルなメソッドです。
SAS の構築後、この SAS を HTTP ヘッダーに配置し、JSON 形式でシリアル化したメッセージを要求本文に配置します。POST 要求を行うと、メッセージがサービス バス キューに送信されて、写真のアップロードに成功したことが示されます。メッセージには、アップロードしたブロブへのリンクと、アプリ設定で指定されたこのドアベル デバイスの一意の識別子を含めています。この識別子は app.config xml ファイルのドアベル リスナー実行可能ファイルの隣にある数値です。
<appSettings>
<add key="DoorBellID" value="123456"/>
...
</appSettings>
これでデバイスは、サービス バスの稼働中、メッセージをサービス バスに送信するようになります。Microsoft Azure Mobile Services の [ログ] タブに移動して、ライブ サービスのログの出力にアクセスすることができます。Visual Studio で C# コードを実行すると、ポータルのログ出力でも、モバイル サービスがメッセージのコンテンツを受信したことを確認できます。
Microsoft Azure Mobile Services は、ストレージ用に (SQL Server を基盤とする) 非の打ち所のないテーブルを提供しますが、今回は少し違うアプローチを採用し、データベース ソリューションに MongoDB を使用します。MongoDB は、ドキュメント指向のデータベースで、ストレージ内の JSON オブジェクトのコレクションに似ているため、Node.js と非常にうまく連携します。MongoDB オープン ソース プロジェクトの詳細については、http://www.mongodb.org/ を参照してください。サービスとしてのデータベース (DaaS) プロバイダーの MongoLab が今回のデータベースをホストします。
データベースでは、次の 2 つを管理します。
- DoorBells — 個別の Raspberry Pi デバイス
- Pictures — ドアベルが撮影した個別の画像
MongoDB データベースをセットアップしたら、Mongoose をモバイル サービスの Git リポジトリにインストールして、Mongoose を Microsoft Azure Mobile Services の Node.js コード用の MongoDB ドライバーとして使用することができます。Mongoose をインストールする手順は、qs ノード モジュールをインストールした手順と同じです。
npm install mongoose
ローカル リポジトリをプッシュすると、モバイル サービスがトリガーされて、Mongoose がリポジトリにインストールされます。この方法を使用して、任意の Node.js パッケージを Microsoft Azure Mobile Services に取り込むことができます。すべての Node.js モジュールと同様、RequireJs を使用してパッケージを参照し、定期タスクで MongoDB 接続文字列を使用して Mongoose を初期化できます。
var mongoose = require('mongoose');
Mongoose には、ドキュメント データベースに構造化されたスキーマを作成する役割があります (図 5 参照)。今回は、doorbell と photo という 2 つのエンティティを使用します。データベース内の doorbell オブジェクトは、それぞれ 1 つの Raspberry Pi デバイスを表します。各オブジェクトは、一意識別子 (doorBellID) と、photo オブジェクトの配列を含みます。各 photo オブジェクトには、ブロブ ストレージにある写真へのリンクと、サービス バス メッセージの受信時にサービスが生成したタイムスタンプを含みます。
図 5 Mongoose で定義されるスキーマ
var photoSchema = mongoose.Schema({
url : String,
timestamp: String
});
var doorbellSchema = mongoose.Schema({
doorBellID: String,
photos: [photoSchema]
});
var Photo = mongoose.model('Photo', photoSchema)
var DoorBell = mongoose.model('DoorBell', doorbellSchema);
// Expose these schemas
exports.DoorBell = DoorBell;
exports.Photo = Photo;
今回は exports キーワードを使用して、DoorBell と Photo のスキーマをパブリックに公開します。photoSchema が doorbellSchema 内にインターリーブされているのが分かります。これが、データベース保存時にデータを反映する方法です。
このコードをモバイル サービス リポジトリの共有フォルダーに配置します。これにより、サービスの任意の場所でスキーマを使用できるようになります。定期タスクでこのスキーマを参照する場合は、require ステートメントを使用してスキーマをインポートするだけです。
var schemas = require('../shared/schemas.js');
これで、定期タスクでこのようなスキーマを使用して、データベースの新しいエンティティを定義できるようになります。次に、MongoDB に接続していることを確認する専用の関数を使用し、コールバックを実行します。サービス バースからのメッセージ受信時に、モバイル サービスは次のことを実行する必要があります。
- メッセージで指定されている doorBellID を持つドアベルがデータベースに存在していることを確認する。
- 存在しなければ、写真を含むドアベルの新しいエンティティを作成する。
- 存在すれば、既存のドアベルの写真の配列に新しい写真を追加する。
このロジックを 図 6 のコードに示します。
図 6 ドアベルを検証して写真を管理するロジック
// Create database entry for this image
dbConnectAndExecute(function(err){
if(err){
console.log('could not record image to database -' + err);
return;
}
DoorBell.findOne({ doorBellID: ringNotification.doorBellID},
function(err, doorbell){
if(err){
return console.error(err);
}
if(doorbell === null){
console.log('Doorbell not found in DB, creating a new one');
// Take the entire body's json, assuming it fits into this schema
var entityObject = {
doorBellID : ringNotification.doorBellID,
photos : []
};
entityObject.photos.push({
url : ringNotification.imageUrl,
// Set timestamp to current server time
timestamp : ((new Date()).getTime()).toString()
})
var doorbell = new DoorBell(entityObject);
}
else{
// We already have this device in the database—add a picture
doorbell.photos.push({
url : ringNotification.imageUrl,
// Set timestamp to current server time
timestamp : ((new Date()).getTime()).toString()
});
}
// Commit changes to db
doorbell.save(function (err, entity) {
if(err){
return console.error(err);
}
console.log('sucessfully created new entity for: ' + entity);
return;
});
});
});
doorbelllistener のデモ全体を確認するには、bit.ly/VKrlYU (英語) を参照してください。
dbConnectAndExecute 関数を呼び出して、MongoLabs の MongoDB データベースに接続していることを確認します。次に、データベースにクエリして、メッセージで指定された ID を持つドアベルを照会します。クエリの結果が空の場合は、ドアベルの新しいエンティティを作成します。結果が得られた場合は、フェッチしたエンティティに写真を追加して、変更をデータベースにコミットします。
図 7 は、Raspberry Pi デバイスを使用して、写真とサービス バス メッセージを送信したときの動作を示しています。
図 7 写真とメッセージを送信した結果
Raspberry Pi がメッセージを送信した後にポータルでモバイル サービスのログを検証すると、写真が処理されてデータベースに追加されたことを確認できます。
また、MongoDB データベースをチェックして、実際に写真が確認するのも良い方法です。MongoLab には、Web ポータルを使ったリッチな MongoDB 検証インターフェイスがあります。図 8 は、写真を複数アップロードした後のドアベル エンティティの様子を示しています。
図 8 エントリを登録した MongoDB
これで、Raspberry Pi が完全にクラウド サービスに統合されます。デバイスは、ファイルをクラウド ストレージに直接アップロードし、サービス バス経由でクラウド サービスに通知して、情報をデータベースに保存できるようになりました。
次回は、画像のプッシュ通知を使用して、モバイル アプリをクラウドのバック エンドに統合します。カスタム API を使用してオブジェクトをデータベースからアプリに移動し、画像の顔認識を行うためにサードパーティ製 API を統合して、プッシュ通知を強化するために通知ハブを統合します。コードのリポジトリを調べて、自分でソリューションをビルドおよび編集することもできます。Microsoft Azure Mobile Services のバックエンドは、bit.ly/1mHCl0q (英語) から入手できます。Raspberry Pi のコードは、bit.ly/1l8aOtF (英語) にあります。
Raspberry PI デバイスのクラウド バック エンドへの統合は不可欠です。軽量なバック エンドを使用して、多くのデバイスに合わせてスケール変換するシナリオでは、Node.js などのオープン ソースのテクノロジを利用するようにします。
Microsoft Azure サービス バスは、常時接続しないデバイスが信頼性のあるメッセージング インフラストラクチャを利用できるように、安全で便利な方法を提供し、多くのクライアントを対象とするようにスケール変換することができます。NoSQL ストアを利用する方法は、データを保存する場合や、データ層とバック エンドの Node.js サービスの両方でネイティブな JavaScript 構文を利用できるようにする場合に人気があります。
Steven Edouard は、マイクロソフトの開発者エバンジェリストです。以前は、.NET Framework 4.5 および .NET Native Compilation といった製品を提供する、.NET Runtime チームのソフトウェア テスト エンジニアを務めていました。現在は、人々が技術的なデモ、オンライン コンテンツ、プレゼンテーションに取り組むことで、クラウド コンピューティング サービスの無限の可能性にワクワクしてもらうことに情熱を注いでいます。
Bruno Terkaly は、マイクロソフトの開発者エバンジェリストです。彼の豊富な知識は、数多くのプラットフォーム、言語、フレームワーク、SDK、ライブラリ および API を使用してコードを記述してきた、何年にも上る業界での経験に基づいています。彼は、特に Microsoft Azure プラットフォームを使用して、クラウド ベースのアプリケーション構築に関するコードの記述、ブログ投稿、およびライブ プレゼンテーションを行って時間を過ごしています。ブログは、blogs.msdn.com/b/brunoterkaly (英語) で公開されています。
この記事のレビューに協力してくれたマイクロソフト技術スタッフの Gil Isaacs と Brent Stineman に心より感謝いたします。