スマートホーム デバイスを Google アシスタントにリンクする

1. 始める前に

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

79266e5f45e6ae20.gif

クラウド間統合ではホームグラフが使用されます。家やスマートホーム デバイスに関するコンテキスト データを提供するホームグラフは、家の論理的な地図のようなものです。このコンテキストにより、アシスタントはユーザーのリクエストを屋内の位置と関連付けて、より自然な形で理解できるようになります。たとえば、ホームグラフには、「さまざまなメーカーの複数のデバイス(エアコン、照明、扇風機、掃除機など)が置かれたリビングルーム」というコンセプトを保存できます。

d009cef0f903d284.jpeg

前提条件

作成するアプリの概要

この Codelab では、仮想スマート洗濯機を管理するクラウド サービスを公開し、クラウド間統合を構築してアシスタントに接続します。

学習内容

  • スマートホーム クラウド サービスをデプロイする方法
  • サービスをアシスタントにリンクする方法
  • デバイスの状態変化を Google に公開する方法

必要なもの

2. はじめに

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

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

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

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

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

Cloud 間インテグレーション プロジェクトを作成する

  1. Developer Console に移動します。
  2. [プロジェクトを作成] をクリックし、プロジェクトの名前を入力して [プロジェクトを作成] をクリックします。

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

クラウド間インテグレーションを選択する

デベロッパー コンソールの [プロジェクトのホーム] で、[クラウド間] の [クラウド間統合を追加] を選択します。

クラウド間インテグレーションを追加する

Firebase CLI をインストールする

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

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

npm install -g firebase-tools

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

firebase --version

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

firebase login

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

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

ソースコードを取得する

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

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

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

プロジェクトについて

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

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

Firebase プロジェクトを作成する

  1. Firebase に移動します。
  2. [プロジェクトを作成] をクリックし、プロジェクト名を入力します。
  3. 同意チェックボックスをオンにして、[続行] をクリックします。同意チェックボックスがない場合は、この手順をスキップできます。
    Firebase プロジェクトを作成する
  4. Firebase プロジェクトが作成されたら、プロジェクト ID を確認します。[プロジェクトの概要] に移動し、設定アイコン > [プロジェクトの設定] をクリックします。
    プロジェクト設定を開く
  5. プロジェクトが [全般] タブに表示されます。
    プロジェクトの一般設定

Firebase に接続する

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

cd washer-start
firebase use <firebase-project-id>

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

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

firebase init

CLI 機能の [Realtime Database]、[Functions]、Firebase Hosting を含む [Hosting] を選択します。

? Which Firebase CLI features do you want to set up for this directory? Press Space to select features, then
 Enter to confirm your choices.
❯◉ Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
 ◯ Firestore: Configure security rules and indexes files for Firestore
 ◉ Functions: Configure a Cloud Functions directory and its files
 ◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
 ◯ Hosting: 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

これにより、プロジェクトに必要な 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

最後に Hosting を設定し、プロジェクト コード内の public ディレクトリと既存の index.html ファイルが使用されるようにします。ESLint の使用を求められたら、[いいえ] を選択します。

? What do you want to use as your public directory?
public

? Configure as a single-page app (rewrite all urls to /index.html)?
Yes

? Set up automatic builds and deploys with GitHub?
No

? File public/index.html already exists. Overwrite?
 No

ESLint が誤って有効になっている場合は、次の 2 つの方法で無効にできます。

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

washer-done/firebase.json ファイルで、コードを次のように完成させます。

{
  "database": {
    "rules": "database.rules.json"
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }],
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log",
        "*.local"
      ]
    }
  ]
}

Firebase にデプロイする

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

firebase deploy

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

...

✔ Deploy complete!

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

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

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

5845443e94705557.png

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

次は、Google Home Developer Console を使用して、デプロイしたクラウド サービスを Google アシスタントにリンクします。

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

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

表示名を追加する

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

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

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

クライアント ID

ABC123

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

DEF456

認証 URL

https://us-central1-
.cloudfunctions.net/fakeauth

トークンの URL

https://us-central1-
.cloudfunctions.net/faketoken

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

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

https://us-central1--cloudfunctions.net/smarthome

Cloud Functions の関数の URL を追加する

[Save] をクリックしてプロジェクトの設定を保存し、[Next: Test](次へ: テスト)をクリックしてプロジェクトでのテストを有効にします。

クラウド間統合をテストする

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

4. 洗濯機を作成する

