Cloud 間インテグレーションを強化して保護する

1. 始める前に

クラウド間インテグレーションでは、デバイスタイプを使用して、各デバイスにどの文法を適用すべきかを Google アシスタントに伝えることができます。これらのデバイスタイプの機能を定義するのがデバイス トレイトです。デバイスは、インテグレーションに追加されている各デバイス トレイトの状態を継承します。

dc8dce0dea87cd5c.png

ユーザーのデバイスの機能をカスタマイズするには、サポートされているトレイトを特定のデバイスタイプに関連付けます。まだデバイス スキーマで使用できないカスタム トレイトをアクションに実装する場合は、Modes トレイトと Toggles トレイトを使用することで、独自に定義したカスタム名で特定の設定をコントロールできます。

Smart Home API には、タイプやトレイトが提供する基本的なコントロール機能に加えて、ユーザー エクスペリエンスを向上させるための追加機能も用意されています。インテント レスポンスが正常に完了しなかった場合には、エラー レスポンスによって詳細なユーザー フィードバックが提供されます。Secondary User Verification でこのようなレスポンスを拡張し、特定のデバイス トレイトのセキュリティを強化することもできます。アシスタントが発行したチャレンジ ブロックに特定のエラー レスポンスを送信することにより、クラウド間インテグレーションでコマンドを実行するための追加の承認を要求することも可能です。

前提条件

作成するアプリの概要

この Codelab では、これまでに構築したスマートホーム統合を Firebase にデプロイし、洗濯量とターボモードの非標準トレイトをスマートホーム洗濯機に追加する方法を学びます。また、エラーと例外の報告を実装するほか、セカンダリ ユーザー認証を使用して洗濯機をオンにするかどうかを音声で確認できるようにします。

学習内容

  • Modes トレイトと Toggles トレイトを統合に追加する
  • エラーと例外を報告できるようにする
  • 二次的なユーザー確認を適用する方法

必要なもの

2. はじめに

アクティビティ管理を有効にする

Google アシスタントを使用するには、特定のアクティビティ データを Google と共有する必要があります。このデータは Google アシスタントが適切に機能するために必要となります。ただし、データの共有は SDK に固有の要件ではありません。データを共有するには、Google アカウントを作成します(まだ作成していない場合)。どの Google アカウントでも使用できます。デベロッパー アカウントである必要はありません。

アシスタントで使用する Google アカウントのアクティビティ管理ページを開きます。

以下の切り替えスイッチが有効になっていることを確認します。

  • [ウェブとアプリのアクティビティ] - これに加えて、[Chrome の履歴と Google サービスを使用するサイト、アプリ、デバイスでのアクティビティを含める] チェックボックスがオンになっていることも確認してください。
  • [デバイス情報]
  • [音声アクティビティ]

Cloud-to-cloud 統合プロジェクトを作成する

  1. 開発用コンソールに移動します。
  2. [プロジェクトを作成] をクリックし、プロジェクトの名前を入力して [プロジェクトを作成] をクリックします。

プロジェクトに名前を付ける

クラウド間統合を選択する

デベロッパー コンソールの [プロジェクト ホーム] で、[Cloud-to-cloud] の [Add cloud-to-cloud integration] を選択します。

Cloud-to-cloud 統合を追加する

Firebase CLI をインストールする

Firebase コマンドライン インターフェース(CLI)を使用すると、ウェブアプリをローカルで提供し Firebase Hosting にデプロイできます。

CLI をインストールするには、ターミナルから次の npm コマンドを実行します。

npm install -g firebase-tools

CLI が正しくインストールされたことを確認するには、次のコマンドを実行します。

firebase --version

Google アカウントで Firebase CLI を承認するには、次のコマンドを実行します。

firebase login

Google Home Developer Console プロジェクトに Firebase を追加する

方法 1: Firebase コンソールを使用する

  1. Firebase に移動します。
  2. [Firebase プロジェクトを作成] をクリックします。
    Firebase プロジェクトを作成する
  3. [プロジェクトを作成] 画面で、[Google Cloud プロジェクトに Firebase を追加] をクリックします。
    Firebase を Google Cloud プロジェクトに追加する
  4. [スタートガイド] 画面で、Google Home Developer Console で作成した Google Cloud プロジェクトを選択し、[続行] をクリックします。
    Google Cloud プロジェクトを選択する

