1. 准备工作
作为一名物联网 (IoT) 开发者,您可以构建 云到云集成,让用户能够通过 Google Home 应用中的触控功能和 Google 助理的语音指令来控制设备。

云到云集成依靠 Home Graph 来提供住宅及其设备的环境数据,从而创建住宅逻辑图。借助这些环境数据,Google 助理能够根据用户在住宅中的位置更自然地理解用户请求。例如,Home Graph 可以存储客厅这个概念,而客厅中包含来自不同制造商的多种类型的设备(例如恒温器、灯、风扇和吸尘器)。

前提条件
- 创建云到云集成开发者指南
构建内容
在此 Codelab 中,您将发布一个用于管理虚拟智能洗衣机的云服务,然后构建一个云到云集成,并将其关联到 Google 助理。
学习内容
- 如何部署智能家居云服务
- 如何将您的服务关联到 Google 助理
- 如何将设备状态更改发布到 Google
所需条件
- 网络浏览器,例如 Google Chrome
- 安装了 Google Home 应用 的 iOS 或 Android 设备
- Node.js 10.16 或更高版本
- Google Cloud 结算账号
- Firebase 的 Blaze(随用随付)方案。
2. 开始使用
启用活动记录控制选项
若要使用 Google 助理,你必须与 Google 分享某些活动数据。Google 助理需要使用这些数据才能正常运行;然而,分享数据的要求并非专门针对该 SDK。如需分享这些数据,请创建 Google 账号(如果您还没有)。您可以使用任何 Google 账号,不一定是开发者账号。
打开要与 Google 助理搭配使用的 Google 账号的活动控件页面。
确保已启用以下切换开关:
- 网络与应用活动记录 - 此外,请务必选中包括 Chrome 历史记录和使用 Google 服务的网站、应用和设备中的活动记录复选框。
- 设备信息
- 语音和音频活动记录
创建云到云集成项目
- 前往开发者控制台。
- 点击创建项目,输入项目名称,然后点击创建项目。

选择云到云集成
在开发者控制台的项目首页 中,选择云到云 下的添加云到云集成 。

安装 Firebase CLI
借助 Firebase 命令行界面 (CLI),您可以在本地提供 Web 应用,并将您的 Web 应用部署到 Firebase Hosting。
如需安装 CLI,请从终端运行以下 npm 命令:
npm install -g firebase-tools
如需验证 CLI 是否已正确安装,请运行以下命令:
firebase --version
运行以下命令,授权您的 Google 账号使用 Firebase CLI:
firebase login
3. 运行入门级应用
开发环境设置完毕后,你可以部署入门级项目以验证所有设置是否已配置正确。
获取源代码
点击以下链接,将此 Codelab 的示例下载到您的开发机器上:
您还可以从命令行克隆 GitHub 代码库:
git clone https://github.com/google-home/smarthome-washer.git
项目简介
入门级项目包含以下子目录:
public:一种前端界面,可轻松地控制和监控智能洗衣机的状态。functions:一种已全面实现的云服务,可使用 Cloud Functions for Firebase 和 Firebase Realtime Database 来管理智能洗衣机。
将 Firebase 添加到您的 Google Home 开发者控制台项目
方法 1:通过 Firebase 控制台
- 前往 Firebase。
- 点击创建 Firebase 项目。

- 在创建项目 界面中,点击将 Firebase 添加到 Google Cloud 项目 。

- 在开始使用 界面中,选择您刚刚在 Google Home 开发者控制台中创建的 Google Cloud 项目,然后点击继续。

方法 2:通过 Firebase CLI
firebase projects:addfirebase
选择您刚刚创建的 Google Home 开发者控制台项目以添加 Firebase。
将 Firebase 添加到您的 Google Home 开发者控制台项目后,它会显示在 Firebase 控制台中。Firebase
项目的项目 ID 将与您的 Google Home 开发者控制台项目 ID 保持一致。

