ローカル フルフィルメント アプリを実装する

ローカル フルフィルメントをサポートするには、次のスマートホーム インテントを処理するアプリを作成する必要があります。

  • IDENTIFY: ローカルで制御可能なスマート デバイスの検出をサポートします。インテント ハンドラは、スマート デバイスが検出の際に返したデータを抽出し、レスポンスで Google に送信します。
  • EXECUTE: コマンドの実行をサポートします。
  • QUERY: デバイスの状態のクエリをサポートします。
  • REACHABLE_DEVICES: (省略可)ハブ(またはブリッジ)デバイスにローカル接続され、ローカルで制御可能なエンドデバイスの検出をサポートします。

このアプリは、ユーザーの Google Home または Google Nest デバイスで動作し、スマート デバイスをアシスタントに接続します。アプリは TypeScript(推奨)または JavaScript を使用して作成できます。

TypeScript が推奨されるのは、 バインディング を利用して、アプリが返したデータが、プラットフォームが想定するタイプと一致することを静的に保証できるためです。

API の詳細については、 Local Home SDK API リファレンスをご覧ください。

次のスニペットで、ローカル フルフィルメント アプリを初期化してハンドラをアタッチする方法を示します。

スタンドアロン
import App = smarthome.App;
const localHomeApp: App = new App("1.0.0");
localHomeApp
  .onIdentify(identifyHandler)
  .onExecute(executeHandler)
  .listen()
  .then(() => {
    console.log("Ready");
  });
ハブ
import App = smarthome.App;
const localHomeApp: App = new App("1.0.0");
localHomeApp
  .onIdentify(identifyHandler)
  .onReachableDevices(reachableDevicesHandler)
  .onExecute(executeHandler)
  .listen()
  .then(() => {
    console.log("Ready");
  });

プロジェクトを作成する

ローカル フルフィルメント アプリをデプロイするには、コードとそのすべての依存関係向けに JavaScript バンドルをビルドする必要があります。

ローカル フルフィルメント アプリの プロジェクト 初期化ツール を使用して、優先のバンドラ 構成が含まれる適切なプロジェクト構造をブートストラップします。

プロジェクト テンプレート

バンドラ構成を選択するには、次の例に示すように、npm init コマンドを実行します。

なし

バンドラ構成が含まれない TypeScript:

npm init @google/local-home-app project-directory/ --bundler none

プロジェクトの構造:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
└── serve.js

project-directory を、ローカル フルフィルメント アプリ プロジェクトを格納する新しいディレクトリに置き換えます。

webpack

webpack バンドラ構成が含まれる TypeScript:

npm init @google/local-home-app project-directory/ --bundler webpack

プロジェクトの構造:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
├── webpack.config.web.js
├── webpack.config.node.js
└── serve.js

project-directory を、ローカル フルフィルメント アプリ プロジェクトを格納する新しいディレクトリに置き換えます。

rollup

rollup バンドラ構成が含まれる TypeScript:

npm init @google/local-home-app project-directory/ --bundler rollup

プロジェクトの構造:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
├── rollup.config.js
└── serve.js

project-directory を、ローカル フルフィルメント アプリ プロジェクトを格納する新しいディレクトリに置き換えます。

Parcel

Parcel

npm init @google/local-home-app project-directory/ --bundler parcel

プロジェクトの構造:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
└── serve.js

project-directory を、ローカル フルフィルメント アプリ プロジェクトを格納する新しいディレクトリに置き換えます。

プロジェクト レベルの共通タスクを実行する

生成されたプロジェクトは、次の npm scripts をサポートしています。

bundle
cd project-directory/
npm run build

このスクリプトは、TypeScript ソースをコンパイルし、dist/web サブディレクトリ内の Chrome ランタイム環境の依存関係と、dist/node サブディレクトリ内の Node.js ランタイム環境の依存関係にアプリをバンドルします。

verify
cd project-directory/
npm run lint
npm run compile
npm test

このスクリプトは、TypeScript コードの構文を確認し、dist/ サブディレクトリに出力を生成することなくこのコードをコンパイルし、test.ts から自動テストを実行します。

serve
cd project-directory/
npm run start

開発時には、このスクリプトが Chrome と Node.js のランタイム環境にローカルでアプリ バンドルを提供します。

IDENTIFY ハンドラを実装する

