スマートホームの統合により、ユーザーの家にある接続済みデバイスを Google アシスタントを通じて制御できるようになります。スマートホーム アクションを構築するには、スマートホーム インテントを処理できるクラウド Webhook エンドポイントを用意する必要があります。たとえば、ユーザーが「OK Google, 電気をつけて」と言うと、アシスタントはクラウド フルフィルメントにコマンドを送信してデバイスの状態を更新します。
一方、Local Home SDK を使用すると、スマートホーム インテントを Google Home デバイスに直接ルーティングするローカルパスを追加できます。これによりスマートホームの統合を強化でき、ユーザー コマンドの処理の信頼性を向上させレイテンシを短縮できます。また、デバイスを識別するローカル フルフィルメント アプリを TypeScript や JavaScript で記述してデプロイし、Google Home スマート スピーカーや Google Nest スマートディスプレイでコマンドを実行することもできます。ユーザー コマンドの実行に既存の標準プロトコルを使用することで、アプリがローカルエリア ネットワーク経由で既存のスマート デバイスと直接通信することが可能になります。
前提条件
- スマートホーム アクションの作成に関するデベロッパー ガイド
- スマートホーム洗濯機の Codelab
- ローカル フルフィルメントのデベロッパー ガイド
目標
この Codelab では、これまでに構築したスマートホーム統合を Firebase にデプロイし、Actions Console でスキャン設定を適用します。さらに、TypeScript でローカルアプリをビルドし、Node.js で記述したコマンドを仮想の洗濯機デバイスに送信します。
演習内容
- Actions Console でローカル フルフィルメントを有効にして設定する
- Local Home SDK を使用してローカル フルフィルメント アプリを記述する
- Google Home スピーカーまたは Google Nest スマートディスプレイに読み込まれたローカル フルフィルメント アプリをデバッグする
必要なもの
- 最新バージョンの Google Chrome
- Google Home アプリがインストールされている iOS または Android デバイス
- Google Home スマート スピーカーまたは Google Nest スマートディスプレイ
- Node.js バージョン 10.16 以降
- Google アカウント
- Google Cloud 請求先アカウント
アクティビティ管理を有効にする
アシスタントで使用する Google アカウントで、次のアクティビティ管理を有効にします。
- ウェブとアプリのアクティビティ
- デバイス情報
- 音声アクティビティ
Actions プロジェクトを作成する
- Actions on Google Developer Console に移動します。
- [New Project](新しいプロジェクト)をクリックし、プロジェクトの名前を入力して [CREATE PROJECT](プロジェクトを作成)をクリックします。
スマートホーム アプリを選択する
Actions Console の概要画面で [Smart Home](スマートホーム)を選択します。
[Smart home](スマートホーム)エクスペリエンス カードを選択すると、プロジェクト コンソールが表示されます。
Firebase CLI をインストールする
Firebase コマンドライン インターフェース(CLI)を使用すると、ウェブアプリをローカルで提供し Firebase Hosting にデプロイできます。
CLI をインストールするには、ターミナルから次の npm コマンドを実行します。
npm install -g firebase-tools
CLI が正しくインストールされたことを確認するには、次のコマンドを実行します。
firebase --version
Google アカウントで Firebase CLI を承認するには、次のコマンドを実行します。
firebase login
HomeGraph API を有効にする
HomeGraph API を使用すると、ユーザーのホームグラフ内のデバイスとその状態を保存して照会できます。この API を使用するには、まず Google Cloud Console を開いて HomeGraph API を有効にする必要があります。
Google Cloud Console でアクションの <project-id>.
に一致するプロジェクトを選択し、HomeGraph API の [API ライブラリ] 画面で [有効にする]をクリックします。
これで開発環境の設定は完了です。スターター プロジェクトをデプロイし、すべてが正しく設定されていることを確認してください。
ソースコードを取得する
下のリンクをクリックして、この Codelab のサンプルを開発マシンにダウンロードします。
または、コマンドラインから GitHub リポジトリのクローンを作成することもできます。
git clone https://github.com/googlecodelabs/smarthome-local.git
プロジェクトについて
スターター プロジェクトには、以下のサブディレクトリが含まれています。
public
- スマート洗濯機を制御、監視するためのフロントエンド ウェブ UIfunctions
- スマートホーム アクション用のクラウド フルフィルメントを実装する Cloud Functionslocal
-index.ts
にインテント ハンドラがスタブされたローカル フルフィルメント アプリのスケルトン プロジェクト
提供されるクラウド フルフィルメントの index.js
には、以下の関数が含まれています。
fakeauth
- アカウント リンク用の認証エンドポイントfaketoken
- アカウント リンク用のトークン エンドポイントsmarthome
- スマートホーム インテントのフルフィルメント エンドポイントreportstate
- デバイスの状態が変化したときに HomeGraph API を呼び出すupdateDevice
- 仮想デバイスが Report State のトリガーに使用するエンドポイント
Firebase に接続する
app-start
ディレクトリに移動し、Actions プロジェクトに Firebase CLI を設定します。
cd app-start firebase use <project-id>
Firebase にデプロイする
functions
フォルダに移動し、npm.
を使用して必要な依存関係をすべてインストールします。
cd functions npm install
これで依存関係のインストールとプロジェクトの設定が完了し、アプリを実行する準備が整いました。
firebase deploy
コンソールに次のような出力が表示されます。
... ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/<project-id>/overview Hosting URL: https://<project-id>.firebaseapp.com
このコマンドによって、いくつかの Cloud Functions for Firebase とともにウェブアプリがデプロイされます。
ブラウザで Hosting URL(https://<project-id>.firebaseapp.com
)を開き、ウェブアプリを表示します。次のようなインターフェースが表示されます。
このウェブ UI は、デバイスの状態を表示したり変更したりするためのサードパーティ プラットフォームを表したものです。データベースへのデバイス情報の入力を開始するには、[UPDATE](更新)をクリックします。ページの表示は変化しませんが、洗濯機の現在の状態がデータベースに保存されます。
次は、Actions Console を使用して、デプロイしたクラウド サービスを Google アシスタントにリンクします。
Actions Console プロジェクトを設定する
[Overview](概要)> [Build your Action](アクションの構築)で、[Add Action(s)](アクションを追加)を選択します。スマートホーム インテントのフルフィルメントを提供する Cloud Functions の URL を入力し、[Save](保存)をクリックします。
https://us-central1-<project-id>.cloudfunctions.net/smarthome
[Develop] > [Invocation](呼び出し)タブで、アクションの [Display Name](表示名)を追加して [Save] をクリックします。この名前は Google Home アプリに表示されます。
アカウントのリンクを有効にするには、左側のナビゲーションで [Develop] > [Account linking](アカウント リンク)を選択します。以下を使用してアカウントのリンクを設定します。
クライアント ID |
|
クライアント シークレット |
|
認証 URL |
|
トークンの URL |
|
[Save] をクリックしてアカウントのリンク設定を保存し、[Test](テスト)をクリックしてプロジェクトでのテストを有効にします。
[Simulator](シミュレータ)にリダイレクトされます。[Testing on Device](デバイスでのテスト)アイコン()にカーソルを移動し、プロジェクトでテストが有効になっていることを確認します。
Google アシスタントにリンクする
スマートホーム アクションをテストするには、プロジェクトを Google アカウントにリンクする必要があります。これにより、同じアカウントにログインしている Google アシスタント画面と Google Home アプリでテストできるようになります。
- スマートフォンで Google アシスタントの設定を開きます。なお、コンソールと同じアカウントでログインする必要があります。
- [Google アシスタント] > [設定] > [スマートホーム]([アシスタント] の下)に移動します。
- 右下にあるプラス(+)アイコンを選択します。
- テストアプリが [test] 接頭辞と設定した表示名とともに表示されます。
- そのアイテムを選択します。Google アシスタントがサービスで認証を行い、
SYNC
リクエストを送信してデバイスのリストをユーザーに提供するようサービスに依頼します。
Google Home アプリを開いて、洗濯機デバイスが表示されることを確認します。
Google Home アプリで、音声コマンドを使用して洗濯機を操作できることを確認します。また、クラウド フルフィルメントのフロントエンド ウェブ UI で、デバイスの状態の変化を確認します。
これで、アクションにローカル フルフィルメントを追加できる状態になりました。
ローカル フルフィルメントをサポートするには、otherDeviceIds
というデバイスごとの新しいフィールドを、デバイス固有のローカル識別子を格納するクラウド SYNC
レスポンスに追加する必要があります。このフィールドは、デバイスをローカルに制御できるかどうかも示します。
次のコード スニペットに示すように、otherDeviceIds
フィールドを SYNC
レスポンスに追加します。
functions/index.js
app.onSync((body) => {
return {
requestId: body.requestId,
payload: {
agentUserId: '123',
devices: [{
id: 'washer',
type: 'action.devices.types.WASHER',
traits: [ ... ],
name: { ... },
deviceInfo: { ... },
willReportState: true,
attributes: {
pausable: true,
},
otherDeviceIds: [{
deviceId: 'deviceid123',
}],
}],
},
};
});
更新したプロジェクトを Firebase にデプロイします。
firebase deploy --only functions
デプロイが完了したら、ウェブ UI に移動してツールバーの更新ボタン をクリックします。これにより Request Sync 操作がトリガーされ、更新された
SYNC
レスポンス データがアシスタントに送信されます。
このセクションでは、ローカル フルフィルメントに必要な設定オプションをスマートホーム アクションに追加します。開発中は、ローカル フルフィルメント アプリを Firebase Hosting に公開し、Google Home デバイスからアクセスしてダウンロードできるようにします。
Actions Console で [Develop] > [Actions](アクション)を選択し、[Configure Local home SDK](Local Home SDK の設定)に移動します。テスト URL として次の URL を入力し、プロジェクト ID を挿入して [Save] をクリックします。
https://<project-id>.firebaseapp.com/local-home/index.html
次に、Google Home デバイスがローカルのスマート デバイスを検出する方法を定義する必要があります。ローカルホーム プラットフォームは、mDNS、UPnP、UDP のブロードキャストなど、さまざまなデバイス検出プロトコルに対応しています。ここでは、UDP ブロードキャストを使用してスマート洗濯機を検出します。
[Add Device Scan configuration](デバイス スキャン設定の追加)で、[New scan config](新しいスキャン設定)をクリックして新しいスキャン設定を追加します。プロトコルとして UDP を選択し、以下の属性を入力します。
項目 | 説明 | 推奨値 |
ブロードキャスト アドレス | UDP ブロードキャスト アドレス |
|
ブロードキャスト ポート | Google Home が UDP ブロードキャストを送信するポート |
|
リッスンポート | Google Home がレスポンスをリッスンするポート |
|
検出パケット | UDP ブロードキャスト データのペイロード |
|
最後に、ウィンドウの上部にある [Save] をクリックして変更内容を公開します。
Local Home SDK の型定義パッケージを使用して、TypeScript でローカル フルフィルメント アプリを開発します。スターター プロジェクトで提供されているスケルトンの中身を見てみましょう。
local/index.ts
/// <reference types="@google/local-home-sdk" />
import App = smarthome.App;
import Constants = smarthome.Constants;
import DataFlow = smarthome.DataFlow;
import Execute = smarthome.Execute;
import Intents = smarthome.Intents;
import IntentFlow = smarthome.IntentFlow;
...
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 fulfillment
}
...
}
const localHomeSdk = new App('1.0.0');
const localApp = new LocalExecutionApp(localHomeSdk);
localHomeSdk
.onIdentify(localApp.identifyHandler.bind(localApp))
.onExecute(localApp.executeHandler.bind(localApp))
.listen()
.then(() => console.log('Ready'))
.catch((e: Error) => console.error(e));
ローカル フルフィルメントの中心となるコンポーネントは smarthome.App
クラスです。スターター プロジェクトでは、IDENTIFY
インテントと EXECUTE
インテントのハンドラをアタッチし、listen()
メソッドを呼び出してアプリの準備ができたことを Local Home SDK に通知します。
IDENTIFY ハンドラを追加する
Local Home SDK は、Actions Console でのスキャン設定に基づいて、ローカル ネットワーク上の未確認のデバイスを検出して IDENTIFY
ハンドラをトリガーします。
一致するデバイスが検出された場合は、スキャン結果のデータに基づいて identifyHandler
を呼び出します。アプリでは、UDP ブロードキャストを使用してスキャンを行い、ローカル デバイスによって送信されたレスポンス ペイロードを含むスキャンデータを IDENTIFY
ハンドラに提供します。
ハンドラは、ローカル デバイス固有の識別子を含む IdentifyResponse
インスタンスを返します。次のコードを identifyHandler
メソッドに追加することで、ローカル デバイスからの UDP レスポンスを処理し、適切なローカル デバイス ID を特定できます。
local/index .ts
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);
}
なお、デバイスをユーザーのホームグラフのローカル フルフィルメントで利用できることを示すためには、verificationId
フィールドが SYNC
レスポンスのいずれかの otherDeviceIds
値と一致している必要があります。一致しているデバイスは検証済みとなり、ローカル フルフィルメントで利用できるものと見なされます。
EXECUTE ハンドラを追加する
Local Home SDK は、ローカル フルフィルメントをサポートするデバイスがコマンドを受け取ると EXECUTE
ハンドラをトリガーします。ローカル インテントのコンテンツは、クラウド フルフィルメントに送信される EXECUTE
インテントと同等であるため、インテントをローカル処理するためのロジックはクラウドでの処理方法と似ています。
アプリは、TCP/UDP ソケットまたは HTTP(S)リクエストを使用してローカル デバイスと通信できます。この Codelab では、プロトコルとして HTTP を使用して仮想デバイスを制御します。ポート番号は、index.ts
で SERVER_PORT
変数として定義されています。
次のコードを executeHandler
メソッドに追加することで、受信コマンドを処理して HTTP でローカル デバイスに送信できます。
local/index.ts
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);
});
}
TypeScript アプリをコンパイルする
TypeScript コンパイラをダウンロードしてアプリをコンパイルするため、local/
ディレクトリに移動して次のコマンドを実行します。
cd local npm install npm run build
これにより、index.ts
(TypeScript)のソースがコンパイルされ、以下のファイルが public/local-home/
ディレクトリに格納されます。
bundle.js
- ローカルアプリと依存関係を含むコンパイル済み JavaScript の出力。index.html
- デバイスでのテストでアプリの配信に使用するローカル ホスティング ページ。
テスト プロジェクトをデプロイする
更新したプロジェクト ファイルを Firebase Hosting にデプロイします。これにより、Google Home デバイスからアクセスできるようになります。
firebase deploy --only hosting
次に、ローカル フルフィルメント アプリとスマート洗濯機の間の通信をテストします。この Codelab スターター プロジェクトでは、Node.js で記述した仮想のスマート洗濯機を使って、スマート洗濯機のローカルでの操作をシミュレーションします。
デバイスを設定する
Actions Console でデバイス検出のスキャン設定に適用したのと同じ UDP パラメータを使用するため、仮想デバイスを設定する必要があります。また、報告するローカル デバイス ID と、デバイスの状態が変更されたときに Report State イベントに使用する Actions プロジェクト ID を、仮想デバイスに指定する必要があります。
パラメータ | 推奨値 |
deviceId |
|
discoveryPortOut |
|
discoveryPacket |
|
projectId | Actions プロジェクトの ID |
デバイスを起動する
virtual-device/
ディレクトリに移動し、引数として設定パラメータを渡してデバイス スクリプトを実行します。
cd virtual-device npm install npm start -- \ --deviceId=deviceid123 --projectId=<project-id> \ --discoveryPortOut=3311 --discoveryPacket=HelloLocalHomeSDK
デバイス スクリプトが、想定どおりのパラメータで実行されたことを確認します。
(...): UDP Server listening on 3311 (...): Device listening on port 3388 (...): Report State successful
次のセクションでは、Google Home デバイスがローカル ネットワーク経由で仮想スマート洗濯機をスキャンして適切に識別し、コマンドを送信できることを確認します。Google Chrome デベロッパー ツールを使用すると、Google Home デバイスへの接続、コンソール ログの確認、TypeScript アプリのデバッグを行うことができます。
Chrome デベロッパー ツールを接続する
次の手順に沿って、デバッガをローカル フルフィルメント アプリに接続します。
- Google Home デバイスが、Actions Console プロジェクトにアクセスできるユーザーにリンクしていることを確認します。
- Google Home デバイスを再起動します。これにより、Actions Console で設定した HTML の URL とスキャン設定が取得されます。
- 開発マシンで Chrome を起動します。
- 新しい Chrome タブを開き、アドレス フィールドに「
chrome://inspect
」と入力して、インスペクタを起動します。
ページ上にデバイスのリストが表示され、Google Home デバイスの名前の下にアプリの URL が表示されます。
インスペクタを起動する
アプリの URL の下にある [Inspect](検査)をクリックして Chrome デベロッパー ツールを起動します。[Console](コンソール)タブを選択し、TypeScript アプリによって出力された IDENTIFY
インテントの内容が表示されることを確認します。
この出力を見ると、ローカル フルフィルメント アプリが仮想デバイスを正常に検出して識別できたことがわかります。
ローカル フルフィルメントをテストする
Google Home アプリのタップ コントロールまたは音声コマンドを使用して、Google Home デバイスにコマンドを送信します。次に例を示します。
「OK Google, 洗濯機をオンにして。」
「OK Google, 洗濯を開始して。」
「OK Google, 洗濯機を止めて。」
これにより、プラットフォームから TypeScript アプリに EXECUTE
インテントが送信されます。
それぞれのコマンドによって、ローカル スマート洗濯機の状態が変化することを確認します。
... ***** The washer is RUNNING ***** ... ***** The washer is STOPPED *****
以上で、Local Home SDK を使用したローカル フルフィルメントとスマートホーム アクションの統合が完了しました。
さらに詳しく
他にも以下のことを試してみてください。
- スキャン設定を変更しても機能するかどうか。たとえば、別の UDP ポートや検出パケットを使用してみます。
- 仮想スマート デバイスのコードベースを修正する。たとえば Raspberry Pi のような組み込みデバイス上で実行できるようにしたり、LED やディスプレイを使って現在の状態を可視化したりしてみます。