将智能家居设备关联到 Google 助理

1. 准备工作

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

79266e5f45e6ae20.gif

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

d009cef0f903d284.jpeg

前提条件

构建内容

在此 Codelab 中,您将发布一个用于管理虚拟智能洗衣机的云服务,然后构建云到云集成并将其关联到 Google 助理。

学习内容

  • 如何部署智能家居云服务
  • 如何将您的服务关联到 Google 助理
  • 如何将设备状态更改发布到 Google

所需条件

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

3. 运行入门级应用

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

获取源代码

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

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

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

项目简介

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

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

创建 Firebase 项目

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

关联到 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
    

washer-done/firebase.json 文件中,使用以下代码完成相应代码:

{
  "database": {
    "rules": "database.rules.json"
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
    "headers": [{
      "source" : "**/*.@(js|html)",
      "headers" : [ {
        "key" : "Cache-Control",
        "value" : "max-age=0"
      } ]
    }],
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log",
        "*.local"
      ]
    }
  ]
}

部署到 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://.web.app) 中打开托管网址以查看此 Web 应用。您会看到以下界面:

5845443e94705557.png

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

现在,您可以通过 Google Home 开发者控制台将您部署的云服务关联到 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 助理。

4. 创建洗衣机

集成配置完毕后,您就可以添加设备并发送数据了。您的云服务需要处理以下 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 账号相关联。这样一来,您就可以使用登录同一账号的 Google 助理界面和 Google Home 应用来进行测试。

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

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

ae252220753726f6.png

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 中存储的当前状态。更新 queryFirebasequeryDevice 函数,以返回洗衣机的状态数据。

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 可处理用于更新设备状态的命令。响应会返回每个命令的状态(例如 SUCCESSERRORPENDING)以及新的设备状态。

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. 测试集成

三个 intent 全部实现后,您可以测试集成是否能够控制洗衣机。

部署到 Firebase

使用 Firebase CLI 部署更新后的云执行方式:

firebase deploy --only functions

测试洗衣机

现在,如果您通过手机尝试下达以下任意一条语音指令,就会看到值发生变化:

“Ok Google,启动洗衣机。”

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

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

您还可以通过提问来查询洗衣机的当前状态。

“Ok Google,我的洗衣机启动了吗?”

“Ok Google,我的洗衣机在运行吗?”

“Ok Google,我的洗衣机洗到哪一步了?”

您可以在 Firebase 控制台函数部分中,查看函数下方显示的日志中显示的这些查询和命令。如需详细了解 Firebase 日志,请参阅写入和查看日志

您还可以在 Google Cloud 控制台中找到这些查询和命令,方法是依次前往 Logging > 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 库屏幕中,点击启用

ee198858a6eac112.png

启用报告状态

对 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

测试实现效果

点击网络界面中的 Refresh ae8d3b25777a5e30.png 按钮,并验证能否在 Firebase 控制台日志中看到同步请求。

接下来,调整前端网络界面中的洗衣机设备属性,然后点击更新。验证能否在 Firebase 控制台日志中看到向 Google 报告的状态更改。

8. 恭喜

674c4f4392e98c1.png

恭喜!您已成功使用云到云集成将 Google 助理与设备云服务集成。

了解详情

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

您还可以详细了解如何测试和提交集成以供审核,包括用于向用户发布集成的认证流程。