1. 准备工作
通过智能家居集成,Google 助理可以控制用户住宅中已连接的设备。如需构建智能家居 Action,您需要提供能够处理智能家居 intent 的云网络钩子端点。例如,当用户说“Ok Google,打开灯”时,Google 助理会将这条指令发给您的云执行方式,以更新设备状态。
Local Home SDK 可以增强智能家居集成。它添加了一条本地路径,能直接将智能家居 intent 传递给 Google Home 设备,从而提高可靠性并缩短处理用户指令时的延迟时间。借助此 SDK,您可以用 TypeScript 或 JavaScript 编写和部署本地执行方式应用,以识别设备并在任意一个 Google Home 智能音箱或 Google Nest 智能显示屏上执行指令。然后,您的应用使用现有的标准协议,通过局域网直接与用户的现有智能设备进行通信,以便执行指令。
前提条件
- 创建智能家居 Action 开发者指南
- 智能家居洗衣机 Codelab
- 本地执行方式开发者指南
构建内容
在此 Codelab 中,您将部署之前构建的智能家居与 Firebase 集成,然后在 Actions 控制台中应用扫描配置,并使用 TypeScript 构建一个本地应用,以将以 Node.js 编写的命令发送到虚拟洗衣机设备。
学习内容
- 如何在 Actions 控制台中启用和配置本地执行方式。
- 如何使用 Local Home SDK 编写本地执行方式应用。
- 如何调试在 Google Home 音箱或 Google Nest 智能显示屏上加载的本地执行方式应用。
所需条件
- 最新版本的 Google Chrome
- 安装了 Google Home 应用的 iOS 或 Android 设备
- Google Home 智能音箱或 Google Nest 智能显示屏
- Node.js 10.16 或更高版本
- Google 账号
- Google Cloud 结算账号
2. 开始使用
启用活动控件
若要使用 Google 助理,你必须与 Google 分享某些活动数据。Google 助理需要使用这些数据才能正常运行;然而,分享数据的要求并非专门针对该 SDK。如果你还没有 Google 账号,可以创建一个,以便分享这些数据。你可以使用任何 Google 账号,不要求必须用你的开发者账号。
打开要与 Google 助理搭配使用的 Google 账号的“活动控件”页面。
确保已启用以下切换开关:
- 网络与应用活动记录 - 此外,请务必选中包括 Chrome 历史记录和使用 Google 服务的网站、应用和设备中的活动记录复选框。
- 设备信息
- 语音和音频活动记录
创建 Actions 项目
- 前往 Actions on Google 开发者控制台。
- 点击 New Project,输入项目名称,然后点击 CREATE PROJECT。
选择智能家居应用
在 Actions 控制台的“Overview”屏幕中,选择 Smart home。
选择 Smart home 体验卡片,然后系统会将您转到项目控制台。
安装 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 Console 中,请务必选择与您的 Actions <project-id>.
相匹配的项目。然后,在 HomeGraph API 的 API 库屏幕中,点击启用。
3. 运行入门级应用
开发环境设置完毕后,你可以部署入门级项目以验证所有设置是否已配置正确。
获取源代码
点击以下链接,将此 Codelab 的示例下载到您的开发机器上:
…或者,您也可以通过命令行克隆 GitHub 代码库:
git clone https://github.com/google-home/smarthome-local.git
项目简介
入门级项目包含以下子目录:
public
- 可控制和监控智能洗衣机的前端网络界面functions
- 为智能家居 Action 实现云执行方式的 Cloud Functions 函数local
- 框架本地执行方式应用项目,包含在index.ts
中打桩的 intent 处理程序
提供的云执行方式在 index.js
中包括以下函数:
fakeauth
- 用于账号关联的授权端点faketoken
- 用于账号关联的令牌端点smarthome
- 智能家居 intent 执行方式端点reportstate
- 在设备状态改变时调用 HomeGraph APIupdateDevice
- 虚拟设备用来触发报告状态的端点
关联到 Firebase
转到 app-start
目录,然后使用您的 Actions 项目设置 Firebase CLI:
cd app-start firebase use <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
配置 Functions 时,您应该使用默认文件,并确保不会覆盖项目示例中的现有 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 时,请选择 No。
? 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,有两种方法可以将其停用:
- 使用 GUI,转到项目下的
../functions
文件夹,选择隐藏文件.eslintrc.js
并将其删除。不要将其误认为名称相似的.eslintrc.json
。 - 使用命令行:
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 应用。您会看到以下界面:
此网络界面表示用于查看或修改设备状态的第三方平台。如需使用设备信息填充数据库,请点击 UPDATE。此页面不会显示任何更改,但洗衣机的当前状态会存储在数据库中。
现在,可以使用 Actions 控制台将你部署的云服务连接到 Google 助理了。
配置 Actions 控制台项目
在 Overview > Build your Action 下,选择 Add Action(s)。输入为智能家居 intent 提供执行方式的 Cloud Functions 函数的网址,然后点击 Save。
https://us-central1-<project-id>.cloudfunctions.net/smarthome
在 Develop > Invocation 标签页中,在 Display name 中为您的 Action 添加显示名,然后点击 Save。此名称会显示在 Google Home 应用中。
如需启用账号关联,请在左侧导航栏中依次选择 Develop > Account linking 选项。使用以下账号关联设置:
客户端 ID |
|
客户端密钥 |
|
授权网址 |
|
令牌网址 |
|
点击 Save 保存您的账号关联配置,然后点击 Test 针对您的项目启用测试。
系统会将您重定向到 Simulator。将鼠标指针悬停在“Testing on Device”() 图标上,以便验证是否已针对您的项目启用测试。
关联到 Google 助理
为了测试您的智能家居 Action,您需要将项目与 Google 账号相关联。这样一来,您就可以使用登录同一账号的 Google 助理界面和 Google Home 应用来进行测试。
- 在手机上打开 Google 助理设置。请注意,您登录的账号应该与控制台所用的账号相同。
- 依次转到 Google 助理 > 设置 > 家居控制(位于“Google 助理”下方)。
- 点击右上角的搜索图标。
- 使用 [test] 前缀搜索您的测试应用,以查找您的特定测试应用。
- 选择此项目。然后,Google 助理会通过您的服务进行身份验证并发送
SYNC
请求,以要求您的服务提供用户设备列表。
打开 Google Home 应用,然后验证您能否看到相应洗衣机设备。
验证您是否可以在 Google Home 应用中通过语音指令控制洗衣机。您还应该会在云执行方式的前端网络界面中看到设备状态更改情况。
现在,可以开始为你的 Action 添加本地执行方式了。
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
部署完成后,转到网络界面,然后点击工具栏中的 Refresh 按钮。这会触发请求同步操作,以便 Google 助理接收更新后的 SYNC
响应数据。
5. 配置本地执行方式
在本部分中,你会将本地执行方式所必需的配置选项添加到你的智能家居 Action。在开发过程中,您可以将本地执行方式应用发布到 Firebase Hosting,Google Home 设备可以从这里访问并下载该应用。
在 Actions 控制台中,选择 Develop >操作,然后找到配置本地主屏幕 SDK 部分。在测试网址字段中输入以下网址,插入您的项目 ID,然后点击 Save:
https://<project-id>.web.app/local-home/index.html
接下来,我们需要定义 Google Home 设备应如何发现本地智能设备。Local Home 平台支持多种设备发现协议,包括 mDNS、UPnP 和 UDP 广播。您将使用 UDP 广播来发现智能洗衣机。
点击 Device scan configuration 下的 New scan config,添加新的扫描配置。选择 UDP 作为协议,并填写以下属性:
字段 | 说明 | 建议值 |
Broadcast address | UDP 广播地址 |
|
Broadcast port | Google Home 用于发送 UDP 广播的端口 |
|
Listen port | Google Home 用于监听响应的端口 |
|
Discovery packet | UDP 广播数据载荷 |
|
最后,点击窗口顶部的 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
类。入门级项目附加针对 IDENTIFY
和 EXECUTE
两个 intent 的处理程序,然后调用 listen()
方法来通知 Local Home SDK 此应用已准备就绪。
添加 IDENTIFY 处理程序
当 Google Home 设备根据 Actions 控制台提供的扫描配置发现本地网络中有未经验证的设备时,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 编写的虚拟智能洗衣机,它用于模拟一个用户可在本地控制的智能洗衣机。
配置设备
您需要配置该虚拟设备,以使用您在 Actions 控制台中为设备发现扫描配置应用的相同 UDP 参数。此外,您还需要告知虚拟设备要报告的本地设备 ID,以及当设备状态发生变化时要用于报告状态事件的 Actions 项目 ID。
参数 | 建议值 |
deviceId |
|
discoveryPortOut |
|
discoveryPacket |
|
projectId | 您的 Actions 项目 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 开发者工具
如需将调试程序关联到您的本地执行方式应用,请按照以下步骤操作:
- 确保您已将 Google Home 设备与有权访问 Actions 控制台项目的用户相关联。
- 重新启动 Google Home 设备,使其能够获取您的 HTML 的网址,以及您在 Actions 控制台中添加的扫描配置。
- 在开发机器上启动 Chrome。
- 打开新的 Chrome 标签页,然后在地址字段中输入
chrome://inspect
以启动检查器。
您应该会在页面上看到设备列表,并且您的应用网址应该会出现在您的 Google Home 设备名称下方。
启动检查器
点击应用网址下方的 Inspect,以启动 Chrome 开发者工具。选择 Console 标签页,并验证您是否可以看到您的 TypeScript 应用输出的 IDENTIFY
intent 内容。
此输出意味着您的本地执行方式应用已成功发现和标识虚拟设备。
测试本地执行方式
使用 Google Home 应用的触控功能向您的设备发送指令,或向 Google Home 设备下达语音指令,例如:
“Ok Google,启动洗衣机。”
“Ok Google,运行洗衣机。”
“Ok Google,让洗衣机停止运行。”
这会触发平台将 EXECUTE
intent 传递给您的 TypeScript 应用的操作。
验证您能否看到每个指令引起的本地智能洗衣机状态更改。
... ***** The washer is RUNNING ***** ... ***** The washer is STOPPED *****
9. 恭喜
恭喜!您使用 Local Home SDK 将本地执行方式集成到了智能家居 Action 中。
了解详情
您还可以尝试下面这些操作:
- 更改扫描配置并让其正常运行。例如,尝试使用其他的 UDP 端口或发现数据包。
- 修改虚拟智能设备代码库,以在嵌入式设备(例如 Raspberry Pi)上运行,并使用 LED 或显示屏呈现当前状态。