为云到云集成启用本地执行方式

1. 准备工作

通过智能家居集成,Google 助理可以控制用户住宅中已连接的设备。如需构建云到云集成,您需要提供一个能够处理智能家居 intent 的云网络钩子端点。例如,当用户说“Hey Google,打开灯”时,Google 助理会将这条指令发给您的云执行方式,以更新设备状态。

Local Home SDK 可以增强智能家居集成。它添加了一条本地路径,能直接将智能家居 intent 传递给 Google Home 设备,从而提高可靠性并缩短处理用户指令时的延迟时间。借助此 SDK,您可以用 TypeScript 或 JavaScript 编写和部署本地执行方式应用,以识别设备并在任意一个 Google Home 智能音箱或 Google Nest 智能显示屏上执行指令。然后,您的应用使用现有的标准协议,通过局域网直接与用户的现有智能设备进行通信,以便执行指令。

72ffb320986092c.png

前提条件

构建内容

在此 Codelab 中,您将利用 Firebase 部署一个以前构建的智能家居集成,然后在 开发者控制台中应用一个扫描配置,并使用 TypeScript 构建一个本地应用,用于将以 Node.js 编写的命令发送到虚拟洗衣机设备。

学习内容

  • 如何在 Play 管理中心内启用和配置本地执行方式。
  • 如何使用 Local Home SDK 编写本地执行方式应用。
  • 如何调试在 Google Home 音箱或 Google Nest 智能显示屏上加载的本地执行方式应用。

所需条件

2. 开始使用

启用活动控件

若要使用 Google 助理,你必须与 Google 分享某些活动数据。Google 助理需要使用这些数据才能正常运行;然而,分享数据的要求并非专门针对该 SDK。如果你还没有 Google 账号,请创建一个 Google 账号,以便分享这些数据。你可以使用任何 Google 账号,不要求必须用你的开发者账号。

打开要与 Google 助理搭配使用的 Google 账号的活动控件页面

确保已启用以下切换开关:

  • 网络与应用活动记录 - 此外,请务必选中包括 Chrome 历史记录和使用 Google 服务的网站、应用和设备中的活动记录复选框。
  • 设备信息
  • 语音和音频活动记录

创建云到云集成项目

  1. 前往开发者控制台
  2. 点击 Create Project,输入项目名称,然后点击 Create Project

为项目命名

选择云到云集成

在 Play 管理中心的项目首页上,选择云到云下的添加云到云集成

添加云到云集成

安装 Firebase CLI

借助 Firebase 命令行界面 (CLI),您可以在本地提供 Web 应用,并将您的 Web 应用部署到 Firebase Hosting。

如需安装 CLI,请从终端运行以下 npm 命令:

npm install -g firebase-tools

如需验证 CLI 是否已正确安装,请运行以下命令:

firebase --version

运行以下命令,授权您的 Google 账号使用 Firebase CLI:

firebase login

启用 HomeGraph API

借助 HomeGraph API,您可以在用户 Home Graph 中存储并查询设备及其状态。如需使用此 API,您必须先打开 Google Cloud 控制台,然后启用 HomeGraph API

在 Google Cloud 控制台中,请务必选择与您的集成 <project-id>. 相匹配的项目。然后,在 HomeGraph API 的 API 库屏幕中,点击启用

5SVCzM8IZLi_9DV8M0nEklv16NXkpvM0bIzQK2hSyKyvnFHBxPOz90rbr72ayxzmxd5aNROOqC_Cp4outbdlwJdObDs0DIE_8vYzw6dovoVrP9IZWlWsZxDS7UHOi1jiRbDMG8MqUA

3. 运行入门级应用

开发环境设置完毕后,你可以部署入门级项目以验证所有设置是否已配置正确。

获取源代码

点击以下链接,将此 Codelab 的示例下载到您的开发机器上:

…或者,您也可以通过命令行克隆 GitHub 代码库:

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

项目简介

入门级项目包含以下子目录:

  • public - 可控制和监控智能洗衣机的前端网络界面
  • functions - 为云到云集成实现云执行方式的 Cloud Functions 函数
  • local - 框架本地执行方式应用项目,包含在 index.ts 中打桩的 intent 处理程序