连接到 Firebase
转到 washer-start 目录,然后使用您的集成项目设置 Firebase CLI:
cd washer-start firebase use <project-id>
配置 Firebase 项目
初始化 Firebase 项目。
firebase init
依次选择 CLI 功能、Realtime Database 和 Functions 功能。
? 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
配置 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 时,请选择否 。
? File functions/.gitignore already exists. Overwrite? No
? Do you want to install dependencies with npm now? Yes
如果意外启用了 ESLint,您可以通过以下两种方法将其停用:
- 使用 GUI,转到项目下的
../functions文件夹,选择隐藏文件.eslintrc.js并将其删除。请勿将其误认为名称相似的.eslintrc.json。 - 使用命令行:
cd functions rm .eslintrc.js
部署到 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。此页面不会显示任何更改,但洗衣机的当前状态会存储在数据库中。
现在,您可以通过 Google Home 开发者控制台 将您部署的云服务关联到 Google 助理。
配置您的开发者控制台项目
在开发 标签页中,为您的互动添加显示名 。此名称会显示在 Google Home 应用中。

在 应用品牌宣传 下,上传应用图标的 png 文件,大小为 144 x 144 像素,名称为 。

如需启用账号关联 ,请使用以下账号关联设置:
客户 ID |
|
客户端密钥 |
|
授权网址 |
|
令牌网址 |
|

在 Cloud Functions 执行方式网址 下,输入为智能家居 intent 提供执行方式的 Cloud Functions 函数的网址。
https://us-central1-<project-id>.cloudfunctions.net/smarthome

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

现在,您可以开始实现必要的网络钩子,以便将设备状态关联到 Google 助理。
4. 创建洗衣机
配置集成后,您现在可以添加设备并发送数据。您的云服务需要处理以下 intent:
- 当 Google 助理想要了解用户关联了哪些设备时,就会发生
SYNCintent。当用户关联账号时,系统会向您的服务发送此 intent。您应该在响应时提供所有用户设备及其功能的 JSON 载荷。 - 当 Google 助理想要了解设备的当前状态时,就会发生
QUERYintent。您应该在响应时提供 JSON 载荷,其中包含所请求的每台设备的状态。 - 当 Google 助理想要代表用户控制设备时,就会发生
EXECUTEintent。您应该在响应时提供 JSON 载荷,其中包含所请求的每台设备的执行状态。 - 当用户将账号与 Google 助理解除关联时,就会发生
DISCONNECTintent。您应该停止向 Google 助理发送此用户设备的相关事件。
您将在后面几个部分中更新之前部署的用于处理这些 intent 的函数。
更新 SYNC 响应
打开 functions/index.js,其中包含用于响应来自 Google 助理的请求的代码。
您需要通过返回设备元数据和功能来处理 SYNC intent。更新 onSync 数组中的 JSON,以便包含洗衣机的设备信息和 推荐特征。
index.js
app.onSync((body) => {
return {
requestId: body.requestId,
payload: {
agentUserId: USER_ID,
devices: [{
id: 'washer',
type: 'action.devices.types.WASHER',
traits: [
'action.devices.traits.OnOff',
'action.devices.traits.StartStop',
'action.devices.traits.RunCycle',
],
name: {
defaultNames: ['My Washer'],
name: 'Washer',
nicknames: ['Washer'],
},
deviceInfo: {
manufacturer: 'Acme Co',
model: 'acme-washer',
hwVersion: '1.0',
swVersion: '1.0.1',
},
willReportState: true,
attributes: {
pausable: true,
},
}],
},
};
});
部署到 Firebase
使用 Firebase CLI 部署更新后的云执行方式:
firebase deploy --only functions
关联到 Google 助理
如需测试云到云集成,您需要将项目与 Google 账号相关联。这样一来,您就可以使用登录同一账号的 Google 助理界面和 Google Home 应用来进行测试。
- 在手机上打开 Google 助理设置。请注意,您登录的账号应该与控制台所用的账号相同。
- 依次转到 Google 助理 > 设置 > 家居控制 (位于“Google 助理”下方)。
- 点击右上角的搜索图标 。
- 使用 [test] 前缀搜索您的测试应用,以找到特定的测试应用。
- 选择此项目。然后,Google 助理会通过您的服务进行身份验证并发送
SYNC请求,以要求您的服务提供用户设备列表。
打开 Google Home 应用,然后验证您能否看到相应洗衣机设备。

