スマートホームの統合により、Googleアシスタントはユーザーの家に接続されているデバイスを制御できます。スマートホームアクションを構築するには、 スマートホームインテント を処理できるクラウドWebhookエンドポイントを提供する必要があります。たとえば、ユーザーが「Hey Google, turn on the light,」と言うと、アシスタントはクラウドフルフィルメントにコマンドを送信してデバイスの状態を更新します。

Local Home SDKは、スマートホームインテントをGoogle Homeデバイスに直接ルーティングするローカルパスを追加することでスマートホームの統合を強化します。これにより、信頼性が向上し、ユーザーのコマンド処理の待ち時間が短縮されます。 TypeScriptまたはJavaScriptでローカルフルフィルメントアプリを作成してデプロイし、デバイスを識別して、GoogleHomeスマートスピーカーまたはGoogleNestスマートディスプレイでコマンドを実行できます。そして、アプリが既存の標準プロトコルを使用してコマンドを実行することにより、ローカルエリアネットワークを介してユーザーの既存のスマートデバイスと直接通信します。

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

事前準備

何を開発するのか

このコードラボでは、スマートホームアクションのローカルフルフィルメントを作成してアシスタントに接続し、 Test Suite for smart home と Google Cloud Platform (GCP) Metrics and Logging を介してローカルホームアプリをデバッグします。

何を学ぶのか

何が必要か

ソースコードの入手

開発マシンにこのコードラボのサンプルをダウンロードするために、以下のリンクをクリックします。

Download source code

...または、コマンドラインからGitHubリポジトリをcloneすることもできます。

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

プロジェクトについて

スターターアプリには、 Enable local fulfillment for smart home Actions コードラボと同様のサブディレクトリと Cloud Functions が含まれています。しかし、 app-start の代わりに、ここでは app-faulty があります。まず、機能するがそれほどうまく機能しないローカルホームアプリから始めます。

Firebaseに接続する

Enable local fulfillment for smart home Actions コードラボで作成したものと同じプロジェクトを使用しますが、このコードラボでダウンロードしたファイルをデプロイします。

app-faulty ディレクトリに移動し、 Enable local fulfillment for smart home Actions コードラボで作成したアクションプロジェクトを使用してFirebaseCLIを設定します。

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

Firebaseにデプロイする

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

$ cd functions
$ npm install

Note: 以下のメッセージが表示された場合は、無視して続行できます。警告はいくつかの古い依存関係によるものであり、詳細については こちら をご覧ください。

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

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

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

これにより、index.ts (TypeScript) ソースがコンパイルされ、次のコンテンツが app-faulty/public/local-home/ ディレクトリに配置されます。

依存関係をインストールしてプロジェクトを構成したので、アプリを初めて実行する準備が整いました。

$ 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 と共に、Webアプリをデプロイします。

HomeGraphの更新

