实现本地执行方式应用

为了支持本地执行方式,你需要构建一个应用来处理以下智能家居 intent:

  • IDENTIFY:支持发现可在本地控制的智能设备。intent 处理程序会提取你的智能设备在发现过程中返回的数据,并在响应 Google 时发送该数据。
  • EXECUTE:支持执行命令。
  • QUERY:支持查询设备状态。
  • REACHABLE_DEVICES:(可选)支持发现 hub(或网桥)设备所连接的本地可控终端设备。

此应用在用户的 Google Home 或 Google Nest 设备上运行,并将你的智能设备连接到 Google 助理。你可以使用 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");
  });
Hub
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 捆绑器配置的 TypeScript:

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 脚本

捆绑
cd project-directory/
npm run build

此脚本会编译 TypeScript 源代码,并在 dist/web 子目录中将你的应用与其 Chrome 运行时环境的依赖项捆绑,以及在 dist/node 子目录中将你的应用与 Node.js 运行时环境的依赖项捆绑。

验证
cd project-directory/
npm run lint
npm run compile
npm test

此脚本会验证 TypeScript 代码的语法、对其进行编译(而不会在 dist/ 子目录中产生任何输出),以及从 test.ts 运行自动测试。

提供
cd project-directory/
npm run start

在开发期间,该脚本会在本地为 Chrome 运行时环境和 Node.js 运行时环境提供 app bundle。

实现 IDENTIFY 处理程序

当 Google Home 或 Google Nest 设备重新启动并发现未经验证的本地设备(包括连接到 hub 的终端设备)时,就会触发 IDENTIFY 处理程序。Local Home 平台将使用你之前指定的扫描配置信息扫描本地设备,并对扫描结果调用 IDENTIFY 处理程序。

Local Home 平台中的 IdentifyRequest 包含 LocalIdentifiedDevice 实例的扫描数据。根据发现了设备的扫描配置,系统仅填充一个 device 实例。

如果扫描结果与您的设备匹配,您的 IDENTIFY 处理程序应返回一个 IdentifyResponsePayload 对象,其中包括带有智能家居元数据(例如类型、特征和报告状态)的 device 对象。

如果 IDENTIFY 响应中的 verificationIdSYNC 响应返回的某个 otherDeviceIds 值匹配,Google 就会建立设备关联。

示例

以下代码段展示了如何分别为独立设备和 hub 集成创建 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;
  };
Hub
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;
  };

识别连接到 hub 的设备

如果 Google 发现一个 hub 设备,则会将该 hub 视为连接到 hub 的终端设备的管道,并尝试验证这些终端设备。

如需让 Google 确认某个 hub 设备的存在,请按照以下 IDENTIFY 处理程序相关说明进行操作:

  • 如果你的 SYNC 响应报告已连接到 hub 的本地终端设备 ID,请在 IdentifyResponsePayload 中将 isProxy 设置为 true
  • 如果您的 SYNC 响应未报告您的 hub 设备,请在 IdentifyResponsePayload 中将 isLocalOnly 设置为 true
  • device.id 字段包含 hub 设备本身的本地设备 ID。

实现 REACHABLE_DEVICES 处理程序(仅限 hub 集成)

REACHABLE_DEVICES intent 由 Google 发送,用于确认哪些终端设备可在本地控制。每当 Google 运行发现扫描(大约每分钟一次)时,只要检测到 hub 处于在线状态,就会触发此 intent。

REACHABLE_DEVICES 处理程序与 IDENTIFY 处理程序的实现方式类似,区别在于前者需要收集本地代理(即 hub)设备可以访问的附加设备 ID。device.verificationId 字段包含连接到 hub 的终端设备的本地设备 ID。

Local Home 平台中的 ReachableDevicesRequest 包含一个 LocalIdentifiedDevice 实例。通过此实例,你可以获取代理设备 ID 以及扫描结果中的数据。

你的 REACHABLE_DEVICES 处理程序应返回一个 ReachableDevicesPayload 对象,该对象包含一个 devices 对象(其中包含代表 hub 控制的终端设备的一组 verificationId 值)。verificationId 值必须与 SYNC 响应中的某个 otherDeviceIds 匹配。

以下代码段展示了如何创建 REACHABLE_DEVICES 处理程序。

Hub
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 通过现有协议访问你的智能设备。

Local Home 平台将同一输入载荷传递给 EXECUTE 处理程序函数,与将 EXECUTE intent 传递到你的云执行方式的操作相同。同样,EXECUTE 处理程序返回的输出数据格式与处理 EXECUTE intent 时相同。为了简化响应创建过程,您可以使用 Local Home SDK 提供的 Execute.Response.Builder 类。

你的应用没有直接访问设备 IP 地址的权限。而是使用 CommandRequest 接口,创建基于以下某个协议的命令:UDP、TCP 或 HTTP。然后,调用 deviceManager.send() 函数来发送创建的命令。

将命令定位到设备时,请使用 SYNC 响应中的设备 ID(以及 customData 字段中的参数,如果包含)与设备进行通信。

示例

以下代码段展示了如何创建 EXECUTE 处理程序。

独立/Hub
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 报告你的智能设备的状态。

Local Home 平台将同一请求载荷传递给“QUERY”处理程序函数,与将 QUERY intent 传递给你的云执行方式的操作相同。同样,QUERY 处理程序返回的数据格式与处理 QUERY intent 时相同。

向连接到 hub 的设备发送命令

如需控制连接到 hub 的终端设备,你可能需要在发送到该 hub 的协议专用命令载荷中提供额外信息,以便该 hub 确定该命令针对的是哪个设备。在某些情况下,这可以根据 device.id 值直接推断出来,但如果不是这种情况,则应将该额外数据作为 customData 字段的一部分包含在内。

如果你使用 TypeScript 创建了应用,请记得将应用编译为 JavaScript。你可以使用自己选择的模块系统来编写代码。 确保 Chrome 浏览器支持你的目标。