5. 处理指令和查询
您的云服务可以向 Google 正确报告洗衣机设备后,您需要添加用于请求设备状态和发送命令的功能。
处理 QUERY intent
A QUERY intent 包含一组设备。对于每种设备,您应该在响应时提供其当前状态。
在 functions/index.js 中,修改 QUERY 处理程序,以处理 intent 请求中包含的目标设备列表。
index.js
app.onQuery(async (body) => {
const {requestId} = body;
const payload = {
devices: {},
};
const queryPromises = [];
const intent = body.inputs[0];
for (const device of intent.payload.devices) {
const deviceId = device.id;
queryPromises.push(queryDevice(deviceId)
.then((data) => {
// Add response to device payload
payload.devices[deviceId] = data;
}
));
}
// Wait for all promises to resolve
await Promise.all(queryPromises);
return {
requestId: requestId,
payload: payload,
};
});
针对请求中包含的每台设备,返回 Realtime Database 中存储的当前状态。更新 queryFirebase 和 queryDevice 函数,以返回洗衣机的状态数据。
index.js
const queryFirebase = async (deviceId) => {
const snapshot = await getFirebaseRef().child(deviceId).once('value');
const snapshotVal = snapshot.val();
return {
on: snapshotVal.OnOff.on,
isPaused: snapshotVal.StartStop.isPaused,
isRunning: snapshotVal.StartStop.isRunning,
};
};
const queryDevice = async (deviceId) => {
const data = await queryFirebase(deviceId);
return {
on: data.on,
isPaused: data.isPaused,
isRunning: data.isRunning,
currentRunCycle: [{
currentCycle: 'rinse',
nextCycle: 'spin',
lang: 'en',
}],
currentTotalRemainingTime: 1212,
currentCycleRemainingTime: 301,
};
};
处理 EXECUTE intent
The EXECUTE intent 可处理用于更新设备状态的命令。响应会返回每个命令的状态(例如 SUCCESS、ERROR 或
PENDING)以及新的设备状态。
在 functions/index.js 中,修改 EXECUTE 处理程序,以处理需要更新的特征列表以及每个命令的目标设备集:
index.js
app.onExecute(async (body) => {
const {requestId} = body;
// Execution results are grouped by status
const result = {
ids: [],
status: 'SUCCESS',
states: {
online: true,
},
};
const executePromises = [];
const intent = body.inputs[0];
for (const command of intent.payload.commands) {
for (const device of command.devices) {
for (const execution of command.execution) {
executePromises.push(
updateDevice(execution, device.id)
.then((data) => {
result.ids.push(device.id);
Object.assign(result.states, data);
})
.catch(() => functions.logger.error('EXECUTE', device.id)));
}
}
}
await Promise.all(executePromises);
return {
requestId: requestId,
payload: {
commands: [result],
},
};
});
针对每个命令和目标设备,更新 Realtime Database 中与所请求的特征相对应的值。修改 updateDevice 函数,以更新相应的 Firebase 引用并返回更新后的设备状态。
index.js
const updateDevice = async (execution, deviceId) => {
const {params, command} = execution;
let state; let ref;
switch (command) {
case 'action.devices.commands.OnOff':
state = {on: params.on};
ref = getFirebaseRef().child(deviceId).child('OnOff');
break;
case 'action.devices.commands.StartStop':
state = params.start
? {isRunning: true, isPaused: false}
: {isRunning: false, isPaused: false};
ref = getFirebaseRef().child(deviceId).child('StartStop');
break;
case 'action.devices.commands.PauseUnpause':
const data = await queryDevice(deviceId);
state = (data.isPaused === false && data.isRunning === false)
? {isRunning: false, isPaused: false}
: {isRunning: !params.pause, isPaused: params.pause};
ref = getFirebaseRef().child(deviceId).child('StartStop');
break;
}
return ref.update(state)
.then(() => state);
};
6. 测试集成
三个 intent 全部实现后,您可以测试集成是否能够控制洗衣机。
部署到 Firebase
使用 Firebase CLI 部署更新后的云执行方式:
firebase deploy --only functions
测试洗衣机
现在,如果您通过手机尝试下达以下任意一条语音指令,就会看到值发生变化:
“Ok Google,启动洗衣机。”
“Ok Google,让洗衣机暂停运行。”
“Ok Google,让洗衣机停止运行。”
您还可以通过提问来查询洗衣机的当前状态。
“Ok Google,我的洗衣机启动了吗?”
“Ok Google,我的洗衣机在运行吗?”
“Hey Google,我的洗衣机洗到哪一步了?”
您可以在 Firebase Console 的 Functions 部分中,在函数下显示的日志中查看这些查询和命令。如需详细了解 Firebase 日志,请参阅写入和查看日志。
您还可以在 Google Cloud 控制台 中找到这些查询和命令,具体方法是依次前往 日志记录 > Logs Explorer。如需详细了解 Google Cloud 日志记录,请参阅使用 Cloud Logging 访问事件日志。
7. 向 Google 报告更新
您已将云服务与智能家居 intent 完全集成,让用户能够控制和查询设备的当前状态。不过,此实现仍缺少让您的服务能够主动向 Google 助理发送事件信息(例如,设备使用状态或状态的更改)的方法。
借助 请求同步功能,您可以在用户添加或移除设备后,或在用户设备功能更改后触发新的同步请求。借助 报告状态 功能,在用户手动更改设备状态(例如,打开灯的开关)或使用其他服务更改状态后,您的云服务可以主动向 Home Graph 发送设备状态。
在此部分中,您将添加代码以从前端 Web 应用调用这些方法。
启用 HomeGraph API
借助 HomeGraph API,您可以在用户 Home Graph 中存储并查询设备及其状态。如需使用此 API,您必须先打开 Google Cloud 控制台,然后启用 HomeGraph API。
在 Google Cloud 控制台中,请务必选择与您的集成 <project-id>. 相匹配的项目。然后,在 HomeGraph API 的 API 库屏幕中,点击 启用。

