ローカルホームのデバッグ

1. 始める前に

スマートホームの統合により、ユーザーの家にある接続済みデバイスを Google アシスタントを通じて制御できるようになります。スマートホーム アクションを作成するには、スマートホーム インテントを処理できるクラウド Webhook エンドポイントを用意する必要があります。たとえば、ユーザーが「OK Google, 電気をつけて」と言うと、アシスタントはクラウド フルフィルメントにコマンドを送信してデバイスの状態を更新します。

一方、Local Home SDK を使用すると、スマートホーム インテントを Google Home デバイスに直接ルーティングするローカルパスを追加できます。これによりスマートホームの統合を強化でき、ユーザー コマンドの処理の信頼性を向上させレイテンシを短縮できます。また、デバイスを識別するローカル フルフィルメント アプリを TypeScript や JavaScript で記述してデプロイし、Google Home スマート スピーカーや Google Nest スマートディスプレイでコマンドを実行することもできます。ユーザー コマンドの実行に既存の標準プロトコルを使用することで、アプリがローカルエリア ネットワーク経由で既存のスマート デバイスと直接通信することが可能になります。

72ffb320986092c.png

スマートホーム アクションのデバッグは、本番環境品質でアクションを構築するための重要なステップですが、有益で使いやすいトラブルシューティング ツールやテストツールがないと、難しく時間もかかります。スマートホーム アクションのデバッグを容易にするために、Google Cloud Platform(GCP)の指標Logging、およびスマートホーム用のテストスイートを使用して、アクションの問題を特定して解決できます。

前提条件

作成するアプリの概要

この Codelab では、スマートホーム アクションのローカル フルフィルメントを作成してアシスタントに接続し、スマートホームと Google Cloud Platform(GCP)の指標とロギングのテストスイートを使用してローカルホーム アプリをデバッグします。

学習内容

  • GCP の指標とロギングを使用して本番環境の問題を特定し、解決する方法。
  • テストスイートを使用して機能や API の問題を特定する方法
  • ローカルホーム アプリの開発時に Chrome デベロッパー ツールを使用する方法。

必要なもの

2. 洗濯機アプリを実行する

ソースコードを取得する

下のリンクをクリックして、この Codelab のサンプルを開発マシンにダウンロードします。

または、コマンドラインから GitHub リポジトリのクローンを作成することもできます。

$ git clone https://github.com/google-home/smarthome-debug-local.git

プロジェクトについて

スターター アプリには、スマートホーム アクションのローカル フルフィルメントを有効にする Codelab と同様のサブディレクトリと Cloud Functions の関数が含まれています。ここでは、app-start の代わりに app-faulty を使用しています。まずは、ローカルの Google Home アプリについて説明します。このアプリは動作していますが、うまくいきません。

Firebase に接続する

ここでは、スマートホーム アクションのローカル フルフィルメントを有効にするの Codelab で作成したプロジェクトと同じものを使用しますが、この Codelab でダウンロードしたファイルをデプロイします。

app-faulty ディレクトリに移動し、Codelab のスマートホーム アクションのローカル フルフィルメントを有効にするで作成した Actions プロジェクトを使用して Firebase CLI を設定します。

$ cd app-faulty
$ firebase use <project-id>

Firebase にデプロイする

app-faulty/functions フォルダに移動し、npm を使用して必要な依存関係をすべてインストールします。

$ cd functions
$ npm install

注: 以下のメッセージが表示された場合は、無視して続行してください。この警告は、いくつかの古い依存関係が原因です。詳しくは、こちらをご覧ください。

found 5 high severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details

TypeScript コンパイラをダウンロードしてアプリをコンパイルするため、app-faulty/local/ ディレクトリに移動して次のコマンドを実行します。

$ cd ../local
$ npm install
$ npm run build

これにより、index.ts(TypeScript)のソースがコンパイルされ、以下のファイルが app-faulty/public/local-home/ ディレクトリに格納されます。

  • bundle.js - ローカルアプリと依存関係を含むコンパイル済み JavaScript の出力。
  • index.html - デバイスでのテストでアプリの配信に使用するローカル ホスティング ページ。

これで依存関係のインストールとプロジェクトの設定が完了し、アプリを実行する準備が整いました。

$ firebase deploy

コンソールに次のような出力が表示されます。

...

✔ Deploy complete!

Project Console: https://console.firebase.google.com/project/<project-id>/overview
Hosting URL: https://<projectcd -id>.web.app