方法 2: Firebase CLI を使用する

firebase projects:addfirebase

作成したばかりの Google Home Developer Console プロジェクトを選択して、Firebase を追加します。

Firebase を Google Home Developer Console プロジェクトに追加すると、Firebase コンソールに表示されます。Firebase プロジェクトのプロジェクト ID は、Google Home Developer Console のプロジェクト ID と一致します。

Cloud プロジェクトを追加しました

HomeGraph API を有効にする

HomeGraph API を使用すると、ユーザーのホームグラフ内のデバイスとその状態を保存して照会できます。この API を使用するには、まず Google Cloud コンソールを開いて HomeGraph API を有効にする必要があります。

Google Cloud Console でアクションの <firebase-project-id>. に一致するプロジェクトを選択し、HomeGraph API の [API ライブラリ] 画面で [有効にする]をクリックします。

ee198858a6eac112.png

3. スターター アプリを実行する

開発環境の設定が完了したので、スターター プロジェクトをデプロイし、すべてが正しく設定されていることを確認しましょう。

ソースコードを取得する

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

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

git clone https://github.com/google-home/smarthome-traits.git

ダウンロードした zip ファイルを解凍します。

プロジェクトについて

スターター プロジェクトには、以下のサブディレクトリが含まれています。

  • public: スマート洗濯機の状態を簡単に制御、モニタリングするためのフロントエンド UI。
  • functions: Cloud Functions for Firebase と Firebase Realtime Database を使用してスマート洗濯機を管理する、実装が完了したクラウド サービス。

提供されるクラウド フルフィルメントの index.js には、以下の関数が含まれています。

  • fakeauth: アカウント リンク用の認証エンドポイント
  • faketoken: アカウント リンク用のトークン エンドポイント
  • smarthome: スマートホーム インテントのフルフィルメント エンドポイント
  • reportstate: デバイスの状態が変化したときに HomeGraph API を呼び出す
  • requestsync: アカウントを再リンクしなくてもデバイスを更新できるようにする

Firebase への接続

washer-start ディレクトリに移動し、統合プロジェクトに Firebase CLI を設定します。

cd washer-start
firebase use <project-id>

Firebase プロジェクトを設定する

Firebase プロジェクトを初期化します。

firebase init

CLI 機能の [Realtime Database] と [Functions] を選択します。

? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter
 to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to
 proceed)
>( ) Data Connect: Set up a Firebase Data Connect service
 ( ) Firestore: Configure security rules and indexes files for Firestore
 ( ) Genkit: Setup a new Genkit project with Firebase
 (*) Functions: Configure a Cloud Functions directory and its files
 ( ) App Hosting: Configure an apphosting.yaml file for App Hosting
 ( ) Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
 ( ) Storage: Configure a security rules file for Cloud Storage
 ( ) Emulators: Set up local emulators for Firebase products
 ( ) Remote Config: Configure a template file for Remote Config
 ( ) Extensions: Set up an empty Extensions manifest
 (*) Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision
default instance
 ( ) Data Connect: Set up a Firebase Data Connect service
 ( ) Firestore: Configure security rules and indexes files for Firestore

これにより、プロジェクトに必要な API と機能が初期化されます。

プロンプトが表示されたら、Realtime Database を初期化します。データベース インスタンスのデフォルトの場所を使用できます。

? It seems like you haven't initialized Realtime Database in your project yet. Do you want to set it up?
Yes

? Please choose the location for your default Realtime Database instance:
us-central1

スターター プロジェクトのコードを使用しているため、セキュリティ ルールのデフォルト ファイルを選択します。既存のデータベース ルール ファイルを上書きしないよう注意してください。

? File database.rules.json already exists. Do you want to overwrite it with the Realtime Database Security Rules for <project-ID>-default-rtdb from the Firebase Console?
No

プロジェクトを再初期化する場合は、コードベースを初期化するか上書きするかを尋ねられたら、[上書き] を選択します。

? Would you like to initialize a new codebase, or overwrite an existing one?
Overwrite

