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

1. 始める前に

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

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

72ffb320986092c.png

クラウド間の統合のデバッグは、本番環境品質の統合を構築するための重要なステップですが、有益で使いやすいトラブルシューティング ツールやテストツールがないと、困難で時間がかかります。クラウド間統合のデバッグを容易にするため、Google Cloud Platform(GCP)の指標ロギングスマートホーム向けの Test Suite を使用して、統合の問題を特定して解決できます。

前提条件

作成するアプリの概要

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

学習内容

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

必要なもの

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

ソースコードを取得する

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

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

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

プロジェクトについて

スターター アプリには、クラウド間インテグレーションでローカル フルフィルメントを有効にするの Codelab と同様のサブディレクトリとクラウド関数が含まれています。ただし、ここでは app-start ではなく app-faulty があります。まず、動作はするものの、あまりうまく動作しないローカルのホームアプリから始めます。

Firebase への接続

Cloud-to-cloud 統合のローカル フルフィルメントを有効にする Codelab で作成したプロジェクトと同じプロジェクトを使用しますが、この Codelab でダウンロードしたファイルをデプロイします。

app-faulty ディレクトリに移動し、Cloud-to-cloud 統合のローカル フルフィルメントを有効にする Codelab で作成した統合プロジェクトに 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 とともにウェブアプリがデプロイされます。

HomeGraph を更新する

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

fa3c47f293cfe0b7.png

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

2a082ee11d47ad1a.png

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

Cloud-to-cloud 統合のローカル フルフィルメントを有効にする 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. ローカルホームアプリをテストする

Google Home デバイスへの音声コマンドを使用して、デバイスにコマンドを送信します。次に例を示します。

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

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

「OK Google, ローカルを強制。」

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

「force local」の後に洗濯機を操作しようとすると、Google アシスタントから「申し訳ありません。Faulty Washer は現在利用できません」という応答が返されます。

つまり、デバイスにローカル パスでアクセスできません。「OK Google, force local」を発行する前は、ローカル パスでデバイスにアクセスできない場合、クラウド パスを使用するようにフォールバックするため、動作していました。ただし、「force local」の後では、クラウドパスにフォールバックするオプションは無効になります。

問題を特定するために、Google Cloud Platform(GCP)の指標ロギング、Chrome デベロッパー ツールなどのツールを使用します。

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

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

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

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

  1. Google Home デバイスが、デベロッパー コンソール プロジェクトにアクセスできるユーザーにリンクしていることを確認します。
  2. Google Home デバイスを再起動します。これにより、Developer 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 デバイスを再起動します。Google Home デバイスが想定どおりのバージョンを使用しているかどうかは、Chrome デベロッパー ツールのコンソール ログで確認できます。

ecc56508ebcf9ab.png

Cloud Logging にアクセスする

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

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

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

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

ローカル デバイスを特定できないためローカルパスが機能せず、IDENTIFY インテントでエラーが発生していることがわかります。ただし、問題の内容を正確に把握したいので、まず IDENTIFY ハンドラで発生したエラーを除外します。

[クエリを表示] 切り替えボタンをクリックします。このボタンが [クエリビルダー] ボックスに変わります。[クエリビルダー] ボックスに「jsonPayload.intent="IDENTIFY"」と入力し、[クエリを実行] ボタンをクリックします。

4c0b9d2828ee2447.png

その結果、IDENTIFY ハンドラでスローされたすべてのエラーログを取得できます。次に、最後のエラーを開きます。プロミスを拒否したときに設定した errorCodedebugString は、IDENTIFY ハンドラで確認できます。

71f2f156c6887496.png

debugString から、ローカル デバイス ID が想定される形式ではないことがわかります。ローカルホーム アプリは、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 デバイスを再起動して、更新されたローカルホームアプリを読み込みます。ローカルホームアプリのバージョンが 1.0.2 であることを確認します。今回は、Chrome デベロッパー ツールのコンソールにエラーが表示されないはずです。

c8456f7b5f77f894.png

これで、デバイスにコマンドを送信できるようになりました。

「OK Google, ローカルを強制」

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

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

...

「OK Google, デフォルトを強制して」

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

Google Home アプリのタップ コントロールまたは音声コマンドを使用してデバイスを確認したら、自動のスマートホーム用テストスイートを使用して、デバイスタイプと、統合に関連付けられたトレイトに基づいてユースケースを検証できます。テストスイートは一連のテストを実行して統合の問題を検出し、失敗したテストケースに関する情報メッセージを表示して、イベントログを調べる前にデバッグを迅速に行えるようにします。

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

テストスイートで Cloud-to-cloud 統合をテストするには、次の手順に沿って操作します。

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

67433d9190fa770e.png

テストが完了すると、ローカルパスの Pause/Resume テストは失敗し、クラウドパスの Pause/Resume テストは成功していることがわかります。

d1ebd5cfae2a2a47.png

エラー メッセージを分析する

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

6bfd3acef9c16b84.png

エラー メッセージから、ローカルパスで isPaused 状態が逆方向に設定されていることがわかります。

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

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

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 デバイスを再起動します。ローカルホーム アプリのバージョンが 1.0.3 であることを確認します。

修正をテストする

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

b7fc8c5d3c727d8d.png

7. 完了

764dbc83b95782a.png

おめでとうございます!スマートホームと Cloud Logging のテストスイートを使用してローカルホームアプリのトラブルシューティングを行う方法を学びました。

詳細

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

インテグレーションの審査(インテグレーションをユーザーに公開するための認定プロセスを含む)を受ける前に行うテストと送信についての詳細もご確認ください。