返回 登录
0

将 Api.ai 助手接入物联网的那些实践

原文:How to Connect Your Api.ai Assistant to the IoT
作者:Patrick Catanzariti
翻译:安翔
审校:屠敏

当个人助理通过物联网访问个人数据和现实世界时,它变得更加迷人,让人心驰神往。这可能出现新的应用场景,比方说请求你的助手打开灯光,或者向它询问你的睡眠质量。我们将把你的 Api.ai 助手连接到 Jawbone Up API,以此作为示例。

前期准备

为了更好地掌握本教程的内容,你需要做如下准备:

  • 连接到一个简单 HTML 网页应用的 Api.ai 代理。如果你想了解此过程,请参阅此文。此外,你也可以从本教程下载代码使用。
  • 指定一个 entity 为 “sleep” 的代理。创建方法可以查阅此文。它至少应当理解诸如“how much sleep did I have last night?” 和 “how much REM sleep did I get?”这样的指令。如果你想要使之适应自己的 IoT 设备,你需要创建自定义的 entity ,用于识别你的 IoT 功能。
  • 需要你对 Node.js 有基本的了解并且能够运行 Node 服务器。因为缺少它们的支持,本系统将无法运行。
  • 知道如何使用Jawbone UP API(或者你打算使用的其他API)。之前我们已经介绍了使用Node.js连接到Jawbone Up API 的方法,本教程将会引用该文章中的一些内容。
  • 基于 HTTPS 运行你的站点的 SSL 证书。它对于 Jawbone Up API 来说是必需的。正如本系列开始时所提到的那样,基于 HTTPS 实现会相对容易。我们在Jawbone Up API文章中介绍了如何设置自签名证书,有兴趣可以查阅,但这并不是最简方案。更轻松的实现方法是本系列的第一篇文章中提到的 Let’s Encrypt 。此外,Glitch.com 还提供了一个默认为 HTTPS 的原型设计环境。

代码

本教程的所有代码提供免费下载和使用。 GitHub地址:https://github.com/sitepoint-editors/Api-AI-Personal-Assistant-IoT-Demo

系统如何工作

你的 Api.ai 助手连接到一个简单的 Web 应用之后,它通过 HTML5 语音识别 API 接受指令。此时,你需要添加一些新功能,用于监听你的 Api.ai 代理的特定 Action 。在本例中, Action 是“sleepHours”。

每当 JavaScript 检测到该 Action 时,它会触发调用你的 Node.js 应用程序,以向Jawbone API 请求数据。一旦 web 应用收到这些数据,它会将数据转换成友好的提示语并将其读出 - 为你的助手提供全新的智能体验!

项目结构

我将应用程序从初始的 HTML 结构调整为使用 EJS 视图的应用程序,以便你通过 OAuth 登录到 Jawbone Up API 后在 Web 应用程序中切换页面。实际上,你只有一个页面,但是如果需要其他 IoT 设备,你可以通过此方法添加更多。这个唯一的界面在 /views/index.ejs 中定义。然后,你的 Node 服务器 server.js 和证书文件将位于根目录中。为了尽可能简单和灵活,所有前端 JavaScript 和 CSS 代码都应当是内联的。你可以随意将它们移动到 CSS 和 JS 文件中,将它们细化并使其变得更好。

在 JavaScript 中为 Api.ai 添加 Action

正如你从以前的文章所了解到的那样,当 Api.ai 返回一个响应时,它提供了一个如下所示的 JSON 对象:

{
  "id": "6b42eb42-0ad2-4bab-b7ea-853773b90219",
  "timestamp": "2016-02-12T01:25:09.173Z",
  "result": {
    "source": "agent",
    "resolvedQuery": "how did I sleep last night",
    "speech": "I'll retrieve your sleep stats for you now, one moment!",
    "action": "sleepHours",
    "parameters": {
      "sleep": "sleep"
    },
    "metadata": {
      "intentId": "25d04dfc-c90c-4f55-a7bd-6681e83b45ec",
      "inputContexts": [],
      "outputContexts": [],
      "contexts": ,
      "intentName": "How many hours of @sleep:sleep did I get last night?"
    }
  },
  "status": {
    "code": 200,
    "errorType": "success"
  }
}

在这个 JSON 对象中有两个比特的数据你需要使用,它们是 action和 parameters.sleep。

"action": "sleepHours",
"parameters": {
  "sleep": "sleep"
},