启用报告状态
对 Realtime Database 的写入操作会触发入门级项目中的 reportstate 函数。更新
functions/index.js 中的 reportstate 函数,以捕获向数据库写入的数据,并通过状态报告将其发布到 Home
Graph。
index.js
exports.reportstate = functions.database.ref('{deviceId}').onWrite(
async (change, context) => {
functions.logger.info('Firebase write event triggered Report State');
const snapshot = change.after.val();
const requestBody = {
requestId: 'ff36a3cc', /* Any unique ID */
agentUserId: USER_ID,
payload: {
devices: {
states: {
/* Report the current state of our washer */
[context.params.deviceId]: {
on: snapshot.OnOff.on,
isPaused: snapshot.StartStop.isPaused,
isRunning: snapshot.StartStop.isRunning,
},
},
},
},
};
const res = await homegraph.devices.reportStateAndNotification({
requestBody,
});
functions.logger.info('Report state response:', res.status, res.data);
});
启用请求同步
刷新前端网络界面的图标会触发入门级项目中的 requestsync 函数。在 functions/index.js 中实现 requestsync 函数,以调用 HomeGraph API。
index.js
exports.requestsync = functions.https.onRequest(async (request, response) => {
response.set('Access-Control-Allow-Origin', '*');
functions.logger.info(`Request SYNC for user ${USER_ID}`);
try {
const res = await homegraph.devices.requestSync({
requestBody: {
agentUserId: USER_ID,
},
});
functions.logger.info('Request sync response:', res.status, res.data);
response.json(res.data);
} catch (err) {
functions.logger.error(err);
response.status(500).send(`Error requesting sync: ${err}`);
}
});
部署到 Firebase
使用 Firebase CLI 部署更新后的代码:
firebase deploy --only functions
测试实现效果
点击网络界面中的刷新
按钮,并验证能否在 Firebase 控制台日志中看到同步请求。
接下来,调整前端网络界面中的洗衣机设备属性,然后点击更新 。验证能否在 Firebase 控制台日志中看到向 Google 报告的状态更改。
8. 恭喜

恭喜!您已成功使用云到云集成将 Google 助理与设备云服务集成。
了解详情
您可以实现以下想法以进行更深入的研究:
您还可以详细了解如何测试和提交集成以供审核,包括用于向用户发布集成的认证流程。