このコマンドは、いくつかの Cloud Functions for Firebase とともにウェブアプリをデプロイします。

ホームグラフを更新する

ブラウザで Hosting URLhttps://<project-id>.web.app)を開いてウェブアプリを表示します。ウェブ UI で [Refresh] ae8d3b25777a5e30.png ボタンをクリックして、[Request Sync] で HomeGraph を更新し、不具合のある洗濯機アプリの最新のデバイス メタデータを使用します。

fa3c47f293cfe0b7.png

Google Home アプリを開き、「洗濯機の不良」という新しい名前で洗濯機が表示されることを確認します。Google Nest デバイスがある部屋にデバイスを割り当てるようにしてください。

2a082ee11d47ad1a.png

3. スマート洗濯機を起動する

スマートホーム アクションのローカル フルフィルメントを有効にする」Codelab をすでに実行している場合、仮想スマート洗濯機はすでに開始しているはずです。停止している場合は、仮想デバイスを再起動してください。

デバイスを起動する

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

4. ローカル Home アプリをテストする

Google Home デバイスに音声コマンドで次のようなコマンドを送信できます。

「OK Google, 洗濯機をオンにして」

「OK Google, 洗濯機をスタートして」

「OK Google, 強制ローカル情報」

「OK Google, 洗濯機を止めて」

「強制ローカル」の後に洗濯機を操作しようとすると、Google アシスタントが「欠陥のある洗濯機は現在利用できないようです」という応答が表示されます。

これは、ローカルパス経由でデバイスに到達できないことを意味します。デバイスがローカルパスでアクセスできない場合はクラウドパスを使用するようフォールバックするため、「OK Google, ローカルを強制して」と言う前は正常に機能していました。ただし、「ローカルに強制」後は、クラウドパスにフォールバックするオプションは無効になります。

何が問題かを特定するため、Google Cloud Platform(GCP)の指標Logging、Chrome デベロッパー ツールという Google のツールを活用しましょう。

5. ローカルホーム アプリをデバッグする

次のセクションでは、Google が提供するツールを使用して、ローカルパス経由でデバイスにアクセスできない理由を調べます。Google Chrome デベロッパー ツールを使用すると、Google Home デバイスへの接続、コンソールログの表示、ローカル Home アプリのデバッグを行うことができます。また、カスタムログを Cloud Logging に送信して、ユーザーがローカル Home アプリで検出している上位エラーを把握することもできます。

Chrome デベロッパー ツールを接続する

次の手順に沿って、デバッガをローカル フルフィルメント アプリに接続します。

  1. Actions Console プロジェクトへのアクセス権限を持つユーザーに Google Home デバイスをリンクしていることを確認します。
  2. Google Home デバイスを再起動します。これにより、Actions Console で設定した HTML の URL とスキャン設定が取得されます。
  3. 開発マシンで Chrome を起動します。
  4. 新しい Chrome タブを開き、アドレス フィールドに「chrome://inspect」と入力して、インスペクタを起動します。

ページ上にデバイスのリストが表示され、Google Home デバイスの名前の下にアプリの URL が表示されます。

567f97789a7d8846.png

インスペクタを起動する

アプリの URL の下にある [Inspect](検査)をクリックして Chrome デベロッパー ツールを起動します。[Console](コンソール)タブを選択し、TypeScript アプリによって出力された IDENTIFY インテントの内容が表示されることを確認します。

774c460c59f9f84a.png

この出力は、IDENTIFY ハンドラが正常にトリガーされたものの、IdentifyResponse で返された verificationId が HomeGraph のどのデバイスにも一致しないことを意味します。カスタムログを追加して原因を調べましょう。

カスタムログを追加する

Local Home SDK によって DEVICE_VERIFICATION_FAILED エラーが出力されていますが、根本原因の特定にはあまり役立ちません。スキャンデータが正しく読み取られて処理されるように、カスタムログをいくつか追加しましょう。また、Promise をエラーで拒否すると、実際にはエラー メッセージが Cloud Logging にも送信されることに注意してください。

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.
  // Is there something wrong here?
  const localDeviceId = Buffer.from(scanData.data);
  console.log(`IDENTIFY handler: received local device id
      ${localDeviceId}`);

  // Add custom logs
  if (!localDeviceId.toString().match(/^deviceid[0-9]{3}$/gi)) {
    const err = new IntentFlow.HandlerError(request.requestId,
        'invalid_device', 'Invalid device id from scan data ' +
        localDeviceId);
    return Promise.reject(err);
  }

  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);
}