関数を設定する際は、デフォルト ファイルを使用する必要があります。プロジェクト サンプル内の既存の index.js ファイルと package.json ファイルを上書きしないようにしてください。

? What language would you like to use to write Cloud Functions?
JavaScript

? Do you want to use ESLint to catch probable bugs and enforce style?
No

? File functions/package.json already exists. Overwrite?
No

? File functions/index.js already exists. Overwrite?
No

プロジェクトを再初期化する場合は、functions/.gitignore を初期化または上書きするかどうかを尋ねられたら、[No] を選択します。

? File functions/.gitignore already exists. Overwrite?
No
? Do you want to install dependencies with npm now?
Yes

ESLint を誤って有効にした場合は、次の 2 つの方法で無効にできます。

  1. GUI を使用して、プロジェクトの ../functions フォルダに移動し、非表示ファイル .eslintrc.js を選択して削除します。名前が似ている .eslintrc.json と混同しないでください。
  2. コマンドラインを使用する場合:
    cd functions
    rm .eslintrc.js
    

Firebase にデプロイする

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

firebase deploy

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

...

✔ Deploy complete!

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

このコマンドによって、いくつかの Cloud Functions for Firebase とともにウェブアプリがデプロイされます。

ブラウザで Hosting URLhttps://<firebase-project-id>.web.app)を開き、ウェブアプリを表示します。次のようなインターフェースが表示されます。

5845443e94705557.png

このウェブ UI は、デバイスの状態を表示したり変更したりするためのサードパーティ プラットフォームを表したものです。データベースへのデバイス情報の入力を開始するには、[UPDATE](更新)をクリックします。ページの表示は変化しませんが、洗濯機の現在の状態がデータベースに保存されます。

次は、デベロッパー コンソールを使用して、デプロイしたクラウド サービスを Google アシスタントにリンクします。

Developer Console プロジェクトを構成する

[Develop](開発)タブで、インタラクションの [Display Name](表示名)を追加します。この名前は Google Home アプリに表示されます。

表示名を追加する

[アプリのブランディング] で、144 x 144 ピクセルのサイズで .png という名前のアプリ アイコンの png ファイルをアップロードします。

アプリアイコンを追加する

アカウント リンクを有効にするには、次のアカウント リンク設定を使用します。

クライアント ID

ABC123

クライアント シークレット

DEF456

認証 URL

https://us-central1-<project-id>.cloudfunctions.net/fakeauth

トークンの URL

https://us-central1-<project-id>.cloudfunctions.net/faketoken

アカウント リンクの URL を更新する

[Cloud fulfillment URL] に、スマートホーム インテントのフルフィルメントを提供する Cloud Functions の URL を入力します。

https://us-central1-<project-id>.cloudfunctions.net/smarthome

Cloud Functions の URL を追加する

[保存] をクリックしてプロジェクト構成を保存し、[次へ: テスト] をクリックしてプロジェクトでのテストを有効にします。

Cloud-to-cloud 統合をテストする

これで、デバイスの状態とアシスタントをリンクするために必要な Webhook の実装を開始できるようになりました。

クラウド間インテグレーションをテストするには、プロジェクトを Google アカウントにリンクする必要があります。これにより、同じアカウントにログインしている Google アシスタント画面と Google Home アプリでテストできるようになります。

  1. スマートフォンで Google アシスタントの設定を開きます。なお、コンソールと同じアカウントでログインする必要があります。
  2. [Google アシスタント] > [設定] > [スマートホーム]([アシスタント] の下)に移動します。
  3. 右上にある検索アイコンをクリックします。
  4. [test] 接頭辞を使用してテストアプリを検索し、特定のテストアプリを見つけます。
  5. そのアイテムを選択します。Google アシスタントがサービスで認証を行い、SYNC リクエストを送信してデバイスのリストをユーザーに提供するようサービスに依頼します。

Google Home アプリを開いて、洗濯機デバイスが表示されることを確認します。

ae252220753726f6.png

Google Home アプリで、音声コマンドを使用して洗濯機を操作できることを確認します。また、クラウド フルフィルメントのフロントエンド ウェブ UI で、デバイスの状態の変化を確認します。

これで、基本的な洗濯機デバイスをデプロイできました。次は、デバイスで使用できるモードをカスタマイズします。

