增强云到云集成并确保其安全无虞

关于此 Codelab
schedule50 分钟
subject上次更新时间:2024年11月20日
account_circleGoogle 员工编写

云到云集成使用设备类型来让 Google 助理了解对某个设备应该使用哪种语法。设备特征定义各个设备类型的功能。设备可继承添加到集成的每个设备特征的状态。

dc8dce0dea87cd5c.png

您可以将任何支持的特征关联到您选择的设备类型,以便自定义用户设备的功能。如果您想在 Action 中实现目前设备架构中并未提供的自定义 trait,则 ModesToggle trait 允许使用您定义的自定义名称来控制特定设置。

除了按类型和特征提供的基本控制功能以外,Smart Home API 还提供其他用于完善用户体验的功能。当 intent 未成功实现时,错误响应可提供详细的用户反馈。通过第二层用户身份验证,您可以扩展这些响应,并增强您所选设备特征的安全性。通过发送特定错误响应验证 Google 助理发出的阻止后,您的云到云集成可能需要其他授权才能完成相应命令。

前提条件

构建内容

在此 Codelab 中,您将利用 Firebase 部署一个预构建的智能家居集成,然后学习如何向智能家居洗衣机添加针对负荷容量和涡轮模式的非标准特征。您还将实现错误和异常报告,并学习如何使用第二层用户身份验证强制执行语音确认以开启洗衣机。

学习内容

  • 如何向集成添加“Modes”和“Toggles”trait
  • 如何报告错误和异常
  • 如何申请第二层用户身份验证

所需条件

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

创建 Firebase 项目

  1. 前往 Firebase
  2. 点击创建项目,然后输入项目名称。
  3. 选中同意复选框,然后点击继续。如果没有协议复选框,您可以跳过此步骤。
    创建 Firebase 项目
  4. 创建 Firebase 项目后,找到项目 ID。前往项目概览,然后依次点击“设置”图标 > 项目设置
    打开项目设置
  5. 您的项目会列在常规标签页下。
    常规项目设置

启用 HomeGraph API

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

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

ee198858a6eac112.png

3. 运行入门级应用

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

获取源代码

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

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

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

解压下载的 ZIP 文件。

项目简介

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

  • public: 一种前端界面,可轻松地控制和监控智能洗衣机的状态。
  • functions: 一种已全面实现的云服务,可使用 Cloud Functions for Firebase 和 Firebase Realtime Database 来管理智能洗衣机。

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

  • fakeauth:用于账号关联的授权端点
  • faketoken:用于账号关联的令牌端点
  • smarthome:智能家居 intent 执行方式端点
  • reportstate:在设备状态改变时调用 Home Graph API
  • requestsync:允许用户设备进行更新,而无需重新关联账号

关联到 Firebase

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

cd washer-start
firebase use <firebase-project-id>

配置 Firebase 项目

初始化 Firebase 项目。

firebase init

选择 CLI 功能 Realtime DatabaseFunctions 以及包含 Firebase 托管的 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

配置函数时,你应使用默认文件,并确保不覆盖项目示例中的现有 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 deploy

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

...

✔ Deploy complete!

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

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

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

5845443e94705557.png

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

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

配置您的 Play 管理中心项目

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

添加显示名称

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

添加应用图标

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

客户 ID

ABC123

客户端密钥

DEF456

授权网址

https://us-central1-
.cloudfunctions.net/fakeauth

令牌网址

https://us-central1-
.cloudfunctions.net/faketoken

更新账号关联网址

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

https://us-central1--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 应用,然后验证您能否看到相应洗衣机设备。

ae252220753726f6.png

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

现在,您已经部署了一个基础洗衣机,接下来,您可以自定义设备上的可用模式。

4. 添加模式

action.devices.traits.Modes 特征可让设备针对一种模式提供任意数量的设置(一次只能设定一项设置)。您可以向洗衣机添加模式,以便定义洗衣负荷容量:小、中或大。

更新 SYNC 响应

您需要向 functions/index.js 中的 SYNC 响应添加新特征的相关信息。这些数据会显示在 traits 数组和 attributes 对象中,如以下代码段所示。

index.js