提供的云执行方式在 index.js 中包括以下函数:

  • fakeauth - 用于账号关联的授权端点
  • faketoken - 用于账号关联的令牌端点
  • smarthome - 智能家居 intent 执行方式端点
  • reportstate - 在设备状态改变时调用 HomeGraph API
  • updateDevice - 虚拟设备用来触发报告状态的端点

关联到 Firebase

前往 app-start 目录,然后使用您的云到云集成项目设置 Firebase CLI:

cd app-start
firebase use <project-id>

配置 Firebase 项目

初始化 Firebase 项目。

firebase init

选择 CLI 功能 Realtime DatabaseFunctions 以及包含 Firebase 托管的 Hosting 功能。

? 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.jspackage.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,可以通过以下两种方法将其停用:

  1. 使用 GUI 前往项目下的 ../functions 文件夹,选择隐藏文件 .eslintrc.js 并将其删除。请勿将其与同名 .eslintrc.json 混淆。
  2. 使用命令行:
    cd functions
    rm .eslintrc.js
    

为确保您拥有正确且完整的 Firebase 配置,请将 firebase.json 文件从 washer-done 目录复制到 washer-start 目录,并覆盖 washer-start 中的文件。

washer-start 目录中执行以下操作:

cp -vp ../washer-done/firebase.json .

部署到 Firebase

依赖性安装完毕且配置好项目后,您就可以首次运行此应用了。

firebase deploy

您应该会看到以下控制台输出:

...

✔ Deploy complete!

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

此命令会部署一个 Web 应用以及几个 Cloud Functions for Firebase

在浏览器 (https://<project-id>.web.app) 中打开托管网址以查看此 Web 应用。您会看到以下界面:

L60eA7MOnPmbBMl2XMipT9MdnP-RaVjyjf0Y93Y1b7mEyIsqZrrwczE7D3RQISRs-iusL1g4XbNmGhuA6-5sLcWefnczwNJEPfNLtwBsO4Tb9YvcAZBI6_rX19z8rxbik9Vq8F2fwg

此网络界面表示用于查看或修改设备状态的第三方平台。如需使用设备信息填充数据库,请点击 UPDATE。此页面不会显示任何更改,但洗衣机的当前状态会存储在数据库中。

现在,您可以使用 Developer Console 将您部署的云服务关联到 Google 助理。

配置您的 Play 管理中心项目

Develop 标签页中,为您的互动添加显示名称。此名称会显示在 Google Home 应用中。

添加显示名称

应用品牌信息下,为应用图标上传一个大小为 144 x 144 像素且名为 .pngpng 文件。

添加应用图标

如需启用账号关联,请使用以下账号关联设置:

客户 ID

ABC123

客户端密钥

DEF456

授权网址

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

令牌网址

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

更新账号关联网址

Cloud fulfillment 网址 下,输入为智能家居 intent 提供执行方式的 Cloud Functions 函数的网址。

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

添加 Cloud Functions 函数网址

点击 Save 保存您的项目配置,然后点击 Next: Test 针对您的项目启用测试。

测试云到云集成

现在,您可以开始实现必要的网络钩子,以便将设备状态关联到 Google 助理。

为了测试云到云集成,您需要将项目与 Google 账号相关联。这样一来,您就可以使用登录同一账号的 Google 助理界面和 Google Home 应用来进行测试。

  1. 在手机上打开 Google 助理设置。请注意,您登录的账号应该与控制台所用的账号相同。
  2. 依次转到 Google 助理 > 设置 > 家居控制(位于“Google 助理”下方)。
  3. 点击右上角的搜索图标
  4. 使用 [test] 前缀搜索测试应用,以查找特定的测试应用。
  5. 选择此项目。然后,Google 助理会通过您的服务进行身份验证并发送 SYNC 请求,以要求您的服务提供用户设备列表。

打开 Google Home 应用,然后验证您能否看到相应洗衣机设备。

XcWmBVamBZtPfOFqtsr5I38stPWTqDcMfQwbBjetBgxt0FCjEs285pa9K3QXSASptw0KYN2G8yfkT0-xg664V4PjqMreDDs-HPegHjOc4EVtReYPu-WKZyygq9Xmkf8X8z9177nBjQ

验证您是否可以在 Google Home 应用中通过语音指令控制洗衣机。您还应该会在云执行方式的前端网络界面中看到设备状态更改情况。

现在,可以开始为您的集成添加本地执行方式了。

4. 更新云执行方式

为了支持本地执行方式,你需要将一个新的设备专用字段(名为 otherDeviceIds)添加到包含设备唯一本地标识符的云 SYNC 响应中。此字段还表示在本地控制该设备的功能。

otherDeviceIds 字段添加到 SYNC 响应中,如以下代码段所示:

functions/index.js

app.onSync((body) => {
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: '123',
      devices: [{
        id: 'washer',
        type: 'action.devices.types.WASHER',
        traits: [ ... ],
        name: { ... },
        deviceInfo: { ... },
        willReportState: true,
        attributes: {
          pausable: true,
        },
        otherDeviceIds: [{
          deviceId: 'deviceid123',
        }],
      }],
    },
  };
});