4. モードを追加する

action.devices.traits.Modes トレイトを使用すると、デバイスにモードを追加し、任意の数の値を設定できます(ただし、同時に複数の値をモードにセットすることはできません)。ここでは、洗濯量を定義するモードを追加し、値として「small」(小)、「medium」(中)、「large」(大)を設定します。

SYNC レスポンスを更新する

新しいトレイトを追加するには、トレイトに関する情報を functions/index.jsSYNC レスポンスに追加する必要があります。追加した情報は、次のコード スニペットに示すように traits 配列と attributes オブジェクトに表示されます。

index.js

app.onSync(body => {
  return {
    requestId: 'ff36a3cc-ec34-11e6-b1a0-64510650abcf',
    payload: {
      agentUserId: USER_ID,
      devices: [{
        id: 'washer',
        type: 'action.devices.types.WASHER',
        traits: [
          'action.devices.traits.OnOff',
          'action.devices.traits.StartStop',
          'action.devices.traits.RunCycle',
          // Add Modes trait
          'action.devices.traits.Modes',
        ],
        name: { ... },
        deviceInfo: { ... },
        attributes: {
          pausable: true,
          //Add availableModes
          availableModes: [{
            name: 'load',
            name_values: [{
              name_synonym: ['load'],
              lang: 'en',
            }],
            settings: [{
              setting_name: 'small',
              setting_values: [{
                setting_synonym: ['small'],
                lang: 'en',
              }]
            }, {
              setting_name: 'medium',
              setting_values: [{
                setting_synonym: ['medium'],
                lang: 'en',
              }]
            }, {
              setting_name: 'large',
              setting_values: [{
                setting_synonym: ['large'],
                lang: 'en',
              }]
            }],
            ordered: true,
          }],
        },
      }],
    },
  };
});

新しい EXECUTE インテント コマンドを追加する

次のコード スニペットに示すように、EXECUTE インテントに action.devices.commands.SetModes コマンドを追加します。

index.js

const updateDevice = async (execution,deviceId) => {
  const {params,command} = execution;
  let state, ref;
  switch (command) {
    case 'action.devices.commands.OnOff':
      state = {on: params.on};
      ref = firebaseRef.child(deviceId).child('OnOff');
      break;
    case 'action.devices.commands.StartStop':
      state = {isRunning: params.start};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
    case 'action.devices.commands.PauseUnpause':
      state = {isPaused: params.pause};
      ref = firebaseRef.child(deviceId).child('StartStop');
      Break;
    // Add SetModes command
    case 'action.devices.commands.SetModes':
      state = {load: params.updateModeSettings.load};
      ref = firebaseRef.child(deviceId).child('Modes');
      break;
  }
};

QUERY レスポンスを更新する

次に、QUERY レスポンスを更新して洗濯機の現在の状態を取得します。

更新された変更を queryFirebase 関数と queryDevice 関数に追加して、Realtime Database に保存されている状態を取得します。

index.js

const queryFirebase = async (deviceId) => {
  const snapshot = await firebaseRef.child(deviceId).once('value');
  const snapshotVal = snapshot.val();
  return {
    on: snapshotVal.OnOff.on,
    isPaused: snapshotVal.StartStop.isPaused,
    isRunning: snapshotVal.StartStop.isRunning,
    // Add Modes snapshot
    load: snapshotVal.Modes.load,
  };
};

const queryDevice = async (deviceId) => {
  const data = await queryFirebase(deviceId);
  return {
    on: data.on,
    isPaused: data.isPaused,
    isRunning: data.isRunning,
    currentRunCycle: [{ ... }],
    currentTotalRemainingTime: 1212,
    currentCycleRemainingTime: 301,
    // Add currentModeSettings
    currentModeSettings: {
      load: data.load,
    },
  };
};

Report State を更新する

最後に reportstate 関数を更新し、現在の洗濯物量の設定がホームグラフに報告されるようにします。

index.js

const requestBody = {
  requestId: 'ff36a3cc', /* Any unique ID */
  agentUserId: USER_ID,
  payload: {
    devices: {
      states: {
        /* Report the current state of your washer */
        [context.params.deviceId]: {
          on: snapshot.OnOff.on,
          isPaused: snapshot.StartStop.isPaused,
          isRunning: snapshot.StartStop.isRunning,
          // Add currentModeSettings
          currentModeSettings: {
            load: snapshot.Modes.load,
          },
        },
      },
    },
  },
};