app.onSync(body => {
 
return {
    requestId
: 'ff36a3cc-ec34-11e6-b1a0-64510650abcf',
    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',
         
// Add Modes trait
         
'action.devices.traits.Modes',
       
],
        name
: { ... },
        deviceInfo
: { ... },
        attributes
: {
          pausable
: true,
         
//Add availableModes
          availableModes
: [{
            name
: 'load',
            name_values
: [{
              name_synonym
: ['load'],
              lang
: 'en',
           
}],
            settings
: [{
              setting_name
: 'small',
              setting_values
: [{
                setting_synonym
: ['small'],
                lang
: 'en',
             
}]
           
}, {
              setting_name
: 'medium',
              setting_values
: [{
                setting_synonym
: ['medium'],
                lang
: 'en',
             
}]
           
}, {
              setting_name
: 'large',
              setting_values
: [{
                setting_synonym
: ['large'],
                lang
: 'en',
             
}]
           
}],
            ordered
: true,
         
}],
       
},
     
}],
   
},
 
};
});

添加新的 EXECUTE intent 命令

EXECUTE intent 中添加 action.devices.commands.SetModes 命令,如以下代码段所示。

index.js

const updateDevice = async (execution,deviceId) => {
 
const {params,command} = execution;
  let state
, 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
= {isRunning: params.start};
     
ref = firebaseRef.child(deviceId).child('StartStop');
     
break;
   
case 'action.devices.commands.PauseUnpause':
      state
= {isPaused: params.pause};
     
ref = firebaseRef.child(deviceId).child('StartStop');
     
Break;
   
// Add SetModes command
   
case 'action.devices.commands.SetModes':
      state
= {load: params.updateModeSettings.load};
     
ref = firebaseRef.child(deviceId).child('Modes');
     
break;
}

更新 QUERY 响应

接下来,更新 QUERY 响应,以便报告洗衣机的当前状态。

将更新后的更改添加到 queryFirebasequeryDevice 函数以获取存储在 Realtime Database 中的状态。

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,
   
// Add Modes snapshot
    load
: snapshotVal.Modes.load,
 
};
}

const queryDevice = async (deviceId) => {
 
const data = await queryFirebase(deviceId);
 
return {
    on
: data.on,
    isPaused
: data.isPaused,
    isRunning
: data.isRunning,
    currentRunCycle
: [{ ... }],
    currentTotalRemainingTime
: 1212,
    currentCycleRemainingTime
: 301,
   
// Add currentModeSettings
    currentModeSettings
: {
      load
: data.load,
   
},
 
};
};

更新报告状态

最后,更新 reportstate 函数,以便将洗衣机的当前负荷设置报告给 Home Graph。

index.js

const requestBody = {
  requestId
: 'ff36a3cc', /* Any unique ID */
  agentUserId
: USER_ID,
  payload
: {
    devices
: {
      states
: {
       
/* Report the current state of your washer */
       
[context.params.deviceId]: {
          on
: snapshot.OnOff.on,
          isPaused
: snapshot.StartStop.isPaused,
          isRunning
: snapshot.StartStop.isRunning,
         
// Add currentModeSettings
          currentModeSettings
: {
            load
: snapshot.Modes.load,
         
},
       
},
     
},
   
},
 
},
};

部署到 Firebase

运行以下命令以部署更新后的集成:

firebase deploy --only functions

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

bf4f6a866160a982.png

现在,您可以发出一个命令来设置洗衣机的模式,例如:

“Ok Google, set the washer load to large.”(Ok Google,把洗衣机负荷容量设为大。)

此外,您还可以提出有关洗衣机的问题,例如:

“Ok Google, what is the washer load?”(Ok Google,洗衣机的负荷容量是多少?)

5. 添加切换开关

action.devices.traits.Toggles 特征表示设备的具有 true 或 false 状态的已命名方面,例如洗衣机是否处于涡轮模式。

更新 SYNC 响应

SYNC 响应中,您需要添加有关新设备特征的信息。这些信息将显示在 traits 数组和 attributes 对象中,如以下代码段所示。

index.js

app.onSync(body => {
 
return {
    requestId
: 'ff36a3cc-ec34-11e6-b1a0-64510650abcf',
    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',
         
'action.devices.traits.Modes',
         
// Add Toggles trait
         
'action.devices.traits.Toggles',
       
],
        name
: { ... },
        deviceInfo
: { ... },
        attributes
: {
          pausable
: true,
          availableModes
: [{
            name
: 'load',
            name_values
: [{
              name_synonym
: ['load'],
              lang
: 'en'
           
}],
            settings
: [{ ... }],
            ordered
: true,
         
}],
         
//Add availableToggles
          availableToggles
: [{
            name
: 'Turbo',
            name_values
: [{
              name_synonym
: ['turbo'],
              lang
: 'en',
           
}],
         
}],
       
},
     
}],
   
},
 
};
});

添加新的 EXECUTE intent 命令

EXECUTE intent 中添加 action.devices.commands.SetToggles 命令,如以下代码段所示。

index.js