将更新后的项目部署到 Firebase:

firebase deploy --only functions

部署完成后,前往 Web 界面,然后点击工具栏中的 Refresh ae8d3b25777a5e30.png 按钮。这会触发请求同步操作,以便 Google 助理接收更新后的 SYNC 响应数据。

bf4f6a866160a982.png

5. 配置本地执行方式

在本部分中,您将为云到云集成添加本地执行方式所必需的配置选项。在开发过程中,您可以将本地执行方式应用发布到 Firebase Hosting,Google Home 设备可以从这里访问并下载该应用。

Google Home 开发者控制台中,依次前往屏幕左侧的项目 > 云到云,然后选择相应集成的修改。在设置和配置页面中,滚动到本地执行方式,然后将该设置切换为开启。在每个测试网址字段中输入以下网址,插入您的项目 ID,然后点击 Save

https://<project-id>.web.app/local-home/index.html

local-fulfillment.png

接下来,需要定义 Google Home 设备应如何发现本地智能设备。Local Home 平台支持多种设备发现协议,包括 mDNS、UPnP 和 UDP 广播。您将使用 UDP 广播来发现智能洗衣机。

点击 Device discovery 下的 + Add scan configuration,添加新的扫描配置。选择 UDP 作为协议,并填写以下属性:

字段

说明

建议值

发现地址

UDP 发现地址

255.255.255.255

广播端口

Google Home 用于发送 UDP 广播的端口

3311

监听端口

Google Home 用于监听响应的端口

3312

Discovery packet

UDP 广播数据载荷

48656c6c6f4c6f63616c486f6d6553444b

device-discovery.png

最后,点击窗口底部的 Save 以发布所作更改。

6. 实现本地执行方式

你将借助 Local Home SDK typings 软件包,使用 TypeScript 开发本地执行方式应用。查看入门级项目中提供的框架:

local/index.ts

/// <reference types="@google/local-home-sdk" />

import App = smarthome.App;
import Constants = smarthome.Constants;
import DataFlow = smarthome.DataFlow;
import Execute = smarthome.Execute;
import Intents = smarthome.Intents;
import IntentFlow = smarthome.IntentFlow;

...

class LocalExecutionApp {

  constructor(private readonly app: App) { }

  identifyHandler(request: IntentFlow.IdentifyRequest):
      Promise<IntentFlow.IdentifyResponse> {
    // TODO: Implement device identification
  }

  executeHandler(request: IntentFlow.ExecuteRequest):
      Promise<IntentFlow.ExecuteResponse> {
    // TODO: Implement local fulfillment
  }

  ...
}

const localHomeSdk = new App('1.0.0');
const localApp = new LocalExecutionApp(localHomeSdk);
localHomeSdk
  .onIdentify(localApp.identifyHandler.bind(localApp))
  .onExecute(localApp.executeHandler.bind(localApp))
  .listen()
  .then(() => console.log('Ready'))
  .catch((e: Error) => console.error(e));

本地执行方式的核心组件是 smarthome.App 类。入门级项目附加针对 IDENTIFYEXECUTE 两个 intent 的处理程序,然后调用 listen() 方法来通知 Local Home SDK 此应用已准备就绪。

添加 IDENTIFY 处理程序

当 Google Home 设备根据开发者控制台中提供的扫描配置发现本地网络中有未经验证的设备时,Local Home SDK 就会触发 IDENTIFY 处理程序。

