10 月 21 日の Google スマートホーム デベロッパー サミットに、ぜひオンラインでご参加ください。こちらから登録すると、Google スマートホームの新機能や最新情報を確認できます。

インテントのフルフィルメント

スマートホーム アクションを作成したら、次の手順として、スマートホーム インテントを処理してアシスタントが認識するレスポンスを返す機能をフルフィルメントに追加します。

このページでは、スマートホーム アクションにインテントのフルフィルメントを実装する手順について説明します。

  1. ユーザーを特定する
  2. デバイスと機能のリストを入手する
  3. クエリとコマンドに応答する
  4. リンク解除イベントを処理する

ユーザーを特定する

アシスタントは、Authorization ヘッダー内の OAuth 2.0 サーバーから提供されるアクセス トークンを使用して、スマートホーム アクションのフルフィルメントにリクエストを送信します。

POST /fulfillment HTTP/1.1
Host: smarthome.example.com
Content-Type: application/json
Authorization: Bearer ACCESS_TOKEN

フルフィルメント ロジックでは、リクエストに応答する前にこのトークンの認証情報が有効であることを確認し、関連付けられているユーザー アカウントを特定する必要があります。アクセス トークンが無効な場合は、フルフィルメントから HTTP 401 Unauthorized エラーが返されます。

デバイスと機能のリストを入手する

アシスタントは、action.devices.SYNC インテントをフルフィルメントに送信し、特定のユーザーとその機能に関連付けられているデバイスのリストをリクエストします。フルフィルメントは、SYNC レスポンスの agentUserId フィールドでユーザーごとに一意の ID を返す必要があります。この ID は、クラウド サービスがユーザーの識別に使用する不変値でなければなりません。メールアドレスなど、ユーザーが変更可能な設定に基づく属性を ID に使用することはおすすめしません。

SYNC レスポンスの devices フィールドには、ユーザーがアシスタントにアクセスを許可したすべてのデバイス、サポートされているタイプとトレイト、特定のデバイスのトレイトの動作を設定するために必要な属性が格納されています。

SYNC インテントは、アカウントをリンクしたとき、またはユーザーが手動でデバイスを再同期したときにトリガーされます。ユーザーのデバイスのリスト、サポートされているトレイト、または属性値が変更された場合は、Request Sync を使用して新しい SYNC インテントをトリガーして変更内容を Google に報告してください。

リクエスト
{
    "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
    "inputs": [{
      "intent": "action.devices.SYNC"
    }]
}
JSON
{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "agentUserId": "1836.15267389",
    "devices": [
      {
        "id": "123",
        "type": "action.devices.types.OUTLET",
        "traits": [
          "action.devices.traits.OnOff"
        ],
        "name": {
          "defaultNames": [
            "My Outlet 1234"
          ],
          "name": "Night light",
          "nicknames": [
            "wall plug"
          ]
        },
        "willReportState": false,
        "roomHint": "kitchen",
        "deviceInfo": {
          "manufacturer": "lights-out-inc",
          "model": "hs1234",
          "hwVersion": "3.2",
          "swVersion": "11.4"
        },
        "otherDeviceIds": [
          {
            "deviceId": "local-device-id"
          }
        ],
        "customData": {
          "fooValue": 74,
          "barValue": true,
          "bazValue": "foo"
        }
      },
      {
        "id": "456",
        "type": "action.devices.types.LIGHT",
        "traits": [
          "action.devices.traits.OnOff",
          "action.devices.traits.Brightness",
          "action.devices.traits.ColorSetting"
        ],
        "name": {
          "defaultNames": [
            "lights out inc. bulb A19 color hyperglow"
          ],
          "name": "lamp1",
          "nicknames": [
            "reading lamp"
          ]
        },
        "willReportState": false,
        "roomHint": "office",
        "attributes": {
          "colorModel": "rgb",
          "colorTemperatureRange": {
            "temperatureMinK": 2000,
            "temperatureMaxK": 9000
          },
          "commandOnlyColorSetting": false
        },
        "deviceInfo": {
          "manufacturer": "lights out inc.",
          "model": "hg11",
          "hwVersion": "1.2",
          "swVersion": "5.4"
        },
        "customData": {
          "fooValue": 12,
          "barValue": false,
          "bazValue": "bar"
        }
      }
    ]
  }
}
Node.js
const {smarthome} = require('actions-on-google');
const app = smarthome();
// ...
app.onSync((body, headers) => {
  // TODO Get devices for user
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: "1836.15267389",
      devices: [{
        id: "123",
        type: "action.devices.types.OUTLET",
        traits: [
          "action.devices.traits.OnOff"
        ],
        name: {
          defaultNames: ["My Outlet 1234"],
          name: "Night light",
          nicknames: ["wall plug"]
        },
        willReportState: false,
        roomHint: "kitchen",
        deviceInfo: {
          manufacturer: "lights-out-inc",
          model: "hs1234",
          hwVersion: "3.2",
          swVersion: "11.4"
        },
        otherDeviceIds: [{
          deviceId: "local-device-id"
        }],
        customData: {
          fooValue: 74,
          barValue: true,
          bazValue: "foo"
        }
      }, {
        id: "456",
        type: "action.devices.types.LIGHT",
        traits: [
          "action.devices.traits.OnOff",
          "action.devices.traits.Brightness",
          "action.devices.traits.ColorSetting"
        ],
        name: {
          defaultNames: ["lights out inc. bulb A19 color hyperglow"],
          name: "lamp1",
          nicknames: ["reading lamp"]
        },
        willReportState: false,
        roomHint: "office",
        attributes: {
          colorModel: 'rgb',
          colorTemperatureRange: {
            temperatureMinK: 2000,
            temperatureMaxK: 9000
          },
          commandOnlyColorSetting: false
        },
        deviceInfo: {
          manufacturer: "lights out inc.",
          model: "hg11",
          hwVersion: "1.2",
          swVersion: "5.4"
        },
        customData: {
          fooValue: 12,
          barValue: false,
          bazValue: "bar"
        }
      }]
    }
  };
});
Java
@NotNull
@Override
public SyncResponse onSync(@NotNull SyncRequest syncRequest, @Nullable Map<?, ?> map) {
  Payload payload = new Payload();
  payload.setAgentUserId("1836.15267389");
  payload.setDevices(
      new Device[] {
        new Device.Builder()
            .setId("123")
            .setType("action.devices.types.OUTLET")
            .addTrait("action.devices.traits.OnOff")
            .setName(
                Collections.singletonList("My Outlet 1234"),
                "Night light",
                Collections.singletonList("Wall plug"))
            .setWillReportState(true)
            .setDeviceInfo("lights-out-inc", "hs1234", "3.2", "11.4")
            .setCustomData(
                new JSONObject()
                    .put("fooValue", 74)
                    .put("barValue", true)
                    .put("bazValue", "foo"))
            .build(),
        new Device.Builder()
            .setId("456")
            .setType("action.devices.types.LIGHT")
            .addTrait("action.devices.traits.OnOff")
            .addTrait("action.devices.traits.Brightness")
            .addTrait("action.devices.traits.ColorTemperature")
            .addTrait("action.devices.traits.ColorSpectrum")
            .setName(
                Collections.singletonList("Lights Out Inc. bulb A19 color hyperglow"),
                "Lamp",
                Collections.singletonList("Reading lamp"))
            .setWillReportState(true)
            .setDeviceInfo("Lights Out Inc.", "hg11", "1.2", "5.4")
            .setCustomData(
                new JSONObject()
                    .put("fooValue", 12)
                    .put("barValue", false)
                    .put("bazValue", "bar"))
            .build(),
      });
  return new SyncResponse(syncRequest.getRequestId(), payload);
}

