前回までにAzure IoT Hub経由で呼ばれたらPS4を制御する部分は実装できました。今回から呼び出す側の実装です。
※全体像および各関連記事へのリンクはこちらから
Azure IoT HubはREST APIが用意されているのでIFTTTからWebhookで直接叩きたいところですが、IFTTTのWebhookではHTTPヘッダーの設定が出来ず認証情報を渡せないのと、認証情報は都度計算が必要なShared Access Signaturesである事から、Azure Functionsでいい感じに処理できるようにします。
処理概要
IFTTTから共通アクセスキー等の情報を要求本文で受け取りShared Access Signaturesに変換してIoT Hub REST APIに送信する。
Functionsの作成
Azure Portalより、リソースの作成「+」ボタンで「Functions」を探して「作成」
あとは必要な情報を入力。
作成されたFunction Appの「関数」から「新しい機能」を押し「HTTP trigger」を選択。
今回は言語をJavaScriptにしてサーバ側もNodejsで実装します。
作成されたFunctionの「統合」でモードを「Webhook」、webhookの種類を「Generic JSON」に設定。
またソースコードが分かりやすいように要求パラメータ名を「req」から「fncReq」に変更しておきます。
Functionsの実装
IoT REST APIの認証情報はAuthorizationヘッダーにShared Access Signaturesが必要です。これは共有アクセスキーやエンドポイントから計算するもので、ソースコードが提示されているのでそのまま使います。
Node.jsでHTTPリクエストを行なう際は通常はrequestモジュールを利用するかと思いますが、Functionsで外部モジュールを使うようにする説明が面倒臭いので 今回は標準のHTTPSモジュールで実装しています。
var https = require("https"); var url = require('url'); var crypto = require("crypto"); module.exports = function (context, fncReq) { var urlInfo = url.parse(fncReq.endpoint, true); var sasToken = generateSasToken(urlInfo.host + urlInfo.path, fncReq.sharedAccessKey, fncReq.policyName, 5); var data = JSON.stringify(fncReq.data); var options = { host: urlInfo.host, method: "POST", path: urlInfo.path, headers: { "Content-Type": "application/json", "Authorization": sasToken, "Content-Length": Buffer.byteLength(data) } }; var hubReq = https.request(options, hubRes => { var body = ""; hubRes.setEncoding("utf-8"); hubRes.on("data", chunk => body = body + chunk); hubRes.on("end", () => { context.res = { status: hubRes.statusCode, body: body }; context.done(); }) }); hubReq.on("error", e => { context.res = { status: 500, body: e.message }; context.done(); }); hubReq.write(data); hubReq.end(); }; // https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-security#security-tokens var generateSasToken = function(resourceUri, signingKey, policyName, expiresInMins) { resourceUri = encodeURIComponent(resourceUri); // Set expiration in seconds var expires = (Date.now() / 1000) + expiresInMins * 60; expires = Math.ceil(expires); var toSign = resourceUri + '\n' + expires; // Use crypto var hmac = crypto.createHmac('sha256', new Buffer(signingKey, 'base64')); hmac.update(toSign); var base64UriEncoded = encodeURIComponent(hmac.digest('base64')); // Construct autorization string var token = "SharedAccessSignature sr=" + resourceUri + "&sig=" + base64UriEncoded + "&se=" + expires; if (policyName) token += "&skn="+policyName; return token; };
Functionsのテスト
Azure PortalでFunctionsを実行する機能があるので、前回作成したSmartHome Clientを起動した状態で実行してみます。要求本文は以下の通り。
{ "endpoint": "https://{IoT Hub名}.azure-devices.net/twins/raspi/methods?api-version=2016-11-14", "policyName": "service", "sharedAccessKey": "{IoT Hubの共有アクセスポリシー「service」の共有アクセスキー}", "data": { "methodName": "sendPS4Command", "payload": { "command": "start", "titleId": "CUSA02988" <-- Netflix } } }
次回はいよいよIFTTTを設定してGoogle Homeから声で操作できるようにします。