また、正しいバージョンを使用しているかどうかを確認できるように、ローカルの Google Home アプリのバージョンも変更します。

local/index.ts

const localHomeSdk = new App('1.0.1');

カスタムログを追加したら、アプリを再度コンパイルして Firebase に再デプロイする必要があります。

$ cd ../app-faulty/local
$ npm run build
$ firebase deploy --only hosting

次に、Google Home デバイスを再起動して、更新したローカル Home アプリを読み込みます。Google Home デバイスが想定どおりのバージョンを使用しているかどうかは、Chrome デベロッパー ツールのコンソールログで確認できます。

ecc56508ebcf9ab.png

Cloud Logging にアクセスする

次に、Cloud Logging を使用してエラーを見つける方法を見ていきましょう。プロジェクトの Cloud Logging にアクセスする手順は次のとおりです。

  1. Cloud Platform コンソールで [プロジェクト] ページに移動します。
  2. スマートホーム プロジェクトを選択します。
  3. [オペレーション] で、[ロギング] > [ログ エクスプローラ] を選択します。

ロギングデータへのアクセスは、Actions プロジェクトのユーザーの Identity and Access Management(IAM)によって管理されます。ロギングデータのロールと権限の詳細については、Cloud Logging のアクセス制御をご覧ください。

高度なフィルタを使用する

ローカル デバイスの識別に失敗し、ローカルパスが機能していないことが原因で、IDENTIFY インテントでエラーが発生しています。しかし、正確にはどのような問題なのかを知るために、まず IDENTIFY ハンドラで発生するエラーを除外します。

[クエリのプレビュー] ボックスを展開すると、[クエリビルダー] ボックスに変わります。[クエリビルダー] ボックスに「jsonPayload.intent="IDENTIFY"」と入力し、[クエリを実行] ボタンをクリックします。

4c0b9d2828ee2447.png

その結果、IDENTIFY ハンドラでスローされるすべてのエラーログを取得します。次に、最後のエラーを展開します。IDENTIFY ハンドラで Promise を拒否したときに設定した errorCodedebugString があります。

71f2f156c6887496.png

debugString から、ローカル デバイス ID が適切な形式ではないことがわかります。ローカル Home アプリは、deviceid の後に 3 桁の数字が続く文字列としてローカル デバイス ID を取得することを想定していますが、このローカル デバイス ID は 16 進数の文字列です。

エラーを修正する

スキャンデータからローカル デバイス ID を解析するソースコードに戻ると、文字列をバイトに変換する際にエンコードを指定していないことがわかります。スキャンデータは 16 進文字列として受信されるため、Buffer.from() を呼び出すときに文字エンコードとして hex を渡します。

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');
  console.log(`IDENTIFY handler: received local device id
      ${localDeviceId}`);

  if (!localDeviceId.toString().match(/^deviceid[0-9]{3}$/gi)) {
    const err = new IntentFlow.HandlerError(request.requestId,
      'invalid_device', 'Invalid device id from scan data ' +
      localDeviceId);
    return Promise.reject(err);
  }

  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);
}

また、正しいバージョンを使用しているかどうかを確認できるように、ローカルの Google Home アプリのバージョンも変更します。

local/index.ts

const localHomeSdk = new App('1.0.2');

エラーを修正したら、アプリをコンパイルして Firebase に再デプロイします。app-faulty/local で、以下を実行します。

$ npm run build
$ firebase deploy --only hosting

修正をテストする

デプロイ後、Google Home デバイスを再起動して、更新したローカル Home アプリを読み込めるようにします。ローカルのホームアプリのバージョンが 1.0.2 であることを確認します。今回は Chrome デベロッパー ツール コンソールにエラーが表示されないはずです。

c8456f7b5f77f894.png

もう一度デバイスにコマンドを送信してみてください。

「OK Google, ローカル検索して」

「OK Google, 洗濯機を止めて」

「OK Google, 洗濯機をオンにして」

...

「OK Google, デフォルトに設定」

6. スマートホーム用のテストスイートを実行する

Google Home アプリのタップ操作または音声コマンドでデバイスを確認したら、自動化されたスマートホーム用テストスイートを使用して、アクションに関連付けられたデバイスの種類とトレイトに基づいてユースケースを検証できます。テストスイートでは、アクションの問題を検出するための一連のテストが実行されます。また、失敗したテストケースについては有益なメッセージが表示されるので、イベントログを詳しく調べる前にデバッグを迅速に進めることができます。

