当个人助理通过物联网访问个人数据和现实世界时,它变得更加迷人,让人心驰神往。这可能出现新的应用场景,比方说请求你的助手打开灯光,或者向它询问你的睡眠质量。我们将把你的
Api.ai 助手连接到 Jawbone Up API,以此作为示例。
前期准备
为了更好地掌握本教程的内容,你需要做如下准备:
1.连接到一个简单 HTML 网页应用的 Api.ai 代理。如果你想了解此过程,请参阅此文。此外,你也可以从本教程下载代码使用。
2.指定一个 entity 为 “sleep” 的代理。创建方法可以查阅此文。它至少应当理解诸如“how
much sleep did I have last night?” 和 “how much REM
sleep did I get?”这样的指令。如果你想要使之适应自己的 IoT 设备,你需要创建自定义的
entity ,用于识别你的 IoT 功能。
3.需要你对 Node.js 有基本的了解并且能够运行 Node
服务器。因为缺少它们的支持,本系统将无法运行。
4.知道如何使用Jawbone UP API(或者你打算使用的其他API)。之前我们已经介绍了使用Node.js连接到Jawbone
Up API 的方法,本教程将会引用该文章中的一些内容。
5.基于 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 格式展示,而不是像以往文章中那样被格式化为一个表单。变量up和options被定义为全局变量,以便多个请求
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。这些尝试仅仅受限于你拥有的设备!
|