1. 始める前に
IoT(モノのインターネット)デベロッパーは、ユーザーが Google Home アプリのタップ コントロールや Google アシスタントの音声コマンドでデバイスを操作できるクラウド間統合を構築できます。

Google アシスタントと本番環境品質の統合を構築するには、クラウド間統合のデバッグツールを学習することが重要なステップです。モニタリングとデバッグを簡単に行うため、Google Cloud Platform(GCP)の指標とロギング、スマートホーム用テストスイートを使用して、統合に関する問題を特定して解決できます。
前提条件
- Cloud-to-cloud 統合を作成するデベロッパー ガイドを読む
- スマートホーム デバイスを Google アシスタントに接続する Codelab を実行する
作成するアプリの概要
この Codelab では、2 つのデフェクトを含む Cloud-to-Cloud 統合をデプロイしてアシスタントに接続し、スマートホームと Google Cloud Platform(GCP)の指標とロギング用のテストスイートを使用して統合のデフェクトをデバッグします。
学習内容
- GCP Metrics と Logging を使用して本番環境の問題を特定して解決する方法
- スマートホーム用テストスイートを使用して機能と API の問題を特定する方法
必要なもの
- ウェブブラウザ(Google Chrome など)
- Google Home アプリがインストールされている iOS または Android デバイス
- Node.js バージョン 10.16 以降
- Google Cloud 請求先アカウント
2. 問題のあるアプリを実行する
ソースコードを取得する
下のリンクをクリックして、この Codelab のサンプルを開発マシンにダウンロードします。
または、コマンドラインから GitHub リポジトリのクローンを作成することもできます。
$ git clone https://github.com/google-home/smarthome-debug.git
プロジェクトについて
洗濯機アプリには、次のサブディレクトリが含まれています。
- public: スマート洗濯機の状態を簡単に制御、モニタリングするためのフロントエンド UI。
- functions: Cloud Functions for Firebase と Firebase Realtime Database を使用してスマート洗濯機を管理する、実装が完了したクラウド サービス。
Firebase への接続
開発マシンでターミナルを開きます。washer-faulty ディレクトリに移動し、スマートホーム デバイスを Google アシスタントに接続する Codelab で作成した統合プロジェクトを使用して Firebase CLI を設定します。
$ cd washer-faulty $ firebase use <firebase-project-id>
Firebase にデプロイする
functions フォルダに移動し、npm. を使用して必要な依存関係をすべてインストールします。
$ cd functions $ npm install
注: 次のメッセージが表示された場合は、無視して続行できます。この警告は、古い依存関係が原因で発生します。詳しくは、こちらをご覧ください。
found 5 high severity vulnerabilities run `npm audit fix` to fix them, or `npm audit` for details
依存関係をインストールしてプロジェクトを構成したので、不具合のある洗濯機アプリをデプロイする準備が整いました。
$ firebase deploy
コンソールに次のような出力が表示されます。
... ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/<Firebase-project-id>/overview Hosting URL: https://<Firebase-project-id>.firebaseapp.com
HomeGraph を更新する
ブラウザで Hosting URL(https://<firebase-project-id>.firebaseapp.com)を開き、ウェブアプリを表示します。ウェブ UI で [更新]  ボタンをクリックし、Request Sync を使用して、不具合のある洗濯機アプリの最新のデバイス メタデータで HomeGraph を更新します。
 ボタンをクリックし、Request Sync を使用して、不具合のある洗濯機アプリの最新のデバイス メタデータで HomeGraph を更新します。

Google Home アプリを開き、[Faulty Washer] という名前の洗濯機デバイスが表示されていることを確認します。

3. 統合をテストする
プロジェクトをデプロイしたら、統合で洗濯機が制御されるかどうかをテストします。
洗濯機をテストする
スマートフォンで以下の音声コマンドを試して、値が変化することを確認します。
「OK Google, 洗濯機をオンにして。」
「OK Google, 洗濯を開始して。」
「OK Google, 洗濯機を一時停止して。」
「OK Google, 洗濯機を再開して。」
「OK Google, 洗濯機を止めて。」
洗濯機を一時停止または再開すると、アシスタントが音声で異常があることを通知します。
「申し訳ございません。<プロジェクトの表示名> にアクセスできませんでした。」
この問題をデバッグするには、まずエラーの詳細情報を収集して、根本原因を特定する必要があります。
スマートホーム アナリティクス ダッシュボード
エラーを確認するには、Smarthome Analytics ダッシュボードが便利です。このダッシュボードには、クラウド フルフィルメントの使用状況と健全性の指標のグラフが集約されています。
- [使用状況] 指標には、1 日あたりのアクティブ ユーザー数やフルフィルメントへのリクエストの合計数など、Cloud 間インテグレーションの使用状況の傾向が反映されます。
- 健全性指標は、リクエストのレイテンシ、成功率、エラーの内訳など、Cloud 間インテグレーションでの異常の発生をモニタリングするのに役立ちます。
エラーの原因を特定するには、次の手順でプロジェクト ダッシュボードにアクセスします。
- デベロッパー コンソールで、[プロジェクト] ページに移動します。
- スマートホーム プロジェクトを選択します。
- 左側のメニューで [アナリティクス] タブをクリックします。

- Google Cloud のプロジェクトのダッシュボードのリストが表示されます。[Google Home アナリティクス - Cloud Integration] ダッシュボードを選択します。

- [Cloud Fulfillment Errors - Status Breakdown] グラフまでスクロールして、ハイライト表示された期間のエラーコードを表示します。

PARTNER_RESPONSE_MISSING_DEVICE エラーコードは根本原因のヒントになります。次に、エラーコードに基づいてイベントログを取得して詳細を確認します。
イベントログにアクセスする
エラーの詳細を確認するには、Cloud Logging を使用して Cloud 間インテグレーションのイベントログにアクセスします。
Google Cloud Platform でナビゲーション メニューを開き、[オペレーション] で [ロギング] > [ログ エクスプローラ] を選択して、プロジェクトのイベントログにアクセスします。または、検索ボックスで「ログ エクスプローラ」を検索することもできます。
[すべてのフィールドを検索] 入力フィールドにクエリ PARTNER_RESPONSE_MISSING_DEVICE を入力し、[クエリを実行] をクリックします。クエリに一致するログが [結果] セクションに表示されます。

エラーログには、次のようなエラーの詳細とともにスマートホーム イベントが表示されます。
- ユーザーが行った操作は「洗濯機の再開」です(actionType:「STARTSTOP_UNPAUSE」)。これは、最近失敗した音声コマンドに対応しています。
- 関連するデバッグ メッセージは「JSON response does not include device.」です。
デバッグ メッセージに基づいて、洗濯機アプリが EXECUTE レスポンスに正しいデバイスを含めない理由を確認する必要があります。
エラーの根本原因を特定する
functions/index.js で、各コマンドのステータスと新しいデバイスの状態を返す EXECUTE ハンドラ(onExecute 配列内)を見つけます。EXECUTE レスポンスへのデバイス ID の挿入は、updateDevice 関数の解決によって異なります。
index.js
app.onExecute(async (body) => {
 ...
 for (const command of intent.payload.commands) {
   for (const device of command.devices) {
     for (const execution of command.execution) {
       executePromises.push(
           updateDevice(execution, device.id)
               .then((data) => {
                 result.ids.push(device.id);
                 Object.assign(result.states, data);
               })
               .catch((e) =>
                 functions.logger.error('EXECUTE',
                     device.id, e.message)));
     }
   }
 }
updateDevice 関数で洗濯機の一時停止 / 再開を処理する方法を確認すると、一時停止 / 再開コマンドに一致する文字列が正しくないことがわかります。
index.js
const updateDevice = async (execution, deviceId) => {
 const {params, command} = execution;
 let state; let ref;
 switch (command) {
   ...
   case 'action.devices.commands.PauseUnpausePause':
     state = {isPaused: params.pause};
     if (params.pause) state.isRunning = false;
     ref = firebaseRef.child(deviceId).child('StartStop');
     break;
 }
 return ref.update(state)
     .then(() => state);
};
エラーを修正する
エラーの根本原因が特定できたので、一時停止 / 再開コマンドの文字列を修正できます。
index.js
const updateDevice = async (execution, deviceId) => {
 const {params, command} = execution;
 let state; let ref;
 switch (command) {
   ...
   case 'action.devices.commands.PauseUnpause':
     state = {isPaused: params.pause};
     if (params.pause) state.isRunning = false;
     ref = firebaseRef.child(deviceId).child('StartStop');
     break;
 }
 return ref.update(state)
     .then(() => state);
};
修正をテストする
Firebase CLI を使用して、更新したコードをデプロイします。
firebase deploy --only functions
次の音声コマンドをもう一度試すと、アシスタントが洗濯機の一時停止 / 再開に正しく応答するようになります。
「OK Google, 洗濯機を一時停止して。」
=>
「かしこまりました。洗濯機を一時停止します。」
「OK Google, 洗濯機を再開して。」
=>
「了解しました。洗濯を再開します。」
また、洗濯機の現在の状態を確認することもできます。
「OK Google, 洗濯機はオンになってる?」
「OK Google, 洗濯機は動いてる?」
「OK Google, 洗濯機の今のステップを教えて」
4. テストスイートで統合をテストする
手動でテストできるだけでなく、自動のスマートホーム用テストスイートを使用して、統合に関連付けられたデバイスタイプとトレイトに基づいてユースケースを検証できます。テストスイートは、一連のテストを実行して統合の問題を検出し、失敗したテストケースに関する有益なメッセージを表示します。これにより、イベントログを確認する前にデバッグを迅速に進めることができます。
スマートホーム用テストスイートを実行する
次の手順に沿って、Test Suite で Cloud-to-Cloud 統合をテストします。
- ウェブブラウザで、スマートホーム用テストスイートを開きます。
- 右上のボタンから Google にログインします。これにより、テストスイートから Google アシスタントに直接コマンドを送信できるようになります。
- [プロジェクト ID] フィールドに、Cloud 間インテグレーションのプロジェクト ID を入力します。選択したら、[次へ] をクリックして続行します。
- [テスト設定] ステップで、洗濯機のデバイスタイプとトレイトがテストスイートに表示されます。

- サンプルの洗濯機アプリには、洗濯機を追加、削除、名前変更するための UI がないため、[Test Request Sync](Request Sync のテスト)オプションを無効にします。本番環境システムでは、ユーザーがデバイスを追加、削除、名前変更するたびに Request Sync をトリガーする必要があります。
- [次へ] をクリックして、テストの実行を開始します。
テストスイートの実行が完了したら、テストケースの結果を確認します。2 件の失敗したテストケースが、それぞれエラー メッセージとともに検出されます。

Cloud 間統合の失敗をデバッグするには、まずエラー メッセージを分析してエラーの根本原因を特定する必要があります。
エラー メッセージを分析する
デベロッパーが根本原因を特定できるように、Test Suite には、失敗したテストケースごとに、失敗の理由を示すエラー メッセージが表示されます。
上記の最初の失敗したテストケースの場合、

エラー メッセージは、Test Suite が Cloud 間インテグレーションから報告された状態に "isPause": true を想定しているのに対し、実際の状態には "isPause": false のみが含まれていることを示しています。
また、2 番目の失敗したテストケースのエラー メッセージは、Cloud 間インテグレーションからの QUERY レスポンスの状態に "isPause": true が含まれていることを示しています。これは、Cloud 間インテグレーションから報告された状態の "isPause": false とは異なります。

どちらのエラー メッセージにも、統合レポートに正しい値の isPaused が記載されているかどうかを確認するよう記載されています。
エラーの根本原因を特定する
functions/index.js を開きます。ここには、Report State を使用して状態の変化をホームグラフに投稿する reportstate 関数があります。Report State ペイロードを調べると、ペイロードに isPaused 状態がないことがわかります。これは、失敗したテストケースでテストスイートがチェックしたものとまったく同じです。
index.js
exports.reportstate = functions.database.ref('{deviceId}').onWrite(
    async (change, context) => {
      ...
      const requestBody = {
        requestId: 'ff36a3cc', /* Any unique ID */
        agentUserId: USER_ID,
        payload: {
          devices: {
            states: {
              /* Report the current state of our washer */
             [context.params.deviceId]: {
                online: true,
                on: snapshot.OnOff.on,
                isRunning: snapshot.StartStop.isRunning,
                currentRunCycle: [{
                  currentCycle: 'rinse',
                  nextCycle: 'spin',
                  lang: 'en',
                }],
                currentTotalRemainingTime: 1212,
                currentCycleRemainingTime: 301,
              },
            },
          },
        },
      };
      const res = await homegraph.devices.reportStateAndNotification({
        requestBody,
      });
      ...
    });
エラーを修正する
エラーの根本原因を特定したので、Report State ペイロードに isPaused 状態を追加して functions/index.js を修正します。
index.js
exports.reportstate = functions.database.ref('{deviceId}').onWrite(
    async (change, context) => {
      ...
      const requestBody = {
        requestId: 'ff36a3cc', /* Any unique ID */
        agentUserId: USER_ID,
        payload: {
          devices: {
            states: {
              /* Report the current state of our washer */
             [context.params.deviceId]: {
                online: true,
                on: snapshot.OnOff.on,
                isPaused: snapshot.StartStop.isPaused,
                isRunning: snapshot.StartStop.isRunning,
                currentRunCycle: [{
                  currentCycle: 'rinse',
                  nextCycle: 'spin',
                  lang: 'en',
                }],
                currentTotalRemainingTime: 1212,
                currentCycleRemainingTime: 301,
              },
            },
          },
        },
      };
      ...
    });
修正をテストする
Firebase CLI を使用して、更新したコードをデプロイします。
$ firebase deploy --only functions
スマートホーム用テストスイートを再実行すると、すべてのテストケースが合格になります。

5. 完了

これで、スマートホームと GCP の指標とロギング用の Test Suite を使用して、Cloud 間統合の問題をトラブルシューティングする方法について学習しました。
その他の情報
この Codelab で学んだことを活かして、追加リソースを参照しながら以下の演習に挑戦してみましょう。
- サポートされているトレイトをデバイスに追加し、テストスイートでテストします。
- ダッシュボードを作成してアラートを設定し、プログラムから指標データにアクセスして、統合に関する有用な使用状況指標を取得します。
- スマートホームのローカル フルフィルメントを検討する。
- GitHub のサンプルで詳しいコードを確認する。
統合の審査(統合をユーザーに公開するための認定プロセスを含む)を受ける前に行うテストと送信についての詳細もご確認ください。