ブラウザで Hosting URL (https:// .web.app) を開いて、Webアプリを表示します。 Web UIで Refresh ボタンをクリックして、 Request Sync を介して、障害のあるウォッシャーアプリからの最新のデバイスメタデータでHomeGraphを更新します。

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

もし Enable local fulfillment for smart home Actions コードラボを行っていた場合は、仮想スマートウォッシャーを既に開始しているはずです。停止している場合は、仮想デバイスを再起動することを忘れないでください。

デバイスを動かす

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デバイスへの音声コマンドを介してデバイスに送信します。

"Hey Google, turn on my washer."

"Hey Google, start my washer."

"Hey Google, force local."

"Hey Google, stop my washer."

"force local" の後に洗濯機を制御しようとすると、Googleアシスタントが "Sorry, it looks like the Faulty Washer isn't available right now" と応答することがわかります。

これは、デバイスがローカルパスを介して到達できないことを意味します。 "Hey Google, force local" を発行する前は、機能しました。これは、デバイスがローカルパスを介して到達できない場合に、クラウドパスの使用にフォールバックするためです。ただし、"force local" の後、クラウドパスにフォールバックするオプションは無効になります。

問題が何であるかを見つけるために、私たちが持っているツールを利用しましょう: Google Cloud Platform (GCP) Metrics andLogging と Chrome DevTools

次のセクションでは、Googleが提供するツールを使用して、ローカルパスを介してデバイスに到達できない理由を確認します。 Google Chrome Developer Tools を使用して Google Home デバイスに接続し、コンソールログを表示して、ローカルホームアプリをデバッグできます。カスタムログを Cloud Logging に送信して、ユーザーがローカルホームアプリで見つけている上位のエラーを認識できるようにすることもできます。

Chrome Developer Tools に接続する

デバッガーをローカルフルフィルメントアプリに接続するには、次の手順に従います。

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

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

インスペクターの起動

アプリのURLの下にある Inspect をクリックして、Chrome Developer Tools を起動します。 Console タブを選択し、TypeScript アプリによって出力された IDENTIFY インテントのコンテンツが表示されることを確認します。

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

カスタムログを追加する

Local Home SDKによって出力された DEVICE_VERIFICATION_FAILED エラーがありますが、根本的な原因を見つけるのにあまり役立ちません。スキャンデータを正しく読み取って処理していることを確認するために、いくつかのカスタムログを追加しましょう。エラーでプロミスを拒否すると、エラーメッセージは実際には 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デバイスを再起動して、更新されたローカルホームアプリを読み込めるようにします。 メトリクスとロギングメトリクスとロギングChrome Developer Tools の Console ログを確認すると、Google Home デバイスが期待されるバージョンを使用しているかどうかを確認できます。

Cloud Logging にアクセスする

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

  1. Cloud Platformコンソールで、 Project ページに移動します。
  2. スマートホームプロジェクトを選択します。
  3. Operations で、 Logging > Logs Explorer を選択します。

ロギングデータへのアクセスは、アクションプロジェクトのユーザーのIdentity and Access Management(IAM)を介して管理されます。データをログに記録するための役割と権限の詳細については、Cloud Logging access control を参照してください。

アドバンスドフィルタを使用する

ローカルデバイスの識別に失敗したためにローカルパスが機能していないため、 IDENTIFY インテントでエラーが発生していることがわかっています。ただし、問題が何であるかを正確に知りたいので、最初に IDENTIFY ハンドラーで発生するエラーを除外しましょう。

Query preview ボックスを展開すると、 Query builder ボックスに変わります。 Query builder ボックスに jsonPayload.intent = 'IDENTIFY' と入力し、 Run query ボタンをクリックします。

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

debugString から、ローカルデバイスIDが予期された形式ではないことがわかります。ローカルホームアプリは、ローカルデバイスIDを deviceid で始まり3桁の数字が続く文字列として取得することを想定していますが、ここでのローカルデバイス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 DevTools Console にエラーが表示されないはずです。

これで、デバイスへのコマンドの送信を再試行できます。

"Hey Google, force local."

"Hey Google, stop my washer."

"Hey Google, turn on my washer."

...

"Hey Google, force default."

Google Homeアプリのタッチコントロールまたは音声コマンドを使用してデバイスを確認した後、 Test Suite for smart home を使用して、アクションに関連付けられたデバイスタイプとトレイトに基づいてユースケースを検証できます。 Test Suiteは、一連のテストを実行してアクションの問題を検出し、失敗したテストケースに関する有益なメッセージを表示して、イベントログに飛び込む前にデバッグを迅速化します。

Test Suite for smart homeを実行する

テストスイートによるスマートホームアクションをテストするには、次の手順に従います。

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

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

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

失敗したテストケースのエラーメッセージを詳しく見てみましょう。それらは、そのテストの予想される状態と実際の状態を教えてくれます。この場合、 "Pause the Washer" に対して期待される状態は isPaused:true ですが、実際の状態では isPaused:false になります。同様に、 "Pause the Washer" の場合、期待される状態は isPaused:true ですが、実際の状態では isPaused:false になります。

エラーメッセージから、ローカルパスのように見えます。 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であることを確認してください。

修正をテストする

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

おめでとう!Test Suite for smart home と Cloud Logging を介して、ローカルホームアプリのトラブルシューティング方法を正常に学習しました。

もっと学ぶ

ここに、あなたが挑戦することができるいくつかの追加のリソースがあります。

また、アクションをユーザーに公開するための認証プロセスなど、レビューのためにアクションを テストして提出する 方法についても学ぶことができます。