Google Home Developer Center へようこそ。スマートホーム アクションの開発方法を学習できます。注: アクションの構築は、引き続き Actions Console で行います。

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

1. 始める前に

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

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

72ffb320986092c.png

スマートホーム アクションのデバッグは、本番環境の品質を維持したアクションを構築するための重要なステップですが、情報に富んだ使いやすいトラブルシューティング ツールとテストツールなしでは困難かつ時間のかかる作業となります。スマートホーム アクションのデバッグを容易にするために、Google Cloud Platform(GCP)の指標ロギングスマートホーム用テストスイートを利用して、問題を特定し、解決できます。

Prerequisites

作成するアプリの概要

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

学習内容

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

必要なもの

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

ソースコードを取得する

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

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

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

プロジェクトについて

スターター アプリには、スマートホーム アクションのローカル フルフィルメントを有効にする Codelab と同様のサブディレクトリと Cloud Functions の関数が含まれています。ここでは app-start ではなく app-faulty を使用します。ローカルホーム アプリは機能するが、適切に機能しない。

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 とともにウェブアプリをデプロイします。

HomeGraph を更新する

ブラウザ(https://<project-id>.web.app)で Hosting URL を開いてウェブアプリを表示します。ウェブ UI で [更新] ボタン 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. ローカルホーム アプリをテストする

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

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

「OK Google, 洗濯機を起動して」

「OK Google, ローカルに強制して」

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

「強制的にローカルで」洗濯機を操作しようとすると、Google アシスタントが「現在、欠陥のあるワッシャーは利用できません。

つまり、ローカルパスからデバイスにアクセスできません。以前は「OK Google, ローカルで強制的に実行」を発行する前は正常に機能していましたが、これはデバイスがローカルパスを介して到達できない場合にはクラウドパスを使用するようになるためです。ただし、「force local」の実行後にクラウドパスへのフォールバック オプションは無効になります。

何が問題なのかを見極めるために、Google Cloud Platform(GCP)の指標ロギング、Chrome デベロッパー ツールといったツールを活用してみましょう。

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

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

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

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

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. [オペレーション] で、[ロギング] > [ログ エクスプローラ] を選択します。

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

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

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

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

4c0b9d2828ee2447.png

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

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

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

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 アプリのタップ操作や音声コマンドを使用してデバイスを確認すると、自動化されたスマートホーム用テストスイートを使用して、アクションに関連付けられたデバイスの種類やトレイトに基づいてユースケースを検証できます。テストスイートが一連のテストを実行してアクションの問題を検出し、失敗したテストケースについて情報メッセージを表示して、イベントログを使用する前にデバッグを高速化します。

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

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

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

67433d9190fa770e.png

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

d1ebd5cfae2a2a47.png

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

失敗したテストケースのエラー メッセージを詳しく見てみましょう。このテストで想定される状態と、実際の状態を知ることができます。この例の「Pause the Washer」では、想定される状態は isPaused: true ですが、実際の状態では isPaused: false になっています。同様に、「Was the Washer」では、想定される状態は 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 {};
    }
}

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

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 のテストスイートを使用してローカルホーム アプリのトラブルシューティングを行う方法を学びました。

詳細

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

審査のためにアクションをテストし、送信する方法の詳細(アクションをユーザーに公開するための認定プロセスを含む)もあわせてご覧ください。