const updateDevice = async (execution,deviceId) => {
 
const {params,command} = execution;
  let state
, 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
= {isRunning: params.start};
     
ref = firebaseRef.child(deviceId).child('StartStop');
     
break;
   
case 'action.devices.commands.PauseUnpause':
      state
= {isPaused: params.pause};
     
ref = firebaseRef.child(deviceId).child('StartStop');
     
break;
   
case 'action.devices.commands.SetModes':
      state
= {load: params.updateModeSettings.load};
     
ref = firebaseRef.child(deviceId).child('Modes');
     
break;
   
// Add SetToggles command
   
case 'action.devices.commands.SetToggles':
      state
= {Turbo: params.updateToggleSettings.Turbo};
     
ref = firebaseRef.child(deviceId).child('Toggles');
     
break;
 
}

更新 QUERY 响应

最后,您需要更新 QUERY 响应以报告洗衣机的涡轮模式。将更新后的更改添加到 queryFirebasequeryDevice 函数以获取存储在 Realtime Database 中的切换状态。

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,
    load
: snapshotVal.Modes.load,
   
// Add Toggles snapshot
   
Turbo: snapshotVal.Toggles.Turbo,
 
};
}

const queryDevice = async (deviceId) => {
 
const data = queryFirebase(deviceId);
 
return {
    on
: data.on,
    isPaused
: data.isPaused,
    isRunning
: data.isRunning,
    currentRunCycle
: [{ ... }],
    currentTotalRemainingTime
: 1212,
    currentCycleRemainingTime
: 301,
    currentModeSettings
: {
      load
: data.load,
   
},
   
// Add currentToggleSettings
    currentToggleSettings
: {
     
Turbo: data.Turbo,
   
},
 
};
};

更新报告状态

最后,更新 reportstate 函数,以便向洗衣机报告洗衣机是否已设为涡轮模式。

index.js

const requestBody = {
  requestId
: 'ff36a3cc', /* Any unique ID */
  agentUserId
: USER_ID,
  payload
: {
    devices
: {
      states
: {
       
/* Report the current state of your washer */
       
[context.params.deviceId]: {
          on
: snapshot.OnOff.on,
          isPaused
: snapshot.StartStop.isPaused,
          isRunning
: snapshot.StartStop.isRunning,
          currentModeSettings
: {
            load
: snapshot.Modes.load,
         
},
         
// Add currentToggleSettings
          currentToggleSettings
: {
           
Turbo: snapshot.Toggles.Turbo,
         
},
       
},
     
},
   
},
 
},
};

部署到 Firebase

运行以下命令以部署更新后的函数:

firebase deploy --only functions

部署完成后,点击网络界面中的 Refresh ae8d3b25777a5e30.png 按钮以触发请求同步。

现在,您可以说出指令,以便将洗衣机设为涡轮模式:

“Ok Google, turn on turbo for the washer.”(Ok Google,开启洗衣机的涡轮模式。)

您还可以提出以下问题,以便检查洗衣机是否已处于涡轮模式:

“Ok Google, is my washer in turbo mode?”(Ok Google,我的洗衣机开启涡轮模式了吗?)

6. 报告错误和异常

借助云到云集成中的错误处理功能,您可以在有问题导致 EXECUTEQUERY 响应失败时向用户报告相关信息。这些通知可在用户与您的智能设备和集成互动时为用户提供更积极的用户体验。

每当 EXECUTEQUERY 请求失败时,您的集成都应返回错误代码。例如,如果您想在用户试图在机盖处于打开状态的情况下启动洗衣机时抛出错误,那么您的 EXECUTE 响应将如以下代码段所示:

{
 
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
 
"payload": {
   
"commands": [
     
{
       
"ids": [
         
"456"
       
],
       
"status": "ERROR",
       
"errorCode": "deviceLidOpen"
     
}
   
]
 
}
}

现在,如果用户要求启动洗衣机,Google 助理会说出以下内容作为响应:

“The lid is open on the washer. Please close it and try again.”(洗衣机的机盖打开了。请关闭机盖,然后重试。)

异常与错误类似,但异常指示的是某个提醒与某个命令相关联的情况,这种情况未必会阻止顺利执行。异常可使用 StatusReport 特征提供相关信息,例如电池电量或近期状态更改。不会阻止执行的异常代码会随 SUCCESS 状态一起返回,而会阻止执行的异常代码会随 EXCEPTIONS 状态一起返回。

以下代码段中提供了包含异常的示例响应:

{
 
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
 
"payload": {
   
"commands": [{
     
"ids": ["123"],
     
"status": "SUCCESS",
     
"states": {
       
"online": true,
       
"isPaused": false,
       
"isRunning": false,
       
"exceptionCode": "runCycleFinished"
     
}
   
}]
 
}
}