与此同时,当 Google 发现匹配的设备时,此平台会使用生成的扫描数据调用 identifyHandler。在您的应用中,使用 UDP 广播执行扫描,而提供给 IDENTIFY 处理程序的扫描数据包括本地设备发送的响应载荷。

处理程序会返回一个包含本地设备唯一标识符的 IdentifyResponse 实例。将以下代码添加到您的 identifyHandler 方法中,以处理来自本地设备的 UDP 响应并确定合适的本地设备 ID:

local/index .ts

identifyHandler(request: IntentFlow.IdentifyRequest):
    Promise<IntentFlow.IdentifyResponse> {
  console.log("IDENTIFY intent: " + JSON.stringify(request, null, 2));

  const scanData = request.inputs[0].payload.device.udpScanData;
  if (!scanData) {
    const err = new IntentFlow.HandlerError(request.requestId,
        'invalid_request', 'Invalid scan data');
    return Promise.reject(err);
  }

  // In this codelab, the scan data contains only local device ID.
  const localDeviceId = Buffer.from(scanData.data, 'hex');

  const response: IntentFlow.IdentifyResponse = {
    intent: Intents.IDENTIFY,
    requestId: request.requestId,
    payload: {
      device: {
        id: 'washer',
        verificationId: localDeviceId.toString(),
      }
    }
  };
  console.log("IDENTIFY response: " + JSON.stringify(response, null, 2));

  return Promise.resolve(response);
}

请注意,verificationId 字段必须与您的 SYNC 响应中的某个 otherDeviceIds 值相匹配,这会在用户的 Home Graph 中将相应设备标记为可用于本地执行方式。Google 找到匹配项之后,相应设备就会被视为已通过验证,并可随时用于本地执行方式。

添加 EXECUTE 处理程序

当支持本地执行方式的设备收到命令时,Local Home SDK 会触发您的 EXECUTE 处理程序。本地 intent 的内容等同于发送到您的云执行方式的 EXECUTE intent,因此本地处理 intent 的逻辑与您在云中的处理方式相似。操作

该应用可使用 TCP/UDP 套接字或 HTTP(S) 请求与本地设备通信。在本 Codelab 中,HTTP 充当用于控制虚拟设备的协议。端口号在 index.ts 中被定义为 SERVER_PORT 变量。

将以下代码添加到您的 executeHandler 方法,以处理传入的命令,并通过 HTTP 将命令发送到本地设备:

local/index.ts

executeHandler(request: IntentFlow.ExecuteRequest):
    Promise<IntentFlow.ExecuteResponse> {
  console.log("EXECUTE intent: " + JSON.stringify(request, null, 2));

  const command = request.inputs[0].payload.commands[0];
  const execution = command.execution[0];
  const response = new Execute.Response.Builder()
    .setRequestId(request.requestId);

  const promises: Array<Promise<void>> = command.devices.map((device) => {
    console.log("Handling EXECUTE intent for device: " + JSON.stringify(device));

    // Convert execution params to a string for the local device
    const params = execution.params as IWasherParams;
    const payload = this.getDataForCommand(execution.command, params);

    // Create a command to send over the local network
    const radioCommand = new DataFlow.HttpRequestData();
    radioCommand.requestId = request.requestId;
    radioCommand.deviceId = device.id;
    radioCommand.data = JSON.stringify(payload);
    radioCommand.dataType = 'application/json';
    radioCommand.port = SERVER_PORT;
    radioCommand.method = Constants.HttpOperation.POST;
    radioCommand.isSecure = false;

    console.log("Sending request to the smart home device:", payload);

    return this.app.getDeviceManager()
      .send(radioCommand)
      .then(() => {
        const state = {online: true};
        response.setSuccessState(device.id, Object.assign(state, params));
        console.log(`Command successfully sent to ${device.id}`);
      })
      .catch((e: IntentFlow.HandlerError) => {
        e.errorCode = e.errorCode || 'invalid_request';
        response.setErrorState(device.id, e.errorCode);
        console.error('An error occurred sending the command', e.errorCode);
      });
  });

  return Promise.all(promises)
    .then(() => {
      return response.build();
    })
    .catch((e) => {
      const err = new IntentFlow.HandlerError(request.requestId,
          'invalid_request', e.message);
      return Promise.reject(err);
    });
}