その他のリファレンスについては、SYNC インテントのリファレンス ドキュメントをご覧ください。

クエリとコマンドに応答する

ユーザーがアシスタントに現在のデバイスのステータスを尋ねると、フルフィルメントは action.devices.QUERY インテントが受け取ります。このインテントには、SYNC レスポンスで提供されたデバイス ID のリストが含まれています。 ユーザーがデバイス操作するためのコマンドをアシスタントに送ると、フルフィルメントは action.devices.EXECUTE インテントを受け取ります。

QUERY インテントを処理する

QUERY レスポンスには、リクエストされたデバイスでサポートされている各トレイトのステータスがすべて含まれます。

リクエスト
{
    "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
    "inputs": [{
      "intent": "action.devices.QUERY",
      "payload": {
        "devices": [{
          "id": "123",
          "customData": {
            "fooValue": 74,
            "barValue": true,
            "bazValue": "foo"
          }
        }, {
          "id": "456",
          "customData": {
            "fooValue": 12,
            "barValue": false,
            "bazValue": "bar"
          }
        }]
      }
    }]
}
JSON
{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "devices": {
      "123": {
        "on": true,
        "online": true
      },
      "456": {
        "on": true,
        "online": true,
        "brightness": 80,
        "color": {
          "name": "cerulean",
          "spectrumRGB": 31655
        }
      }
    }
  }
}
Node.js
const {smarthome} = require('actions-on-google');
const app = smarthome();
// ...
app.onQuery((body, headers) => {
  // TODO Get device state
  return {
    requestId: body.requestId,
    payload: {
      devices: {
        123: {
          on: true,
          online: true
        },
        456: {
          on: true,
          online: true,
          brightness: 80,
          color: {
            name: "cerulean",
            spectrumRGB: 31655
          }
        }
      }
    }
  };
});
Java
@NotNull
@Override
public QueryResponse onQuery(@NotNull QueryRequest queryRequest, @Nullable Map<?, ?> map) {
  QueryResponse.Payload payload = new QueryResponse.Payload();
  payload.setDevices(
      new HashMap<String, Map<String, Object>>() {
        {
          put(
              "123",
              new HashMap<String, Object>() {
                {
                  put("on", true);
                  put("online", true);
                }
              });
          put(
              "456",
              new HashMap<String, Object>() {
                {
                  put("on", true);
                  put("online", true);
                  put("brightness", 80);
                  put(
                      "color",
                      new HashMap<String, Object>() {
                        {
                          put("name", "cerulean");
                          put("spectrumRGB", 31655);
                        }
                      });
                }
              });
        }
      });

  return new QueryResponse(queryRequest.getRequestId(), payload);
}