統合が構成されたので、デバイスを追加してデータを送信できます。クラウド サービスでは、以下のインテントを処理する必要があります。

  • SYNC インテント。ユーザーがリンク済みのデバイスについてアシスタントが問い合わせたときに発生します。これは、ユーザーがアカウントをリンクしたときにサービスに送信されます。ユーザーのすべてのデバイスとその機能を格納した JSON ペイロードで応答する必要があります。
  • QUERY インテント。デバイスの現在の状態をアシスタントが問い合わせたときに発生します。リクエストされた各デバイスの状態を格納した JSON ペイロードで応答する必要があります。
  • EXECUTE インテント。アシスタントがユーザーに代わってデバイスを制御するときに発生します。リクエストされた各デバイスの実行状態を格納した JSON ペイロードで応答する必要があります。
  • DISCONNECT インテント。ユーザーがアシスタントとアカウントのリンクを解除したときに発生します。このユーザーのデバイスのイベントをアシスタントに送信するのを停止する必要があります。

以降のセクションでは、すでにデプロイした関数を、これらのインテントを処理できるように更新します。

SYNC レスポンスを更新する

functions/index.js を開きます。このファイルには、アシスタントからのリクエストに応答するコードが含まれています。

SYNC インテントを処理するには、デバイスのメタデータと機能を返す必要があります。onSync 配列の JSON に、デバイス情報と、洗濯機の推奨トレイトを追加します。

index.js

app.onSync((body) => {
  return {
    requestId: body.requestId,
    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',
        ],
        name: {
          defaultNames: ['My Washer'],
          name: 'Washer',
          nicknames: ['Washer'],
        },
        deviceInfo: {
          manufacturer: 'Acme Co',
          model: 'acme-washer',
          hwVersion: '1.0',
          swVersion: '1.0.1',
        },
        willReportState: true,
        attributes: {
          pausable: true,
        },
      }],
    },
  };
});

Firebase にデプロイする

Firebase CLI を使用して、更新したクラウド フルフィルメントをデプロイします。

firebase deploy --only functions

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

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

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

ae252220753726f6.png

5. コマンドとクエリを処理する

クラウド サービスが洗濯機デバイスを Google に正しく報告できたので、次は、デバイスの状態をリクエストしてコマンドを送信する機能を追加します。

QUERY インテントを処理する

QUERY インテントには一連のデバイスが含まれています。デバイスごとに現在の状態を返す必要があります。

functions/index.js で、インテント リクエストに含まれる対象デバイスのリストを処理するよう QUERY ハンドラを編集します。

index.js

app.onQuery(async (body) => {
  const {requestId} = body;
  const payload = {
    devices: {},
  };
  const queryPromises = [];
  const intent = body.inputs[0];
  for (const device of intent.payload.devices) {
    const deviceId = device.id;
    queryPromises.push(queryDevice(deviceId)
        .then((data) => {
        // Add response to device payload
          payload.devices[deviceId] = data;
        }
        ));
  }
  // Wait for all promises to resolve
  await Promise.all(queryPromises);
  return {
    requestId: requestId,
    payload: payload,
  };
});

リクエストに含まれるデバイスごとに、Realtime Database に保存されている現在の状態を返します。洗濯機の状態データを返すように queryFirebase 関数と queryDevice 関数を更新します。

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

const queryDevice = async (deviceId) => {
  const data = await queryFirebase(deviceId);
  return {
    on: data.on,
    isPaused: data.isPaused,
    isRunning: data.isRunning,
    currentRunCycle: [{
      currentCycle: 'rinse',
      nextCycle: 'spin',
      lang: 'en',
    }],
    currentTotalRemainingTime: 1212,
    currentCycleRemainingTime: 301,
  };
};

EXECUTE インテントを処理する

EXECUTE インテントは、デバイスの状態を更新するコマンドを処理します。レスポンスが返すのは、各コマンドのステータス(SUCCESSERRORPENDING など)と更新後のデバイスの状態です。

functions/index.js で、更新が必要なトレイトのリストと各コマンドの対象デバイスのセットを処理するよう EXECUTE ハンドラを編集します。

index.js

app.onExecute(async (body) => {
  const {requestId} = body;
  // Execution results are grouped by status
  const result = {
    ids: [],
    status: 'SUCCESS',
    states: {
      online: true,
    },
  };

  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) => {
                  result.ids.push(device.id);
                  Object.assign(result.states, data);
                })
                .catch(() => functions.logger.error('EXECUTE', device.id)));
      }
    }
  }

  await Promise.all(executePromises);
  return {
    requestId: requestId,
    payload: {
      commands: [result],
    },
  };
});

コマンドと対象デバイスごとに、リクエストされたトレイトに対応する Realtime Database の値を更新します。該当する Firebase 参照を更新して更新後のデバイスの状態を返すように updateDevice 関数を変更します。

index.js