Firebase にデプロイする

次のコマンドを実行して、更新したインテグレーションをデプロイします。

firebase deploy --only functions

デプロイが完了したら、ウェブ UI に移動してツールバーの更新 ボタン ae8d3b25777a5e30.png をクリックします。これにより Request Sync がトリガーされ、更新された SYNC レスポンス データがアシスタントに送信されます。

bf4f6a866160a982.png

洗濯機のモードは、次のようなコマンドで設定できます。

「OK Google, 洗濯量を大にセットして。」

次のように、洗濯量を確認することもできます。

「OK Google, 洗濯量はどれくらい?」

5. トグルを追加する

action.devices.traits.Toggles トレイトは、デバイスの状態を true または false で示す名前付きアスペクトを表します。たとえば、洗濯機がターボモードになっているかどうかを示すことができます。

SYNC レスポンスを更新する

SYNC レスポンスで、新しいデバイス トレイトに関する情報を追加する必要があります。追加した情報は、次のコード スニペットに示すように traits 配列と attributes オブジェクトに表示されます。

index.js

app.onSync(body => {
  return {
    requestId: 'ff36a3cc-ec34-11e6-b1a0-64510650abcf',
    payload: {
      agentUserId: USER_ID,
      devices: [{
        id: 'washer',
        type: 'action.devices.types.WASHER',
        traits: [
          'action.devices.traits.OnOff',
          'action.devices.traits.StartStop',
          'action.devices.traits.RunCycle',
          'action.devices.traits.Modes',
          // Add Toggles trait
          'action.devices.traits.Toggles',
        ],
        name: { ... },
        deviceInfo: { ... },
        attributes: {
          pausable: true,
          availableModes: [{
            name: 'load',
            name_values: [{
              name_synonym: ['load'],
              lang: 'en'
            }],
            settings: [{ ... }],
            ordered: true,
          }],
          //Add availableToggles
          availableToggles: [{
            name: 'Turbo',
            name_values: [{
              name_synonym: ['turbo'],
              lang: 'en',
            }],
          }],
        },
      }],
    },
  };
});

新しい EXECUTE インテント コマンドを追加する

次のコード スニペットに示すように、EXECUTE インテントに action.devices.commands.SetToggles コマンドを追加します。

index.js