action 决定了触发Api.ai 行为的名称。在本示例中,你将其命名为“sleepHours”。parameters 包含了你的语句中可以改变细节的变量。在本例中,你的 parameters 定义了睡眠类型: “睡眠”,“深睡眠”,“浅睡眠”或“REM睡眠”(或“REM”)。

最初,在早期的一篇关于 Api.ai 的文章中,prepareResponse()函数从 Api.ai 获取 JSON 响应,将其放在右下方的调试文本字段中,它提取出 Api.ai 的文本响应并显示在Web应用程序中。你完全依赖 Api.ai 代理的反馈,无需自己添加任何功能。

function prepareResponse(val) {
  var debugJSON = JSON.stringify(val, undefined, 2),
    spokenResponse = val.result.speech;

  respond(spokenResponse);
  debugRespond(debugJSON);
}

在 action 代码片段中,如果 action 包含”sleepHours”,就调用你自己的requestSleepData()函数。函数中的睡眠参数将决定请求什么类型的睡眠数据。

function prepareResponse(val) {
  var debugJSON = JSON.stringify(val, undefined, 2),
    spokenResponse = val.result.speech;

  if (val.result.action == "sleepHours") {
    requestSleepData(val.result.parameters.sleep);
  } else {
    respond(spokenResponse);
  }

  debugRespond(debugJSON);
}

requestSleepData()函数中将从 Node 服务器请求所有的睡眠数据,然后通过数组data.items[0].details(昨晚的数据)的第一个值进行睡眠类型的区分。 data.items[0].details.rem代表 REM 睡眠;data.items[0].details.sound代表深度睡眠;data.items[0].details.light代表浅睡眠;data.items[0].details.duration则代表睡眠汇总数据。

function requestSleepData(type) {
  $.ajax({
    type: "GET",
    url: "/sleep_data/",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(data) {
      console.log("Sleep data!", data);

      if (data.error) {
        respond(data.error);
        window.location.replace("/login/jawbone");
      }

      switch (type) {
        case "REM sleep":
          respond("You had " + toHours(data.items[0].details.rem) + " of REM sleep.");
          break;
        case "deep sleep":
          respond("You had " + toHours(data.items[0].details.sound) + " of deep sleep.");
          break;
        case "light sleep":
          respond("You had " + toHours(data.items[0].details.light) + " of light sleep.");
          break;
        case "sleep":
          respond("You had " + toHours(data.items[0].details.duration) + " of sleep last night. That includes " + toHours(data.items[0].details.rem) + " of REM sleep, " + toHours(data.items[0].details.sound) + " of deep sleep and " + toHours(data.items[0].details.light) + " of light sleep.");
          break;
      }
    },
    error: function() {
      respond(messageInternalError);
    }
  });
}

toHours()函数将时间格式转换为普通语句,比如:1小时53分59秒。

function toHours(secs) {
  hours = Math.floor(secs / 3600),
  minutes = Math.floor((secs - (hours * 3600)) / 60),
  seconds = secs - (hours * 3600) - (minutes * 60);

  hourText = hours + (hours > 1 ? " hours, " : " hour, ");
  minuteText = minutes + (minutes > 1 ? " minutes " : " minute ");
  secondText = seconds + (seconds > 1 ? " seconds" : " second");

  return hourText + minuteText + "and " + secondText;
}

requestSleepData() 函数最终将调用response()函数,细心的你会发现response()函数在之前的获取 Api.ai 语音响应时使用过。通过复用现有的功能,从语音获得响应,让你的助手在监听到信息之后通知给用户。

不要忘了在前端 JavaScript 代码的最后加上错误处理。如果 Jawbone 返回数据出现问题(通常是因为没有登录到服务),服务器将以 JSON 字段{“error” : “Your error message”}的形式提示错误。助理识别到错误提示时将自动跳转到 OAuth 登录页面。

if (data.error) {
  respond(data.error);
  window.location.replace("/login/jawbone");
}

Node.js 服务器

你的 Node 服务器基于使用Node.js连接到Jawbone Up API 一文中的服务器。此文讲述了所有关于通过OAuth连接到Jawbone API并设置一个HTTPS服务器且运行成功的方法,如果对代码有任何疑惑,请参考这篇文章。如果你手头没有 Jawbone Up ,可以使用其他 IoT 设备代替。这里的 Jawbone Up 数据只是一个例子,你可以自定义响应方法以获取不同的响应数据(你可能无需担心OAuth)。

