1. 准备工作
作为一名物联网 (IoT) 开发者,您可以构建智能家居 Action,让用户能够通过 Google Home 应用中的触控功能和 Google 助理的语音指令来控制设备。
智能家居 Action 依靠 Home Graph 提供有关住宅及其设备的情境数据,从而创建住宅的逻辑地图。借助这些环境数据,Google 助理能够根据用户在住宅中的位置更自然地理解用户请求。例如,Home Graph 可以存储客厅这个概念,而客厅中包含来自不同制造商的多种类型的设备(例如恒温器、灯、风扇和吸尘器)。
前提条件
- 创建智能家居 Action 开发者指南
构建内容
在此 Codelab 中,您将发布一个用于管理虚拟智能洗衣机的云服务,然后构建一个智能家居 Action,并将其关联到 Google 助理。
学习内容
- 如何部署智能家居云服务
- 如何将您的服务关联到 Google 助理
- 如何将设备状态更改发布到 Google
所需条件
- 网络浏览器,例如 Google Chrome
- 安装了 Google Home 应用的 iOS 或 Android 设备
- Node.js 10.16 或更高版本
- 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。
选择智能家居体验卡片,点击开始构建,然后你会进入项目控制台。
安装 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
转到 washer-start
目录,然后使用您的 Actions 项目设置 Firebase CLI:
cd washer-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。如果您没有看到“Test now enabled”(立即启用测试),请点击重置测试以验证是否已启用测试。
现在,您可以开始实现必要的网络钩子,以便将设备状态关联到 Google 助理。
4. 创建洗衣机
Action 配置完毕后,您就可以添加设备并发送数据了。您的云服务需要处理以下 intent:
- 当 Google 助理想要了解用户关联了哪些设备时,就会发生
SYNC
intent。当用户关联账号时,系统会向您的服务发送此 intent。您应该在响应时提供所有用户设备及其功能的 JSON 载荷。 - 当 Google 助理想要了解设备的当前状态时,就会发生
QUERY
intent。您应该在响应时提供 JSON 载荷,其中包含所请求的每台设备的状态。 - 当 Google 助理想要代表用户控制设备时,就会发生
EXECUTE
intent。您应该在响应时提供 JSON 载荷,其中包含所请求的每台设备的执行状态。 - 当用户解除其账号与 Google 助理的关联时,就会发生
DISCONNECT
intent。您应该停止向 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 助理
为了测试智能家居 Action,您需要将项目与 Google 账号相关联。这样一来,您就可以使用登录同一账号的 Google 助理界面和 Google Home 应用来进行测试。
- 在手机上打开 Google 助理设置。请注意,您登录的账号应该与控制台所用的账号相同。
- 依次转到 Google 助理 > 设置 > 家居控制(位于“Google 助理”下方)。
- 点击右上角的搜索图标。
- 使用 [test] 前缀搜索您的测试应用,以查找您的特定测试应用。
- 选择此项目。然后,Google 助理会通过您的服务进行身份验证并发送
SYNC
请求,以要求您的服务提供用户设备列表。
打开 Google Home 应用,然后验证您能否看到相应洗衣机设备。
5. 处理命令和查询
您的云服务可以向 Google 正确报告洗衣机设备后,您需要添加用于请求设备状态和发送命令的功能。
处理 QUERY intent
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 firebaseRef.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
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 = firebaseRef.child(deviceId).child('OnOff');
break;
case 'action.devices.commands.StartStop':
state = params.start
? {isRunning: true, isPaused: false}
: {isRunning: false, isPaused: false};
ref = firebaseRef.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 = firebaseRef.child(deviceId).child('StartStop');
break;
}
return ref.update(state)
.then(() => state);
};
6. 测试您的 Action
三个 intent 全部实现后,您可以测试 Action 是否能够控制洗衣机。
部署到 Firebase
使用 Firebase CLI 部署更新后的云执行方式:
firebase deploy --only functions
测试洗衣机
现在,如果您通过手机尝试下达以下任意一条语音指令,就会看到值发生变化:
“Ok Google,启动洗衣机。”
“Ok Google,让洗衣机暂停运行。”
“Ok Google,让洗衣机停止运行。”
您还可以通过提问来查询洗衣机的当前状态。
“Ok Google,我的洗衣机启动了吗?”
“Ok Google,我的洗衣机在运行吗?”
“Ok Google,我的洗衣机洗到哪一步了?”
您可以在 Firebase 控制台的函数部分中的函数下方的日志中查看这些查询和命令。如需详细了解 Firebase 日志,请参阅写入和查看日志。
您还可以在 Google Cloud 控制台中找到这些查询和命令,方法是依次前往 Logging >日志浏览器。如需详细了解 Google Cloud 日志记录,请参阅使用 Cloud Logging 访问事件日志。
7. 向 Google 报告更新
您已将云服务与智能家居 intent 完全集成,让用户能够控制和查询设备的当前状态。不过,此实现仍缺少让您的服务能够主动向 Google 助理发送事件信息(例如,设备使用状态或状态的更改)的方法。
通过请求同步,您可以在用户添加或移除设备,或用户设备功能发生变化时触发新的同步请求。借助报告状态,当用户实际更改设备状态(例如打开灯开关)或使用其他服务更改状态时,您的云服务可以主动向 Home Graph 发送设备状态。
在此部分中,您将添加代码以从前端 Web 应用调用这些方法。
启用 HomeGraph API
借助 HomeGraph API,您可以在用户的 Home Graph 中存储和查询设备及其状态。如需使用此 API,您必须先打开 Google Cloud 控制台并启用 HomeGraph API。
在 Google Cloud Console 中,请务必选择与您的 Actions <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. 恭喜
恭喜!您已成功使用智能家居 Action 将 Google 助理与设备云服务集成。
了解详情
您可以实现以下想法以进行更深入的研究:
您还可以详细了解如何测试和提交 Action 以供审核,包括向用户发布 Action 的认证流程。