Google 助理会说出以下内容作为响应:

“The washer has finished running.”(洗衣机已结束运行。)

如需为您的洗衣机添加错误报告,请打开 functions/index.js,然后添加错误类别定义,如以下代码段所示:

index.js

app.onQuery(async (body) => {...});

// Add SmartHome error handling
class SmartHomeError extends Error {
  constructor
(errorCode, message) {
   
super(message);
   
this.name = this.constructor.name;
   
this.errorCode = errorCode;
 
}
}

更新执行响应以返回错误代码和错误状态:

index.js

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) => {
         
...
       
})
       
//Add error response handling
       
.catch((error) => {
          functions
.logger.error('EXECUTE', device.id, error);
          result
.ids.push(device.id);
         
if (error instanceof SmartHomeError) {
            result
.status = 'ERROR';
            result
.errorCode = error.errorCode;
         
}
       
})
     
);
   
}
 
}
}

现在,Google 助理可以告知用户您报告的任何错误代码了。在下一部分中,您会看到具体示例。

7. 添加第二层用户身份验证

如果您的设备具有任何需要安全保障或应限定特定授权用户群组使用的模式(例如软件更新或开锁),您应该在集成中实现第二层用户验证

您可以针对所有设备类型和特征实现第二层用户身份验证,以便自定义是否每次都要进行安全验证,或是否需要满足特定条件。

支持的验证类型有以下三种

  • No challenge - 不使用身份验证的请求和响应(这是默认行为)
  • ackNeeded - 需要进行显式确认(是或否)的第二层用户身份验证
  • pinNeeded - 需要输入个人识别码 (PIN) 的第二层用户身份验证

在本 Codelab 中,请向用于开启洗衣机的命令添加 ackNeeded 验证,并添加在次级验证失败时返回错误的功能。

打开 functions/index.js,然后添加错误类别定义以返回错误代码和验证类型,如以下代码段所示:

index.js

class SmartHomeError extends Error { ... }

// Add secondary user verification error handling
class ChallengeNeededError extends SmartHomeError {
 
/**
   * Create a new ChallengeNeededError
   * @param {string} suvType secondary user verification challenge type
   */

  constructor
(suvType) {
   
super('challengeNeeded', suvType);
   
this.suvType = suvType;
 
}
}

此外,您还需要更新相应的执行响应以返回 challengeNeeded 错误,具体如下所示:

index.js

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) => {
         
...
       
})
       
.catch((error) => {
          functions
.logger.error('EXECUTE', device.id, error);
          result
.ids.push(device.id);
         
if (error instanceof SmartHomeError) {
            result
.status = 'ERROR';
            result
.errorCode = error.errorCode;
           
//Add error response handling
           
if (error instanceof ChallengeNeededError) {
              result
.challengeNeeded = {
                type
: error.suvType
             
};
           
}
         
}
       
})
     
);
   
}
 
}
}

最后,修改 updateDevice 以要求通过显式确认来开启或关闭洗衣机。

index.js

const updateDevice = async (execution,deviceId) => {
 
const {challenge,params,command} = execution; //Add secondary user challenge
  let state
, ref;
 
switch (command) {
   
case 'action.devices.commands.OnOff':
     
//Add secondary user verification challenge
     
if (!challenge || !challenge.ack) {
       
throw new ChallengeNeededError('ackNeeded');
     
}
      state
= {on: params.on};
     
ref = firebaseRef.child(deviceId).child('OnOff');
     
break;
   
...
 
}

 
return ref.update(state)
     
.then(() => state);
};

部署到 Firebase

运行以下命令以部署更新后的函数:

firebase deploy --only functions

部署更新后的代码后,当您要求 Google 助理开启或关闭洗衣机时,您必须使用语音确认相应 Action,具体如下所示:

:“Ok Google, turn on the washer.”(Ok Google,开启洗衣机。)

Google 助理:“Are you sure you want to turn on the washer?”(确定要开启洗衣机吗?)

:“Yes.”(是的。)

此外,您还可以打开 Firebase 日志以查看辅助用户验证流程的每个步骤的详细响应。

289dbe48f4bb8106.png

8. 恭喜

674c4f4392e98c1.png

恭喜!您已通过 ModesToggles 特征扩展了云到云集成的功能,并通过第二层用户验证确保其能够安全执行。

了解详情

您可以实现以下想法以进行更深入的研究:

  • 向您的设备添加本地执行功能。
  • 使用其他第二层用户身份验证质询类型来修改设备状态。
  • 更新 RunCycle 特征 QUERY 响应以进行动态更新。
  • 探索此 GitHub 示例