スマートホーム用のテストスイートを実行する

テストスイートでスマートホーム アクションをテストする手順は次のとおりです。

  1. ウェブブラウザで、Test Suite for smart home を開きます。
  2. 右上のボタンを使用して Google にログインします。これにより、テストスイートから Google アシスタントに直接コマンドを送信できます。
  3. [プロジェクト ID] フィールドに、スマートホーム アクションのプロジェクト ID を入力します。[次へ] をクリックして先に進みます。
  4. [Test Settings] ステップの [Devices and Trais] セクションに、不具合のある洗濯機が表示されます。
  5. 洗濯機のサンプルアプリには洗濯機の追加 / 削除 / 名前変更を行う UI がないため、[Test Request Sync] オプションを無効にします。本番環境システムでは、ユーザーがデバイスの追加、削除、名前の変更を行うたびに Request Sync をトリガーする必要があります。
  6. ローカルパスとクラウドパスの両方をテストするため、[Local Home SDK] オプションを有効にしておきます。
  7. [次へ] をクリックしてテストの実行を開始します。

67433d9190fa770e.png

テストが完了すると、ローカルパスの一時停止/再開テストが失敗し、クラウドパスの一時停止/再開テストに合格します。

d1ebd5cfae2a2a47.png

エラー メッセージを分析

失敗したテストケースのエラー メッセージを詳しく確認します。そのテストで想定される状態と実際の状態が示されます。この場合、「洗濯機を一時停止」の場合、想定される状態は isPaused: true ですが、実際の状態では isPaused: false になっています。同様に、「洗濯機を一時停止」の場合、想定される状態は isPaused: true ですが、実際の状態では isPaused: false になっています。

6bfd3acef9c16b84.png

エラー メッセージから、ローカルパスにあるように、逆に isPaused 状態を設定しています。

エラーを特定して修正する

ローカル Home アプリが実行コマンドをデバイスに送信するソースコードを見てみましょう。getDataCommand() は、デバイスに送信される実行コマンドで payload を設定するために executeHandler() によって呼び出される関数です。

local/index.ts

getDataForCommand(command: string, params: IWasherParams): unknown {
    switch (command) {
        case 'action.devices.commands.OnOff':
            return {
                on: params.on ? true : false
            };
        case 'action.devices.commands.StartStop':
            return {
                isRunning: params.start ? true : false
            };
        case 'action.devices.commands.PauseUnpause':
            return {
                // Is there something wrong here?
                isPaused: params.pause ? false : true
            };
        default:
            console.error('Unknown command', command);
            return {};
    }
}

実際には、isPause を逆の状態に設定しています。params.pausetrue の場合は true に設定し、そうでない場合は false に設定します。これを修正しましょう。

local/index.ts

getDataForCommand(command: string, params: IWasherParams): unknown {
    switch (command) {
        case 'action.devices.commands.OnOff':
            return {
                on: params.on ? true : false
            };
        case 'action.devices.commands.StartStop':
            return {
                isRunning: params.start ? true : false
            };
        case 'action.devices.commands.PauseUnpause':
            return {
                isPaused: params.pause ? true : false
            };
        default:
            console.error('Unknown command', command);
            return {};
    }
}

ローカルの Google Home アプリのバージョンを変更して、正しいバージョンを使用しているかどうかを確認できるようにします。

local/index.ts

const localHomeSdk = new App('1.0.3');

アプリを再度コンパイルし、Firebase に再デプロイしてください。app-faulty/local で、以下を実行します。

$ npm run build
$ firebase deploy --only hosting

次に、Google Home デバイスを再起動して、更新したローカル Home アプリを読み込めるようにします。ローカルの Google Home アプリのバージョンが 1.0.3 であることを確認してください。

修正をテストする

スマートホーム用のテストスイートを同じ構成で再実行すると、すべてのテストケースに合格したことがわかります。

b7fc8c5d3c727d8d.png

7. 完了

764dbc83b95782a.png

これで完了です。スマートホームと Cloud Logging のテストスイートを使用して、ローカルホーム アプリのトラブルシューティング方法を学びました。

詳細

他にも以下のことを試してみてください。

また、アクションをユーザーに公開するための認定プロセスなど、アクションを審査のためにテストして送信する方法の詳細もご確認ください。