const updateDevice = async (execution, deviceId) => {
  const {params, command} = execution;
  let state; let 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 = params.start
      ? {isRunning: true, isPaused: false}
      : {isRunning: false, isPaused: false};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
    case 'action.devices.commands.PauseUnpause':
      const data = await queryDevice(deviceId);
      state = (data.isPaused === false && data.isRunning === false)
        ? {isRunning: false, isPaused: false}
        : {isRunning: !params.pause, isPaused: params.pause};
      ref = firebaseRef.child(deviceId).child('StartStop');
      break;
  }

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

6. 統合をテストする

3 つのインテントをすべて実装したら、統合で洗濯機を制御できるかどうかをテストします。

Firebase にデプロイする

Firebase CLI を使用して、更新したクラウド フルフィルメントをデプロイします。

firebase deploy --only functions

洗濯機をテストする

スマートフォンで以下の音声コマンドを試して、値が変化することを確認します。

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

「OK Google, 洗濯機を一時停止して。」

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

また、洗濯機の現在の状態を確認することもできます。

「OK Google, 洗濯機はオンになってる?」

「OK Google, 洗濯機は動いてる?」

「OK Google, 洗濯機の今のステップを教えて」

これらのクエリとコマンドは、Firebase コンソールの [Functions] セクションの関数の下に表示されるログで確認できます。Firebase ログの詳細については、ログの出力と確認をご覧ください。

これらのクエリとコマンドは、Google Cloud コンソールの [ロギング] > [ログ エクスプローラ] に移動して確認することもできます。Google Cloud のロギングの詳細については、Cloud Logging でイベントログにアクセスするをご覧ください。

7. Google に更新を報告する

以上でクラウド サービスとスマートホーム インテントの統合がすべて完了し、洗濯機デバイスの現在の状態の制御とクエリの実行ができるようになりました。ただし、サービスがアシスタントにイベント情報(デバイスのプレゼンスや状態の変化など)を自動的に送信する手段がまだ実装されていません。

Request Sync を使用すると、ユーザーがデバイスを追加または削除したときやデバイスの機能が変更されたときに、新しい同期リクエストをトリガーできます。Report State を使用すると、ユーザーがデバイスの状態を物理的に変更したとき(たとえば照明のスイッチをオンにしたとき)や、別のサービスを使用してその状態を変更したときに、デバイスの状態をクラウド サービスから自動的にホームグラフに送信できるようになります。

このセクションでは、フロントエンドのウェブアプリからこれらのメソッドを呼び出すためのコードを追加します。

HomeGraph API を有効にする

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

Google Cloud コンソールで、統合の <project-id>. に一致するプロジェクトを選択し、HomeGraph API の [API ライブラリ] 画面で [有効にする] をクリックします。

ee198858a6eac112.png

Report State を有効にする

Realtime Database への書き込みによって、スターター プロジェクトの reportstate 関数がトリガーされます。データベースに書き込まれたデータをキャプチャして Report State 経由でホームグラフに送信するよう、functions/index.jsreportstate 関数を更新します。

index.js

exports.reportstate = functions.database.ref('{deviceId}').onWrite(
    async (change, context) => {
      functions.logger.info('Firebase write event triggered Report State');
      const snapshot = change.after.val();

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

      const res = await homegraph.devices.reportStateAndNotification({
        requestBody,
      });
      functions.logger.info('Report state response:', res.status, res.data);
    });

Request Sync を有効にする

フロントエンド ウェブ UI のアイコンを更新すると、スターター プロジェクトの requestsync 関数がトリガーされます。functions/index.jsrequestsync 関数に、HomeGraph API の呼び出しを実装します。

index.js

exports.requestsync = functions.https.onRequest(async (request, response) => {
  response.set('Access-Control-Allow-Origin', '*');
  functions.logger.info(`Request SYNC for user ${USER_ID}`);
  try {
    const res = await homegraph.devices.requestSync({
      requestBody: {
        agentUserId: USER_ID,
      },
    });
    functions.logger.info('Request sync response:', res.status, res.data);
    response.json(res.data);
  } catch (err) {
    functions.logger.error(err);
    response.status(500).send(`Error requesting sync: ${err}`);
  }
});

Firebase にデプロイする

Firebase CLI を使用して、更新したコードをデプロイします。

firebase deploy --only functions

実装をテストする

ウェブ UI で更新 ボタン ae8d3b25777a5e30.png をクリックし、Firebase コンソールのログに同期リクエストが表示されていることを確認します。

次に、フロントエンド ウェブ UI で洗濯機の属性を調整し、[Update] をクリックします。状態の変化が Google に報告されていることを Firebase コンソールのログで確認します。

8. 完了

674c4f4392e98c1.png

これで、Cloud-to-Cloud インテグレーションを使用して、アシスタントとデバイス クラウド サービスの統合が完了しました。

詳細

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

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