你的 Jawbone 数据在本例中以简单的 JSON 格式展示,而不是像以往文章中那样被格式化为一个表单。变量upoptions被定义为全局变量,以便多个请求 API 调用(其他SitePoint例子中,一次请求一大块数据)。

用户可以访问/login/jawbone,通过 OAuth 登录 Jawbone API。然而,如上所述,其实你不需要刻意关注此事,因为你的助理发现登录失败则会自动重定向到/login/jawbone。您的助理还可以重定向它们。

如果你想该功能更加完整,还可以向你的 Api.ai 代理添加一个新的 intent,用于识别“log me into my Jawbone Up data”。Node.js 登录流程如下:

app.get("/login/jawbone",
  passport.authorize("jawbone", {
    scope: ["basic_read","sleep_read"],
    failureRedirect: "/"
  })
);

一旦通过passport.use(“jawbone”, new JawboneStrategy())登录成功,将访问权限分配给up变量并将用户转到/barry。你可以将用户重定向到根目录(将会造成死循环)之外的任何路径。我选择重定向到/barry是因为我将我的助手命名为 Barry,它具有一定的自我标识性(页面显示完全相同的索引视图,通常很难区分)。如果您愿意,你也可以使用此方式为已经成功登录到 Jawbone 设备的用户提供不同的视图。登录后,用户可以返回到根页面:https://localhost:5000 ,并使用Up 函数。

返回 IoT 数据

收到/sleep_data的 GET 请求后,对 Jawbone 数据的检索非常简单。首先确认up变量是否被定义,如果没有则证明用户未登录,此时你需要告知 web 应用从而执行重定向操作并且提示用户登录。当你调用up.sleeps.get()而 Jawbone 返回错误或者jawboneData.items未定义时,你都需要重复此操作。

app.get("/sleep_data", function(req, resp) {
  if (up !== undefined) {
    up.sleeps.get({}, function(err, body) {
      if (err) {
        console.log("Error receiving Jawbone UP data");
        resp.send({"error": "Your sleep tracker isn't talking to me. Let's try logging in again."});
      } else {
        var jawboneData = JSON.parse(body).data;

        if (jawboneData.items) {
          resp.send(jawboneData);
        } else {
          console.log("Error: " + jawboneData);
          resp.send({"error": "Your sleep tracker isn't talking to me. Let's try logging in again."});
        }
      }
    });
  } else {
    console.log("Up is not ready, lets ask to log in.");
    resp.send({"error": "Your sleep tracker isn't talking to me. Let's try logging in again."});
  }
});

这里的错误可能是由其他因素引起的,但是为了教程更加简单易懂,我选用重复登录来举例。在正式产品的应用程序中,你需要查看各种原因并调整对应的响应。

如果一切顺利,你将会收到有效的响应信息,将其用 JSON 的格式发送给 web 应用程序,以便更好的读取和解析:

if (jawboneData.items) {
  resp.send(jawboneData);
}

随着 Web 应用程序和 Node.js 服务器协同工作,你应该能够从 Jawbone Up 设备中检索睡眠数据了。接下来,我们一同测试一下。

Action 测试

通过指令 node server.js 运行你的服务器。在这之前你需要执行 npm install 安装 npm 模块,同时需要运行基于 HTTPS 的服务器证书
在浏览器中输入网址 https://localhost:5000 的方式访问你的 AI 助手(如果你使用Glitch 一类的服务,则需用具体的 Glitch URL 替换)。在界面中询问你的睡眠信息:

系统会提示你未登录,并且弹出 Jawbone Up OAuth 登录界面。你需要在此界面中登录并同意提供你的数据访问权限,然后单击“同意”:

此时你再次询问将会得到正确的回复。

你还可以询问一些更加细节的问题来进行参数测试,比如“How much REM did I get?”。

总结

这是对当前 多种 Api.ai 功能的又一种探索和体验。你可以扩展这个例子,添加上日期范围(例如“星期二有多少睡眠?”),或者更好地格式化时间(在其个响应中报个小 bug)。你可以进行个性化的扩展,用更精巧的方式来描述响应。

如你所见,使用本文中的方法你可以将任何 Node.js 服务或者 web 应用连接到 Node.js 服务器,为 Api.ai 绑定一个 intent,并指定它如何进行反馈。你可以通过 IFTTT 连接大量的物联网设备,通过 IFTTT 连接 LIFX 智能灯 或者 连接你自己的 Nodebot。这些尝试仅仅受限于你拥有的设备!

评论