const updateDevice = async (execution,deviceId) => {
  const {params,command} = execution;
  let state, ref;
  switch (command) {
    case 'action.devices.commands.OnOff':
      state = {on: params.on};
      ref = firebaseRef.child(deviceId).child('OnOff');
      break;
    case 'action.devices.commands.StartStop':
      state = {isRunning: params.start};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
    case 'action.devices.commands.PauseUnpause':
      state = {isPaused: params.pause};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
    case 'action.devices.commands.SetModes':
      state = {load: params.updateModeSettings.load};
      ref = firebaseRef.child(deviceId).child('Modes');
      break;
    // Add SetToggles command
    case 'action.devices.commands.SetToggles':
      state = {Turbo: params.updateToggleSettings.Turbo};
      ref = firebaseRef.child(deviceId).child('Toggles');
      break;
  }

QUERY レスポンスを更新する

最後に QUERY レスポンスを更新して、洗濯機のターボモードを報告できるようにする必要があります。更新された変更を queryFirebase 関数と queryDevice 関数に追加して、Realtime Database に保存されているトグルの状態を取得します。

index.js

const queryFirebase = async (deviceId) => {
  const snapshot = await firebaseRef.child(deviceId).once('value');
  const snapshotVal = snapshot.val();
  return {
    on: snapshotVal.OnOff.on,
    isPaused: snapshotVal.StartStop.isPaused,
    isRunning: snapshotVal.StartStop.isRunning,
    load: snapshotVal.Modes.load,
    // Add Toggles snapshot
    Turbo: snapshotVal.Toggles.Turbo,
  };
}

const queryDevice = async (deviceId) => {
  const data = queryFirebase(deviceId);
  return {
    on: data.on,
    isPaused: data.isPaused,
    isRunning: data.isRunning,
    currentRunCycle: [{ ... }],
    currentTotalRemainingTime: 1212,
    currentCycleRemainingTime: 301,
    currentModeSettings: {
      load: data.load,
    },
    // Add currentToggleSettings
    currentToggleSettings: {
      Turbo: data.Turbo,
    },
  };
};

Report State を更新する

最後に reportstate 関数を更新し、洗濯機がターボモードに設定されているかどうかがホームグラフに報告されるようにします。

index.js

const requestBody = {
  requestId: 'ff36a3cc', /* Any unique ID */
  agentUserId: USER_ID,
  payload: {
    devices: {
      states: {
        /* Report the current state of your washer */
        [context.params.deviceId]: {
          on: snapshot.OnOff.on,
          isPaused: snapshot.StartStop.isPaused,
          isRunning: snapshot.StartStop.isRunning,
          currentModeSettings: {
            load: snapshot.Modes.load,
          },
          // Add currentToggleSettings
          currentToggleSettings: {
            Turbo: snapshot.Toggles.Turbo,
          },
        },
      },
    },
  },
};

Firebase にデプロイする

次のコマンドを実行して、更新した関数をデプロイします。

firebase deploy --only functions

デプロイが完了したら、ウェブ UI の [Refresh ](更新) ae8d3b25777a5e30.png ボタンをクリックして Request Sync をトリガーします。

これで、次のようなコマンドにより洗濯機をターボモードに設定できるようになりました。

「OK Google, 洗濯機をターボモードに設定して。」

次のようなコマンドで、洗濯機がターボモードになっているかどうかを確認することもできます。

「OK Google, 洗濯機はターボモードになってる?」

6. エラーと例外を報告する

Cloud-to-cloud 統合のエラー処理を使用すると、EXECUTE レスポンスや QUERY レスポンスが失敗したことをユーザーに報告できます。スマート デバイスやインテグレーションの操作中に適切に通知することで、ユーザー エクスペリエンスを向上させることができます。

EXECUTE リクエストや QUERY リクエストが失敗したときには、必ずインテグレーションからエラーコードを返すようにしてください。たとえば、ユーザーが蓋を開けたまま洗濯機を始動しようとしたときにエラーを返す場合は、EXECUTE レスポンスを次のコード スニペットのようにします。

{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "commands": [
      {
        "ids": [
          "456"
        ],
        "status": "ERROR",
        "errorCode": "deviceLidOpen"
      }
    ]
  }
}

これで、ユーザーが蓋を開けたまま洗濯機を始動しようとすると、アシスタントは次のように応答します。

「洗濯機の蓋が開いています。閉じてからもう一度試してください。」

例外はエラーと似ていますが、コマンドに関連するアラートを示すためのもので、コマンドの実行をブロックする場合としない場合があります。例外では、StatusReport トレイトを使用して関連情報(電池残量、状態の変化など)を提供できます。実行をブロックしない例外コードの場合は SUCCESS ステータスが返され、実行をブロックする例外コードの場合は EXCEPTIONS ステータスが返されます。

次のコード スニペットに、例外を含むレスポンスの例を示します。

{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "commands": [{
      "ids": ["123"],
      "status": "SUCCESS",
      "states": {
        "online": true,
        "isPaused": false,
        "isRunning": false,
        "exceptionCode": "runCycleFinished"
      }
    }]
  }
}

アシスタントは次のように応答します。

「洗濯は終了しています。」

洗濯機からのエラーを報告できるようにするには、functions/index.js を開いてエラークラスの定義を追加します。次のコード スニペットを参照してください。

index.js

app.onQuery(async (body) => {...});

// Add SmartHome error handling
class SmartHomeError extends Error {
  constructor(errorCode, message) {
    super(message);
    this.name = this.constructor.name;
    this.errorCode = errorCode;
  }
}

EXECUTE レスポンスを次のように変更し、エラーコードとエラー ステータスが返されるようにします。

index.js

const executePromises = [];
const intent = body.inputs[0];
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) => {
          ...
        })
        //Add error response handling
        .catch((error) => {
          functions.logger.error('EXECUTE', device.id, error);
          result.ids.push(device.id);
          if (error instanceof SmartHomeError) {
            result.status = 'ERROR';
            result.errorCode = error.errorCode;
          }
        })
      );
    }
  }
}

