前回の「Nest MiniでLocal Home SDKを使ってお家のIoTロボットを操作する 第2回」では、コードラボの「Smart Home Local Execution」をベースにクラウド経由での実行まで確認しました。
今回は、ローカル実行をできるようにしてみようと思います。
クラウドフルフィルメントを更新する
ローカル実行をサポートするには、otherDeviceIdsフィールドを追加する必要があります。「smarthome-local/app-start/functions」ディレクトリの 「index.js」に追加します。
「app.onSync」メソッドの 「// TODO: Add otherDeviceIds for local execution」の箇所に「otherDeviceIds」を追記します。
app.onSync((body) => { return { ... // TODO: Add otherDeviceIds for local execution otherDeviceIds: [{ deviceId: 'deviceid123', }], ... }; });
下記コマンドを実行してデプロイします。
firebase deploy --only functions
Web UIを開いていたら、「Smart Home Codelab」の右側の更新アイコンを押下してください。
ローカル実行を構成する
ローカル実行アプリをFirebase Hostingに公開し、Google Homeデバイス(Nest Miniなど)がアクセスしてダウンロードできるようにします。
Actionsコンソールにアクセスし、「piccorobo-iot」プロジェクトを選択し、 「Test > On device testing」を選択します。
「Development server URL」に、「https://<project-id>.firebaseapp.com/local-home/index.html」を入力します。
これは、以降でローカルホームアプリを公開するURLになります。
「<project-id>」は自分のプロジェクトIDに置き換えてください。
入力したら、上部の「Save」ボタンを押下してください。
次にGoogle Homeデバイス(Nest Mini)が、ローカルスマートデバイス(ピッコロボIoT)を検出する方法を定義します。
「Develop > Actions」を選択します。
「Configure local home SDK」に下記項目を設定していきます。
フィールド | 説明 | 設定値 |
---|---|---|
UDP discovery address | UDPブロードキャストアドレス | 255.255.255.255 |
UDP discovery port out | Google HomeデバイスがUDPブロードキャストを送信するポート | 3311 |
UDP discovery port in | Google Homeデバイスが応答のためにリッスンするポート | 3312 |
UDP discovery packet | UDPブロードキャストデータペイロード。(ここでは「HelloLocalHomeSDK」という文字列の16進エンコードデータとしている) | 48656c6c6f4c6f63616c486f6d6553444b |
フィールドは、ドロップダウンボックスの一覧から選択することができます。
全部入力すると、このようになります。
入力が終わったら、上部の「Save」ボタンを押下してください。
ローカル実行を実装する
「app-start/local」ディレクトリにある、 「index.ts」を編集していきます。
「app-start/local」ディレクトリは、インテントハンドラを持つローカル実行アプリプロジェクトになります。
Local Home SDKでは、Google Homeデバイス(Nest Mini)が、ローカルネットワーク上の未検証のデバイスを検出すると、IDENTIFYハンドラをトリガーします。
Google Homeデバイス(Nest Mini)にコマンドが送信されると、EXECUTEハンドラをトリガーします。
そのため、IDENTIFYハンドラとEXECUTEハンドラを 「index.ts」に実装します。
IDENTIFYハンドラは、identifyHandlerの「// TODO: Implement device identification」の箇所に、EXECUTEハンドラは、executeHandlerの「// TODO: Implement local execution」の箇所に実装します。
class LocalExecutionApp { constructor(private readonly app: App) { } identifyHandler(request: IntentFlow.IdentifyRequest): Promise<IntentFlow.IdentifyResponse> { // TODO: Implement device identification } executeHandler(request: IntentFlow.ExecuteRequest): Promise<IntentFlow.ExecuteResponse> { // TODO: Implement local execution } ... }
2019/11/20にLocal Home SDKが更新されました。identifyHandlerのリクエストデータの構造が変わっています。
詳細はLocal Home SDKのCHANGELOG.mdに記載があります。
コードラボでは前のバージョンのままなので、「app-start/local」ディレクトリにある、「package.json」を修正しておきます。
{ ... "devDependencies": { "@google/local-home-sdk": "^0.2.0", ... } }
identifyHandlerの実装を以下のようにします。
verificationIdの値は、前回設定したotherDeviceIdsのいずれかが一致する必要があります。
identifyHandler(request: IntentFlow.IdentifyRequest): Promise<IntentFlow.IdentifyResponse> { console.log("IDENTIFY intent: " + JSON.stringify(request, null, 2)); const scanData = request.inputs[0].payload.device.udpScanData; if (!scanData) { const err = new IntentFlow.HandlerError(request.requestId, 'invalid_request', 'Invalid scan data'); return Promise.reject(err); } // In this codelab, the scan data contains only local device id. const localDeviceId = Buffer.from(scanData.data, 'hex'); const response: IntentFlow.IdentifyResponse = { intent: Intents.IDENTIFY, requestId: request.requestId, payload: { device: { id: 'washer', verificationId: localDeviceId.toString(), } } }; console.log("IDENTIFY response: " + JSON.stringify(response, null, 2)); return Promise.resolve(response); }
executeHandlerの実装を以下のようにします。
ここでは、HTTP経由でローカルデバイス(ピッコロボIoT)に送信します。
※ SERVER_PORTは3388で設定しています。ローカルデバイスは3388ポートで待受ける事になります。
executeHandler(request: IntentFlow.ExecuteRequest): Promise<IntentFlow.ExecuteResponse> { console.log("EXECUTE intent: " + JSON.stringify(request, null, 2)); const command = request.inputs[0].payload.commands[0]; const execution = command.execution[0]; const response = new Execute.Response.Builder() .setRequestId(request.requestId); const promises: Array<Promise<void>> = command.devices.map((device) => { console.log("Handling EXECUTE intent for device: " + JSON.stringify(device)); // Convert execution params to a string for the local device const params = execution.params as IWasherParams; const payload = this.getDataForCommand(execution.command, params); // Create a command to send over the local network const radioCommand = new DataFlow.HttpRequestData(); radioCommand.requestId = request.requestId; radioCommand.deviceId = device.id; radioCommand.data = JSON.stringify(payload); radioCommand.dataType = 'application/json'; radioCommand.port = SERVER_PORT; radioCommand.method = Constants.HttpOperation.POST; radioCommand.isSecure = false; console.log("Sending request to the smart home device:", payload); return this.app.getDeviceManager() .send(radioCommand) .then(() => { const state = {online: true}; response.setSuccessState(device.id, Object.assign(state, params)); console.log(`Command successfully sent to ${device.id}`); }) .catch((e: IntentFlow.HandlerError) => { e.errorCode = e.errorCode || 'invalid_request'; response.setErrorState(device.id, e.errorCode); console.error('An error occurred sending the command', e.errorCode); }); }); return Promise.all(promises) .then(() => { return response.build(); }) .catch((e) => { const err = new IntentFlow.HandlerError(request.requestId, 'invalid_request', e.message); return Promise.reject(err); }); }
localディレクトリに移動し、npmで依存ライブラリを取得し、コンパイルをします。
cd local npm install npm run build
次のファイルが、「public/local-home」ディレクトリに配置されます。
- bundle.js
コンパイル済みのローカルアプリと依存関係を含むJavaScriptで出力されたファイル。 - index.html
デバイスのテスト用にアプリを提供するために使用されるローカルホスティングページ。Actionsコンソールの「Test > On device testing」で設定したURLにホストされる。
下記コマンドを実行し、Firebase Hostingにデプロイします。
firebase deploy --only hosting
仮想スマートウォッシャーを起動する
「smarthome-local/virtual-device」ディレクトリ以下にある、テスト用に用意されている仮想スマートウォッシャーを起動します。
※ 最終的にはピッコロボIoTがこれに置き換わるようにします。
「virtual-device」ディレクトリに移動し、依存ライブラリを取得します。
cd virtual-device npm install
実行時に下記パラメータを指定します。
パラメータ | 値 |
---|---|
deviceId | deviceid123 |
discoveryPortOut | 3311 |
discoveryPacket | HelloLocalHomeSDK |
projectId | 自分のプロジェクトID |
下記コマンドを実行します。
「<project-id>」は自分のプロジェクトIDに置き換えてください。
npm start -- \ --deviceId=deviceid123 --projectId=<project-id> \ --discoveryPortOut=3311 --discoveryPacket=HelloLocalHomeSDK
ローカル実行されているか確認する
Google Homeデバイス(Nest Mini)がローカルネットワーク経由で仮想デバイス(仮想スマートウォッシャー)にコマンド実行できているか確認します。
まずはGoogle Homeデバイス(Nest Mini)を再起動します。
このステップにより、デバイスはHTMLのURLと、Actionsコンソールに配置したスキャン構成を取得できます。
Chromeブラウザで新しいタブを開き、「chrome://inspect」をアドレスフィールドに入力してChromeインスペクターを起動します。
デバイスのリストが表示され(少し時間がかかります)、使用しているGoogle Homeデバイス(Nest Mini)の名前の下に、アプリのURLが表示されていると思います。
アプリのURLの下にある「inspect」をクリックして、Chrome DevToolsを起動します。
「コンソール」タブを選択し、TypeScriptアプリによって出力された、IDENTIFYインテントの内容が表示されることを確認します。
Google Homeデバイス(Nest Mini)へ下記のように話し、音声コマンドを介してコマンドを仮想デバイス(仮想スマートウォッシャー)に送信します。
話しかける時は、「OK、Google。ウォッシャーをオンにして」といった感じで実行できます。
※ 「ウォッシャー」じゃなく「洗濯機」でも操作できます。
- ウォッシャーをオンにして
- ウォッシャーをスタートして
- ウォッシャーを一時停止
- ウォッシャーを再開して
- ウォッシャーの動作を止めて
- ウォッシャーをオフにして
TypeScriptアプリによって出力された、EXECUTEインテントの内容が表示されることを確認します。
仮想デバイス(仮想スマートウォッシャー)の方では、以下のような出力がされていることを確認します。
※ ついでに、Web UI側も同期していることが確認できると思います。
info: { "on": true } info: ***** The washer is STOPPED ***** info: Report State successful info: { "isRunning": true } info: ***** The washer is RUNNING ***** info: Report State successful info: { "isRunning": false } info: ***** The washer is STOPPED ***** info: Report State successful info: { "on": false } info: ***** The washer is OFF *****
これでローカル実行ができるようになりました。
次回は仮想デバイス(仮想スマートウォッシャー)を、ピッコロボIoTに置き換えるように実装していこうと思います!