IDENTIFY ハンドラは、Google Home または Google Nest デバイスが再起動して、未確認のローカル デバイス(ハブに接続されたエンドデバイスを含む)を検出したときにトリガーされます。ローカルホーム プラットフォームは、以前に指定されたスキャン設定情報を使用してローカル デバイスをスキャンし、スキャン結果を使用して IDENTIFY ハンドラを呼び出します。

ローカルホーム プラットフォームからの IdentifyRequest には、 LocalIdentifiedDevice インスタンスのスキャンデータが含まれています。デバイスを検出したスキャン設定に基づいて作成される device インスタンスは 1 つのみです。

スキャン結果がデバイスと一致すると、IDENTIFY ハンドラは IdentifyResponsePayload オブジェクトを返します。このオブジェクトには、device オブジェクトと スマートホーム メタデータ(タイプ、トレイト、レポート ステータスなど)が含まれます。

IDENTIFY レスポンスからの verificationIdSYNC レスポンスによって返された otherDeviceIds の値のいずれかと一致すると、Google でデバイスの関連付けが確立されます。

次のスニペットで、スタンドアロン デバイスとハブ統合用の IDENTIFY ハンドラの作成方法をそれぞれ示します。

スタンドアロン
const identifyHandler = (request: IntentFlow.IdentifyRequest):
  IntentFlow.IdentifyResponse => {

    // Obtain scan data from protocol defined in your scan config
    const device = request.inputs[0].payload.device;
    if (device.udpScanData === undefined) {
      throw Error("Missing discovery response");
    }
    const scanData = device.udpScanData.data;

    // Decode scan data to obtain metadata about local device
    const verificationId = "local-device-id";

    // Return a response
    const response: IntentFlow.IdentifyResponse = {
      intent: Intents.IDENTIFY,
      requestId: request.requestId,
      payload: {
        device: {
          id: device.id || "",
          verificationId, // Must match otherDeviceIds in SYNC response
        },
      },
    };
    return response;
  };
ハブ
const identifyHandler = (request: IntentFlow.IdentifyRequest):
  IntentFlow.IdentifyResponse => {

    // Obtain scan data from protocol defined in your scan config
    const device = request.inputs[0].payload.device;
    if (device.udpScanData === undefined) {
      throw Error("Missing discovery response");
    }
    const scanData = device.udpScanData.data;

    // Decode scan data to obtain metadata about local device
    const proxyDeviceId = "local-hub-id";

    // Return a response
    const response: IntentFlow.IdentifyResponse = {
      intent: Intents.IDENTIFY,
      requestId: request.requestId,
      payload: {
        device: {
          id: proxyDeviceId,
          isProxy: true,     // Device can control other local devices
          isLocalOnly: true, // Device not present in `SYNC` response
        },
      },
    };
    return response;
  };

ハブに接続されたデバイスを識別する

ハブデバイスを確認すると、そのハブに接続されているエンドデバイスへのコンジットとして処理してから、それらのエンドデバイスの確認を試みます。

ハブデバイスがあることを Google が確認できるようにするには、IDENTIFY ハンドラに対し次の手順を行います。

REACHABLE_DEVICES ハンドラを実装する(ハブ統合のみ)

ローカルで制御できるエンドデバイスを確認するため、REACHABLE_DEVICES インテントが Google から送信されます。Google が検出スキャンを実行するたびに(毎分約 1 回)、ハブがオンラインであることが検出されると、このインテントがトリガーされます。

IDENTIFY ハンドラと同様に REACHABLE_DEVICES ハンドラを実装します。ただし、このハンドラでは、ローカル プロキシ(つまりハブ自体)のデバイスがアクセスできる追加のデバイス ID を収集する必要があります。device.verificationId フィールドには、ハブに接続されているエンドデバイスのローカル デバイス ID が含まれます。

ローカルホーム プラットフォームの ReachableDevicesRequest には、 LocalIdentifiedDeviceのインスタンスが含まれます。 このインスタンスを介し、スキャン結果のデータとともに、プロキシ デバイス ID を取得できます。

REACHABLE_DEVICES ハンドラは ReachableDevicesPayload オブジェクトを返します。このオブジェクトには、ハブが制御しているエンドデバイスを表す verificationId 値の配列を含む devices オブジェクトが含まれます。verificationId 値は SYNC レスポンスからの otherDeviceIds のいずれかと一致する必要があります。

次のスニペットで、REACHABLE_DEVICES ハンドラの作成方法を示します。