これで、定義したエラーコードが、アシスタントからユーザーに通知されるようになりました。次のセクションでは、具体的な例に沿って説明します。

7. 二次的なユーザー確認を追加する

たとえばソフトウェアの更新やロックの解除など、デバイスのモードにセキュリティを設定したり、特定の承認済みユーザー グループに限定したりする必要がある場合は、インテグレーションに予備のユーザー確認を実装します。

すべてのデバイスタイプとデバイス トレイトに Secondary User Verification を実装し、セキュリティ チャレンジを毎回行うか、特定の条件を満たした場合のみ行うかをカスタマイズできます。

以下の 3 種類のチャレンジがサポートされています。

  • No challenge - 認証チャレンジを使用しないリクエストとレスポンス(デフォルト)
  • ackNeeded - 明示的な確認応答(はい / いいえ)を必要とする Secondary User Verification
  • pinNeeded - 個人識別番号(PIN)を必要とする Secondary User Verification

この Codelab では、洗濯機の電源をオンにする際の ackNeeded チャレンジと、二次検証チャレンジが失敗した場合にエラーを返す機能を追加します。

functions/index.js を開き、次のコード スニペットに示すように、エラーコードとチャレンジ タイプを返すエラークラスの定義を追加します。

index.js

class SmartHomeError extends Error { ... }

// Add secondary user verification error handling
class ChallengeNeededError extends SmartHomeError {
  /**
   * Create a new ChallengeNeededError
   * @param {string} suvType secondary user verification challenge type
   */
  constructor(suvType) {
    super('challengeNeeded', suvType);
    this.suvType = suvType;
  }
}

EXECUTE レスポンスを次のように変更し、challengeNeeded エラーが返されるようにします。

index.js

const executePromises = [];
const intent = body.inputs[0];
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) => {
          ...
        })
        .catch((error) => {
          functions.logger.error('EXECUTE', device.id, error);
          result.ids.push(device.id);
          if (error instanceof SmartHomeError) {
            result.status = 'ERROR';
            result.errorCode = error.errorCode;
            //Add error response handling
            if (error instanceof ChallengeNeededError) {
              result.challengeNeeded = {
                type: error.suvType
              };
            }
          }
        })
      );
    }
  }
}

最後に updateDevice を変更し、洗濯機の電源をオンまたはオフにする際に、明示的な承認が必要となるようにします。

index.js

const updateDevice = async (execution,deviceId) => {
  const {challenge,params,command} = execution; //Add secondary user challenge
  let state, ref;
  switch (command) {
    case 'action.devices.commands.OnOff':
      //Add secondary user verification challenge
      if (!challenge || !challenge.ack) {
        throw new ChallengeNeededError('ackNeeded');
      }
      state = {on: params.on};
      ref = firebaseRef.child(deviceId).child('OnOff');
      break;
    ...
  }

  return ref.update(state)
      .then(() => state);
};

Firebase にデプロイする

次のコマンドを実行して、更新した関数をデプロイします。

firebase deploy --only functions

デプロイが完了した後は、洗濯機の電源をオンまたはオフに切り替えようとすると、アシスタントから次のように承認を求められるようになります。

あなた: 「OK Google, 洗濯機をオンにして。」

アシスタント: 「洗濯機をオンにしてもよろしいですか?」

あなた: 「はい。」

Firebase ログを開くと、セカンダリ ユーザー確認フローの各ステップでの詳細なレスポンスを確認することもできます。

289dbe48f4bb8106.png

8. 完了

674c4f4392e98c1.png

これで、Modes トレイトと Toggles トレイトでクラウド間インテグレーションの機能を拡張し、二次ユーザー認証でデバイス実行時のセキュリティを強化することができました。

その他の情報

さらに詳しく学びたい方は、以下のことをお試しください。

  • デバイスにローカル実行機能を追加する。
  • 別の Secondary User Verification チャレンジを使用してデバイスの状態を変更する。
  • RunCycle トレイトの QUERY レスポンスに変更を加えて動的に更新する。
  • GitHub サンプルを見る。