その他のリファレンスについては、QUERY インテントのリファレンス ドキュメントをご覧ください。

EXECUTE インテントを処理する

QUERY と同様に、1 つのインテントで複数のデバイス ID をターゲットにできます。また、1 つの EXECUTE インテントには、デバイスのグループに対するコマンドを複数含めることができます。たとえば、トリガーされた 1 つのインテントで、複数のライトの明るさと色の両方を設定することも、複数のライトをそれぞれ異なる色に設定することもできます。EXECUTE レスポンスにより、実行後のデバイスの新しいステータスが返されます。

EXECUTE インテントやローカル ステータスの変更(たとえばライトのスイッチが手動で切り替えられた)などにより、ユーザーのデバイスのステータスが変更された場合は、Report State を使用します。これにより、ホームグラフとクラウド サービスの同期を維持できます。

リクエスト
{
    "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
    "inputs": [{
      "intent": "action.devices.EXECUTE",
      "payload": {
        "commands": [{
          "devices": [{
            "id": "123",
            "customData": {
              "fooValue": 74,
              "barValue": true,
              "bazValue": "sheepdip"
            }
          }, {
            "id": "456",
            "customData": {
              "fooValue": 36,
              "barValue": false,
              "bazValue": "moarsheep"
            }
          }],
          "execution": [{
            "command": "action.devices.commands.OnOff",
            "params": {
              "on": true
            }
          }]
        }]
      }
    }]
}
JSON
{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "commands": [
      {
        "ids": [
          "123"
        ],
        "status": "SUCCESS",
        "states": {
          "on": true,
          "online": true
        }
      },
      {
        "ids": [
          "456"
        ],
        "status": "ERROR",
        "errorCode": "deviceTurnedOff"
      }
    ]
  }
}
Node.js
const {smarthome} = require('actions-on-google');
const app = smarthome();
// ...
app.onExecute((body, headers) => {
  // TODO Send command to device
  return {
    requestId: body.requestId,
    payload: {
      commands: [{
        ids: ["123"],
        status: "SUCCESS",
        states: {
          on: true,
          online: true
        }
      }, {
        ids: ["456"],
        status: "ERROR",
        errorCode: "deviceTurnedOff"
      }]
    }
  };
});
Java
@NotNull
@Override
public ExecuteResponse onExecute(
    @NotNull ExecuteRequest executeRequest, @Nullable Map<?, ?> map) {
  ExecuteResponse.Payload payload = new ExecuteResponse.Payload();

  payload.setCommands(
      new Commands[] {
        new Commands(
            new String[] {"123"},
            "SUCCESS",
            new HashMap<String, Object>() {
              {
                put("on", true);
                put("online", true);
              }
            },
            null,
            null),
        new Commands(new String[] {"456"}, "ERROR", null, "deviceTurnedOff", null)
      });
  return new ExecuteResponse(executeRequest.getRequestId(), payload);
}

その他のリファレンスについては、EXECUTE インテントのリファレンス ドキュメントをご覧ください。

ステータス レスポンス

QUERYEXECUTE のレスポンスには、リクエストの結果を報告するための status フィールドが含まれています。ステータス レスポンスで返される可能性のある値は次のとおりです。

  • SUCCESS: リクエストが成功した。
  • OFFLINE: ターゲット デバイスがオフラインであるか、接続できない。
  • EXCEPTIONS: リクエストに関連する問題またはアラートがある。
  • ERROR: 対応する errorCode でリクエストが失敗した。

ERROREXCEPTIONS の詳細については、エラーと例外を処理するおよびエラーと例外をご覧ください。

ユーザーがアシスタントからスマートホーム アクションのリンクを解除すると、フルフィルメントは action.devices.DISCONNECT インテントを受け取ります。このインテントは、該当のユーザーに対するアシスタントからのインテントが送信されなくなることを意味し、クラウド サービスでのこのデバイスに対する Home Graph API(Request Sync と Report State)の呼び出しを停止する必要があります。

リクエスト
{
    "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
    "inputs": [{
      "intent": "action.devices.DISCONNECT",
    }]
}
JSON
{}
Node.js
const {smarthome} = require('actions-on-google');
const app = smarthome();
// ...
app.onDisconnect((body, headers) => {
  // TODO Disconnect user account from Google Assistant
  // You can return an empty body
  return {};
});
Java
@Override
public void onDisconnect(
    @NotNull DisconnectRequest disconnectRequest, @Nullable Map<?, ?> map) {
  // TODO Disconnect user account from Google Assistant
  // This function does not return anything
}

その他のリファレンスについては、DISCONNECT インテントのリファレンス ドキュメントをご覧ください。

これで、スマートホーム アクション プロジェクトのフルフィルメントの実装が完了し、Request SyncReport State を使用して非同期の変更を公開できるようになりました。