编译 TypeScript 应用

转到 local/ 目录并运行以下命令,以下载 TypeScript 编译器并编译应用:

cd local
npm install
npm run build

这会编译 index.ts (TypeScript) 源代码,并将以下内容放入 public/local-home/ 目录:

  • bundle.js - 已编译的 JavaScript 输出,包含本地应用和依赖项。
  • index.html - 用于提供应用以在设备上进行测试的本地托管网页。

部署测试项目

将更新后的项目文件部署到 Firebase Hosting,以便你可以从 Google Home 设备访问这些文件。

firebase deploy --only hosting

7. 启动智能洗衣机

现在,你可以测试本地执行方式应用与智能洗衣机之间的通信了!该 Codelab 入门级项目包括一个使用 Node.js 编写的虚拟智能洗衣机,它用于模拟一个用户可在本地控制的智能洗衣机。

配置设备

您需要配置该虚拟设备,以使用您在开发者控制台中为设备发现扫描配置应用的相同 UDP 参数。此外,您还需要告知虚拟设备要报告的本地设备 ID,以及当设备状态更改时用于报告状态事件的云到云集成的项目 ID。

参数

建议值

deviceId

deviceid123

discoveryPortOut

3311

discoveryPacket

HelloLocalHomeSDK

projectId

云到云集成的项目 ID

启动设备

转到 virtual-device/ 目录并运行设备脚本,以将配置参数作为实际参数进行传递:

cd virtual-device
npm install
npm start -- \
  --deviceId=deviceid123 --projectId=<project-id> \
  --discoveryPortOut=3311 --discoveryPacket=HelloLocalHomeSDK

验证设备脚本是否以预期参数运行:

(...): UDP Server listening on 3311
(...): Device listening on port 3388
(...): Report State successful

8. 调试 TypeScript 应用

在下一部分中,你将验证 Google Home 设备是否可以通过本地网络正确扫描、识别虚拟智能洗衣机以及向其发出命令。您可以使用 Google Chrome 开发者工具连接到 Google Home 设备、查看控制台日志,并调试 TypeScript 应用。

关联 Chrome 开发者工具

如需将调试程序关联到您的本地执行方式应用,请按照以下步骤操作:

  1. 确保您已将 Google Home 设备与有权访问 Developers Console 项目的用户相关联。
  2. 重新启动 Google Home 设备,使其能够获取您的 HTML 的网址,以及您在开发者控制台中添加的扫描配置。
  3. 在开发机器上启动 Chrome。
  4. 打开新的 Chrome 标签页,然后在地址字段中输入 chrome://inspect 以启动检查器。

您应该会在页面上看到设备列表,并且您的应用网址应该会出现在您的 Google Home 设备名称下方。

567f97789a7d8846.png

启动检查器

点击应用网址下方的 Inspect,以启动 Chrome 开发者工具。选择 Console 标签页,并验证您是否可以看到您的 TypeScript 应用输出的 IDENTIFY intent 内容。

6b67ded470a4c8be.png

此输出意味着您的本地执行方式应用已成功发现和标识虚拟设备。

测试本地执行方式

使用 Google Home 应用的触控功能向您的设备发送指令,或向 Google Home 设备下达语音指令,例如:

“Ok Google,启动洗衣机。”

“Ok Google,运行洗衣机。”

“Ok Google,让洗衣机停止运行。”

这会触发平台将 EXECUTE intent 传递给您的 TypeScript 应用的操作。

bc030517dacc3ac9.png

验证您能否看到每个指令引起的本地智能洗衣机状态更改。

...
***** The washer is RUNNING *****
...
***** The washer is STOPPED *****

9. 恭喜

764dbc83b95782a.png

恭喜!您使用 Local Home SDK 将本地执行方式集成到了云到云集成中。

了解详情

您还可以尝试下面这些操作:

  • 更改扫描配置并让其正常运行。例如,尝试使用其他的 UDP 端口或发现数据包。
  • 修改虚拟智能设备代码库,以在嵌入式设备(例如 Raspberry Pi)上运行,并使用 LED 或显示屏呈现当前状态。