ハブ
const reachableDevicesHandler = (request: IntentFlow.ReachableDevicesRequest):
  IntentFlow.ReachableDevicesResponse => {

    // Reference to the local proxy device
    const proxyDeviceId = request.inputs[0].payload.device.id;

    // Gather additional device ids reachable by local proxy device
    // ...

    const reachableDevices = [
      // Each verificationId must match one of the otherDeviceIds
      // in the SYNC response
      { verificationId: "local-device-id-1" },
      { verificationId: "local-device-id-2" },
    ];

    // Return a response
    const response: IntentFlow.ReachableDevicesResponse = {
      intent: Intents.REACHABLE_DEVICES,
      requestId: request.requestId,
      payload: {
        devices: reachableDevices,
      },
    };
    return response;
  };

EXECUTE ハンドラを実装する

アプリ内の EXECUTE ハンドラは、ユーザー コマンドを処理し、Local Home SDK を使用して既存のプロトコルを介してスマート デバイスにアクセスします。

ローカルホーム プラットフォームは、クラウド フルフィルメントへの EXECUTE インテントと同じ入力ペイロードを EXECUTE ハンドラ関数に渡します。同様に、EXECUTE ハンドラは、EXECUTE インテントの処理と同じ形式で出力データを返します。 Local Home SDK が提供する Execute.Response.Builder クラスを使用すると、レスポンスを簡単に作成できます。

アプリは、デバイスの IP アドレスに直接アクセスできません。代わりに、 CommandRequest インターフェースを使用して、UDP、TCP、HTTP のいずれかのプロトコルに基づいてコマンドを作成します。その後、 deviceManager.send() 関数を呼び出してコマンドを送信します。

デバイスにコマンドをターゲティングする場合は、SYNC レスポンスからのデバイス ID(および含まれる場合は customData フィールドからのパラメータ)を使用してデバイスと通信します。

次のコード スニペットで、EXECUTE ハンドラの作成方法を示します。

スタンドアロン / ハブ
const executeHandler = (request: IntentFlow.ExecuteRequest):
  Promise<IntentFlow.ExecuteResponse> => {

    // Extract command(s) and device target(s) from request
    const command = request.inputs[0].payload.commands[0];
    const execution = command.execution[0];

    const response = new Execute.Response.Builder()
      .setRequestId(request.requestId);

    const result = command.devices.map((device) => {
      // Target id of the device provided in the SYNC response
      const deviceId = device.id;
      // Metadata for the device provided in the SYNC response
      // Use customData to provide additional required execution parameters
      const customData: any = device.customData;

      // Convert execution command into payload for local device
      let devicePayload: string;
      // ...

      // Construct a local device command over TCP
      const deviceCommand = new DataFlow.TcpRequestData();
      deviceCommand.requestId = request.requestId;
      deviceCommand.deviceId = deviceId;
      deviceCommand.data = devicePayload;
      deviceCommand.port = customData.port;
      deviceCommand.operation = Constants.TcpOperation.WRITE;

      // Send command to the local device
      return localHomeApp.getDeviceManager()
        .send(deviceCommand)
        .then((result) => {
          response.setSuccessState(result.deviceId, state);
        })
        .catch((err: IntentFlow.HandlerError) => {
          err.errorCode = err.errorCode || IntentFlow.ErrorCode.INVALID_REQUEST;
          response.setErrorState(device.id, err.errorCode);
        });
    });

    // Respond once all commands complete
    return Promise.all(result)
      .then(() => response.build());
  };

QUERY ハンドラを実装する

アプリ内の QUERY ハンドラは、ユーザー リクエストを処理し、Local Home SDK を使用してスマート デバイスの状態を報告します。

ローカルホーム プラットフォームは、クラウド フルフィルメントへの QUERY インテントと同じリクエスト ペイロードを QUERY ハンドラ関数に渡します。同様に、QUERY ハンドラは、QUERY インテントの処理と同じ形式でデータを返します。

ハブに接続されたデバイスにコマンドを送信する

ハブに接続されたエンドデバイスを制御するには、ハブがコマンド対象のデバイスを識別できるよう、ハブに送信されるプロトコル固有のコマンドのペイロードに、追加情報を提供する必要があります。これは device.id 値から直接推定できる場合もありますが、推定できない場合は、この追加データを customData フィールドの一部として含める必要があります。

TypeScript を使用してアプリを作成した場合は、必ずアプリを JavaScript にコンパイルしてください。任意のモジュール システムを使用してコードを作成できます。 ターゲットが Chrome ブラウザでサポートされていることをご確認ください。