Web-物联网构建指南-全-

Web 物联网构建指南(全)

原文:Building the Web of Things

译者:飞龙

协议:CC BY-NC-SA 4.0

第一部分:物联网和万物互联的基础

在第一部分中,我们为物联网和万物互联奠定了基础,解释了它是什么,以及它与物联网的比较和关联。在第一部分的结尾,你将广泛了解构建物联网系统的问题和挑战。

第一章 介绍了物联网的方法,并讨论了它在众多用例和环境中提供的各种优势。

第二章 提供了与物联网的第一次高级但实用的接触。你将学习如何向伦敦的设备发送请求,并快速编写与设备交互的简单应用程序。

第三章 描述了为什么 JavaScript 非常适合嵌入式设备和物联网,并提供了对 Node.js 框架和生态系统的快速入门课程。

第四章 介绍了嵌入式系统的世界,它们的类型及其差异。接下来,你将熟悉树莓派平台,学习如何将传感器和执行器连接到你的设备,然后从 Node.js 应用程序中控制它。

第五章 对连接物理对象的多种方法和途径进行了广泛的介绍。特别是,你将了解在物联网中常用各种网络协议的优缺点。本章结尾,我们提出了物联网的分层架构。

第一章:从物联网到万物互联

本章涵盖

  • 物联网(IoT)的概念和历史简介

  • 我们何时以及为何应该将物理对象进行数字化连接

  • 传统物联网方法的局限性

  • 物联网(WoT)为何不同以及为何有前景

如果你手里拿着这本书,你很可能已经听说过“物联网(IoT)”和“万物互联(WoT)”这两个术语。也许你想了解这个趋势是什么。或者也许你已经理解为什么这个话题变得如此受欢迎,并且你想成为其中的一员,但你不确定从哪里开始。或者——更进一步——你意识到物联网对你所在行业可能意味着什么,你希望获得构建网络连接产品和服务的所需的技术技能。如果你有任何这些共鸣,你将有一个美好的体验!

物联网到底是什么?它是什么时候在哪里被发明的?物联网使哪些新的应用和场景成为可能?它将如何改变未来几年的技术和商业格局?接下来的章节将回答所有这些问题以及更多。但不要扔掉这本书,因为它不仅会讨论理论。它还会详细涵盖所有可以帮助你将物联网变为现实的技术和工具。另一方面,我们相信,从一些背景知识开始将帮助你更好地理解物联网到底是什么,以及你如何在你的项目中使用它,而不仅仅是停留在表面和刻板化的描述。深入了解物联网的历史将帮助你理解物联网和物联网之网之间的微妙差异,特别是为什么这种区别很重要。

在过去几年里,物联网已经成为技术和商业中最有希望和最激动人心的进展之一。一个世界的愿景,在这个世界里,微型计算机、传感器和通信接口嵌入到我们城市的基础设施中,或者嵌入到汽车、办公室或衣服中,可能会彻底改变我们生活的每一个领域——我们如何玩耍,我们如何工作和做生意,以及我们如何生活。直到最近,物联网项目主要关注构建小规模、封闭和孤立的部署,其中设备不是设计成易于访问或可重编程的。在特定用例中,设备和应用程序之间的定制耦合意味着对现有部署的任何更改都是复杂且昂贵的。这限制了物联网的维护和演变,因为每次添加新功能都需要大量的资源(时间、金钱和技术技能)。

与之相反,在过去的二十年里,互联网之所以取得了广泛的成功,是因为它易于学习和使用,并且它还强调了服务器、浏览器和应用程序之间松散耦合的重要性。HTTP 简单且明确定义的编程模型使得任何人都可以在不破坏整个系统的情况下更改系统的某些部分。因此,构建新的网络应用相对便宜且易于接触,对更大一群技术爱好者来说更加可行。

物联网之网(Web of Things)是物联网的一个专门领域,它利用了使互联网取得成功的因素,并将其应用于嵌入式设备,以便让尽可能多的开发者能够访问物联网的最新进展。在物联网之网——就像在互联网上一样——任何拥有文本编辑器和基本网络标准(HTML 和 HTTP)理解的人都可以快速开始将设备和物体连接到互联网。但它还使你能够达到下一个层次,并帮助你有效地构建交互性和创新性的现实世界应用,这些应用融合了物理世界和数字世界。

1.1. 定义物联网

用一句话概括物联网的本质几乎是不可能的。这些概念已经存在了几十年,物联网是什么或不是什么并没有明确的界限。尽管如此,物联网愿景的广泛定义是一个互联网远远超出今天的多媒体内容集合的世界:它通过无数小型甚至微型的计算机扩展到物理、实时世界。简而言之,我们可以为物联网提供的最简单定义如下。

定义

物联网是一个物理对象系统,可以通过在多种网络接口上通信的电子设备发现、监控、控制或与之交互,最终可以连接到更广泛的互联网。

二十年前,一个可以通过传感器感知世界、然后分析、存储或交换信息的日常物体世界,仅存在于科幻小说或《杰森一家》中。如今,由于嵌入式设备的巨大进步,带来了一个新类别的物体:智能物体。一个智能物体(在本书的其余部分我们将称之为物体——首字母大写T)是一个通过以下一个或多个方式数字化增强的物理对象:

  • 传感器(温度、光线、运动等)

  • 执行器(显示器、声音、电机等)

  • 计算(可以运行程序和逻辑)

  • 通信接口(有线或无线)

物体通过启用一系列全新的应用扩展了我们生活的世界;参见图 1.1。通过在我们周围部署大量微型且价格低廉——但越来越强大——的计算机,我们现在能够以前所未有的精细空间和时间分辨率监控和交互物理世界。

图 1.1. 物联网的景观。物联网是一个物体的网络,任何可以通过某种形式连接到互联网的东西都可以成为物体。从带有 RFID 标签的橙子箱,到智能城市,再到中间的每一个物体,所有数字化增强的对象共同构成了物联网。

图片描述

具体来说,物联网中的“物”可以是从简单的带有自动识别标签的产品,比如你带有自动识别标签(如条形码、二维码、NFC 和 RFID 标签)的联邦快递包裹,这样它可以从发货中心追踪到你的门口;到更复杂、无线连接的产品、设备或机器,如安全系统、你的汽车或工厂装配线;甚至是一个建筑或整个城市。术语中的“互联网”部分意味着“物”(或至少其服务或关于/来自它的数据)可以通过现有的互联网基础设施被其他应用程序访问和处理。请注意,这并不意味着“物”本身必须直接连接到互联网。所使用的通信网络可以是自动识别方法、短距离无线电(蓝牙、ZigBee 等)、建筑中的 Wi-Fi 网络。

不幸的是,今天几乎不可能建立一个单一且全球的物联网生态系统,使得各种设备能够无缝通信。没有一种独特且通用的物联网应用协议可以在今天可用的许多网络接口上工作。直言不讳地说,今天的物联网本质上是一个不断增长的孤立物联网内联网集合,它们无法相互连接。

为了使物联网成为现实,我们需要一个单一的全局应用层协议(想想语言),使得设备和应用程序能够相互交流,无论它们如何物理连接。与其从头开始发明另一个协议(正如许多物联网项目所做的那样,并且仍在继续这样做),为什么不重用已经广泛用于构建可扩展和交互式应用程序的东西,比如互联网本身呢?这正是物联网(以及这本书)的主题:使用和重用现成的、广泛流行的互联网协议、标准和蓝图,使物联网提供的数据和服务更容易被更多的(网络)开发者访问。

1.2. 物联网的诞生

正如我们将在第 1.4 节中详细描述的那样,当人们想要将来自不同制造商的设备集成到单个应用程序或系统中时,物联网的局限性就会显现出来。为了说明物联网如何处理这些局限性,让我们考虑一下约翰尼·B.的生活,他是全球多个城市的著名酒店连锁店的老板。约翰尼希望将所有酒店的每个房间中的所有电器都进行数字化连接,这样他就可以通过巴哈马的游艇甲板上的单个控制中心应用程序来监控、控制和改善他酒店的管理。同时,这个系统还可以为他的酒店中的每位客人提供更愉快和个性化的体验,如图图 1.2 所示。

图 1.2. 约翰尼希望将酒店所有房间的电器进行数字化连接。首先,客人可以通过他们的手机访问各种服务,从控制他们的房间(灯光、空调、娱乐等),到预订酒店设施,到订购食物和饮料——所有这些都可以在他们的手机上完成。其次,这个系统将允许约翰尼以集中和高效的方式协调和优化他酒店的各个方面,而无需使用各种孤岛化的应用程序和工具。

图片描述

1.2.1. 物联网场景:联网酒店

构建这个智能酒店系统可能需要由公司Alpha制造的电子门锁,由公司Beta提供的安全摄像头,以及由公司Gamma提供的用于管理所有这些的控制应用程序。使这些设备和系统能够相互通信和协同工作需要大量的定制系统集成。约翰尼可以与一家专业公司签订合同,并花费他辛苦赚来的资源在一个需要数月才能完成的大项目上。这样一个复杂和定制的项目将具有 Jenga 塔的坚固性(触摸错误的部件,所有事情都会失控):它将充斥着错误和黑客攻击,因此维护和扩展将是一场噩梦。在这种情况下,毫无疑问,约翰尼在得到他想要的系统之前就会耗尽资金。

如果约翰尼喜欢 DIY(自己动手做),他当然可以决定自己构建整个系统。他需要从同一家公司购买所有设备,这样他就不会遇到任何不兼容的问题。不幸的是,他不太可能找到一家拥有他所需所有传感器和设备的单一制造商。即使他找到了这个完美的系统, chances are high that the control application that comes with it won’t be what he wants: easy to use and to configure. 他很可能会不得不从头开始自己编写一个全新的控制中心应用程序。哦,如果他还想让系统具有可扩展性、可靠性和安全性,他可以轻易地将所需时间翻倍——如果不是三倍。我们是否也应该谈谈需要为酒店客人构建的移动应用程序?你明白我的意思了。

约翰尼的生活可能看起来很超现实。遗憾的是,这正是今天物联网的样子。我们知道这一点,因为我们过去十年中与许多约翰尼有过合作,从想要将现有的安全摄像头与 RFID 门结合以创建更智能的安全系统的小店经理,到想要通过互联网控制其灯光的 LED 制造商。我们反复经历了这种场景。

如果任何设备都能轻松集成并被任何应用程序消费,无论它们使用的是哪种网络协议或标准,那岂不是太棒了?这正是物联网所实现的,如图 1.3 所示链接

图 1.3。在物联网中,今天存在数百种不兼容的协议。这使得从各种设备中集成数据和服务的复杂性和成本极高。在物联网络中,任何设备都可以使用标准网络协议进行访问。将异构设备连接到网络使得跨系统和应用程序的集成变得更加简单。

图片

1.2.2. 物联网与物联网络的比较

由于更多日常物品将被数字化增强,下一步合乎逻辑的步骤是利用万维网生态系统和基础设施来构建物联网应用程序,有效地打破这种持续的“一个设备,一个协议,一个应用”模式。将帮助现代网站如 Facebook 或 Google 扩展到数百万并发用户的技术推向每个这些微小的设备,同时不牺牲安全或性能,将特别有趣。将现有和新兴的工具和技术最大化,并将它们应用于物联网场景的开发,这是物联网络的最终目标。

当物联网正忙于解决网络问题时,物联网络完全依赖于应用层协议和工具(如第五章中描述的开放系统互连(OSI)模型的第 7 层);参见图 1.4。将任何设备映射到网络思维中,使得物联网络对设备使用的物理和传输层协议变得无关紧要。正如你将在下一章中学到的,好消息是,几乎任何定制的物联网协议或标准都可以通过称为网关的软件或硬件桥接器与网络连接起来。

图 1.4。物联网络只关注最高的 OSI 层(7),该层处理应用程序、服务和数据。与这样高层次的抽象工作使得连接来自许多设备的数据和服务成为可能,无论它们实际使用的传输协议是什么。相比之下,物联网并不提倡单一的应用层协议,通常关注 OSI 堆栈的底层。

图片

将底层协议的复杂性和多样性抽象到简单的网络模型背后提供了许多优势。就像网络已经成为互联网上分布式应用程序的全球集成平台一样,物联网络促进了各种设备和与之交互的应用程序的集成。换句话说,通过隐藏物联网中使用的各种传输协议的复杂性和差异,物联网络允许开发者专注于其应用程序的逻辑,而无需担心这个或那个协议或设备实际上是如何工作的。

回到我们的智能酒店场景,如果所有设备(无论制造商如何)都能提供标准的 Web API,那么跨设备和应用程序的数据集成将几乎“开箱即用”,因为所有设备都会说同一种语言。在这种情况下,酒店业主(或系统集成商)只需担心构建控制中心应用程序,这很可能是 Web 混合应用——一个结合来自各种来源的数据和服务的单一 Web 应用程序。他不必费心学习他想要使用的各种设备所使用的每个协议的具体细节.^([1]) 这不仅将显著减少构建所需的时间,而且还将最大限度地减少每次添加、删除或更新设备或服务时维护系统所需的努力。

¹

一份不太短的自动化协议列表:en.wikipedia.org/wiki/List_of_automation_protocols

将这一愿景变为现实一直是我们在 2007 年启动的物联网社区的目标.^([2]) 使用 HTTP 和其他网络标准或工具与嵌入式设备交互对我们来说非常合理。当时,这个想法似乎是不切实际的,甚至毫无意义,我们受到了不少批评,主要是因为物联网中的嵌入式网络服务器通常比访问它们的客户端(如浏览器或手机)拥有更有限的资源。但情况已经改变:最近具有高级功能的嵌入式网络服务器只需 8 KB 的内存即可实现。多亏了高效的跨层 TCP/HTTP 优化,它们可以在微小的嵌入式系统或智能卡上运行。此外,多亏了 JavaScript 社区的巨大发展,将大量工作负载从设备转移到客户端应用程序甚至云端的操作变得越来越容易。

²

webofthings.org

在物联网中,设备和它们的服务完全集成在网络上,因为它们使用与传统网站相同的标准和技巧。这意味着你可以编写与嵌入式设备交互的应用程序,就像你与任何其他使用网络 API 的 Web 服务交互一样——特别是 RESTful 架构。

正如我们在第六章中将要描述的,REST 是一种用于开发分布式应用程序的架构风格,是现代网络的基础。REST 的本质是专注于创建松散耦合的服务,这些服务可以轻松重用,并使用 URI、HTTP 和标准化的媒体类型实现。由于统一的接口(HTTP 动词和响应代码)抽象了服务的应用特定语义,因此它使得构建松散耦合的服务变得容易,因为它为客户端提供了一个简单的机制来选择最佳可能的交互表示。这使得网络成为构建通用架构和应用程序编程接口(API)的理想基础,如图 1.5 所示。

图 1.5. 物联网是能够在嵌入式设备上使用现代网络标准的能力。通过使用所有这些标准来处理物联网场景,我们既能够构建新的交互式应用程序类型,又确保设备可以以最小的努力集成到现代网络应用程序和服务中。

图片

在实践中,这意味着您可以通过网络浏览器开始与事物互动,就像您在网上冲浪(通过链接到其他相关事物)一样探索物联网。然后可以轻松检索、处理和显示在网页上,使用 HTML、CSS 和 JavaScript。

与物联网中存在的许多协议和标准相比,物联网背后的编程模型更容易学习和使用。这一点特别有趣,因为它使任何具有基本网络编程技能的人都能构建网站和应用,不仅围绕多媒体内容,还可以使用来自物理世界的实时数据,如图 1.6 所示。

图 1.6. 物联网允许开发者和应用程序使用标准的 HTTP 请求与任何物理对象或设备交换数据,无论设备如何连接。

图片

尽管物联网强调使用网络标准在设备之间交换数据,但它并没有涉及设备之间应该如何物理连接。换句话说,设备可以(但不必)公开连接到网络,任何人都可以像访问网站一样公开访问它们。物联网在本地网络(例如,您公司的内部网络或您家的 Wi-Fi 网络)中同样工作得很好。

在某些情况下,让事物拥有公共 URL 并在网络上公开访问是有意义的——例如,由公共当局运营的城市交通或污染传感器。在这种情况下,设备也可以像任何其他网页一样被搜索引擎抓取和索引,并允许用户在物理世界中“谷歌”或为智能对象的 URL 设置书签并与朋友分享。网络连接的对象也可以像其他用户一样变得活跃并参与网络,通过发布自己的博客或使用如 Twitter 等服务的 API 相互交谈。

使用如 IFTTT 等服务,用户可以创建小型的、逻辑上的规则,将现实世界中的设备,如家中的传感器,与云中的虚拟服务混合;例如,一个短信网关或天气预报服务。这样的应用被称为物理混搭,这是第十章的主题,你将学习到创建物理混搭所需的原则和工具。

³

ifttt.com/

要真正理解为什么物联网络代表了物联网演变中的一个有趣的新阶段,我们首先需要回顾这一领域至今的历史。为什么连接设备的想法最初会出现?如果全球连接设备的愿景如此有希望,为什么它还没有实现?我们将在下一节尝试回答这些问题。

1.2.3. 物联网——简史

要理解物联网这一概念从何而来,我们必须探究一个名为计算机科学研究的领域,它有许多名称,最常见的是普适计算泛在计算。这一学科的创始人之一是马克·魏斯。在 20 世纪 90 年代初领导施乐帕克研究中心时,魏斯开始思考计算机的下一波浪潮:

最深刻的技术是那些消失的技术。它们融入日常生活的织物中,直到它们与日常生活无法区分……基于硅的信息技术,相比之下,远未成为环境的一部分。已经售出了超过 5000 万台个人电脑,但电脑仍然主要存在于自己的世界中。人们只能通过与人们实际使用电脑的任务无关的复杂术语来接近它。

马克·魏斯,《21 世纪的计算机》,1991

魏斯比其他人更早地理解到,计算机显然正在从桌面和办公室中的大型、笨重的盒子向更小、更智能的设备发展,这些设备很快将无缝地嵌入我们周围的世界中,变得无影无踪。

1991 年之后的几年是我们今天所知道的互联网的早期年份,它已经成长为一个庞大的公共全球计算机网络,这得益于蒂姆·伯纳斯-李爵士的发明和网络的开发(HTTP 和 HTML)——互联网的顶层应用层。

互联网惊人的成功对无处不在的计算研究社区产生了深远的影响。许多研究人员开始思考将物理对象连接到互联网。特别是,这些人包括来自 MIT 的 Auto-ID 实验室(一个最初在 MIT 成立的国际研究实验室集群)的研究人员,如 Sanjay Sarma、Kevin Ashton、David Brock 和 Daniel Engels,以及 ETH Zurich 的 Friedemann Mattern 和 Elgar Fleisch。他们的主要焦点是利用射频识别(RFID)标签自动识别商品,以创建一个全球电子标签产品网络,并能够优化物流和供应链。为了找到一个描述这个全球网络的术语,Kevin Ashton 提出了“物联网”这个术语。其余的都是历史。

www.rfidjournal.com/articles/view?4986

尽管在 1999 年就提出了“物联网”这个术语,但这个概念直到最近几年才被公众所关注,当时人们意识到它不仅仅是一个时髦的术语。根据 Google Trends 的数据,自 2013 年 12 月以来,“物联网”这个术语在新闻标题中明显超过了“Web 2.0”。图 1.7。尽管 Web 2.0 是 2000 年代最受欢迎的互联网新兴趋势之一,但在过去几年中,随着物联网的指数级增长,它已经从聚光灯下淡出。

图 1.7。自 2013 年 12 月以来,“物联网”这个术语在新闻标题中的流行度已经超过了“Web 2.0”。[来源:Google Trends,2015 年 9 月 25 日]

当谷歌在 2013 年 12 月以“适度”的 33 亿美元收购 NEST 时,发生了一个集体“啊哈!”的时刻:“等等!实际上可以从物联网中赚到钱。而且还有很多!2014 年,Gartner 预测到 2020 年将有超过 250 亿个连接设备。参考文献 5]思科则更为乐观,预测到 2020 年将有超过 500 亿个设备连接到互联网。参考文献 6]

www.gartner.com/newsroom/id/2905717

www.cisco.com/web/about/ac79/docs/innov/IoT_IBSG_0411FINAL.pdf

究竟谁对谁错并不重要,因为有一点是确定的:在下一个十年里,我们周围将会有更多互联网连接的设备。2008 年是一个重要的里程碑,当时连接到互联网的“事物”数量超过了人类数量。因此,从思科到三星再到 IBM,世界上许多最大的公司都在 2014 年将物联网视为一项关键的战略投资。([7])

物联网投资

我们可以从对物联网的突然兴趣中学习到两点。首先,无论你是后端大牛、前端开发者还是业余黑客,现在正是提升你的物联网技能的理想时机。其次,那些事物只在自己的小世界里相互通信的内联网时代已经为数不多。但是,为了让物联网成为现实并释放其潜力,所有这些物体都需要说同一种语言。为了设备和应用程序能够轻松、安全、灵活地相互交互,我们需要一个通用和开放的标准,它能够促进松散耦合、可扩展性和灵活性。

1.3. 用例——为什么是连接的对象?

询问物联网何时到来是不正确的,因为它已经在这里了。今天到处都可以找到无数的例子。你的智能电视连接到互联网并记录你喜欢的节目。你的鞋中的 Nike+传感器将你所有的跑步记录上传到互联网,这样你可以与朋友竞争。你的手机流式传输你的位置,这样你可以跟踪它,或者如果它被偷了,你可以远程禁用它。

尽管如此,物联网仍然处于其青少年早期阶段,并且肯定会以比这些早期用例更深远的方式影响我们的世界。让我们看看物联网最有可能产生重大影响的领域。这是一个看到物联网如何为多个领域带来好处的机会。希望这能激发你未来周末的破解或更严肃的产品开发。

1.3.1. 无线传感器网络和分布式传感

在 20 世纪 80 年代和 90 年代计算领域的惊人进步,尤其是嵌入式计算机和无线网络芯片的微型化,导致了无线传感器网络(WSNs)在 21 世纪初的出现。这些网络由如图 1.8 中所示的小型单板计算机组成。这些设备由于价格低廉、电池供电,可以在大范围内部署,使用多种传感器连续监测各种物理环境或结构。例如,WSNs 已被用于监测意大利的托雷阿基拉等历史建筑的结构,[9]以了解人类对鸟类栖息地的影响,[10]以及监测农作物以促进食品生产。11)

en.wikipedia.org/wiki/List_of_wireless_sensor_nodes

d3s.disi.unitn.it/projects/torreaquila

¹⁰

www.cs.berkeley.edu/~culler/papers/wsna02.pdf

¹¹

www.mdpi.com/1424-8220/9/6/4728/htm

图 1.8. 三代无线传感器节点

尽管早期的无线传感器网络(WSN)部署并未连接到互联网,但这些系统以多种方式影响了物联网(IoT),因为这一富有成果的研究社区孕育了塑造物联网技术的创新思想。为 WSN 开发的技巧、工具和协议使得在现实世界中能够使用低功耗平台进行大规模分布式传感应用成为可能。

这些设备的需要和操作环境也催生了许多针对低功耗传感优化的操作系统,例如 TinyOS^([12])或 Contiki.^([13])。实际上,当电池供电的设备部署在自然和不可预测的环境中时,确保其鲁棒性和最小能耗是至关重要的,因为人工干预来调试或修复软件和硬件问题——甚至更换电池——显然是不切实际的。

¹²

www.tinyos.net

¹³

www.contiki-os.org

无线传感器网络(WSN)和物联网(IoT)

很明显,大多数 WSN 设备并非为公众设计。这些平台主要是由专家编程的。尽管 Web 协议比嵌入式设备上使用的优化协议更复杂(更冗长),但针对受限设备的优化 HTTP 库已经取得了很大进展.^([14]) 此外,设备变得越来越强大,许多设备都内置了 Wi-Fi 连接。使用标准 Web 协议与嵌入式传感器交互的能力使得从异构传感器收集、存储和分析数据变得更加简单。实际上,由于 REST API 的简单性和普遍性,跨多个云服务集成数据要快得多。

¹⁴

research.microsoft.com/pubs/73067/tws.pdf

1.3.2. 可穿戴设备和量化自我

物联网(IoT)的另一个有趣用例是制造人们可以携带或佩戴的小型传感器,以被动地监控他们的日常活动,甚至监测身体因素,如心跳或血液或汗液中的化学物质。心跳监测器长期以来一直被商业化,用于长跑运动员跟踪和调节他们的心脏活动。这一领域的重大突破是 Nike+,它易于使用,并且可以开箱即用地连接到 iPhone(见图 1.9)。

图 1.9.耐克+生态系统是量化自我或可穿戴趋势的先驱之一。[照片由 Flickr 上的 ivyfield 提供,授权使用 CC BY 2.0]

这种趋势在过去的几年中经历了繁荣,产品种类繁多,从活动追踪器,^([15]) 到连接到您的手机的智能秤,帮助您控制体重和体脂,^([16)) 到智能踏板跟踪您的骑行并作为防盗设备,^([17)) 到智能枕头、智能药盒、闹钟和智能手表,让您能够访问关于自己的全新信息世界。

¹⁵

例如:misfit.comjawbone.com/up

¹⁶

www.withings.com/us/en/products/smart-body-analyzer

¹⁷

connectedcycle.com

可穿戴设备和物联网

将可穿戴设备和量化自我设备集成到网络中,以便数据可以直接由其他设备和应用程序访问,将使开发新的可扩展应用程序类别(如养老服务、健康和健身、娱乐和体育)变得容易得多。它还将确保您不需要为每个它们都安装一个单独的应用程序(我们将在第九章中讨论的有趣的隐私和安全挑战)。

如耐克的成功故事所示,早期的可穿戴设备专注于体育的社会方面,通过分享和比较个人数据,如比赛时间、距离等。在这里,物联网再次发挥了作用,因为它允许可穿戴设备与网络上的社交网络无缝连接。

1.3.3.智能家居和建筑

在 20 世纪 60 年代和 70 年代,未来的房子被设想为一个完全自动化和响应的系统,就像你在《杰森一家》或《星际迷航》中看到的那样。门会自动打开;食物和咖啡将由机器人制作并随时准备好,一旦你从床上跳起来。你的环境会使你的生活变得更轻松,并在你需要的时候照顾好你需要的一切。

智能家居——智能和连接式家庭的术语——在 21 世纪初变得非常流行,这些系统包括娱乐系统、照明系统、供暖、通风、空调系统(HVAC)等等。但是,在物联网发明之前,智能家居就已经存在了。传统系统与智能家居第二波之间的最重要的区别是使用互联网或互联网友好型协议,通过将设备直接连接到互联网或通过家庭网关连接,将家庭自动化从专有系统世界推向了互联网。

令人惊讶的是,随着开源硬件和软件平台(如 Arduino)的发展,这种将我们的家庭数字化的趋势甚至进一步发展了.^([18]) 确实,许多业余爱好者开始在家中破解并连接各种部件。从能源和煤气表到照明和存在探测器,业余开发者突然对将他们的房屋连接到互联网产生了兴趣并获得了能力。

¹⁸

www.arduino.cc

智能家居和物联网

智能家居环境可能是连接事物到网络存在的(过于)众多标准和协议的一个典型例子。尽管你家里的所有设备都应该相互通信,但由于这些协议不兼容,你最终会拥有比以往更多的应用程序和遥控器。物联网提供了一种替代方法,其中网络语言是基础,设备应直接或通过网关间接提供的最小 API。在我们自己的公司——EVRYTHNG^([19])——我们使用了物联网方法,以规模连接了来自不同制造商的许多智能家居设备。

¹⁹

evrythng.com

物联网允许不同制造商的设备之间实现互操作性,并促进跨设备应用的发展。它还使得更大范围的业余爱好者能够购买各种设备,快速构建他们的智能家居系统,并且特别容易根据他们的独特需求和愿望对这些系统进行重用和定制。

1.3.4. 智能城市和能源网格

物联网最有希望的应用案例之一可能是智能城市的出现。随着每年越来越多的人从农村地区迁移到城市,很明显,为了确保其居民的安全和福祉,需要对大型城市的设计和运营方式做出改变。由于有大量传感器和计算机,能够实时监控城市环境是一个非常有前景的基础,可以使我们的城市更智能、更高效。

智能城市一直是 WSN 研究人员工作的核心:基于由微型计算机收集的数据,更多地了解我们的环境。但大部分这项工作都是基于异步和线性工作流程:

1. 部署无线传感器节点。

2. 收集数据。

3. 在实验室分析数据。

4. 编写报告。

5. 基于这些报告采取行动。

物联网通过大幅缩短所需步骤,为这个领域带来了新的维度。数据现在作为实时流在互联网上可用,可以直接采取行动。这些可以用于实时监控安全、交通或公用事业(水、废物处理等),并在检测到任何异常时迅速反应——理想情况下在严重问题发生之前。

在过去几年中,对智慧城市的兴趣显著增加,许多城市都押注于物联网的潜力,例如英国的米尔顿凯恩斯市(20)、西班牙的桑坦德市(21)、美国的纽约市(22)和韩国的松岛市(23)。

^(20)

www.mksmart.org

^(21)

www.smartsantander.eu

^(22)

nycopendata.socrata.com

^(23)

www.bbc.co.uk/news/technology-23757738

作为实现未来城市愿景的垫脚石,智能电网正在利用物联网优化我们的能源消费和分配方式。得益于现实世界服务,家庭和工业设备可以越来越实时地交流其能源消耗,并提高消费者的能源意识。此外,设备之间可以相互通信,通过优化 HVAC 等方式使整个建筑更加智能。更重要的是,通过使用现实世界服务的复合应用,工业机械和城市基础设施将能够协商能源消耗并限制消费高峰。

智慧城市和物联网

在智慧城市的背景下使用网络标准尤其有趣,因为它们使得与公众共享传感器数据变得更加容易,同时也让开发者能够轻松消费他们自己城市应用中的实时交通、污染或公共交通数据。

1.3.5. 智能制造和工业 4.0

制造业经历了三个主要的演变周期。首先是在 19 世纪工业时代,利用水和蒸汽动力实现生产过程的机械化。之后,由于电力的发展,商品的大规模生产成为可能。第三次革命是数字时代,电子计算机使得进一步自动化生产、分销和通信过程成为可能。

物联网正在推动制造业和工业系统第四次革命,通常被称为工业 4.0。德国在这一变革中处于领先地位,由博世、西门子和 SAP 等公司推动。物联网可以为传统行业带来两大主要好处。首先是无与伦比的数据访问量。将机器连接到互联网并为其提供实时数据是朝着更透明、更高效的工业系统迈出的一步。其次,物联网为机器带来了服务。工业机器不再局限于单一的操作集,现在可以提供服务,通过连接它们各自的服务,使这些机器能够与其他机器结合使用和重复使用。这种能力将使制造机器和工厂转变为可重新配置的生产线的灵活生态系统,可以重新排列自身以尽可能高效地执行任何给定任务。工业 4.0 是一场持续的革命,该领域的多数活动仍在世界各地的研究实验室中进行。但在过去两年中,大多数大型公司都积极参与了物联网项目和产品,这表明这不仅仅是一个趋势:这是一个彻底重塑任何业务的巨大机会。而且它将长期存在。

行业与物联网

使用网络标准将业务流程中的所有元素,如车间机械、企业软件、各部门的员工、产品、客户和供应商,相互连接,这将代表公司经营方式的重大变化。将工厂中的所有元素转变为易于组合的乐高积木,将使公司更容易、更快地适应不断变化的环境,更快地将产品推向市场,优化其业务和制造流程等。当那些流程中的所有参与者都能根据实时数据自动决定如何最好地履行其职责时,毫无疑问,我们将设计、制造和分销物理产品的方式将发生深刻的变化。

1.3.6 智能物流与供应链

如前所述,物联网的首次提及源于日常消费品(CPGs)或更便宜、使用寿命更短的快速消费品(FMCGs)的 Auto-ID 世界。物流和供应链领域成为第一个探索各种现实世界物体与互联网之间连接性的领域,这并不奇怪。实际上,物联网并不仅限于设备,它可以包括任何物理对象。即使物体本身没有任何通信能力,人们也可以使用手机或 RFID 阅读器来识别产品并与它互动。因此,食品产品等 CPGs 以及更昂贵、使用寿命更长的物品,如奢侈品手表或手袋,只需要一个可机器读取的标签就可以成为物联网的一部分。

EPCglobal 网络^([24])可能是第一个将物联网应用于物流的标准化系统。EPCglobal 网络是一套标准,描述了如何将 RFID 标签对象连接到标准读取器,这些读取器再通过互联网连接到 RFID 信息系统和数据库。

^((24))

www.gs1.org/epcglobal

被动 RFID 标签是小型计算机,它们从附近 RFID 读取器产生的电磁场中获取能量。RFID 标签当然不是识别快速消费品(FMCGs)的唯一方式,但它们相对于条形码、图像识别或二维码等其他技术具有关键优势:它们可以自动读取,无需人工干预或视线,并且已经在整个供应链的实时系统中广泛部署。

物流和供应链操作中 RFID 采用的最大障碍一直是 RFID 相对较高的成本,这使得为每个对象贴标签变得昂贵。但最近的发展正在这个领域产生根本性的变化:世界上的一些公司已经能够打印 RFID 标签、传感器和电池(见图 1.10)。在几年内,将嵌入式计算机和传感器打印在产品及其包装上将会变得既容易又便宜。

图 1.10. Thinfilm^([25])打印的 NFC 标签和温度传感器。现在可以打印 RFID 标签、传感器和电池,这降低了成本,并使包装变得智能。[来源:Thinfilm,经许可使用]

^((25))

thinfilm.no/products-nfc-solutions/

在产品上大规模采用自动识别和跟踪方法将对供应链产生巨大影响,使它们变得更加高效,并且能够为消费者提供更好的服务。

智能物流和物联网

想象一个能够实时了解您草莓温度的、通过互联网连接的供应链,一旦条件发生变化,它就可以发送警报,甚至可以根据存储和运输产品的类型自动调节卡车、船只和仓库的温度——所有这些信息都可以通过 Web API 访问。使用 Web 标准共享设备的历史数据将使多个应用程序在整个产品生命周期中协同工作变得更加容易。这意味着集成成本大大降低,并且在不同系统之间处理和操作这些产品的数据完整性很高。

1.3.7. 营销 2.0

将 CPG 和 FMCG 连接到互联网的能力为产品制造商和零售商带来了许多有趣的应用,但不仅限于供应链,在消费者方面,它还允许新的服务,通常被称为营销 2.0。使用手机通过条形码、二维码或图像识别来识别产品,使得将产品变成消费者和零售商之间直接沟通和服务交付渠道成为可能,从而可以启动个性化的营销活动。该领域的典型应用范围从产品个性化、送礼到客户忠诚度、数字用户指南和售后服务,以及个性化的保修和产品召回。

用于营销目的的物联网技术并不仅限于贴有标签的产品。智能设备也可以用来推动最疯狂和最具创意(我们让您来评判这一点)的营销活动。这些例子包括依云水滴,一个让您可以直接从冰箱订购水并送货上门的小设备^([26]), 在伦敦著名的萨沃伊酒店房间内按一下按钮就能订购香槟的 Dom Pérignon 按钮(图 1.11), 以及当您的最爱球队进球时亮起明亮的红灯的百威红光^([28])。

²⁶

theinspirationroom.com/daily/2012/evian-smart-drop/

²⁷

www.altomagazine.com/newsdetails/travel/hotels/dom-prignon-at-the-press-of-a-button-4310934/

²⁸

www.wired.com/2013/02/budweiser-red-light/

图 1.11. 萨沃伊酒店房间内 DP 按钮的按压管理着 Dom Pérignon 香槟的配送,几分钟内完成。[来源:LVMH,经许可使用]

营销与万物互联

移动应用程序可以快速、轻松地从网络上检索 CPG 和 FMCG 产品的数据,与之互动以附加数字内容,并在社交网络上分享有关它们的信息。如果世界上每个产品都有自己的 URL 和 Web API,那么任何应用程序都可以轻松识别产品并访问其数据,而无需太多的集成工作。在 EVRYTHNG,我们使用我们的万物互联平台将产品连接到网络并交付此类营销 2.0 应用程序。例如,巴西的帝亚吉欧在其威士忌瓶上打印了独特的二维码,以便客户可以为每个瓶子附加个性化的信息;在这种情况下,这是一段在客户智能手机上为父亲节制作的视频^([29])。

²⁹

adage.com/article/global-news/diageo-personalizes-whiskeys-videos-gift-givers/238015/

1.4. 物联网之网——超级增强的物联网

如前所述,大多数物联网系统对开放和大规模异构设备之间的通信问题关注不多。这部分的理由是因为物联网强烈关注网络堆栈的底层(数据如何在参与者之间传输),而对如何促进新应用的发展(如何收集、可视化或处理数据)关注较少。特别是,在实现即席互操作性方面投入的努力有限,因此,在异构设备之上构建可扩展的应用仍然很困难。

这并不是技术原因,更多的是商业原因。在过去十年中,标准化机构、工业联盟和供应商提出了大量针对物联网的协议。本质上这是一个好事。但残酷的现实是,这些标准中的任何一个都没有达到足够的吸引力成为物联网的“唯一”通用协议(参见图 1.12)。今天,如果你想拥有一个智能家居,最好的办法是购买来自同一制造商的所有组件。正因为如此,你控制该系统的唯一选择将是使用随附的应用程序。如果该应用程序主要针对 iPhone 设计且在 Android 上不可用,那真是太糟糕了。如果该应用程序设计得糟糕、运行缓慢或没有你需要的半数功能,你就只能忍受它。

图 1.12. 物联网的问题。物联网建立在现有和广泛使用的网络标准之上,以便它可以利用整个网络生态系统。 [来源:xkcd.com/927/,根据 Creative Commons 2.5 许可使用]

简而言之,目前市场上的大多数物联网解决方案与互联网——一个独特、开放、全球的网络,其中一切事物都相互连接——几乎没有共同之处。今天的物联网更应该被称为“物联网内网”,因为它是一组功能孤岛,这些孤岛没有设计成可以相互通信。尽管越来越多的网络设备提供了控制它们和访问它们数据的 API,但仍然需要为每个 API 开发特定的自定义应用程序。这种情况不仅是因为不同的设备具有不同的功能,而且是因为每个 API 都使用不同的应用程序协议,并具有不同的数据模型,而没有共享和标准化的语言。

互联网及其标准(URL、HTTP、HTML、JavaScript 等)的简单性和开放性可能是我们今天所知道的互联网得以实现的原因。这种通用语言使得世界上任何用户都能阅读任何其他网页,而无需安装任何东西,并且一直是互联网成功的主要因素之一。通过使网页、浏览器、服务器和服务都能使用相同的应用语言,大量内容的集成被极大地简化了。不幸的是,对于物联网中的设备和应用程序,尚未找到类似的推动者。

在本节中,我们描述了现有物联网方法中存在的局限性以及问题,这些方法没有优先考虑为设备提供一个开放、通用和简单的应用层协议。对于这些局限性中的每一个,我们都展示了使用物联网方法带来的好处。

1.4.1. 更容易编程

首先,让我们看看事物编程的方式。

物联网

当前现有解决方案和产品面临的首要问题是许多这些协议复杂且难以使用。这种高门槛的采用,就像 70 年代的互联网一样,使得物联网对大多数人来说遥不可及。学习连接使用各种接口和协议的多种设备是一项特别艰巨的任务,这将阻止最坚韧的业余爱好者开始编程他们的智能家居。如果您对此有任何疑问,我们邀请您查阅 ZigBee^([30])协议或 Web 服务设备配置文件(DPWS).^([31])

³⁰

zigbee.org/zigbee-for-developers/zigbee3-0/

³¹

docs.oasis-open.org/ws-dd/dpws/wsdd-dpws-1.1-spec.html

物联网

网络协议可以轻松地用于从/到设备读取和写入数据,并且与复杂的物联网协议相比,使用起来更加简单,学习起来也更快。此外,如果所有设备都能提供 Web API,开发者就可以使用相同的编程模型与任何设备进行交互。一旦你掌握了构建简单 Web 应用程序所需的基本技能,你就可以以最小的努力快速与新的设备进行通信。

1.4.2. 开放和可扩展的标准

接下来,我们来看看这两个世界的标准开放程度。

物联网

另一个问题是一些协议随着新技术的发展而不断演变,因为新技术使得新的用例成为可能。由于一些这些标准由一个或少数几家大型公司资助和监管,它们不如由社区领导的开源项目那样中立。此外,这些公司可以决定按照他们的意愿引入破坏性变化,从而使现有的设备和应用程序无法相互通信。

此外,其中一些标准并未公开文档,并且不能简单地使用和实施,除非支付一笔相当大的年度费用。这自动限制了它们的采用,只限于大型工业组织。封闭和专有协议也导致供应商锁定。确保切换到不同供应商需要时间和成本密集型,这是大型软件玩家众所周知的企业策略——这里没有新内容。但在物联网环境中,障碍要高得多,因为切换协议有时也意味着改变硬件(例如使用不同的射频芯片)。同样,切换应用程序协议需要固件更新,这在现实世界中很难应用。

物联网

网络标准之所以达到如此高的普及率,是因为它们完全开放且免费,因此几乎不存在它们会一夜之间改变的零风险。它们确保数据可以快速轻松地在系统之间移动,因此当有人想要提供对某些数据的公开访问时,HTTP 和 REST 是显而易见的选择。

1.4.3. 快速部署、维护和集成

让我们看看每种方法对部署、维护和集成的具体影响。

物联网

因为整个系统需要使用单个协议,因此需要大量努力为每个需要集成的新的设备或应用程序编写自定义转换器。维护这样一套精细的自定义代码集是一个风险任务,在商业应用中意味着重大的投资。

物联网

网络突然停止工作并需要升级的风险并不存在。然而,过去十年中,网络上可以做的事情的界限并没有停止重新定义,例如从相机捕获图像或分享位置的能力。相比之下,物联网世界中总是有新的设备和协议,每次许多协议中的任何一个发生变化时,所有使用该设备的其他拼图部件都需要更新。

1.4.4. 元素之间的松散耦合

接下来,我们看看每种方法的依赖关系,重点关注可重用性。

物联网

前几节的意义最重要的在于网络中设备和应用之间的紧密耦合。只要所有部件都按预期行为并按预期使用,系统就能良好运行。遗憾的是,这几乎不留空间进行即兴、未计划的交互以及将服务重新用于新的用例,而这些在大规模开放设备网络中是基本需求。

物联网

HTTP 由于设计上松散耦合,因为网络中行为者之间的合同(API 规范)既简单又定义良好,这留下了很少的模糊空间。这允许任何行为者独立于彼此进行更改和演变(只要合同不改变)。这就是为什么你仍然可以访问自 20 世纪 90 年代初以来未更新的网页(我们将跳过对其视觉设计的任何评论)。物联网设备在添加新设备时能够与之通信,而无需进行任何固件更新,这对于全球物联网网至关重要。

1.4.5. 广泛使用的安全和隐私机制

物联网/物联网网系统中的个人数据、隐私和安全问题始终是构建和部署现实世界应用时的一个主要关注点。需要考虑的两个角度是这些:

  • 安全—— 如何确保系统不会被未经授权的用户或系统轻易访问或以有害方式使用。换句话说,这是关于确保没有人可以访问他们不应该访问的数据或设备。

  • 用户隐私—— 假设安全措施已经到位,并且只有授权和经过身份验证的各方或应用程序可以访问某些数据,我们如何确保不会有人访问或从其中推导出有关用户的任何私人信息(例如,个人信息或行为数据——用户在哪里,用户在做什么等)?这尤其困难,因为即使关于用户的某些数据本身无害,当与来自另一个传感器或系统的另一份数据结合时,它可以被用来明确识别用户及其行为。

事实上,尽管已经有许多项目和努力来提高这些系统的安全性,但截至今天,物联网世界中的安全和隐私的圣杯仍然有待发现。真正的挑战是,物联网在这个规模上的能力相对较新,与这些技术相关的风险在很大程度上是未知的,并且在现实世界应用中难以识别或衡量。

物联网

如前所述,由于物联网中的应用程序通常是单独开发的,因此这些部署的安全机制往往是从头开始编写的,在现实世界中测试不足,或者根本不存在。即使今天,许多物联网设备仍在部署时没有使用足够的安全级别,危险地将它们的认证密钥暴露于世界之中。32 这主要是因为物联网特定的安全系统往往被设计成在封闭生态系统中运行良好,其中每个元素都受到控制。

[32]

www.ioactive.com/news-events/IOActive_advisory_belkinwemo_2014.html

物联网网

网络也能在这方面提供帮助,我们将在第九章(kindle_split_017.html#ch09)中探讨这一点。回顾网络的历程,我们可以看到我们在构建可用且可靠的安全机制和协议方面取得了巨大的进步。这些方法并非坚不可摧——没有任何安全系统是完美的——但它们是在可靠性、易用性、性能和可用性之间的一种实际折衷。

事实上,即使在今天,听到一家大型知名在线公司遭到黑客攻击,数百万用户的资料被公开泄露的情况也并不少见。更糟糕的是,那些被视为安全和可信的协议可能仍然存在微小的未知问题,一旦被发现就会变得脆弱(比如 SSL Heartbleed^([33])?)。除非有少数例外,只要那些系统被正确实施,它们被黑客攻击的可能性仍然很小,尤其是考虑到这些系统每天被数十亿用户使用。与为物联网开发的定制和新型标准相比,使用基于网络的通用标准的优势在于它们已经被广泛使用和测试。许多此类系统的实现都是开源的(例如,OpenSSL),这意味着代码被数千名开发者不断使用、测试、更新和修复。使用这些既定方法降低了失败的风险,相比之下,为物联网从头开发并仅在野外进行有限测试的尖端(有意为之)技术风险更大。

(33)

Heartbleed 漏洞是在流行的 OpenSSL 加密软件库中发现的一个严重漏洞(更多信息请参阅heartbleed.com/

1.4.6. WoT 的不足之处

我们意识到,在这个阶段,你可能正在想,“这些家伙对他们的 WoT 有点过于热情了!”也许吧。但使用传统的物联网工具超过十年后,很难描述我们直接在浏览器中使用几行 JavaScript 创建相同类型的应用程序时的愉悦感,而且所需付出的努力、时间和痛苦都要少得多。

尽管如此,物联网并不是“生命、宇宙和万物的终极问题的答案”(也就是 42)。就像每一种颠覆性技术或方法一样,它也伴随着自己的挑战。安全和一般数据隐私就是其中的一些。将我们物理世界中的所有事物连接到互联网并在网络上使其可访问,这也意味着我们可能将它们暴露于侵入性的政府、病毒或可能利用这一点进行拒绝服务攻击或挖掘关于现实世界信息的不名誉公司。我们应该假设他们会这么做。对于物联网来说,考虑安全性已经是必须的;物联网网增加了更多关注点,尤其是在数据隐私方面。我们将在第九章中讨论这些高级主题,但简短的答案是,一个高度连接的系统总是比一个孤立的系统更脆弱。但是,使用开放标准的连接系统通常比基于定制安全机制的系统更好。此外,这并不是我们第一次面临这样的困境:我们的计算机可以是孤立的,但这会减少它们的应用范围。物联网和物联网网也不例外:作为公民,我们有选择权,应该在风险和收益之间权衡每一种新技术。物联网网应该关于使我们的生活更轻松、更愉快,而不是更艰难。

³⁴

goo.gl/l4rG1b

重要的是要认识到,将网络标准强加给每一个设备并不总是正确的事情,在某些情况下,这并不实际或可行。在一定的约束条件下(例如,当电池供电的设备需要长时间运行时),你可能更愿意使用优化的物联网协议。在第五章中,我们将帮助你理解这些权衡,并为你提供一个框架来决定各种情况下的最佳选项。好消息是,物联网网完全是关于集成模式:我们将向你展示如何通过代理、网关或云将非网络或甚至非 IP 设备集成到网络中。这种混合解决方案有时会使部署更复杂,但更实用,因为它允许在需要的地方进行优化。我们将在第七章中讨论这些集成模式。

1.5. 摘要

  • 物联网的存在时间比你想象的要长,当然在它被称为这个名字之前就已经存在了。

  • 早期的物联网系统被设计为在孤立状态下运行;因此,今天的物联网是一个碎片化的世界——事物内网。

  • 物联网网不同之处在于,它不关心底层网络协议或标准,只关心如何将各种孤立系统和设备编织成一个单一的、基于网络的生态系统。

  • 使用简单且普遍的网络标准,如 HTTP、WebSocket 和 JSON,将各种设备和应用程序集成在一起,这使得快速原型设计和扩展企业级解决方案变得更加容易。

  • 物联网仍处于起步阶段,对于那些想要掌握物理世界连接的复杂性的世界的人来说,前方有许多机会。

  • 本书将教你如何使用物联网构建新一代的物联网解决方案,这些解决方案通过利用自 20 世纪 90 年代初互联网发明以来积累的基础设施、工具和经验,更加灵活、可扩展和互操作。

在下一章中,我们将给你一个物联网的感觉初体验。通过进行一些动手练习,你将亲身体验构建读取传感器数据、向设备发送命令以及从各种来源合并信息以创建混合应用程序的 Web 物联网应用程序是多么容易。我们通过隐藏设备背后的复杂性,使用网络标准来简化下一章的内容,这样你就可以快速构建和定制你的第一个 Web 物联网应用程序,而无需直接在设备上实现它(目前还不必)。在随后的章节中,你将了解如何将这些原则应用于创建任何设备和场景的复杂应用程序和连接器。

第二章. 嗨,全球物联网

本章涵盖

  • 悄悄一瞥物联网架构的不同层级

  • 使用 HTTP、URL、WebSocket 和浏览器访问设备

  • 使用 REST API 消费 JSON 数据

  • 了解网络语义的概念

  • 创建你的第一个物理混合应用

在我们一头扎进物联网架构并展示如何从头开始实现它之前,我们想先让你了解一下物联网是什么样子。本章结构为一套练习,你将构建使用真实设备生成数据的微型网络应用程序。每个练习都将平滑地引入你在构建连接到网络的设备和与之交互的应用程序时可能会遇到的各种问题和技术难题。

在本章中,你将有机会亲自动手编写一些简单(以及不那么简单)的物联网应用程序。哦,你还没有设备?没问题;只需使用我们的设备!为了让你能够在不购买真实设备的情况下进行这些练习,我们将我们的设备连接到网络上,这样你就可以通过 Web 从你的电脑访问它。当然,如果你已经有了设备,你也可以下载本章中使用的源代码并在自己的设备上运行它。如何在设备上运行代码将在第七章中详细说明。

2.1. 认识物联网设备

本章组织了一系列简短而精炼的练习。每个练习都允许你与我们在办公室实际运行的物联网设备进行交互,这些设备全天候 24/7 在线。这样你就可以在没有实际设备的情况下完成练习。

我们办公室的设备是图 2.1(figure 2.1)中所示的树莓派 2(或简称为 Pi,对朋友和家人来说),我们将在第四章(chapter 4)中详细描述。如果你从未见过,你可以想象一个信用卡大小的计算机板,上面连接了一些传感器,并通过以太网线连接到我们的本地网络和互联网。在我们的设置中,Pi 充当连接到其上的各种传感器或设备的网关,因此你可以通过 Web 与这些资源进行交互。网关在第七章(chapter 7)中有详细描述,但你现在只需记住,Pi 运行一个 Web 服务器,允许你通过 Web 访问这些资源,如图 2.2(figure 2.2)所示。

图 2.1. 你正在访问的树莓派和摄像头,它们在我们伦敦办公室的设置情况

图 2.2. 本章示例中使用的设备和传感器的设置

在撰写本文时,我们已经将液晶显示器(LCD)、摄像头、温度传感器和 PIR 传感器连接到我们的树莓派上。我们将随着时间的推移继续添加各种传感器和执行器,因此欢迎你进行实验,并超越我们在此提供的示例。你很快就会意识到,本书中描述的各种技术和模式将允许你快速扩展和定制我们提供的示例,以适应任何你能想到的设备、传感器或物体。

2.1.1. 嫌疑人:树莓派

我们将在第四章(chapter 4)中更详细地介绍树莓派,因此你现在需要了解的是,Pi 是一个小型计算机,你可以连接多个传感器和附件。它提供了你从台式计算机上期望的所有功能,但功耗更低,体积更小。此外,你可以使用输入/输出(I/O)引脚将其连接到各种数字传感器或执行器。“执行器”是一个总称,指任何连接到设备并对现实世界产生影响的元素,例如,打开/关闭一些 LED 灯,在液晶面板上显示文本,旋转电动机,解锁门,播放音乐等等。在物联网中,正如你使用 HTTP 向 Web API 发送写请求一样,你也会这样做来激活执行器。现在回到我们的练习。你需要做的第一件事是从我们这里的存储库下载这些页面中使用的示例:book.webofthings.io

您可以在自己的电脑上查看仓库,在其中您会发现几个文件夹——每个章节一个。本章的练习位于文件夹 chapter2-hello-wot/client 中。如果您想知道服务器的代码,不用担心!您将在本书的其余部分学习如何构建它。

如何获取本章的代码示例

我们使用 GitHub^([a]) 服务在电脑和我们的 Pi 之间同步代码。作为替代,Bitbucket^([b]) 服务也可以使用,并且配置方式类似。这两个服务都基于 Git 源代码控制系统,所有章节的源代码都可在 GitHub 上找到(这里是链接:book.webofthings.io)。本章的示例位于 chapter2-hello-wot 文件夹中。

^a

GitHub 是一个广泛流行的基于网络的源代码管理系统。许多开源项目都托管在 GitHub 上,因为,嗯,它相当酷。这里有 GitHub 的一个优秀介绍:bit.ly/intro-git

^b

bitbucket.com

如果您对 Git 及其命令不熟悉,不用担心——网上有大量关于这方面的信息,但这里有一些与 Git 一起工作的最关键的命令:

  • git clone——在本地获取仓库的一个版本。对于本书的代码,您需要使用 recursive 选项,这将克隆所有子项目:git clone github.com/webofthings/wot-book --recursive

  • git commit –a –m "your message"——在本地提交代码更改。

  • git push origin master——将最后的提交推送到远程仓库(origin)的 master 分支。

2.2. 练习 1——浏览物联网网络中的设备

我们将用一项简单的练习开始对物联网网络的探索,您几乎没有什么事情要做,只需在浏览器中点击几个链接即可。我们想要说明的第一个要点是,在物联网网络中,设备可以同时提供视觉用户界面(网页)以允许人类控制和与之交互,以及应用程序编程接口(API)以允许机器或应用程序执行相同的操作。

2.2.1. 第一部分——网络作为用户界面

在这个第一个练习中,您将使用浏览器与我们在办公室连接的一些真实物联网设备进行交互。首先,通过网络摄像头看看我们办公室的设置;见 图 2.3。在您最喜欢的浏览器中打开以下链接以访问网络摄像头拍摄的最新图像:devices.webofthings.io/camera/sensors/picture。此链接将始终返回我们的摄像头拍摄的最新截图,因此您可以查看您将要与之交互的设备(晚上试试——晚上更有趣!)。不过,您不会看到摄像头本身。

图 2.3。我们设置中使用的摄像头的网页。图像是摄像头实时捕获的截图。

02fig03_alt.jpg

你可能已经注意到你输入的 URL 有一个特定的路径结构。让我们对这个结构玩一点,回到这个 URL 的根目录,在那里你会看到允许你浏览我们办公室设备的网关主页(图 2.4)。在你的浏览器中输入以下 URL:devices.webofthings.io

图 2.4。我们 WoT 设备网关的 HTML 主页。页面底部的两个超链接允许你访问连接到网关的设备的页面。

02fig04_alt.jpg

这个 URL 将始终重定向到你办公室运行的网关的根页面,它显示了附加到网关的设备列表。在这里,你可以看到两个设备连接到了网关:

  • 一个带有各种传感器和执行器的 Raspberry Pi

  • 一个网络摄像头(你之前访问的那个)

注意,这个页面是根据我们附加到它的物理设备自动生成的,所以当你我们附加更多设备或传感器时,你可能会看到更多设备。是的,尽管它看起来像任何其他网页,但实际上它是在实时真实办公室的真实设备上提供的真实数据!

现在,点击“我的 WoT Raspberry Pi”链接以访问设备的根页面。因为你通过浏览器跟随了一个链接,所以你会看到 URL 已经更改到了devices.webofthings.io/pi,如图 2.5 所示。

图 2.5。Raspberry Pi 的主页。在这里,你可以使用底部的链接浏览和探索该设备提供的各种资源;例如,它的传感器和执行器。

02fig05_alt.jpg

这是另一个根页面——这次是设备的根页面。在这种情况下,我们只是将/pi附加到了网关的根 URL。

回到我们的设备根页面,将鼠标悬停在各个链接上方以查看它们的结构,然后点击“传感器列表”链接。你会看到 URL 再次更改到这个(图 2.6):devices.webofthings.io/pi/sensors

图 2.6。Pi 上的传感器列表。你可以点击每个传感器并查看每个传感器的最新已知值。

02fig06_alt.jpg

到目前为止,这相当直接:你的浏览器正在请求一个 HTML 页面,显示连接到devices.webofthings.io网关的/pi设备的/sensors列表。记住,这里也连接了一个摄像头,所以在你浏览器的地址栏中,将 URL 中的/pi/替换为/camera/,你将直接进入相机的传感器页面:devices.webofthings.io/camera/sensors;见图 2.7。

图 2.7。相机上的传感器。这里只有一个传感器,即当前图像。

图片

现在,回到您 Pi 上的传感器列表,查看连接到设备的各种传感器。目前,您可以访问三个传感器:温度、湿度和被动红外。打开温度传感器链接,您将看到温度传感器的页面,其中包含传感器的当前值。最后,就像您对传感器所做的那样,转到 Pi 的执行器列表,打开执行器详细信息页面(见图 2.13),以下 URL:devices.webofthings.io/pi/actuators/display

显示屏是连接到 Pi 的一个简单 LCD 屏幕,可以显示一些文本,您将在练习 2.4 中使用它。您可以看到关于这个执行器的信息——特别是当前显示的值、发送数据到它的 API 描述以及显示新数据的表单。现在您不会使用这个表单,但这个功能将在第 2.4 节中介绍。

2.2.2. 第二部分——Web 作为 API

在第一部分中,您开始从浏览器中与物联网进行交互。您已经看到了人类用户如何探索设备的资源(传感器、执行器等)以及如何从网页中与该设备进行交互。所有这些操作都是通过浏览物理设备的资源来完成的,就像您浏览网站的各种页面一样。但如果不是人类用户,而是希望一个软件应用或另一个设备执行相同操作,而不需要人类介入,怎么办?您如何让任何 Web 客户端都能轻松找到设备,了解其功能,查看其 API 的样子,确定它可以发送哪些命令等等?

在本书的后面部分,我们将详细向您展示如何做到这一点。现在,我们将通过向您展示当另一个设备或应用浏览您的设备时看到的内容,来展示 Web 如何使支持人类和应用变得容易。

对于这个练习,您需要安装 Chrome 并安装我们最喜欢的一个浏览器扩展程序,名为 Postman.^([1]) 或者如果您更愿意使用命令行,可以使用 cURL^([2)). Postman 是一个小巧的应用程序,当您与 Web API 一起工作时,它将极大地帮助您,因为它允许您轻松发送 HTTP 请求并自定义这些请求的各种选项,如头部、负载等。Postman 将使您在本书中更容易地完成任务,所以请继续安装它。

¹

在这里获取:www.getpostman.com/

²

cURL 是一个命令行工具,允许您使用各种协议传输数据,其中包括 HTTP。如果您的机器上没有预先安装,您可以在 Mac、Linux 或 Windows 上轻松安装它。网站:curl.haxx.se/

在 第一部分 中,你的浏览器只是一个请求服务器内容的网络客户端。浏览器会自动请求以 HTML 格式的内容,然后由服务器返回并由浏览器显示。

在 第二部分 中,你将几乎与 第一部分 中相同的练习,但这次是通过请求服务器返回 JSON 文档而不是 HTML 页面。JSON 几乎是互联网上使用最成功的数据交换格式。它具有易于理解的语法,且轻量级,与它的老式父格式 XML 相比,这使得它在传输时更加高效。此外,JSON 便于人类阅读和编写,也便于机器解析和生成,这使得它特别适合成为物联网的数据交换格式。请求特定编码的过程在 HTTP 1.1 规范中称为 内容协商,将在 第六章 中详细介绍。

第 1 步——从网关获取设备列表

就像你之前做的那样,你将向网关的根页面发送 GET 请求以获取设备列表。为此,你需要在 Postman 中输入网关的 URL 并点击“发送”,如图 2.8 所示。

图 2.8. 使用 Postman 网络客户端获取网关的根页面。请求是针对网关 URL 的 HTTP GET (1)。响应体将包含一个 HTML 文档 (4)。

图片 2.8

由于大多数网络服务器默认返回 HTML,你会在主体区域看到服务器返回的 HTML 页面内容 (4)。这基本上就是每次你从浏览器访问网站时幕后发生的事情。现在要获取 JSON 而不是 HTML,请点击“头部”按钮,添加一个名为Accept的头部,其值为 application/json,然后再次点击“发送”,如图 2.9 所示。向请求添加此头部是在告诉 HTTP 服务器,“嘿,如果你能的话,请返回给我编码为 JSON 的结果。”因为网关支持这一点,你现在将看到与之前检索到的网页相同的 JSON 内容,但这次只有内容,没有视觉元素(即 HTML 代码)。

图 2.9. 使用 Postman 获取连接到网关的设备列表。现在Accept头部设置为 application/json 以请求返回 JSON 格式的结果。

图片 2.9

返回的 JSON 主体包含连接到网关的设备的机器可读描述,其外观如下:

{
  "pi": {
    "id": "1",
    "name": "My WoT Raspberry Pi",
    "description": "A simple WoT-connected Raspberry Pi for the WoT book.",
    "url": "http://devices.webofthings.io/pi/",
    "currentStatus": "Live",
    "version": "v0.1",
    "tags": [
      "raspberry",
      "pi",
      "WoT"
    ],
    "resources": {
      "sensors": {
        "url": "sensors/",
        "name": "The list of sensors"
      },
      "actuators": {
        "url": "actuators/",
        "name": "The list of actuators"

      }
    },
    "links": {
      "meta": {
        "rel": "http://book.webofthings.io",
        "title": "Metadata"
      },
      "doc": {
        "rel": "https://www.raspberrypi.org/products/raspberry-pi-2-model-b/",
        "title": "Documentation"
      },
      "ui": {
        "rel": ".",
        "title": "User Interface"
      }
    }
  },
  "camera": {
    [ ... description of the camera object... ]
  }
}

在这个 JSON 文档中,你可以看到两个一级元素 (picamera),它们代表连接到网关的两个设备,以及一些关于它们的细节,例如它们的 URL、名称、ID 和描述。现在如果你不理解这里的一切,请不要担心;所有这些内容将在接下来的几章中变得清晰易懂。

第 2 步——获取单个设备

现在更改 Postman 中的请求 URL,使其指向 Pi 设备(这与你在第一部分中输入浏览器中的 URL 完全相同),然后再次点击发送,如图 2.10 所示。

图 2.10. 获取 Raspberry Pi 的 JSON 表示形式。JSON 有效负载包含有关设备的元数据以及其子资源的链接。

02fig10_alt.jpg

现在设备中包含的 Pi JSON 对象与之前显示的信息相同,但你可以看到 resources 对象包含 sensorsactuators 等内容:

"resources": {
  "sensors": {
    "url": "sensors/",
    "name": "The list of sensors"
  },
  "actuators": {
    "url": "actuators/",
    "name": "The list of actuators"
  }
}
第 3 步——获取设备上的传感器列表

要获取设备上可用的传感器列表,就像你之前做的那样,只需在 Postman 中将 /sensors 添加到 Pi 的 URL,然后再次发送请求。HTTP GET 请求将返回此 JSON 文档作为响应:

{
  "temperature": {
    "name": "Temperature Sensor",
    "description": "A temperature sensor.",
    "type": "float",
    "unit": "celsius",
    "value": 23.4,
    "timestamp": "2015-10-04T14:39:17.240Z",
    "frequency": 5000
  },
  "humidity": {
    "name": "Humidity Sensor",
    "description": "A temperature sensor.",
    "type": "float",
    "unit": "percent",
    "value": 38.9,
    "timestamp": "2015-10-04T14:39:17.240Z",
    "frequency": 5000
  },
  "pir": {
    "name": "Passive Infrared",
    "description": "A passive infrared sensor. True when someone present.",
    "type": "boolean",
    "value": true,
    "timestamp": "2015-10-04T14:39:17.240Z",
    "gpio": 20
  }
}

你可以看到 Pi 上连接了三个传感器(分别是 temperaturehumiditypir),以及每个传感器的详细信息及其最新值。

第 4 步——获取单个传感器的详细信息

最后,你会得到一个特定传感器的详细信息,因此将 /temperature 添加到 Postman 中的 URL 并再次点击发送。现在 URL 应该是 devices.webofthings.io/pi/sensors/temperature,如图 2.11 所示。

图 2.11. 从 Raspberry Pi 获取温度传感器对象。你可以看到最新的读数(23.4 摄氏度)以及它发生的时间(2015 年 10 月 4 日 14:43)。

02fig11_alt.jpg

你将获得有关温度传感器的详细信息,特别是读取的最新值(值字段)。如果你只想检索传感器值,你可以将 /value 添加到传感器的 URL 来检索它,这也适用于其他传感器:

{
  "value":22.4
}

2.2.3. 那么,这又意味着什么呢?

现在是时候让你在这个练习中看到的不同 URL 进行一番探索了。看看它们如何不同以及它们的结构,浏览设备,并尝试理解每个传感器有什么数据,其格式等。作为一个扩展,看看你周围的电子设备——厨房里的电器或你客厅里的电视或音响系统,咖啡馆的订餐系统,或者你所在地区的火车通知系统。现在想象一下,所有这些设备提供的服务和数据可能都具有相似的结构:URL、内容、路径等。尝试使用你刚刚看到的相同 JSON 结构来映射这个系统,并写出返回的 URL 和 JSON 对象。

您所看到的是,人类和应用程序使用完全相同的 URL 获取数据,但使用不同的编码格式(人类使用 HTML,应用程序使用 JSON)。显然,两种情况下的数据都是相同的,这使得应用程序开发者可以轻松地在两种格式之间来回转换。这是简单但强大的 Web 技术的一个例子。多亏了像 HTTP 和 URL 这样极其流行的 Web 标准,从任何 Web 浏览器与真实世界交互变得非常简单。您将在第六章及以后学到更多关于这些概念的内容。

2.3. 练习 2—从 WoT 传感器轮询数据

在第一个练习中,您学习了 WoT 设备的结构以及它是如何工作的。特别是,您看到设备的每个元素都是一个资源,具有唯一的 URL,人们和应用程序都可以使用它来读取和写入数据。现在,您将戴上开发者的帽子,开始编写您的第一个与这个物联网设备交互的 Web 应用程序。

2.3.1. 第一部分—轮询当前传感器值

对于这个练习,请转到您从 GitHub 检出的文件夹,进入 chapter2-hello-wot/client 文件夹。双击 ex-2.1-polling-temp.html 文件,在现代浏览器中打开它.^([3]) 此页面显示我们办公室 Pi 上的温度传感器值,并每隔五秒通过检索 JSON 格式来更新此值,正如您在 图 2.11 中所看到的那样。

³

我们在 Firefox (>41) 和 Chrome (>46) 上完全测试了我们的示例,并建议您安装这些浏览器的最新版本。Safari (>9) 也应该可以工作。如果您真的想使用 Internet Explorer,请注意您将需要版本 10 及以上;较旧版本将无法工作。

此文件使用 jQuery^([4]) 从我们 Pi 上的温度传感器轮询数据。现在请打开此文件到您最喜欢的代码编辑器中,查看源代码。您将看到以下两点:

jQuery 是一个方便的 JavaScript 库,它使得做很多事情变得更容易,例如与 REST API 通信、操作 HTML 元素、处理事件等等。更多信息请访问:jquery.com/

  • 一个 <h2> 标签显示当前传感器值将被写入的位置。

  • 一个名为 doPoll() 的 JavaScript 函数,它从 Pi 读取值,显示它,并在五秒后再次调用自己。此函数在下面的列表中显示。

列表 2.1. 轮询温度传感器

在开发(尤其是调试!)Web 应用程序时,显示页面外的 JavaScript 内容可能很有用;为此,您有一个 JavaScript 控制台。要在 Chrome 中访问它,请右键单击页面上的某个位置,然后选择“检查元素”;然后查找出现在当前页面 HTML 代码下方出现的控制台。console.log(data) 语句在此控制台中显示从服务器接收到的 data JSON 对象。

2.3.2. 第二部分—轮询和图表化传感器值

这很好,但在某些情况下,你可能希望显示比传感器当前值更多的信息——例如,过去一小时或一周的所有读数的图表。打开练习中的第二个 HTML 文件(ex-2.2-polling-temp-chart.html)。这是一个稍微复杂一些的例子,它跟踪温度传感器的最后 10 个值并在图表中显示它们。当你用浏览器打开这个第二个文件时,你会看到图表每两秒更新一次,如图 2.12 所示。

图 2.12. 这个图表每隔几秒从设备获取一个新值,并自动更新。

图片

我们使用 Google Charts 构建了这个图表,^([5]) 这是一个用于显示各种图表和图形的不错且轻量级的 JavaScript 库。请参阅我们下一列表中的注释代码示例。

developers.google.com/chart/

列表 2.2. 轮询并显示传感器读数

图片

图片

2.3.3. 第三部分—实时数据更新

在之前的练习中,轮询 Pi 的温度传感器工作得很好。但这似乎有些低效,不是吗?我们不需要每隔两秒或更长时间从设备获取温度,如果我们的脚本在温度变化时被通知,并且只有当值发生变化时,这不是更好吗?

正如我们在第六章中将要更深入地探讨的那样,这一直是网络模型和无线传感器应用的事件驱动模型之间主要的阻抗不匹配之一。现在,我们将探讨使用相对较新的网络附加组件来解决这个问题的方法:WebSocket。简而言之,WebSocket 是一种简单而强大的机制,允许网络服务器将通知推送到作为 HTML5 标准努力的一部分引入的网络客户端。

WebSocket 标准由两个不同的部分组成:一个用于服务器,一个用于客户端。由于服务器已经为我们实现,我们在这里将只使用客户端部分的规范。客户端 WebSocket API 基于 JavaScript,相对简单直接。以下列表中的两行代码就是连接到 WebSocket 服务器并在控制台显示所有接收到的消息所需的所有内容。

列表 2.3. 连接到 WebSocket 并监听消息
var socket = new WebSocket('ws://ws.webofthings.io');
socket.onmessage = function (event) {console.log(event);};

让我们回到我们的例子。转到文件夹。双击 ex-2.3-websockets-temp-graph.html 文件,在您喜欢的浏览器中打开它。您在页面上看到的内容与之前的练习完全相同,但在底层,事情相当不同。请查看下一列表中显示的新代码。

列表 2.4. 注册 WebSocket 并获取实时温度更新

图片

在这个练习中,你不需要定期轮询新数据,而是通过订阅 /sensors/temperature 端点来通过 WebSockets 注册你对这些更新的兴趣。当服务器有新的温度数据可用时,它将将其发送到你的客户端(你的网页浏览器)。这个事件将由你注册的匿名函数捕获,并将包含最新温度值的事件对象作为参数传递。

2.3.4. 那么,这又意味着什么呢?

让我们回顾一下你在这次练习中所做的事情:你成功与一个嵌入式设备(树莓派)进行了通信,这个设备可能位于世界另一端(如果你不是住在多雨美丽的英格兰的话)。从网页上,你能够定期获取连接到设备的传感器数据,并在图表上显示。对于一个只有 60 行 HTML、JavaScript 和 CSS 代码的简单网页来说,这已经很不错了。你没有就此止步:通过少于 10 行的 JavaScript,你还订阅了来自我们的 Pi 的通知,并在实时显示我们办公室的温度。作为这个练习的扩展,你可以编写一个简单的页面,自动从摄像头获取图像(理想情况下,你应该避免每秒做 25 次这种操作!)。

如果这是你第一次接触物联网,那么在这个阶段应该让你印象深刻的是这些示例的简单性。让我们想象一下,如果我们的 Pi 并不是通过 HTTP、JSON 或 WebSockets 提供数据,而是通过一个“复古”的基于 XML 的机器到机器应用程序堆栈,比如 DPWS(如果你从未听说过它,不要担心;这正是我们的观点!)。基本上,你将无法直接从你的浏览器与设备通信,除非你付出更多的努力。你将被迫使用更低级和更复杂的语言,如 C 或 Java 来编写你的应用程序。你将无法使用像 URL、HTML、CSS 和 JavaScript 这样广泛的概念和语言。这也是物联网的宗旨:通过将事物带入大众,使现实世界中的事物可编程且普遍可访问,从而推动许多今天的创新。

如前所述,在这本书中,你将学习更多关于为物理事物制作 API 的艺术。在第六章 [kindle_split_014.html#ch06] 中,我们将探讨 HTTP、REST 和 JSON 以及实时网络,而在第七章 [kindle_split_015.html#ch07] 中,我们将发现如何使用网关将其他协议和系统带入网络的美好之中。

2.4. 练习 3—对现实世界采取行动

到目前为止,你已经看到了从网络设备读取各种传感器数据的不同方法。那么,“写入”设备呢?例如,你可能想向设备发送一个命令来更改配置参数。在其他情况下,你可能想控制一个执行器(例如,打开车库门或关闭所有灯光)。

2.4.1. 第一部分——使用表单更新要显示的文本

为了说明你如何向执行器发送命令,这个练习将向你展示如何构建一个简单的页面,允许你向连接到我们办公室 Pi 的 LCD 发送一些文本。为了测试这个功能,首先打开 LCD 的执行器页面:devices.webofthings.io/pi/actuators/display

在本页(如图 2.13 所示)中,你现在可以看到 LED 执行器的各种属性。首先,你看到的是亮度,你可以更改它(但无法更改,因为我们将其设置为只读)。然后,你有内容,这是你想要发送的值,最后是持续时间,它指定文本将在我们的 LCD 上显示多长时间。使用 Postman 通过输入上一段中显示的 URL 来获取描述显示执行器的 JSON 对象,正如你在本章第一项练习中学到的那样:

{
  "name": "LCD Display screen",
  "description": "A simple display that can write commands.",
  "properties": {
    "brightness": {
      "name": "Brightness",
      "timestamp": "2015-02-01T21:06:02.913Z",
      "value": 80,
      "unit": "%",
      "type": "integer",
      "description": "Percentage of brightness of the display. Min is 0
       which is black, max is 100 which is white."
    },
    "content": {
      "name": "Content",
      "timestamp": "2015-02-01T21:06:32.933Z",
      "type": "string",
      "description": "The text to display on the LCD screen."
    },
    "duration": {
      "name": "Display Duration",
      "timestamp": "2015-02-01T21:06:02.913Z",
      "value": 5000,
      "unit": "milliseconds",
      "type": "integer",
      "read-only": true,
      "description": "The duration for how long text will be displayed
       on the LCD screen."
    }
  },
  "commands": [
    "write",
    "clear",
    "blink",
    "color",
    "brightness"
  ]
}
图 2.13. LCD 执行器的详细信息,以及你可以设置的各个属性,例如,设备上接下来要显示的文本

显然,如果你无法看到显示的内容,那么在我们的办公室显示东西就不会很有趣。因此,我们设置了一个可以查看我们 Pi 上 LCD 的摄像头,这样你就可以始终看到它上显示的内容。以下是 URL:devices.webofthings.io/camera/sensors/picture。继续打开这个页面,你将看到你在图 2.3 中看到的最新相机图像(要查看最新图像,请刷新页面)。

现在,你将向 Pi 发送一条新消息,以便通过 LCD 显示。内容属性始终是 LCD 上当前显示的消息,因此要更新它,你需要通过 POST 一个新值作为消息体来更新该属性(例如,{"value": "Hello World!"})。你可以继续在 Postman 中尝试这样做,但最简单的方法是通过浏览器中的显示执行器页面:devices.webofthings.io/pi/actuators/display。见图 2.13 以了解 LCD 执行器的详细信息。

在这个页面上,你可以看到 LCD 执行器的各种属性。其中一些是可编辑的,而另一些则不是。内容属性是你想要编辑的属性,因此输入你想要显示的文本并点击更新。如果一切正常,你将看到如下 JSON 有效负载:

{
  "id":11,
  "messageReceived":"Make WoT, not war!",
  "displayInSeconds":20
}

返回的有效负载包含将要显示的消息、你消息的唯一 ID 以及文本将在 LCD 屏幕上显示的估计延迟(以秒为单位),这样你就知道何时查看相机图像以查看你的文本。

2.4.2. 第二部分——创建自己的表单来控制设备

现在,让我们构建一个简单的 HTML 页面,允许你使用简单的表单向网络设备发送各种命令。从你的浏览器中,打开练习文件夹中的文件 ex-3.1-actuator-form.html,你将看到 图 2.14 中所示的屏幕。

图 2.14. 这个简单的客户端表单允许你向 Pi 发送要显示的新文本。

图片 2.14

此页面有一个输入文本字段和一个发送到 Pi 的按钮,如下所示。你输入的任何文本都将显示在我们办公室的 Pi 的液晶屏幕上。请保持礼貌,并且由于我们的 Pi API 对公众开放,我们对此处人们所写的内容不承担任何责任。

列表 2.5. 简单的 HTML 表单,用于向执行器发送命令
<form action="http://devices.webofthings.io/pi/actuators/display/content/"
 method="post">
  <label>Enter a message:</label>
  <input type="text" name="value" placeholder="Hello world!">
  <button type="submit">Send to Pi</button>
</form>

这是一个简单的 HTML 表单,它将 HTTP POST(method 的值)发送到显示的 URL(action 的值)。输入文本栏被称为 valuename="value"),这样 Pi 就知道要显示什么文本。这种方法对于基本网站来说效果很好。不幸的是,你无法看到幕后的是,网络浏览器不会使用 JSON 负载体(正如你以前可以用 Postman 容易做到的那样)提交数据到服务器,而是使用一种称为 application/x-www-form-urlencoded 的格式。Pi 需要能够理解这种格式,以及 application/json,以便处理来自 HTML 表单的数据输入。

HTML 表单只能使用动词 POST 或 GET,不能使用 DELETE 或 PUT。遗憾的是,即使是现代浏览器也因为一些神秘的历史原因而没有将 HTML 表单的内容作为 JSON 对象发送,但嘿,这就是生活!

正如你将在本书后面看到的那样,Web 物联网上所有实体接收和传输 JSON 内容的能力对于确保一个真正开放的生态系统至关重要。因此,我们将向你展示如何从 HTML 表单页面发送实际的 JSON 数据(通过使用 AJAX 和 JavaScript),因为这样做是与网络设备通信的一个基本部分。

打开 ex-3.2-actuator-ajax-json.html 文件,可以看到一个类似表单,但这次包含一大段 JavaScript,如下所示。

列表 2.6. 从表单发送带有 JSON 负载的 HTTP POST 请求

图片 4.9fig01_alt

在此代码中,定义了一个名为 processForm() 的函数,它从表单中获取数据,将其打包成一个 JSON 对象,并将其 POST 到 Pi,如果成功则显示结果(否则在控制台显示错误)。url 参数指定了端点 URL(Pi 显示),method 是要使用的 HTTP 方法,而 contentType 是发送到服务器的内容的格式(在这种情况下为 application/json)。最后一行将表单 #message-form 的提交按钮点击事件附加到调用 processForm() 函数。

这段代码有一个变体,即 ex-3.2b-actuator-ajax-form.html,它将数据编码为application/x-www-form-urlencoded格式,而不是 JSON,就像我们在练习 3 的第一部分中展示的简单表单那样。

2.4.3. 那么,这又意味着什么呢?

在本节中,你学习了如何使用网页表单和 API 向设备发送数据和命令的基础知识。你接受了现代网络限制、挑战和问题的快速课程(别担心,还有更多!),特别是不同的网络浏览器如何以不同的方式解释和实现相同的网络标准。最后,你学习了如何使用 AJAX 绕过这些限制,并向树莓派发送 JSON 命令以远程控制它。

我们希望你在完成这个练习后意识到,只要这些设备连接到互联网并提供了简单的 HTTP/JSON 接口,发送执行器命令到各种设备是非常直接的。但最后一个问题是如何找到附近的设备,理解其 API,确定设备提供的功能,以及知道在你的命令中需要包含哪些参数,包括它们的类型、单位、限制等。下一节将向你展示如何解决这个问题,所以请继续阅读。

2.5. 练习 4——向世界介绍你的设备

在之前的练习中,你学习了如何轻松地将设备暴露在网络上,然后由其他客户端应用程序探索和使用。但那些例子假设你(作为一个人类开发者或你编写的应用程序)知道 JSON 对象的字段(例如,传感器或执行器)的含义以及如何使用它们。但这怎么可能呢?如果你对设备的唯一了解就是它的 URL,而其他一无所知呢?

假设你想构建一个可以控制本地网络中智能家居设备的 Web 应用程序。你如何确保这个应用程序始终可以工作,即使你身处他人的网络,你对那里的设备一无所知?

首先,你需要在网络层面上找到设备(即设备发现问题)。换句话说,你的 Web 应用程序如何发现你周围所有设备的根 URL?

其次,即使你偶然知道(通过某种魔法技巧)你周围所有 Web of Things 兼容设备的根 URL,你的应用程序“理解”这些设备提供的传感器或执行器,它们使用的格式,以及这些设备、属性、字段等含义又该如何呢?

正如你在练习 2(第 2.3.2 节)中看到的,如果你知道设备的根 URL,你可以轻松地浏览设备并找到关于它及其传感器、服务等方面的数据。这很简单,因为你是一个人类,但想象一下,如果你有一个包含无法理解的单词或字符的 JSON 文档,并且没有任何解释这些单词含义的文档——你将如何知道设备的功能?你又将如何知道它是一个设备呢?

在你的浏览器中打开 ex-4-parse-device.html,你会看到一个预先填充了 Pi 的 URL(图 2.15)的表单。点击浏览此设备。

图 2.15. 一个解析你的设备元数据并显示结果的迷你浏览器

图片 2.15

ex-4-parse-device.html 的这段 JavaScript 代码将读取树莓派的根文档(作为 JSON)并生成一个关于设备及其传感器的简单报告,以及指向此设备文档的链接。首先,让我们看看显示报告的 HTML 代码,如下一列表所示。

列表 2.7. 一个基本的设备浏览器
<form id="message-form">
    <input type="text" id="host" name="host" value="http://devices.webofthings.io/pi"
placeholder="The URL of a WoT device" />
    <button type="submit">Browse this device</button>
</form>

<h4>Device Metadata</h4>
<p><b>Metadata.</b> A general model used by this device can be found here:
<div id="meta"></div></p>
<p><b>Documentation.</b> A human-readable documentation specifically for
this device can be found here: <div id="doc"></div></p>
<p><b>Sensors.</b> The sensors offered by this device:
   <div id="sensors"></div></p>
<ul id="sensors-list">
</ul>

你首先可以看到一个表单,你可以在这里输入设备的根 URL 并带有浏览按钮。然后,有一些 HTML 文本元素将充当占位符(metadoc等)。现在让我们看看以下列表中的 AJAX 调用。

列表 2.8. 使用 AJAX JSON 调用检索和解析设备元数据

图片 2.2.8

图片 2.2.8-1

看着这段代码,你可以看到你首先使用表单中输入的 URL($('#host').val())设置了设备的根 JSON 文档。如果 JSON 文件已成功检索,则success回调函数将被触发,其中data变量包含设备的根 JSON 文档(如第 2.2.2 节的第 2 步所示)。然后你解析这个 JSON 以提取你正在寻找的元素;在这种情况下,代码正在寻找返回的 JSON 对象中的links元素(因此是data.links),它包含各种链接,可以获取更多关于此设备的信息,如下面的代码所示:

"links": {
  "meta": {
    "rel": "http://book.webofthings.io",
    "title": "Metadata"
   },
  "doc": {
    "rel":
"https://www.raspberrypi.org/products/raspberry-pi-2-model-b/",
    "title": "Documentation"
  },
  "ui": {
    "rel": ".",
    "title": "User Interface"
  }
}

特别是,meta元素包含一个链接(rel的值)指向此设备使用的通用模型(它描述了描述此设备元素所使用的语法),然后是一个doc,它链接到一个人类可读的文档,该文档描述了此特定设备的含义(即,哪些传感器存在以及它们测量什么)。

在前一段代码中链接的元数据文档不过是一个可机器读取的 JSON 文档模型,它允许用户以结构化的方式描述 WoT 设备,并定义了所有 WoT 设备必须具备的逻辑元素。如果成百上千的设备制造商都使用这个相同的数据模型来公开他们的设备服务,这意味着任何能够读取和解析这个文件的应用程序都能够读取设备返回的 JSON 文件并理解设备的组件(例如它有多少个传感器,它们的名称或限制,它们的类型等等)。

现在,关于传感器或执行器本身呢?links元素只定义了关于设备的元数据(例如文档),而不是设备本身的内容。要找到设备中包含的传感器,你需要解析resources元素的sensors字段,这就是在第二个 AJAX 调用中发生的事情,你在设备的传感器资源上执行 GET 操作。一旦你得到了传感器的 JSON 文档,你将遍历每个传感器,并使用以下模式创建一个链接:

<li><a href=\""+sensorsPath+key+"\">"+data[key].name+"</a></li>

在这里,sensorsPath是传感器资源的 URL(在这种情况下为devices.webofthings.io/pi/sensors),你将添加每个传感器的传感器 ID(key),以及相应传感器的名称(data[key].name)。

2.5.1. 那又如何?

如果你没有完全理解前一个练习的所有细节,那完全没问题——这并不代表你有问题!发生的事情是你第一次亲身体验了语义网,或者更确切地说,是它试图解决的难题。你之所以听到很多关于它,却从未见过或使用过(或者理解过),是因为它对计算机和编写它们的程序员来说是一个复杂的问题:你怎么向计算机解释现实世界及其存在性疑问呢?好吧,结果是你实际上还不能给你的机器教授哲学。但正如我们在这里所展示的,并在第八章中详细说明的那样,有一些小技巧可以成功地应用,使网络和计算机变得更聪明一点。

你已经看到了网络设备如何以机器可读的方式宣传它们的基本能力、数据和服务。我们使用的是众所周知的网络模式,这使得构建与我们的设备交互的 Web 应用程序变得容易。不幸的是,没有单一的标准来定义这种信息,我们使用的 JSON 模型是多年来通过试错产生的。为了释放物联网的潜力,我们必须能够使用具有清晰语义的单个数据模型来定义关于一个对象的全部细节,这样所有机器和应用都能理解,没有任何歧义的空间。我们将在第八章中更详细地探讨如何使用 Web 和轻量级语义网技术实现这一点。

2.6. 练习 5—创建您的第一个物理混合应用

在之前的练习中,您学习了如何访问网络设备,理解它提供的服务和数据,以及从设备中读取和写入数据。在这个练习中,我们将向您展示如何构建您的第一个混合应用。混合应用的概念起源于嘻哈场景,用来描述由其他歌曲采样组成的歌曲。同样,网络混合应用是一个从各种来源获取数据、处理数据并将其组合成新应用的网页应用。

在这里,您将创建不仅是一个网络混合应用,还是一个 物理混合应用——一个使用连接到网络的实时传感器数据的网页应用。在这个练习中,您将从雅虎天气服务获取本地温度数据,将其与办公室中连接到 Pi 的温度传感器进行比较,并将结果发布到连接到伦敦 Pi 的 LCD 屏幕。最后,为了查看您的消息看起来像什么,您将使用摄像头的网络 API 拍摄一张照片并在我们的网页上显示!请参阅图 2.16 了解此过程的说明。

图 2.16. 物理混合应用。首先(1),您从雅虎天气获取本地温度,然后从连接到我们的 Pi 的传感器获取远程温度(2)。您将其与伦敦的温度进行比较,并将结果发送到 LCD 屏幕(3)。当屏幕显示您发送的文本时,您从摄像头获取屏幕的图片(4)并在混合应用中显示它。

图片

好的,请同时在您的编辑器和浏览器中打开文件 ex-5-mashup.html。这段代码比您之前看到的要长一些,但并不复杂,如下所示。

列表 2.9. 混合函数

图片

图片

mashup() 函数负责运行混合应用的不同部分。它接受两个参数:第一个参数是您的名字;第二个参数是您居住的城市名称,格式为 城市, 国家代码(例如,苏黎世,CH;伦敦,UK;或纽约,US)。它本质上由两个 AJAX 的 HTTP GET 调用组成,请求以 application/json 表示形式的响应。第一个调用是到雅虎天气服务 API,给定一个位置返回其当前天气和温度。

一旦这个调用返回(即,匿名回调函数已被调用),第二个函数将被调用以从 Pi 温度传感器获取最新值,就像您在第 2.3.1 节中所做的那样。

接下来,您调用 prepareMessage(),该函数格式化您的消息并将结果传递给 publishMessage()。这个最后的函数通过 AJAX 运行一个 HTTP POST 调用,带有包含要推送到 LCD 屏幕的消息的 JSON 有效负载,就像在练习 3—对现实世界采取行动中所做的那样。

由于你需要等待你的信息显示在队列中,你设置了一个定时器来触发takePicture()函数。这个最后的函数运行一个最终的 HTTP GET 请求,通过网络摄像头获取液晶显示屏显示的图片。然后你将返回的图片动态添加到你的 HTML 页面中的图像容器中。

要开始这一系列现实世界和虚拟世界的事件,你只需要编辑源代码,使其调用mashup(x,y)函数,使用你自己的名字和城市。例如,来自瑞士苏黎世的 Rachel 需要如下调用此函数:

mashup('Rachel', 'Zurich, CH')

然后在浏览器中打开文件,哇!几秒钟内,你将看到来自网络摄像头的实时图像,你的信息出现在我们办公室的树莓派屏幕上。

2.6.1. 那又如何?

你已经使用来自各种来源的数据,包括物理和实时数据,构建了第一个基于网络的物理混合应用,并运行了一个简单的算法来判断你的天气是否比我们的更好(尽管与伦敦的天气竞争在某种程度上有些不公平)。想想看。这个混合应用涉及一个连接到嵌入式设备的温度传感器、一个视频摄像头、一个液晶显示屏和一个虚拟天气服务,而你却能够创建一个全新的应用,它只包含 80 行 HTML 和 JavaScript 代码,包括 UI!这不是很棒吗?这一切都归功于所有参与者(设备和其他服务)都在网络上公开了它们的 API,因此可以直接使用 JavaScript 进行访问!在整个书中,你将学到更多关于物理混合应用的知识,尤其是在第十章中,我们将调查可用的各种工具和技术。

2.7. 摘要

  • 你第一次亲手接触到了全球范围内的网络连接设备,并可以浏览它们的元数据、内容、传感器、执行器等等。

  • 网络连接设备可以像任何其他网站一样进行浏览。通过 HTTP 或 WebSocket API,可以实时获取传感器的数据,就像在网络上获取其他内容一样。

  • 与物联网中常用的各种复杂协议相比,理解 HTTP API 的基础要容易得多和快得多。

  • 在短短几分钟内,你通过发送 HTTP 请求使用 Postman,就能读取和写入世界各地的设备数据。

  • 将物理世界连接到网络,可以快速原型化需要少量 HTML/JavaScript 代码的交互式应用。

  • 随着各种设备的数据和服务作为网络资源提供,构建将各种来源的内容集成在一起且集成工作最少的物理混合应用变得容易。

我们希望您对物联网的第一次接触感到满意,以至于愿意阅读接下来的章节,并学习如何在您的设备上实现这些概念。在接下来的章节中,我们将探讨如何在设备上实现 JavaScript,并提供 Node.js 的简短而全面的介绍。然后,我们将探讨如何配置您的设备,使其适合物联网。我们将向您展示如何在 Raspberry Pi 设备上创建和部署 Node.js 应用程序,您将能够创建您的第一个网络连接设备,并将这些示例适应您自己的用例。

第三章. 物联网中的 Node.js

本章涵盖

  • 概述 JavaScript 如何用于物联网和物联网

  • 对 Node.js 的全面而易于理解的介绍

  • 使用 Node.js 实现简单 HTTP 服务器的示例

  • Node.js 模块化和 NPM 的介绍

  • 异步编程和控制流库的基础

上一章为您提供了对网络连接设备的第一次接触。我们希望这使您意识到构建与各种网络连接设备交互的应用程序是多么容易。但这只是冰山一角,因为我们为您做了所有艰苦的工作。在本书的其余部分,我们将向您传授您需要知道的一切,以便实现您自己的网络连接设备和应用程序。

在我们直接跳到代码和其他有趣的部分之前,您有两个重要的决定要做。首先,您必须选择一个嵌入式平台,您的应用程序将在该平台上运行。这将是第四章的主题。其次,您需要选择您将用其编写代码的编程语言,这正是本章的主题。

为了选择一种编程语言来构建您的物联网原型,您有两个基本要求:首先,您选择的编程语言应完全支持 Web 协议和标准。嗯,这并没有太大的帮助,因为今天几乎任何严肃的语言(谁说“空白字符”?^([1]))都提供了工具和库来支持 HTTP。第二个要求是您应该能够使用一种语言来构建客户端应用程序、云引擎或网关(我们将在第六章中介绍),甚至是在嵌入式设备上运行的代码。结果证明,JavaScript 可以是“唯一的选择”。

¹

en.wikipedia.org/wiki/Whitespace_(programming_language)

因此,本章首先探讨了 JavaScript 社区的最新发展及其在互联网和物联网中的日益增长的重要性。之后,我们将向您介绍 Node.js,这是一个用 JavaScript 编写服务器端应用程序的环境。这个介绍不会让您成为 Node.js 专家,但它肯定会为您提供理解 Node.js 工作原理以及构建和部署本书示例所需的所有要素。

如何获取代码

如果您不想从头开始编写我们展示的代码示例,您可以克隆我们的 GitHub 存储库(链接见此处:book.webofthings.io)。本章中所有的代码示例都位于 chapter3-node-js 文件夹中。

3.1. JavaScript 的崛起:从客户端到服务器再到物联网!

JavaScript 是一种动态编程语言,由网络浏览器执行的客户端脚本可以异步处理数据并改变显示的页面。JavaScript 仅用于在网页上动画化横幅的日子已经一去不复返了!多亏了几乎所有网络浏览器对其广泛的支持、相对易用性和灵活性,JavaScript 已经成为编写动态客户端应用程序的事实上的解决方案。根据 GitHub 上公共存储库的数量,它也已经成为历史上最受欢迎的编程语言之一,拥有一个增长速度超过其他任何语言的开发者社区;参见图 3.1。

²

www.tiobe.com/tiobe_index

图 3.1. GitHub 上最受欢迎的语言排名。自 2008 年以来,JavaScript 经历了稳步增长,使其在 GitHub 上可用的项目数量上超过了所有其他语言。[来源:GitHub.com]

这场正在进行的 JavaScript 革命与物联网的核心思想非常契合,即整合设备到网络中,使它们更易于访问和编程。换句话说,通过使用众所周知的网络标准,使与设备交互变得与在网络上交互任何其他资源一样简单。当物理对象暴露的服务可以通过简单的 HTTP 请求访问时,为物理世界编写交互式应用程序变得与创建任何基本网络应用程序一样简单:通过使用 HTML、CSS 和 JavaScript 编写!

在服务器端,应用程序通常使用各种语言实现,如 PHP、Ruby、Python 或 Java。但在这里,JavaScript 最近已经成为一个非常受欢迎的选择。确实,JavaScript 越来越多地被用于编写高度可扩展的服务器端应用程序,尤其是在 Node.js 等运行时环境中,我们将在稍后介绍它,^([3])。

³

Node.js 并不是唯一的服务器端 JavaScript 框架。另一个例子是 Vert.x:vertx.io

所有这些平台的融合对本书有幸运的启示:这意味着我们可以主要专注于使用 JavaScript 编写本书的所有示例。我们将使用 JavaScript 和 jQuery 构建客户端示例(正如我们在第二章中已经做的那样),使用 JavaScript 和 Node.js 构建提供物联网服务的服务器,甚至使用 JavaScript 和 Node.js 代码来管理物联网本身的硬件资源,正如我们接下来要解释的。

3.1.1. 将 JavaScript 推向物联网

有趣的是,由 Node.js 引发的 JavaScript 革命并没有止步于浏览器。也没有止步于服务器。在过去的几年里,它还渗透到了设备本身的世界!在一个被运行底层 C 程序的低级设备大规模主导的世界中,JavaScript 和 Node.js 已经设法脱颖而出,成为为各种事物供电的可行且易于使用的替代方案,从机器人(例如,使用 Cylon.js 库)到无线传感器节点。今天,许多嵌入式设备平台直接支持 JavaScript 和 Node.js 来编写嵌入式代码。这包括我们在下一章中将要介绍的基于 Linux 的大多数平台,如 Raspberry Pi、Intel Edison 和 Beagle Board,5 以及一些低功耗平台,如 Tessel6 和 Espruino7]。

cylonjs.com/

beagleboard.org/

tessel.io/

www.espruino.com

“当你手里有一把锤子,看什么都是钉子!”我们听到你这么说。但并不完全是这样;我们并不提倡在每一个物联网实现中都用 JavaScript 和 Node.js。我们更愿意将 JavaScript 和 Node.js 比作现代物联网和 WoT 开发的瑞士军刀,而不是锤子。当然,它并不是我们所能想到的每一个物联网项目的最佳解决方案,但它是许多项目的绝佳选择。

对于需要绝对可预测和实时性能的嵌入式应用(例如,在高速列车上运行代码),最好用 C 这样的底层语言编写。此外,JavaScript 作为一种语言,经常受到批评者对其缺乏静态类型和众多不同的编程模式和风格的批评,这导致代码有时难以维护,尤其是在涉及大量人员的庞大项目中。尽管如此,其普遍性、可移植性和异步事件驱动模型,以及庞大的活跃在线社区,使其成为值得认真考虑的可靠选择。当使用 Node.js 构建可扩展和实时网络系统时,这一点尤为正确,而对于硬件项目的快速原型设计也是如此。

3.2. Node.js 简介

Node.js——或者如其爱好者所称呼的 Node,首次在 2009 年出现,当时一位名叫 Ryan Dahl 的杰出开发者开始在德国以“饥饿艺术家”模式构建它。后来,Ryan 被云服务提供商公司 Joyent 雇佣,该公司是 Node 的早期支持者。2015 年,Node.js 基金会成立,Joyent、IBM、Microsoft 和 Intel 等关键公司加入,为 Node.js 带来了光明的职业前景的希望。8

nodejs.org/en/foundation/

Node.js 提供了一个事件驱动的架构和非阻塞 I/O API(关于这个的更多细节将在后面介绍),这优化了应用程序的吞吐量和可伸缩性。这种模型通常用于设计高性能的实时网络应用程序。

Node 背后的理念是提供一个框架,在其中可以编写高性能的服务器端网络应用程序。与其他服务器不同,您在运行的服务器实例中部署应用程序,而在 Node 中,您的应用程序就是服务器。Node 建立在高效的 Google V8 JavaScript 引擎之上,这是 Chrome 浏览器核心。Node 不是 JavaScript,但您使用 JavaScript 语言来构建 Node 应用程序,尽管也可以使用像 CoffeeScript 这样的其他语言。

尽管这本书假设您对客户端 JavaScript 有一些基本了解,但它将向您介绍服务器端和设备端 JavaScript。Node.js 是我们将用于构建在云中或设备本身上运行的服务的框架。我们还将使用 Node.js 访问设备的物理外围设备,如传感器或执行器。

这本书绝对不是 Node.js 的全面手册:有许多优秀的书籍专门介绍 Node。例如,参见 Mike Cantelo 的《Node.js in Action,第二版》(Manning,2015);^([9]) Alex Young 和 Marc Harter 的《Node.js in Practice》(Manning,2014);^([10]) 或者像 Manuel Kiessling 的《The Node Beginner Book》这样的优秀教程。^([11]) 尽管我们可以假设大多数开发者都知道客户端 JavaScript 的基础知识,但 Node 仍然相对较新,并且有一些相当不寻常的方面,这使得它既强大又有时难以掌握。

manning.com/cantelon2/?a_aid=wot&a_bid=9b654188

^(10)

www.manning.com/young/?a_aid=wot&a_bid=f45747b3

^(11)

www.nodebeginner.org/

在接下来的几节中,我们将介绍 Node 的基础知识,以确保您不会对后续章节中的示例感到过于困惑。首先,我们将向您展示如何编写您的第一个 Node 网络应用程序(是的,也包括 Node HTTP 服务器)。然后,因为我们重用许多优秀的库来构建我们的示例,我们将探讨 Node 中模块化和包管理的方面。接着,我们将更深入地探讨 Node 和其他单线程网络框架的工作方式。最后,我们将探讨 Node 中异步编程的核心概念,为您提供构建越来越复杂的 Node 代码的工具。

3.2.1. 在您的机器上安装 Node.js

如果您没有树莓派,请不要担心!多亏了 Node.js 的神奇之处,您可以在没有实际拥有设备的情况下运行本章(甚至本书)中的所有示例!在设备上运行会更好一些。

您将首先在本地机器上安装 Node.js。^([12]) 幸运的是,这和在任何您喜欢的平台上安装任何应用程序一样简单。安装完成后,打开一个终端窗口并输入以下内容:

¹²

您可以在官方 Node.js 页面上找到不同的安装程序:nodejs.org/en/download/

$ node --version

这应该会返回您安装的 Node.js 版本(应该至少是 4.2.2,以确保本书中的代码可以运行)。现在您已经准备好运行第一个示例了!

3.2.2. Node.js 中的第一个 Web 服务器

现在 Node 已经安装到您的计算机上,您就可以开始使用了。Node 特别擅长的是仅用几行代码就构建服务器。您可以使用它构建接受各种协议的各种服务器:从套接字到 TCP/IP,再到 HTTP,到 WebSocket。

您将在本书中使用 Node 构建各种服务器,但您将从基于 HTTP 的 Web 服务器开始,因为这是 Node 内置的,不需要您导入任何依赖项;请参阅图 3.2。

图 3.2. 使用 Node 启动第一个 Web 服务器(底部)并将传统的“Hello World”返回到您的网页浏览器(顶部)

如果您习惯了 PHP 和 Apache 或 Java 和 Tomcat 这样的 Web 服务器,您熟悉创建一个 Web 应用程序然后将其部署到现有服务器上。在 Node 中,事情有所不同,因为您的应用程序就是服务器 让我们开始构建一个简单的 Web 服务器,该服务器对所有的传入请求总是返回“Hello World”;请参阅下面的列表。

列表 3.1. Node.js 中的 Hello World HTTP 服务器

这是最小的了!首先,您require http模块。这基本上是加载 HTTP 模块并将其提供给您的应用程序(我们将在下一节中详细介绍 Node 依赖项的工作方式)。之后,您使用 HTTP 对象创建一个新的服务器。您将一个函数传递给这个服务器,这个函数将在客户端连接到您的服务器时被调用。当客户端实际连接时,该函数会带两个参数被调用:

  • req代表客户端请求,并提供了一些函数来检索有关它的信息,例如请求的 URL 或发送的数据负载。

  • res代表您希望发送回客户端的响应。

使用res.writeHeader()您可以写入 HTTP 头。在这个例子中,您使用值为200Status头(表示一切顺利)和Content-Type头为text-plain,这意味着您将向客户端返回纯文本。现在,如果您不完全理解这到底意味着什么,请不要担心,因为我们在第六章中会详细讲解这一点。然后,您通过调用listen(PORT)来启动服务器,这将使服务器在端口 8585 上启动。

要运行你的第一个服务器,将 列表 3.1 复制并粘贴到一个以 .js 扩展名(例如,helloworld.js)的文件中^([13])。将此文件复制到名为 hello-node 的文件夹中,打开一个终端窗口,进入此文件夹,然后使用命令“node”后跟文件名来启动你的应用程序,如下所示:

[1]

你可以用任何文本编辑器创建简单的 JavaScript 文件,但对于更严肃的项目,你可能需要一个高级文本编辑器,如 Sublime Text (www.sublimetext.com)、Atom (atom.io/) 或 Brackets (brackets.io/)。你也可以使用功能丰富的 IDE(集成开发环境),如 WebStorm (www.jetbrains.com/webstorm/) 或 NetBeans (netbeans.org/)。

$ node helloworld.js

你应该在终端中看到文本 Server Started! 出现,告诉你 Node 应用程序正在运行。现在在你的浏览器中访问 http://localhost:8585。你应该看到“Hello World”消息,如图 3.2 所示。虽然并不十分令人印象深刻,但想想看:仅用五行代码你就创建了一个与浏览器通信的 Web 应用程序。你甚至不需要为你的特定操作系统安装和配置 Apache 服务器!你现在可以通过在终端窗口中按 Ctrl-C 来停止服务器。

3.2.3. 以 JSON 格式返回传感器数据

让我们转换一下方向,构建一个真正能提供一些价值的服务器!你将构建一个服务器,当浏览到 /temperature 时返回温度传感器的值,如图 3.3 所示,当浏览到 /light 时返回光传感器的值。你将在下一章将真实传感器连接到你的 Node 代码中,但到目前为止,你将返回随机数据。你还将更改浏览器返回的数据格式为 JSON,这在第二章中你已经遇到过了。

图 3.3. 一个简单的 Node Web 服务器,在 Firefox 中以 JSON 表示形式返回温度数据

就像你之前做的那样,你首先创建一个 HTTP 服务器,如图 3.2 所示。但这次你通过使用 {'Content-Type': 'application/json'} 标头通知客户端响应是 JSON 格式。然后你通过 req.url 查看请求,它将包含客户端请求的路径(例如,/temperature)。然后你创建一个 switch 语句来处理对您不同路径的请求。对于每个路径,你生成相应的随机值并将其作为 JSON 返回。

列表 3.2. 一个简单的返回 JSON 数据的 HTTP 服务器

现在保存这个文件,并像之前一样运行你的应用程序:

$ node listing-3.2-webserver.js

使用你的浏览器导航到 http://localhost:8686/temperaturehttp://localhost:8686/light,以查看一个随机的传感器值,以 JSON 格式显示。

3.3. Node.js 中的模块化

您已经基于内置的 Node 模块创建了自己的第一个服务器。这做得很好,但您如果想通过使用第三方模块来利用不断增长和活跃的 Node 开发者社区的工作,该怎么办呢?本节就是关于这个的:首先了解 Node 模块管理系统,然后了解模块本身的架构。

3.3.1. npm——Node 包管理器

就像 Java 有 Maven 仓库,Ubuntu 有 apt-get,Ruby 有 Gem 一样,Node 也有自己的包或模块管理器(而且是一个非常好的!),称为 npm。正如 npm 团队成员所说,

“npm 并不代表‘Node 包管理器’。它代表‘npm Is Not An Acronym’。为什么不叫‘NINAA’?因为那样它就会成为一个缩写词。

开个玩笑,不叫 npm 为 Node 包管理器的原因是,它不仅是一个 Node 的包管理器,也是一个客户端 JavaScript 的包管理器。

如果您按照我们在“在您的机器上安装 Node.js”一节中建议的方式安装了 Node,那么您应该已经准备好通过npm命令行工具使用 npm 了。第一步显然是选择一个模块进行安装。这可以通过多种方式完成,但一种流行的方式是使用 npm 主仓库的搜索引擎通过关键词搜索模块,网址为www.npmjs.com/。作为一个练习,寻找您稍后将要使用的request模块。所有 npm 模块都有一个独特的名称,所以一旦您找到了想要的模块,就把它写下来。

由于 Node.js 的流行,每个搜索查询都会返回大量选项,有时选择要使用的模块可能会让人感到不知所措。在 Node 这样的快速发展的生态系统中,代码提交速度很快,有时会以稳定性和质量为代价,选择正确的模块尤为重要。快速评估项目相关性和成熟度的好方法之一是查看其 GitHub 页面,这可以在每个模块的详细页面中访问,如图 3.4 所示。在这个页面上,您可以找到关注该模块的人数(Watch)、喜欢该模块的开发者数量(Star)以及创建该模块新版本的人数(Fork);这些都是模块流行度和稳定性的良好指标。

图 3.4. Node 模块的 GitHub 页面。在选择模块时寻找模块的流行度指标。

图片

现在您已经选择了一个模块,您可以使用具有模块唯一名称的npm命令行工具来安装它。让我们安装request,这是一个旨在使 HTTP 调用尽可能简单直接的直观模块。创建一个新的文件夹(这里我们命名为hello-npm)并将目录更改为这个文件夹;然后运行以下命令:

$ npm install request

这将与 npm 服务器通信,并将新模块安装到名为 node_modules/ 的目录中。然后您可以通过使用 require 指令从 hello-npm 文件夹内的任何 Node 源文件中使用该模块,该指令将模块加载到内存中并使其对源代码可用。这是通过以下语句完成的:

var request = require('request');

3.3.2. 使用 package.json 和 npm 清理依赖项

我们刚才展示的系统运行良好,但向项目中添加更多模块并将其从一个地方移动到另一个地方需要多次手动运行 npm 命令。幸运的是,npm 通过允许您在单个名为 package.json 的 JSON 文件中指定代码所依赖的模块来解决此问题。您可以在以下列表中查看典型 package.json 文件的结构。

列表 3.3. 一个简单的 package.json 文件

首先,您为项目命名并指定版本。请注意,如果您决定将项目作为模块发布到 npm,则将使用此名称。然后,您添加简短描述、作者以及可以找到项目代码的源代码控制系统链接(可以是私有的)。接下来是文件的核心:项目所依赖的模块。在这里,您有一个单独的模块:request。Node 模块通常遵循语义版本控制模式,^([14]) MAJOR.MINOR.PATCH

^((14))

semver.org/

  • 当您进行不兼容的 API 变更时使用 MAJOR 版本

  • 当您以向后兼容的方式添加功能时使用 MINOR 版本

  • 当您进行向后兼容的错误修复时使用 PATCH 版本

支持通配符,因此 2.x.x 表示 npm 将获取 request MAJOR 版本 2 的最新 MINORPATCH 版本。devDependencies 仅在您在开发环境中构建项目时才需要依赖项。此类依赖项的一个好例子是测试库,在部署代码的最终版本时不需要导入。最后,使用 engine 您还可以指定项目应运行的 Node 版本。这是一个 package.json 文件的简化示例,因为那里可以指定更多内容。网上提供了一份构建 package.json 文件的优秀交互式指南。^([15])

^((15))

browsenpm.org/package.json

当然,您不必手动编写 package.json 文件。相反,您可以使用 npm init 命令,该命令将询问您有关应用程序的基本信息,并为您自动生成 package.json 文件。

npm 的另一个有用功能是您不必手动将每个新模块添加到 package.json 文件中。相反,您可以使用以下命令中的 –-save 标志来安装它们:

$ npm install request –-save

此命令将自动将此依赖项添加到您的 package.json 文件中。

3.3.3. 您的第一个 Node 模块

现在你已经看到了如何在 Node 中管理打包的模块,我们将展示如何通过创建自己的简单模块来组织你的代码。想象一下,你想创建一个为应用程序提供算术运算的模块。你的第一个模块的文件夹结构如下所示。模块文件(operations.js)位于/lib 文件夹中,并通过 modules-client.js 文件访问:

首先,为你的项目创建一个名为 hello-modules 的文件夹。为了保持你的代码整洁并尊重 Node 约定,在 hello-modules 内部创建一个 lib 文件夹。然后,你将模块本身写入一个名为 operations.js 的文件,并将其放入 lib 文件夹中;operations.js 的代码如下所示。

列表 3.4. operations.js:Node.js 中的数学模块

此模块包含三个你希望提供给模块用户的函数:addsubmul。你通过在exports对象上定义属性来提供它们。请注意,你也可以通过此机制提供任何其他对象、字符串或变量。仅在你模块文件中使用的其他函数(例如,logOp)将不会对模块外的文件可用,因为你没有将它们附加到exports对象上。

最后一步是为你的库创建一个客户端。在项目的根目录下(即 hello-modules 文件夹),创建一个名为 module-client.js 的新文件(注意,与模块名称不同,客户端文件的名称并不重要)。这个文件的代码相当简单,如下所示。

列表 3.5. 使用 operations 模块的简单 Node 应用程序

关键在于require,其中你导入你的新模块。本质上,你是在告诉 Node 去获取位于 lib 子目录中的模块operations并将其保存在ops变量中。请注意,在使用require时,你不需要指定.js 扩展名。

就这样!你的第一个模块已经准备好使用了。运行node module-client.js来测试它。如果一切正常,你应该在你的控制台中看到以下输出:

Computing 42+42
84
Computing 42*42
1764
Computing 42-42
0

模块的内容远不止我们在这里所涵盖的,但这将帮助你开始并理解我们在这本书的其余部分如何使用模块。

3.4. 理解 Node.js 事件循环

构建服务器端 Web 应用程序,例如你在第二章中使用的该应用程序——或者通常任何需要同时处理多个客户端的 Web 服务器——需要能够以并发方式处理大量连接。如果连接不能被并发接受,每个新的 Web 客户端都必须等待先到达的客户端被服务。这几乎和伦敦的 Waterloo 到 Bank 的地下铁高峰时段一样慢!为了使用一个更恰当的类比,想象一个繁忙交叉路口上的一个单独的加油站,只有一个加油员和一个加油机。

3.4.1. 多线程 Web 服务器

可以使用两种常见的模式来解决这个问题。如图 3.5 所示,第一种模式是为每个请求创建一个进程——或者更好的是,一个线程。线程本质上是一个轻量级进程,因为它能够在执行时共享一些进程的资源(例如,一些分配的内存),同时大部分独立执行。

图 3.5. 使用线程或进程分叉方法处理多个并发请求:为每个新客户端创建一个新的线程或进程。

考虑这个代码片段,它从数据库中获取数据然后显示:

var result = database.query("SELECT things FROM deviceTable");
console.log(result);

如果你曾经使用过在 Apache 上运行的 PHP 或 Tomcat 上的 Java 这样的服务器端 Web 语言,你很可能使用过这样的代码,以顺序方式工作,等待 I/O 操作完成。在 PHP 和 Java 的世界里,这没问题,因为当一个客户端等待服务时,底层服务器(例如,Apache)会为下一个客户端提供服务。它是通过为每个传入的客户端创建一个线程来做到这一点的。

3.4.2. 单线程、非阻塞 Web 服务器

让我们通过将其应用于我们的加油站来思考这个例子:想象每个线程是一个带有服务员的水泵,每个 Web 客户端是一个客户,数据库是中央油罐。我们比只有一个泵和服务员的情况要好,但每个服务员仍然会在某些时候空闲,等待我们的油罐被填满。如果我们只有一个服务员以高效的方式管理几个泵会怎样?这从成本角度来看不是更经济吗?

简而言之,这就是现代非阻塞 Web 服务器的主要内容:通过最小化每个新客户端所需的内存开销来处理更多请求。第二种模式是一个基于单线程(或有限数量的线程)和事件循环的事件驱动系统,并且是非阻塞的或异步 I/O。

Node.js 运行时建立在这些原则之上。它运行一个线程和一个事件循环,如图 3.6 所示,并且强烈倾向于异步 I/O 操作。当 Node 服务器接受一个客户端时,它会将其挂起,直到它请求的 I/O 操作(例如,从数据库读取,从传感器读取值,或在远程服务器上上传文件)返回,同时继续为其他客户端提供服务。

图 3.6. 使用单线程事件驱动方法处理多个并发请求。事件循环将客户端挂起,直到 I/O 操作返回。

单线程事件循环模式的一个直接后果是,Node 实际上不喜欢主动等待,因为它会完全阻塞 Node 服务器,并阻止它处理任何后续请求!

为了更好地理解阻塞和非阻塞调用的区别,想象一下我们在 Node 服务器上运行之前显示的同步代码。Node 运行时会从数据库获取数据,等待直到获取到数据,然后执行console.log()指令。如果有大量数据要获取,你可能需要等待相当长的时间,在这段时间内,Node 服务器将无法服务或接受任何其他传入请求——这不是扩展系统的好方法!让我们以适合 Node 模型的方式重写数据库函数,如下所示。

列表 3.6. 对数据库的异步调用

![072fig01_alt.jpg]

与这个版本的主要区别在于,在调用database.query函数时,你传递一个函数作为参数(我们将在下一节中更多地讨论这些函数,称为回调)。然后,Node 事件循环将挂起此调用,直到从数据库收到响应。这次,在等待响应的同时,其他指令将被执行,直到从数据库获取结果并准备好消费。当结果检索到时,事件循环将使用结果调用回调函数function(results)

令人惊讶的是,这意味着在调用database.query指令后,它将直接返回。因此,console.log(results)将立即执行,在结果实际上准备好之前!此外,结果本身仅对匿名函数(由事件循环注入回调中)可用,对于console.log指令,变量将是未定义的。

这种在单个线程上提供服务而不阻塞所有客户端的方式仅当链中的所有调用都是异步时才有效,因此你使用的所有库和编写的所有代码都应该异步,除非有非常好的理由不这样做。一个合理的同步调用例子发生在 Node 程序启动时加载配置或依赖项。作为一种惯例,为了确保你跟踪你的同步调用,Node 中同步函数通常以Sync后缀结尾,并且通常不接收一个函数作为参数。例如,fs.readFileSync(filename)以同步方式读取文件(即,阻塞单个线程直到文件完全读取),而fs.readFile(filename,callback)则以异步方式读取文件(即,在等待文件读取的同时释放线程执行其他工作)。

不要误解我们:单线程服务器使用异步 I/O 并不简单,也不是万能的。但它们已经在许多情况下证明了更好的性能和扩展性,尤其是在数据密集型实时(DIRT)应用中。有趣的是,现实世界中的事物,如传感器,其本质非常适合 DIRT 类型的应用,这在一定程度上解释了为什么在物联网背景下,对这些类型服务器的兴趣日益增长。从现实世界读取数据需要收集大量的传感器数据(通过物理传感器的 I/O 操作),并且客户端期望能够及时、基于事件和几乎实时地了解这些传感器状态的变化!这非常适合 DIRT 应用的定义,将在第六章中介绍。

我们希望我们没有让你感到困惑!如果我们让你感到困惑了,别担心,因为你在本书中还会看到更多这方面的例子。现在,记住一件事:Node.js 只有一个线程,所以你需要确保你的代码在读取文件、从网络获取资源或从船上传感器读取数据时不会阻塞等待 I/O。确保这一点不发生的一个简单方法是传递回调函数,当数据可用时,事件循环会调用这些函数。

3.5. 开始异步编程

如前节所示,Node 主要基于异步编程的原则。让我们说实话:异步编程是扩展服务器的好步骤,但掌握它并不容易。PHP 的顺序模型,你可以依赖的事实是指令一个接一个地执行,使得代码清晰易懂。但好的扩展性很少没有代价。必须处理异步编程是我们使用像 Node 这样的基于事件的运行时所要付出的代价之一。

Node 提供了两种处理异步调用的主要模式:回调和事件监听器。我们将在本书的后面部分使用事件监听器,并在适当的时候简要介绍这个概念。另一方面,回调在 Node 中非常重要且无处不在,所以我们将在这里深入探讨它。

3.5.1. 匿名回调

考虑一个函数 F,它执行一些异步操作,例如从网络获取一些数据,如图 3.7 所示。图 3.7。我们需要一种方式来表达当 F 返回时下一步要做什么,所以我们给 F 另一个函数来调用,当 F 完成消息发送操作时。这是一个回调的例子——也就是说,一个作为异步函数参数传递的函数,描述了异步函数完成后要做什么。

图 3.7. 回调和事件循环(简化)。F异步从网络获取数据。同时,其他函数正在被服务。当F请求的数据准备好时,事件循环调用在数据准备好时指定的函数。这种函数被称为回调。

图片

例如,在列表 3.6 中,作为database.query()参数传递的匿名函数是一个回调。请注意,我们称这些为匿名函数,因为它们没有名字,因此,与我们将很快讨论的命名回调不同,除了最初传递给它们的函数之外,任何代码都无法调用它们。使用回调的另一个好例子是第二章中列表 2.9 的混合;它包含的mashup()函数有几个客户端 JavaScript 回调,在每一项响应之后调用一个物理事物。

虽然你可以为你的回调使用任何函数,但创建具有两个参数的回调函数是一个好习惯;第一个参数,通常命名为errerror,将包含在调用异步函数时发生错误的情况。第二个参数,通常命名为respresponse或任何反映你期望返回的内容的名称,将在一切顺利时包含预期的结果。

为了更好地理解异步调用和回调的使用,让我们创建一个类似于列表 2.9 的混合,但是在服务器端,如图 3.8 所示。当对/log资源发出请求时,我们的应用程序将执行以下操作:

1. 从服务器获取一个温度值。

2. 从服务器获取一个亮度值。

3. 为这两个值创建一个日志条目并将其追加到 log.txt 文件中。

图 3.8. 运行列表 3.2 的服务器(终端窗口,顶部)并通过从浏览器调用的回调在服务器端混合其数据(底部)

图片

这里关键的一点是,所有三个操作都需要 I/O 操作:1 和 2 需要从你的 HTTP 服务器获取数据;3 需要写入磁盘。因此,如果你以阻塞、同步的方式运行它们,整个 Node 事件循环将被阻塞几个 CPU 周期,无法接受来自其他客户端的任何连接,直到这些 I/O 操作返回。你肯定不希望这样!以下代码演示了如何使用回调以非阻塞方式构建此混合。

对于这段代码,你将使用之前安装的外部模块request。在新的目录中创建一个 package.json 文件,并使用npm安装该模块,如前面所示:

$ npm install request –-save

使用request调用 HTTP 资源很容易!以下列表将获取 Web of Things 主页并返回其 HTML 内容。

列表 3.7. 使用 request 库

你可能已经注意到对 request 的调用带有一个回调参数。这是预期的,因为该调用将以异步方式执行。使用 request 库,你的混合应用变得相当直接,如 列表 3.8 所示。你首先使用 request 从你的服务器获取温度。然后,在温度请求的回调中,你获取光线,在光线回调中,你使用异步内置的 Node fs.appendFile() 函数写入文件,并最终使用你获取的值回复客户端。

列表 3.8. 使用异步调用导致的“回调地狱”

保存此代码,并通过首先启动 列表 3.2 的服务器来测试它:

$ node listing-3.2-webserver.js
Server listening on http://localhost:8686

然后,在新的终端窗口中运行混合应用:

$ node listing-3.8-callbacks.js
Server listening on http://localhost:8787

现在,将你的浏览器指向 http://localhost:8787/log;如果一切按预期工作,你应该会看到类似于 图 3.8 的输出。这种方式在另一个回调内部调用回调,等等,被称为 嵌套回调。不幸的是,如 列表 3.8 的代码所示,嵌套回调很快就会将你的代码推到右边,使得随着每个额外回调的增加,代码的阅读和管理变得更加困难。这种异步编程使用回调的不幸后果被称为 回调地狱。正如你所见,这个问题在相对较少的回调数量下就会出现。一个保持理智的经验法则是不要尝试嵌套超过三个这样的回调。

3.5.2. 命名回调

不要就此放弃希望!有几种方法可以解决你的回调地狱。第一种是使用实际的命名函数而不是匿名函数。使用命名函数可以使代码更易于阅读,因为它封装了异步调用。它将回调地狱减少为一系列函数调用,每个函数首先调用一个异步函数,然后调用链中的下一个函数。如果你使用这种策略重写你的混合应用,它看起来就像下面的代码列表。

列表 3.9. 使用异步调用和命名回调的混合

这段代码基本上与 列表 3.8 相同,但这次工作流程被拆分到几个函数中。在服务器的回调中,你调用 getTemperature();这个函数异步获取温度,并在温度到达后立即调用 getLight() 函数。getLight() 从服务器获取光线,并在光线值准备好后立即调用 logValuesReply()。最后,logValuesReply() 异步将值记录到文件并回复客户端。

结果是代码可读性更高,缩进级别可管理。但另一方面,你现在必须手动将值作为函数参数传递给调用链;这些包括 res 参数,它包含在链的末尾所需的响应对象的句柄。此外,每个函数都需要知道下一个要调用的函数。这意味着你的函数与其他函数紧密耦合,代码难以在其他工作流程中重用。

3.5.3. 控制流库

我们显然不是第一个面临嵌套回调挑战的开发者;因此,许多开发者已经将他们处理该问题的方法贡献给了社区。这导致了大量 控制流 模块的产生。本质上,这些模块提供了优雅的解决方案来处理回调地狱问题;它们还以干净、灵活和可重用的方式解决了使用命名回调的不足。我们无法在此列出所有控制流库,但我们推荐使用 Async。^([16]) 这个库可能是处理浏览器和 Node 中异步 JavaScript 的最全面的工具集。

¹⁶

github.com/caolan/async

让我们使用以下命令安装的 async 控制流库重写我们的混合代码:

$ npm install async --save

你将使用 async.series([]) 构造,该构造函数接受一个函数数组作为参数,以及一个可选的最终回调函数,当所有其他函数返回后将被调用。此构造函数的一般结构在下一列表中展示。

列表 3.10. 使用 async.series 进行串行控制流

将此结构应用于你的混合代码,你将获得 列表 3.11 中的代码,它类似于你第一次尝试解决问题(使用嵌套回调)的方式,但整个结构更加扁平,可读性也更高。为了促进重用,我们还使用了之前创建的命名函数。在控制流模式中的关键是,在链中的每个函数中调用 callback(err, result) 方法,该方法通过控制流库调用数组中的下一个函数,并将结果添加到提供给最后一个回调函数的结果数组中。在这种情况下,结果数组以有序的方式包含温度和光照值,正如在数组中预期的那样(注意,Async 也提供了一个使用对象的替代方案)。

列表 3.11. 使用控制流库

不仅这种模型更易于阅读,而且它还更加灵活。例如,尽管你按顺序执行了温度和光度的调用,但没有任何东西强迫你这样做;你只需要以任何顺序得到响应。现在,使用Async库的另一个结构async.parallel,你可以通过并行运行调用来加快你的混合操作。为此,你只需要将初始调用从async.series([...])更改为async.parallel([...])

正如你所见,异步编程并不总是直截了当的,掌握它需要一定的学习曲线。幸运的是,有一些技术和工具可以帮助你在这种复杂性中保持代码的整洁和结构。在本书的后续内容中,我们将使用简单的匿名和命名回调作为例子,这些例子不需要超过三层回调嵌套。对于所有其他例子,我们将使用我们刚刚介绍过的伟大的Async库。

技术角落——你答应我更多!

前一节是关于帮助你高效使用异步编程的模式的介绍。还有许多其他流行的模式,如承诺和事件。承诺主要来自客户端 JavaScript 世界,但你也可以使用 Node.js 和像 Bluebird(www.npmjs.com/package/bluebird)这样的库来实现。事件模式被许多 Node 核心模块使用,是实现异步库的好方法(nodejs.org/api/events.html)。不要犹豫,去尝试这些模式,找到最适合你需求的那一个!

3.6. 摘要和本书之外的内容

  • 在过去的几年里,嵌入式设备变得越来越强大,这使得在设备上直接使用 JavaScript 和 Node.js 变得可行且具有吸引力。

  • 仅使用 JavaScript 从头到尾构建物联网原型提供了相当大的灵活性,并且使代码维护变得更加容易。

  • 你可以在多个平台和环境上运行 Node.js 应用程序,而无需修改代码,这使得在异构部署中开发和扩展应用程序变得容易。

  • Node.js 的模块化允许你利用数千个社区库快速构建复杂的应用程序。

  • 如同你在本节中看到的,单线程系统,例如 Node 运行时和事件循环,需要一种新的应用程序设计方式。代码和库必须保持异步。

  • 异步编程的基本思想是在结果可用时传递将被调用的回调函数。

  • 使用匿名和命名回调可能会导致你经历回调地狱!像async模块这样的控制流库可以帮助你解决它,并更好地组织你的代码。

如果 Node.js 引起了你的好奇心,那么现在可能是你购买 Node.js 书籍^([17])并关注官方 Node.js^([18])博客的好时机。

¹⁷

www.manning.com/catalog/by/subject/

¹⁸

blog.nodejs.org/

现在你已经将 WoT 工具箱的所有软件基础都准备好了,你就可以学习关于硬件部分的知识了,特别是选择哪些设备以及如何配置它们。在下一章中,我们将首先提供一个关于嵌入式设备世界和现有平台类型的高层次概述。之后,我们将深入探讨设置树莓派设备,连接多个传感器和执行器,并使用 Node.js 管理它们,然后你将一切准备就绪,让你的树莓派成为物联网的一部分。

第四章. 开始使用嵌入式系统

本章涵盖

  • 理解各种嵌入式系统类别

  • 树莓派的简介

  • 介绍如何设置和使用 Node.js 进行物联网原型设计

  • 学习使用 GPIO 连接传感器和执行器

正如我们之前讨论的,物联网中的物理对象大致分为两大类:标记对象和连接对象。第一类包括各种标记技术,这些技术附着在产品上,例如条形码、二维码、NFC 或 RFID 标签等。在这种情况下,对象并不是直接连接到网络,而是被动地连接,因为需要另一个设备或应用程序与产品交互。连接对象直接连接到物联网,是嵌入式系统和嵌入式设备的领域,它们本质上是非常小、相对便宜、低功耗的计算机,具有有限的资源和能力。你可以将本书中将要学习的技巧和架构应用于标记对象和连接对象,但本书的重点主要在于连接对象。

在第二章中,你学习了如何从位于我们办公室的真实嵌入式设备——树莓派——获取服务,以获得对物联网的第一印象。但这是因为你交互的设备既不属于你,也不在你身边,所以这并不是一个非常物理的过程。

在本章中,我们将向您展示如何设置和配置您自己的物联网设备。到本章结束时,您将拥有一个连接到万物的真实设备,您将拥有所有工具来编程它并实现本书下一章中介绍的所有概念。您将从选择硬件平台开始。市面上有很多选择,所以我们会确保帮助您。然后,您将通过安装各种软件包和库来使您的设备适合物联网。您还将通过将真实传感器和执行器直接连接到您的树莓派来学习物联网原型设计的基础知识,并了解一些电子学知识。

如果这是你第一次接触嵌入式设备和电子设备,本章将是一个温和(但具有挑战性)且有趣的快速入门课程!

4.1. 嵌入式设备的世界

实际上,可用的嵌入式平台类型数以千计,从为研究人员或黑客构建的小规模通用传感器节点生产,到专门为烟雾报警器、微波炉和闹钟等设备设计的廉价和大规模生产的电路。显然,在这本书中我们没有时间对这些平台进行深入审查。你应该记住的是,嵌入式设备分为两大类:一类是针对爱好者的(不那么具体和优化,但更可重用和灵活),另一类是旨在集成到实际工业产品中的(针对特定用例进行了更多优化,因此扩展和在其他环境中使用更困难)。

4.1.1. 爱好者设备与工业设备

将计算机嵌入到日常物品中的想法并不新颖:我们的洗衣机已经包含集成电路几十年了。但它们并没有连接到互联网,也不是为了便于应用开发者或客户访问或重编程而设计的。物联网的出现极大地改变了游戏规则。首先,物联网设备连接到互联网,这对低功耗设备来说可能是一个挑战。这种限制催生了一系列工业级嵌入式平台,它们支持各种网络协议,开箱即用,适用于商业应用。其次,研究社区和爱好者开始越来越对小型计算机感兴趣,这些计算机不仅易于编程,而且可以支持各种传感器或执行器。

这两个趋势催生了大量针对现实世界和工业用例以及爱好者 DIY 项目的平台。这两个类别之间的主要区别是用户关注的焦点。对于工业平台,目标一直是降低成本,以便它们可以嵌入各种消费产品,同时保持高水平稳定性(你不会希望每隔一段时间就要重启你的洗衣机)。然而,爱好者对工业级性能和坚固性不太敏感,他们更喜欢开放、易于使用和扩展的平台,并且还配备了详尽且易于使用的工具套件。

话虽如此,近年来物联网的吸引力模糊了两个世界的界限,你可以看到工业平台制造商正努力通过更好的工具使他们的设备更易于访问。同样,爱好者平台也在变得更加坚固和便宜,因此现在也被嵌入到现实世界的产品中。

提供这些平台的详细概述可能会消耗掉这本书的剩余部分,所以我们只描述了一些最受欢迎的表 4.1。请注意,这个表格是对品牌及其产品的一个过度简化,提供了一个对一些大玩家的概述。请参考平台的相关网站以获取更多详细信息。

表 4.1. 一些物联网嵌入式平台的概述。针对爱好者的平台通常成本更高,但资源(RAM,CPU 等)也更丰富。工业平台倾向于提供较低的规格,但成本通常较低。
品牌 型号 CPU RAM + 价格 类型 连接性
Arduino 20+及众多克隆(Spark,Intel 等) ATmega,8-64 MHz,Intel Curie,Linino 16 KB-64 MB 最大的社区 ~30 美元 实时操作系统,Linux,爱好者 可插拔扩展板(Wi-Fi,GPRS,BLE,ZigBee 等)
Raspberry Pi A,A+,B,B+,2,3,Zero ARMv6 或 v7,700 MHz-1.2 GHz 256-1 GB 全 Linux,GPU,大型社区 ~5-35 美元 Linux,爱好者 以太网,通过 USB 扩展,BLE(Pi3)
英特尔 Edison Intel Atom 500 MHz 1 GB X86,全 Linux ~50 美元 Linux,爱好者到工业级 Wi-Fi,BLE
BeagleBoard BeagleBone Black,X15 等 AM335x 1 GHz ARMv7 512 MB-2 GB 稳定性,全 Linux,SDK ~50 美元 Linux,爱好者到工业级 以太网,通过 USB 和屏蔽扩展
德州仪器 CC3200,SoC IoT 等 ARM 80 MHz 等 256 KB 起 成本,Wi-Fi <10 美元 实时操作系统,工业级 Wi-Fi,BLE,ZigBee
Marvell 88MC200, SoC IoT 等 ARM 200 MHz 等 256 KB 起 成本,Wi-Fi,SDK <10 美元 实时操作系统,工业级 Wi-Fi,BLE,ZigBee
Broadcom WICED 等(也是树莓派的核心) ARM 120 MHz 等 从 256 KB 起 成本、Wi-Fi、SDK 低于 10 美元 RTOS、工业 Wi-Fi、BLE、ZigBee、Thread

4.1.2. 实时操作系统与 Linux

在表 4.1 中,你可能注意到了显示 RTOS 和 Linux 设备的类型列。用于嵌入式设备的操作系统主要有两大类:实时操作系统(RTOS)和,嗯,不那么实时的操作系统!

从本质上讲,使操作系统成为实时的是它对传入数据的快速和可预测的响应能力。对于控制“大而复杂的事物”的应用程序,如核电站、制造链和飞机,确定性响应时间比其他任何因素都更重要,实时操作系统是必要的。它们通常还会导致功耗降低或至少功耗相当可预测。

当涉及到嵌入式设备时,RTOS 的世界主要由开源实时操作系统 Free-RTOS 统治,^([1])尽管存在一些可行的替代方案,如 Contiki,([2])TinyOS,([3])mbed OS,^([4])以及商业的 VxWorks.^([5])

¹

www.freertos.org/

²

www.contiki-os.org/

³

www.tinyos.net/

www.mbed.com/en/development/software/mbed-os/

www.windriver.com/products/vxworks/

实时操作系统的缺点之一是它不太擅长并行处理多个任务,这使得构建提供简单抽象的复杂层变得困难。这就是非实时操作系统可以发挥作用的地方。对于不那么关键的使命,用户体验和功能比恒定的非常快速响应时间更重要,这种情况下它尤其有用。在这个世界里——甚至比 RTOS 世界——一个操作系统统治着一切:Linux.^([6])

注意,许多项目提供了 Linux 内核的修改版本,将其转变为实时操作系统;例如,参见www.osadl.org/Realtime-Linux.projects-realtime-linux.0.html

由于其庞大的社区、丰富的工具、抽象和支持的架构,Linux 是开始尝试和革新物联网设备的理想环境。但不要误解;它也越来越成为现实世界和稳健物联网应用的合适候选者,例如家庭自动化或构建应用网关,如第五章所述(kindle_split_012.html#ch05)。

“极客角落——我想有更多的操作系统!”

在过去的几年里,Linux 已经成为了在嵌入式设备上使用的一种非常流行的操作系统,以至于 Linux 基金会的一个名为 Yocto 的项目现在致力于为嵌入式设备创建定制的 Linux 发行版。同样,谷歌正在开发 Brillo,它是 Android(同样基于 Linux)的一个扩展,用于物联网,而 Ubuntu 推出了 Ubuntu Core 用于物联网。[[a]][[b]][2] 尽管 Linux 占据了主导地位,但在这个领域还有一些 Linux 的替代品,例如 Windows 10 for IoT。[3] 我们将在 Pi 上使用 Linux Raspbian,但您也可以尝试 Yocto、Ubuntu Core 或 Windows 10,所有这些都可以在 Pi 上运行(从 Pi 2 开始)。

^a

www.yoctoproject.org/

^b

developers.google.com/brillo/?hl=en

^c

www.ubuntu.com/internet-of-things

^d

dev.windows.com/en-us/iot

4.1.3. 摘要和 Pi 之外

在本节中,我们为您提供了嵌入式设备世界的简要介绍。显然,关于这个主题已经写了很多整本书,所以我们并没有试图全面搜索,而是试图让您对选择项目硬件平台时需要考虑的选项和元素有一个一般性的了解。[4]

可以在这里找到对流行硬件平台的良好总结:postscapes.com/internet-of-things-hardware

我们决定将 Raspberry Pi 作为本书剩余部分的参考平台。为什么选择 Raspberry Pi?首先,因为它可能是最流行的嵌入式平台之一,与 Arduino 一样。但与 Arduino 不同,[5] 它是为 Linux 构建的,从一开始就被设计成一个强大且易于访问的平台,用于自动化和物联网。基本上,它将让您深入挖掘网络和物联网,而无需处理 RTOS 平台带来的所有挑战(例如,C 编程、有限的 RAM 和粗糙的工具)。

值得注意的是,还有一些基于 Linux 的新 Arduino;例如,Arduino Yun。

此外,正如您将在下一节中看到的,它价格低廉,在许多国家都有销售,并且带有多个 USB 端口用于连接附件,以及一个 HDMI 输出,可以连接到您的电视或屏幕。因为它基于 Linux,并提供对操作系统和 Node.js 的直接访问,所以您可以在上面轻松安装所有必需的依赖项,就像在您的 PC 上安装东西一样。

这并不意味着 Pi 是运行本书中提供的代码或学习物联网的唯一选择。只要您选择一个可以运行 Node.js 的设备,您基本上就可以开始了。

从概念验证到工业原型

树莓派无疑是开始使用物联网和构建各种原型最容易的方式之一。但对于任何更严肃的应用,如工业和商业级原型或实际产品,经典版本的树莓派并不是最佳选择。这有几个原因,但最主要的是使用 SD 卡作为存储和树莓派的尺寸。

依赖于 SD 卡在现实世界中并不适用。SD 卡的使用寿命有限。它们有时会损坏、被破坏或容易脱落。一个更实用的方法是将操作系统和所需的数据存储在板载闪存中。与 SD 卡一样,闪存是持久的,这意味着即使设备未供电,数据也会被保留。它们焊接在板上,并且比 SD 卡更快、更稳定。

此外,树莓派并非主要用于商业应用,因此它拥有更多可能对于大多数使用场景并非必需的组件和连接器。

这是否意味着你将无法将在这里学到的知识应用于构建商业级原型?绝对不是!有现成的平台与你在本书中学到的知识和你将编写的代码兼容。

首先,有一种树莓派版本更适合现实世界:树莓派计算模块.^([9)) 计算模块是一个类似于经典树莓派的嵌入式平台,但具有更小的尺寸和板载闪存。同样,树莓派 Zero 是 Pi 家族的最新成员,也是最小的(65mm x 30mm)。再加上令人震惊的 5 美元价格标签,它也成为商业解决方案中嵌入的可行候选者,尽管它缺少网络连接或板载闪存。与树莓派 Zero 类似,C.H.I.P^([10))具有小型化设计,以 9 美元的价格还提供了 Wi-Fi 和蓝牙连接。

www.raspberrypi.org/products/compute-module/

¹⁰

getchip.com

存在着其他非常好的(但稍微贵一点的)替代品,例如英特尔爱迪生,^([11)) 它与树莓派 3 一样,也支持蓝牙低功耗(见第五章)和 Wi-Fi,但体积更小,并具有板载闪存。

¹¹

www.intel.com/content/www/us/en/do-it-yourself/edison.html

如果你正在寻找一种设备来构建更加机械稳定的原型,而且不需要焊接太多,你应该考虑来自 BeagleBone 的 BeagleBoard^([12))平台,这是一个与树莓派类似但以坚固和稳定著称的平台。此外,BeagleBone 提供 SD 卡和基于闪存的存储,这使得将你的原型从概念转移到实际世界的试验变得容易。

¹²

beagleboard.org/BLACK

可用的平台很多,但有趣的是,Pi、BeagleBone 和 Edison 都运行在 Linux 上并支持 Node.js。这意味着大多数示例将直接在这三个平台(以及许多其他平台)上运行,但一些更高级的示例,例如使用 GPIO 的示例,可能需要稍微不同的设置或替代库。附录为您提供了使用本书的架构、概念和代码示例与其他设备(如 BeagleBone Black、Intel Edison 和 Arduino 板)的一些指南。

4.2. 设置您的第一个 WoT 设备——Raspberry Pi

到目前为止,本章已经描述了嵌入式系统的世界,以及在选择最适合您自己项目的硬件平台时需要考虑的各个方面。接下来,我们将简要介绍运行在您的硬件平台之上的软件层,通过讨论设备上的操作系统,并描述 JavaScript 和 Node.js 如何成为构建物联网设备的一个非常有趣的应用开发生态系统。

4.2.1. 认识 Raspberry Pi

Raspberry Pi 是一系列流行的单板计算机:想象一下,一台电脑的大小几乎不比您用来购买它的信用卡大(见图 4.1)。这些设备主要由 Raspberry Pi 基金会开发,主要是作为教育工具,让更多的人了解基本的计算机科学和物理计算。

图 4.1. Raspberry Pi 3 和 Pi Zero 及其不同的端口和接口

到目前为止最具颠覆性的型号是 Raspberry Pi Zero。这款设备开启了一场小小的革命:售价仅为 5 美元的全功能 Linux 电脑,这个价格通常只属于资源有限、低成本 RTOS 设备。Pi 基金会甚至将其免费赠送,附在 2015 年 12 月号的 The MagPi 杂志上,^([13]) 显示出计算机以极低的价格附加到任何物体上的日子已经不远了!

¹³

www.raspberrypi.org/magpi/raspberry-pi-zero-out-today-get-it-free-with-the-magpi-40/

在性能方面,Pi Zero 与 Pi A 非常相似,但其 ARMv6 CPU 被超频至 1 GHz,并且拥有两倍多的 RAM。仅 9 克重,它还配备了 micro-SD 卡槽、mini-HDMI 接口和两个 micro USB 端口(一个用于供电,另一个用于数据传输)。

写作时的最新树莓派型号是树莓派 3。它比树莓派零贵,但提供的功能也更多。树莓派 3 拥有四核 1.2 GHz CPU、1 GB RAM、一个 micro-SD 插槽,以及 Broadcom VideoCore IV 图形单元。在连接性方面,树莓派 3 有四个 USB 端口(以及一个用于供电的 Micro USB 端口)、一个 HDMI 端口、一个 3.5mm 耳机插孔、一个以太网连接器和 40 个通用输入/输出端口(GPIO)。最后,与前辈不同,树莓派 3 还提供了即插即用的 Wi-Fi 和蓝牙连接性,使其成为一个完全符合物联网(WoT)的设备。所有这些加起来总重量仅为 45 克!

4.2.2. 选择你的树莓派

本书中的所有示例都在 Pi B+、Pi 2、Pi 3 和 Pi Zero 上进行了测试。你应该选择哪一个?

如果你刚开始接触物联网和嵌入式设备,Pi 3(或任何后续型号)是一个安全的选择:它提供了所有必要的即插即用连接性,并且不需要焊接将传感器和执行器连接到 GPIO。但 Pi 3 的体积明显更大,而且也更耗电(Pi 2 或 3 为 4 瓦,而 Pi Zero 为 0.8 瓦)。

如果你重视尺寸或计划使用电池为你的物联网(WoT)设备供电,那么树莓派零是一个有趣的选择。但它将需要更多的焊接和调试。

理想情况下,到现在我们已经说服你继续购买一个树莓派。如果我们没有,希望并未完全丧失!大多数示例可以在支持客户端 JavaScript 和 Node.js 的任何平台上运行(比如,嗯,你的笔记本电脑!)。对于那些需要树莓派的人——本质上是与连接到嵌入式设备的传感器和执行器直接交互的示例——我们创建了一个小型库来模拟它们。最后,使用 JavaScript 和 Node.js 进行物理网络的美丽之处在于,这些示例几乎可以在任何设备上直接运行。唯一的重大例外是实际与你的传感器通信的最后一英里代码,你可能需要为每个设备进行定制。

不言而喻,如果你身边真的有一个设备来实施示例,你会从这本书中获得更多的乐趣。毕竟,在没有实物的情况下发现物联网就像在夏天没有雪的滑雪一样:有点令人沮丧。

4.2.3. 购物清单

如果你决定购买树莓派,不妨也把购物清单上的其他物品一起拿下。再次强调,这些物品并非必须跟随本书,但它们将为本书的虚拟示例增添实物触感。表 4.2 列出了你需要购买或收集的所有物品,以便能够创建本书的所有原型。

表 4.2. 创建本节和本书其余部分所述物理原型所需的组件
描述 价格
树莓派(任何从 B+开始的型号,推荐 Pi 3) 约 35 美元
4-16 GB SD 卡(例如,SanDisk Ultra Class 10 MicroSDHC,16 GB) 约 10 美元
HCSR501 树莓派 PIR 传感器 ~5 美元
DHT22 湿度温度传感器 ~5 美元
小型面包板或原型板 ~2 美元
树莓派跳线(4 M/M 和 4 M/F) ~2 美元
330 欧姆电阻 <1 美元
LED 灯 <1 美元
Wi-Fi USB 网卡(可选,用于 Pi Zero) ~10 美元
Pi Zero 线缆套装(可选,用于 Pi Zero) ~5 美元

要知道在哪里购买这些物品和你的 Pi,请查看本书的官方网站book.webofthings.io,在那里你可以找到我们合作伙伴零售商的列表,以及他们为本书读者提供的特别优惠或捆绑包。

4.2.4. 设置你的树莓派

这本书不涉及如何使用树莓派的详细教程,其使用方法已在网络上广泛记录。^([14)] 尽管如此,我们将帮助你设置 Pi,使其适合物联网。

¹⁴

一个好的起点是树莓派门户www.raspberrypi.org/

首先安装正确的操作系统。许多操作系统可以在树莓派上运行。出于实用性的考虑,在这本书中,我们将专注于 Raspbian,它本质上是一个针对 Pi 及其用户定制的 Debian Linux 系统的移植版本。Raspbian 的优势在于它已经在 Pi 上广泛使用并经过测试,易于安装和定制;因此,它提供了一个稳定且流行的操作系统,可以在此基础上构建。

在你的 Pi 上安装 Raspbian 的最简单方法就是使用一个名为 NOOBS(New Out Of the Box Software)的工具。NOOBS 是一个操作系统管理器,它将帮助你进行安装,我们将在本节中向你展示如何使用它。

如果你想要走最快的路线,我们还创建了一个包含所有启动所需内容的 WoT 版本 Raspbian。你可以在book.webofthings.io找到它,并直接跳转到第 4.3 节。走手动路线会让你学到更多,并确保你手头有一个定制且最新的系统,所以这取决于你。

极客角落——让我们谈谈电流

Pi 从 Micro USB 连接器(如图 4.1 所示)获取所有所需的电源;这意味着这条电缆提供的电流应该足够高。在压力下,Pi B+或 Zero 将消耗大约 500 mA。Pi 2 需要大约 1000 mA,而 Pi 3 需要大约 1.5 mA。你需要多少电流取决于你连接到 Pi 的东西,特别是 Pi 的 USB 端口。在这本书中,我们不会连接耗电的附件,所以你可以考虑裸机 Pi 的电源需求,但一个好的折衷方案是提供 2000 mA 的 USB 电源(检查 USB 适配器的背面或你电脑 USB 端口的规格以了解这一点)。

使用 NOOBS 安装 Raspbian

首先,您需要将 micro SD 卡格式化为 FAT32。确保您的卡足够大,因为它是 Pi 的主要存储空间。它应该至少有 4GB,但我们建议使用更大的卡以确保您有足够的空间安装软件。

注意!

根据 SD 规范,大于 32GB 的卡将使用 exFAT 而不是 FAT 进行格式化。这意味着它可能无法在 NOOBS 使用的 Raspberry Pi 上工作。您可以使用 Linux 上的 GParted 或 Mac OS 上的磁盘工具等格式化工具将其转换为 FAT32,但一些用户也报告了这方面的问题。为了避免任何麻烦,选择一个 4GB 到 32GB 之间的 SD 卡,理想情况下是一个来自可信品牌的快速卡。我们使用 SanDisk Ultra Class 10 16GB 卡取得了良好的效果。

要在 SD 卡上安装 Linux 以便您可以将它插入 Pi 并使用它,请按照以下说明操作:

1. 格式化将要安装 Raspbian 的 micro SD 卡。

在 Linux 上,打开 GParted^([15])并使用设备菜单选择与您的 SD 卡对应的设备(确保您正在格式化 SD 卡而不是您的计算机)。然后,右键单击最大的分区将其格式化为 FAT32。

在 Mac OSX 上,使用磁盘工具。选择 SD 卡读取器并点击擦除。选择 MS-DOS(FAT)作为格式,给它起个名字(例如,WOT_PI),然后选择擦除以继续格式化卡;参见图 4.2。或者,您可以安装免费的 ApplePi-Baker^([16))实用程序——这是一个很好的小工具,它还允许您备份和恢复您的 Pi 镜像。

图 4.2. 使用 Mac OS 上的磁盘工具格式化 SD 卡以供 NOOBS 安装程序使用。确保您使用 MS-DOS(FAT)格式进行格式化。

在 Windows 上,下载并使用 SD 卡格式化工具^([17])。选择覆盖格式选项;然而,请注意,此工具在大于 32GB 的卡上无法工作,因为它将以 exFAT 格式化它们。

¹⁵

gparted.org/

¹⁶

www.tweaking4all.com/hardware/raspberry-pi/macosx-apple-pi-baker/

¹⁷

www.sdcard.org/downloads/formatter_4/

2. 从 Raspberry Pi 社区下载最新的 NOOBS 软件(选择 NOOBS)。通常,使用种子文件(在你问之前:是的,在这种情况下这是绝对合法的)下载发行版是最快的方式。

¹⁸

www.raspberrypi.org/downloads/

3. 解压 NOOBS 存档的内容并将其传输到新格式化的 SD 卡。不要将内容放在子文件夹中;将所有内容复制到 SD 卡的根目录。文件复制完成后,弹出(或卸载)该卡。

4. 将 SD 卡插入 Pi 侧面的插槽,将 HDMI 线缆插入屏幕,将 USB 鼠标和键盘插入 USB 插槽,最后将 micro USB 线缆插入电源(参见图 4.1 以找到正确的端口)。

5. NOOBS 现在应该启动。大约一分钟后,您将看到一个启动屏幕,允许您安装 Pi 支持的不同操作系统。选择 Raspbian(为 Pi 量身定制的 Linux Debian 操作系统的一个特殊版本)并点击安装。这将开始安装操作系统,这可能需要长达 30 分钟。

操作系统现在应该准备好了。当您第一次启动时,Pi 将带有 X Window 图形环境启动。如果您想禁用此图形用户界面,请选择菜单 > 首选项 > Raspberry 配置,然后在系统选项卡中选择 CLI 作为启动选项。现在重新启动 Pi;选择菜单 > 关机 > 重新启动。Pi 应该重新启动,您很快就会看到一个终端。如果这些步骤中的任何一步出现问题,请在线阅读完整的安装手册([19])或在本书论坛上发帖.([20])

^(19)

www.raspberrypi.org/help/noobs-setup/

^(20)

book.webofthings.io

将 Pi 连接到网络

接下来,您需要将 Pi 连接到网络。我们将在第五章中讨论一系列网络协议,但到目前为止,我们将使用以太网或 Wi-Fi。如果您选择了 Pi B、B+或 2,这一步很简单:将路由器中的以太网线缆插入 Pi(参见图 4.1)。

如果您选择了 Pi Zero 或 Pi 3 并且没有将线缆连接到路由器的选项,您需要做更多的工作来将 Wi-Fi 连接添加到您的 Pi 上。这项额外的工作也带来了便携性的优势:您的 Pi 变得无线,只要在无线路由器的范围内,您就可以将其放置在您想要的位置。

支持 Wi-Fi 连接的软件已经存在于 Raspbian 中,但您需要通过修改 Wi-Fi 网络配置文件来启用此功能,如下所示。

列表 4.1. 修改 Wi-Fi 配置文件

图片

您现在可以通过按CTRL+X,然后Y,然后ENTER来保存并关闭此文件。完成此操作后,使用sudo shutdown –h now进行干净关机。然后,如果您使用 Pi Zero,将兼容的 Wi-Fi USB 扩展卡插入任何空闲的 USB 端口(在 Pi 3 上不需要这样做,因为 Wi-Fi 是内置的)。请注意,当使用 Pi Zero 时,您需要拔掉任何其他设备(例如,您的键盘)并使用 USB 到 Micro USB 转换器。在表 4.2 的购物清单中查看有关这些组件的更多详细信息。

一旦您的 Pi 重新启动,它应该会连接到您的 Wi-Fi 网络。这个过程在某些网络上可能需要长达一分钟。在下一节中,我们将通过远程访问 Pi 来验证这一切是否成功。

极客角落——我想要更多的 Wi-Fi

这里描述的方法可以用来将你的 Pi 连接到 WPA(无线保护访问)或更安全的 WPA2。如果你的 Wi-Fi 使用的是不同的安全协议,如(不那么安全的)WEP 或 WPA2 企业版,则可能不起作用。然而,并非所有的希望都破灭了,你可以在网上找到许多关于如何将 Pi 连接到不同 Wi-Fi 网络的优秀教程。一个不错的起点是www.raspberrypi.org/documentation/configuration/wireless

远程访问你的 Pi

你的 Pi 现在应该已经启动,运行,并且连接上了。虽然你可以使用键盘、鼠标和屏幕直接在 Pi 上编写和运行所有练习,但更实用的选项是将其“无头”(即,不连接显示器/键盘)运行,并通过 SSH 远程连接到它。在这种模式下唯一的问题是首先找到你的 Pi。这实际上是物联网中一个众所周知的问题,称为引导或发现问题:给定一个设备第一次连接到网络,你如何找到它的地址?

为了解决这个问题,使用你的 Pi 的 Avahi mDNS 服务器。mDNS 是一种发现协议,我们将在第八章中介绍,但就现在而言,了解它为你的 Pi 提供了一个地址,附近的计算机可以使用该地址找到它就足够了。Avahi 默认安装在最新的 Raspbian 版本中,所以你可以继续使用它.^([21]) 默认情况下,Avahi 将设置 Pi 以响应raspberrypi.local域名。你可以通过在 Linux/Mac OS 的终端上运行列表 4.2 中显示的命令来检查这一点。Windows 用户应尝试以下列表中的 ping 命令,使用命令提示符(cmd.exe)。不幸的是,你可能找不到你的 Pi,因为 Windows 机器上默认不支持 mDNS。如果你安装了捆绑 mDNS 服务的应用程序,如 iTunes,则它将工作。但如果你没有,你需要安装一个 mDNS 服务,如 Bonjour Print Services for Windows.^([22])

²¹

如果它没有安装在你的 Pi 上,运行sudo apt-get install avahi-daemon来安装它。

²²

你可以免费从www.apple.com/support/bonjour下载它。

列表 4.2. Ping 你的 Pi

如果一切正常,你现在应该能够通过其本地 DNS 地址访问你的 Pi:raspberrypi.local。请注意,如果你需要,你可以更改此地址;例如,如果你家里有多个 Pi。^([23])

²³

查看www.howtogeek.com/167195/how-to-change-your-raspberry-pi-or-other-linux-devices-hostname/

为你的 Pi 创建网络

如果您可以访问附近带有以太网端口的路由器或已将 Wi-Fi 拨号器添加到您的 Pi 上,那么上述方法将很好地工作。如果情况不是这样——例如,如果您在酒店房间——还有另一种与 Pi 一起工作的简单且不错的方法:在您的 Pi 和台式机/笔记本电脑计算机之间创建有线网络。

此过程在 Windows、Mac OS 和 Linux 上都受支持,并在互联网上有很好的文档记录。²⁴ 例如,我们将描述如何在 Mac OS 机器上执行此操作。

^(24)

pihw.wordpress.com/guides/direct-network-connection/

如果您的机器没有以太网端口,您需要一个以太网到 USB 或 Thunderbolt 适配器。要开始,将您的 Pi 插入机器上的以太网端口。然后,打开系统偏好设置并选择共享。如图 图 4.3 所示,启用从 Wi-Fi 到 Thunderbolt 以太网的互联网共享。这将有效地将您的机器通过 Wi-Fi 获得的互联网连接与 Pi 共享。

图 4.3. 在 Mac OS 上通过 Thunderbolt 以太网与您的 Pi 共享互联网

图片

如果您没有更改以太网端口的配置,这应该会直接工作。如果不工作,请再次转到系统偏好设置并检查以太网连接(例如,USB 以太网或 Thunderbolt 以太网)是否设置为使用 DHCP。

4.2.5. 连接到您的设备

一旦 Pi 成功启动,您就可以使用 SSH(安全外壳)连接到它。

在 Linux 或 Mac OS 上 SSH 到您的 Pi

在 Linux 或 Mac OS 机器上,SSH 客户端已经安装,所以您需要做的就是使用以下命令打开终端(默认密码是“raspberry”)。

列表 4.3. 使用 ssh 连接到 Pi

图片

在 Windows 上 SSH 到您的 Pi

要在 Windows 上使用 SSH,您可以下载 PuTTy SSH 客户端。²⁵ 此客户端轻量级,甚至不需要安装:下载它并双击以启动它。在主机名字段中输入您的 Pi 地址(raspberrypi.local 或 IP 地址),然后单击打开。然后,您的 Pi 应该会提示您输入用户名(默认为“pi”)和密码(默认为“raspberry”)。

^(25)

www.chiark.greenend.org.uk/~sgtatham/putty/

注意!

登录后,使用 passwd 命令更改您的密码。如果您要将设备连接到互联网的“野生”世界,这可能是个好主意,除非您准备好将您的家开源!

4.3. 在 Raspberry Pi 上安装 Node.js

如前一章所述,Node 正在缓慢但稳步地进入嵌入式系统领域,为嵌入式世界的传统 C 环境提供了一个很好的替代方案,因此让我们看看如何在 Raspberry Pi 上使用 Node。

Node.js 框架默认安装在最新的 Raspbian 版本上,但您需要在您的 Pi 上安装最新版本。安装过程相当简单,但您需要特殊版本的 Node。我们经常听到您说:“为什么我不能直接使用标准的 Node.js 版本?”好吧,正如之前提到的,Pi 以及大多数嵌入式设备都运行在 CPU 架构上,这些架构与您的 PC 上运行的架构(x86 或 x64)不兼容。更确切地说,许多嵌入式设备运行在 ARM^([26]) 处理器上,这正是您的 Pi 上的处理器。因此,您在 Pi 上需要的 node 二进制文件与您在 PC 上需要的不同。幸运的是,自从 Node 版本 4.0.0 以来,ARM 二进制文件也已在官方 Node 网站上提供。

²⁶

如果您决定使用非 ARM 架构的平台,您需要找到适用于您系统的编译好的 Node.js 版本,或者从目标平台上的源代码编译它。

要安装 Node.js 的 ARM 版本,请访问 Node 下载页面 nodejs.org/en/download/ 并选择适合您的 Pi 或其他嵌入式设备的正确版本。如图 4.4 所示,要下载的存档取决于您的设备使用的 ARM 架构版本。例如,Pi 3 运行在 ARMv7 架构上。

图 4.4. Node.js 下载页面:支持 ARM CPU,但您需要为您的嵌入式设备型号选择正确的架构。例如,Pi 2 和 3 是基于 ARMv7 CPU 架构构建的,而 Pi Zero 使用 ARMv6 架构。

一旦找到正确的链接,复制它(右键单击复制链接位置)并运行以下列表中显示的命令。这将安装 Node 到您的 Pi 上。

列表 4.4. 在您的 Pi 上安装 Node.js

如果安装成功(当然成功了!),此命令应给出 Pi 上安装的 Node.js 版本。在撰写本文时,4.x 是 Node.js 的最新长期支持(LTS)版本,代码已与该版本进行了测试。

4.3.1. 在 Pi 上使用 Git 和 GitHub

现在 Node.js 已安装在 Pi 上,您需要一种方法来编写与它一起工作的代码。尽管您可以使用 Nano/Pico 或 Vi 等编辑器,但您会发现使用您喜欢的文本编辑器或集成开发环境(IDE)在您的桌面或笔记本电脑上编写代码要高效得多,并且更舒适,您可以在想测试代码时随时将其与 Pi 同步。

首先从 GitHub 上 fork 我们的项目。如果您不熟悉 Git,forking意味着创建 WoT-Book 代码仓库的自己的副本,这将允许您按需修改代码。这特别有用,因为它让您可以在桌面机器或 Pi 上编写代码,本地commit,然后将它push到 GitHub 上的 fork,最后在 Pi 上pull回来。要 fork 项目,请访问我们的 GitHub 仓库github.com/webofthings/wot-book,并点击 Fork 按钮。这将在您的 GitHub 空间中创建 WoT-Book 仓库的副本。然后您可以在 Pi 上安装 Git 并克隆项目,如下一列表所示。

列表 4.5. 在 Pi 上配置 GitHub 并 fork 项目

4.3.2. 那么,这又意味着什么呢?

希望您在旅途中没有受到太多伤害,并且能够按照描述完成所有操作。如果是这样,您做得非常好,可以给自己鼓掌。现在您有一个功能齐全且已准备好 WoT 的 Raspberry Pi,您不仅可以将第一台传感器和执行器连接到它,如下一节所示,还可以运行即将到来的章节中等待您的所有代码示例。

4.4. 将传感器和执行器连接到您的 Pi

您的 Pi 现在已准备好征服万维物联网,但在现实世界中它还没有太多可以工作的东西。为了使其与现实世界更加紧密相连,您需要将一些传感器(例如,湿度传感器)和执行器(例如,LED)连接到 Pi 上。

4.4.1. 理解 GPIO 端口

在大多数平台(包括 Pi)上执行此操作的方法是使用通用输入/输出(GPIO)端口连接传感器和执行器。本质上,GPIO 是一个可以读取或输出电流的引脚。GPIO 有两种模式:输入模式和输出模式。当选择输出模式时,引脚可以设置为 HIGH,这意味着它输出 3.3 伏特;当引脚设置为 LOW 时,它是关闭的,不输出任何电压。在输入模式下,您实际上可以读取引脚上的值。与 Arduino 等其他嵌入式平台不同,Pi 仅支持数字输入。这意味着您只能与向输入引脚提供一系列 0s(LOW,约 0 伏特)或 1s(HIGH,3.3 伏特)的组件一起工作——即数字组件。例如,LED 是一个数字执行器,按钮是一个数字传感器。

另一方面,模拟组件是指那些不仅提供或消耗 LOW 和 HIGH,而且在引脚上还提供或消耗可变电压的组件。例如,一个便宜的光敏电阻是一个模拟光传感器,一个电位器按钮是一个模拟执行器。如果您想尝试模拟传感器和执行器,有一些扩展板可以连接到您的 Pi 上,使其更适合模拟设备。27

²⁷

这里有一个简单的教程,教你如何从你的 Pi 读取模拟传感器:learn.adafruit.com/reading-a-analog-in-and-controlling-audio-volume-with-the-raspberry-pi/overview

回到我们的 GPIO:它们的编号取决于 Pi 的型号。不幸的是,编号一点也不直观!图 4.5 帮助你理解每个 GPIO 引脚对应于 Raspberry Pi 3、2 和 B+ 的内容。GPIO 引脚自 Pi A+ 以来布局完全相同。

图 4.5. Raspberry Pi Zero 和 Pi 3 上的 GPIO、电源和地线布局。

在这本书中,当我们提到,例如,引脚 12 时,我们指的是图 4.5 中的引脚 12,而不是 GPIO12,后者将是引脚 32。

在下一节中,你将动手连接一个被动红外传感器、温度传感器和湿度传感器到 Pi 的 GPIO。

4.4.2. 使用面包板和电子组件工作

让我们从硬件部分开始。为此,你需要一个面包板,但不是你可能在厨房里找到的那种。如图 4.6 所示,面包板是由塑料和金属制成的板,可以防止你在创建原型时焊接组件。基本上,外部的蓝色行是连接到地线(GND,-)的那一行。这一行中的所有孔都通过一个金属板连接。外部的红色行是接收电源(VCC,+)的那一行。所有行都是连接的。内部的列是用来放置像 LED 和传感器或电阻这样的组件的。

图 4.6. 一个典型的面包板,其中外部的行以及内部的列都是连接的。用细蓝色线标记的线通常用于连接到地线,而用细红色线标记的线用于连接到电源。

对于我们的“Hello World”闪烁 LED 示例,我们首先将元件放置在面包板上,如图 4.7 所示。28 如果这是你第一次与电子设备打交道,我们建议你使用防静电垫或接地带,以避免损坏你的 Pi。29

²⁸

这是由 Fritzing 创建的,一个用于创建电子原理图的非常酷的工具:fritzing.org/.

²⁹

在这里了解更多关于防静电产品的信息:www.explainthatstuff.com/howantistaticcoatingswork.html

图 4.7. 通过电阻将 LED 连接到 Pi 的 GPIO 端口。电阻和 LED 插入到面包板的孔中。不需要焊接任何东西!

根据图中的原理图,将 LED 和 330 欧姆电阻(颜色代码:橙色、橙色、棕色、金色/银色)放置在面包板上。请注意,电阻没有方向;它们只需插入即可,因此您可以以任何方式连接它们。如果您想知道电阻的用途,它通过限制通过 LED 的电流来防止 LED 熔化。它还确保在您反转 VCC 和 GND 引脚的连接时 LED 不会烧毁。请注意,您也可以使用更大电阻的电阻;例如,1K 欧姆——棕色、黑色、红色、金色/银色。这将降低 LED 的亮度。然后使用电阻将 LED 短腿所在的列连接到 GND(-,蓝色)线,使用电缆将长腿所在的列连接到 VCC(+,红色)线。最后,将一根电缆(理想情况下是黑色电缆以表示地线)连接到引脚 6(GND),另一根(理想情况下是红色电缆以表示电源)连接到引脚 7(GPIO4)。

4.4.3. 从 Node.js 访问 GPIOs

硬件现在已准备就绪,您可以开始使用 GPIOs。在 Linux 中,GPIOs 并不那么神秘。正在读取或写入引脚的值可以通过文件访问,因此理论上您可以直接从 Node 代码中读取这些虚拟文件。正确地这样做对于初学者来说不是最容易的事情,所以我们不会在本书中介绍这种方法,但我们将使用其他人制作的库。您可以在 Pi 上找到数十个 Node GPIO 库,提供不同的抽象层和功能。我们决定使用一个我们喜欢的名为onoff的库。^([30]) 对于更高级的用户,pi-gpio^([31]) 将是一个极好的替代方案。

³⁰

github.com/fivdi/onoff

³¹

github.com/rakeshpai/pi-gpio

要完成本章的下一组练习,您可以从我们的 GitHub 仓库中 fork 它们并进入 chapter4-gpios 文件夹,或者从头创建一个新的文件夹。进入该文件夹并使用 NPM 安装onoffnpm install onoff --save)。

注意,以下使用onoff库的示例在您的 PC 上无法工作,因为 PC 没有可访问的 GPIO;它们只能在您的 Pi 上运行。

blink.js——物联网的 Hello World

您现在可以准备将 Pi 与传感器和执行器进行接口。在软件工程中,一个人可以编写的最简单的代码——著名的 Hello World——在控制台显示“Hello World”。物联网的 Hello World 等价物是使一个真正的 LED 闪烁,所以让我们构建一个,如列表 4.6 所示。

如前所述,您将使用引脚 7,对应 GPIO4(见图 4.5)。然后您将创建一个函数,将引脚设置为输出模式,这意味着您将在其上“推送”电流。然后根据模运算的结果激活或去激活引脚,并在指定的时间间隔后再次启动。

最后,你应监听SIGINT,这对应于按下 Ctrl-C,并在退出前确保释放引脚并关闭 LED。

列表 4.6. blink.js:物联网的 Hello World

图片

保存此文件,通过输入node blink.js来运行它。如果一切按预期工作,你现在应该看到 LED 在闪烁。如果是你的第一个物理原型,做得好!

pir.js—连接一个接近传感器

现在,让我们通过向 Pi 添加一个传感器来转向一个稍微更有趣的使用案例。你将添加的传感器被称为被动红外(PIR)传感器。PIR 对红外光敏感,并捕捉由人体或其他温暖物体(例如你的猫)发出的光束——但不是僵尸。这使得它成为检测运动和入侵的便宜且理想的传感器,因此这些传感器通常用于简单的防盗报警器或自动开关灯,以便在需要时打开/关闭灯光。

再次,让我们从项目的硬件部分开始。你需要一个数字 PIR 传感器,如我们在“认识 Raspberry Pi”部分的购物清单中提到的,以及五根电缆和一个面包板。

如图 4.8 所示,PIR 传感器有三个引脚:一个标记为 VCC(这是它的 5 伏电源),一个标记为 OUT(在任何时刻将包含传感器状态的数字值:如果检测到热体,则为 1,否则为 0),最后一个标记为 GND(接地)。标记为 OUT 的引脚需要连接到一个数据引脚(在我们的例子中是 GPIO 17)。

图 4.8. 将被动红外传感器连接到 Pi。面包板上的大传感器是 PIR,它连接到 5 伏电源、一个 GPIO 引脚和地。

图片

按照如图 4.8 所示连接组件。图 4.8。首先,将 GND 引脚连接到 Pi 上的地 GPIO(例如,引脚 39),如果你有公对公电缆(一种可以插入每一边引脚的电缆),可以直接连接,或者通过面包板连接。

然后,将 PIR 的 OUT 引脚连接到 Pi 上的 GPIO 17(引脚 11);这是你将读取结果的引脚。最后,将 VCC 引脚连接到一个 GPIO(例如,引脚 4),在 Pi 上提供 5 伏电压,同样可以直接连接或通过面包板连接。

您现在可以进入编码部分。为了使这可行,您可以使用onoff readSync()函数定期轮询 PIR 传感器。但与其不断轮询传感器的状态并多次读取相同的状态,不如在物理世界状态改变时调用您的代码要好得多。这正是onoff库提供的抽象级别;参见列表 4.7。watch(callback)函数允许您使您的代码能够监听 GPIO 端口的更改状态。然后库会自动在状态改变时调用您传递给它的callback函数。这是一个事件驱动编程的好例子,其中您的代码仅对您感兴趣的事件做出反应;在这种情况下,您的代码不需要持续检查某个值,而只需在它改变时执行某些操作。这使您的代码更简单,同时也减少了出现难以理解的错误的机会。事件驱动方法非常适合许多现实世界的事物,这在第六章中您将看到,对网络来说这是一个挑战。

列表 4.7. pir.js:使用onoff库读取 PIR 传感器

105fig01_alt.jpg

正如您可以从列表中看到的那样,onoff库将监听上升和下降的硬件中断,并且每当 GPIO 引脚的状态改变时,它将调用通过watch()函数注册的回调函数。

dht.js—连接温度和湿度传感器

最后,您将通过将其连接到结合温度和湿度读数的传感器来使您的 Pi 感知环境。我们将使用的传感器是 DHT22(也称为 AM2302)。首先,按照图 4.9 所示将其连接到您的 Pi。DHT22 有四个引脚。从右到左,按照以下步骤操作:

^([32])

这也可以与 DHT11 传感器一起工作。

图 4.9. 将 DHT22 温度和湿度传感器连接到 Pi

04fig09.jpg

1. 将第一个 DH22 引脚连接到地(GND)引脚;例如,引脚 39。您不需要连接到第二个引脚。

2. 将第三个 DH22 引脚连接到您的 Pi 的 GPIO12(引脚 32),并在 DH22 引脚和 Pi 引脚之间放置一个 4.7K 欧姆电阻(黄色、紫色、红色、金色/银色)^(33)。

^([33])

您可以使用 4.7K 到 10K 欧姆的电阻。

3. 将此电阻连接到面包板上的 VCC 线,红色线。

4. 将第四个 DH22 引脚连接到面包板上的 VCC 线。

5. 将 3.3 伏电源连接到面包板上的 VCC 线。

由于 DHT22 使用特殊的协议,您首先需要在 Pi 上安装一个额外的驱动程序,称为 BCM 2835 C 库。^(34) 下一个列表显示了如何安装它。

^(32)

www.airspayce.com/mikem/bcm2835/index.html

列表 4.8. 安装 BCM2835 驱动程序

图片

你现在可以使用 Node 代码与传感器进行交互。为此,你需要使用一个名为node-dht-sensor的 Node 库,你首先需要使用npm install --save node-dht-sensor命令进行安装。要在 Pi 上运行的代码如下所示。

列表 4.9. dht.js:与 DHT22 传感器通信

图片

将此文件保存,并使用sudo命令以超级用户权限运行,因为访问 BCM 2835 驱动程序需要这样做:sudo node dht.js。如果一切正常,你应该每两秒看到温度和湿度值。

这标志着你对 GPIO 的第一次接触,GPIO 是向你的 Pi——或者任何其他嵌入式设备——添加功能的一种极好方式,以便它可以感知或控制现实世界。现在你已经掌握了基础知识,没有什么可以阻止你向 Pi 添加其他传感器和执行器;你将在网上找到大量的教程。

极客角落——我想看到比特!

类似于onoffnode-dht-sensor的库可以防止你处理与底层硬件传感器和执行器交互的繁琐细节。这也是物联网的核心:抽象这些(复杂的)细节,以便你可以专注于你的创意应用,并使用网络工具构建它们。你可能仍然想知道这些库大致是如何工作的。基本上,onoff通过名为epoll的库监视 Linux 使用的虚拟文件,以更新 GPIO(1 或 0)的值。node-dht-sensor需要检索更复杂的二进制数据,并使用一个 C 库,通过在底层嵌入式世界的比特中使用两种流行的协议进行通信:I2C(集成电路间)和 SPI(串行外围接口)。如果你想深入了解,阅读有关这些协议的内容是一个很好的开始。

4.4.4. 超出书本范围

如果你想了解更多关于嵌入式平台、传感器和硬件原型设计的信息,有许多优秀的资源和书籍可以帮助你。关于新兴嵌入式平台的信息来源是 Postscapes([35])以及他们的物联网工具包.([36])对于更工业化的方面,你可以使用嵌入式门户.^([37])关于硬件原型设计和电子资源,确保你查看 Make^([38])博客文章、书籍和杂志。你也可以考虑 Instructables^([39]), Element 14^([40]), 和 Sparkfun 社区^([41]), 在那里你可以找到许多逐步教程和大量有用的建议。

^((35))

postscapes.com

^((36))

postscapes.com/internet-of-things-resources/

^((37))

www.embedded.com/

^((38))

makezine.com/

^((39))

www.instructables.com/

^((40))

www.element14.com/community/welcome

⁴¹

learn.sparkfun.com/

毫无疑问,这一章是一个挑战!在一个章节中涵盖如此多的不同技术和具体技能并不容易,所以你到目前为止做得非常出色。但让你的树莓派为物联网做好准备只是开始。

4.5. 概述

  • 许多类型的嵌入式平台在物联网中普遍使用,您学习了如何为您的用例选择正确的平台。

  • 设备操作系统的两大主要类别:实时操作系统(RTOS)和 Linux。

  • 通过在树莓派上安装 Linux,您可以轻松地通过 SSH 远程访问它,并配置它以准备好物联网。

  • 嵌入式设备允许您使用面包板、电线和电阻将各种传感器和执行器连接到 GPIO 引脚。

  • 在您的树莓派上使用 Node.js 可以轻松编写简单的应用程序,这些应用程序可以从您的传感器读取数据,并使用onoff库异步地通过 GPIO 端口控制 LED。

现在您家里有了真实的嵌入式设备,是时候将其连接到物联网了。接下来的章节将确保它无缝地集成到万维物联网中。第一步将在下一章中介绍:为您的树莓派、传感器和执行器设计 API,并使用 REST、HTTP、WebSockets 和 JSON。在接下来的章节中,您将看到如何通过 Web API 使这些传感器和执行器可访问。

第五章. 构建物联网网络

本章涵盖

  • 网络分类模型和分层架构简介

  • 网络化事物各种协议概述

  • 传输层和应用层之间差异的回顾

  • 一种系统化的方法来选择适合您用例的通信方式

  • 物联网分层架构概述

在上一章中,你学习了如何配置单个设备——你的树莓派——以及它是如何通过各种传感器和执行器与真实世界互动的。但你的树莓派仍然非常孤独:它不是任何大型网络的一部分,而且(目前)还不能与其他事物进行交流。它也无法通过互联网与其他应用程序和服务进行通信。显然,物联网的真实价值在于设备变得社会化,能够与其他设备或应用程序进行交流。今天,物联网系统中正在使用数百种不同且不兼容的网络协议,遗憾的是,我们离所有应用程序和设备都能轻松交流的物联网还有很长的路要走。我们是如何走到这一步的,为什么没有统一的官方物联网协议?我应该为我的设备使用哪种网络协议?我应该为我的智能鸟食器使用蓝牙还是 Wi-Fi?这些都是有效的问题,本章的目标是为你提供一个关于最常见协议的广泛概述,以及针对特定场景的最佳选择。尽管物联网的魅力在于你选择哪个并不重要,因为物联网是一个高于此的抽象层,但你将极大地受益于理解设备如何连接形成大型事物网络的基础知识。

事实是,如果你只想为物联网编写客户端应用程序和服务,你不需要担心底层协议,可以直接跳转到第 5.4 节。但为了真正理解物联网是如何工作的,或者如果你正在构建一个联网产品,接下来的几节将提供关于用于构建网络设备的底层协议和技术的快速课程。

图 5.1 展示了本书中考虑的三种连接类型(阶段)。首先,我们有一个孤独的设备,它可以感知和与其周围环境互动,但没有任何连接性。其次,该设备支持至少一种通信协议,可以与其他设备通信,形成一个小的设备网络。第三,这些设备可以连接到更广泛的网络生态系统,这样任何应用程序或服务都可以通过互联网与这些设备进行通信。你在第二章中已经看到了这种最后阶段的缩影,当时你与全球的一些真实网络设备进行了交互。

图 5.1. 从单个树莓派到网络化的嵌入式设备,再到与网络及其生态系统交互的网络化嵌入式设备

图 5.1

在本章中,我们探讨一个孤立设备如何进入 2 和 3 阶段。首先,我们查看各种设备网络协议,例如 ZigBee 和蓝牙,并解释它们各自的优缺点。我们并不期望你一夜之间成为物联网网络协议专家,但你将足够了解这些协议,以了解它们之间的区别以及哪个更适合哪种使用场景。接下来,我们将向您介绍事物的应用层,并通过观察它们的优缺点来描述最常见的协议。最后,我们将展示如何利用后续章节所基于的物联网架构,以系统化的方式将事物集成到网络中。

5.1. 连接事物

你可能已经知道,计算机通过使用网络协议相互通信。让我们为深入了解物联网中最常见的网络协议做好准备,以便你知道哪个最适合你的下一个物联网项目。在深入探讨协议之前,我们必须介绍两个重要的网络概念:拓扑和分类模型。

5.1.1. 网络拓扑

了解一个设备网络与另一个设备网络的不同的一种方法是通过观察其拓扑,这是一个指代设备之间连接结构的术语。网络成员通常被称为节点,拓扑是节点在空间上的组织形式,以形成一个网络。让我们看看网络的不同的拓扑结构以及在不同情况下使用哪种拓扑。

点对点

最简单的网络拓扑结构发生在任何两个设备建立直接连接并开始相互交谈时。这种模型被称为点对点,特别用于可穿戴设备的环境:你通过配对两个设备,使用蓝牙将你的健身追踪器与你的手机同步。这种模型也可以用于 Wi-Fi 设备的初始配置。例如,恒温器可以创建一个名为Wi-Fi ad hoc 模式的点对点网络,你可以在其中通过手机连接并发送你的家庭网络凭据和配置到恒温器。

星型网络

在星型网络拓扑结构中,如图 5.2 所示,多个节点与单个中心节点进行通信,可能不知道网络中的其他节点。这种模型也常被用作星形星形,其中每个中心节点又连接到另一个附近的中心节点。

图 5.2. 星型拓扑:所有节点都与单个中心节点通信。星形星形拓扑:节点连接到中间节点(网关),这些中间节点又使用星型拓扑连接到中心节点。

图 5.2

在物联网中,蜂窝电话网络通常采用星形拓扑结构,其中您的手机(一个节点)连接到最近的手机天线(中心节点)。星形星形拓扑也可以用于家庭自动化系统,例如智能照明系统。例如,灯泡(节点)可以使用无线无线电协议,如 ZigBee,与多个网关进行通信。这些网关随后通过以太网连接到您的互联网路由器(中心节点)。

网状网络

在物联网中您可能遇到的最后一个网络拓扑也是最复杂的:网状网络。在网状网络中,没有中心节点,因为网络中的任何节点都能够将消息从一个节点转发到另一个节点。例如,考虑图 5.3 中显示的设备。左侧的 Pi 与右侧的 Intel Edison 距离太远,无法直接通信。在网状网络中,Pi 可以使用附近的设备作为中间跳转,称为中继,将消息转发到目的地。在这种情况下,Pi 可以使用连接到 Edison 的 Arduino 来中继消息。简而言之,网状网络意味着您可以通过添加更多节点来扩展每个设备的范围。您还可以使网络对单个节点的故障更具鲁棒性。例如,如果 Arduino 失败,Pi 仍然可以通过所有其他设备与 Edison 通信。

图 5.3. 网状拓扑:消息通过多个设备转发以到达目的地。

互联网是网状网络的最佳和最大例子:您与想要与之通信的服务器之间没有直接的通信(点对点)。相反,消息通过互联网跳跃从一个路由器到另一个路由器,直到到达最终目的地。

您会看到网状网络在物联网中扮演着重要的角色。这在没有固定网络基础设施(例如,以太网或 Wi-Fi)可用的情况下尤其有用。以监测偏远森林中的污染水平为例。那里没有 Wi-Fi 基础设施,因此形成网络的最佳方式是在森林中放置节点,并通过形成网状网络相互通信。只需要连接到互联网的节点数量很少,可以使用 3/4/5G 甚至卫星链路。网状拓扑由许多专门的物联网协议支持,我们将在稍后介绍,例如 ZigBee 和 6LoWPAN。

5.1.2. 网络分类模型

计算机网络是一个庞大而复杂的话题,人们很容易在其中迷失方向。为了使导航通信系统的复杂性变得更容易,已经提出了网络分类模型来组织不同层中存在的各种协议,每一层都有其特定的目的,并且只知道其直接下一层的细节。在本节提供的网络模型简介阅读后,您将对该协议在地图上的位置有一个合理的理解,特别是所有这些协议之间是如何相互关联的。一旦您理解了应用层协议与较低层(网络/传输层协议)之间的区别,您将很快意识到为什么物联网(IoT)提出了一种与物联网根本不同,但并非必然不兼容的方法,并且在许多其他情况下是互补的。现在让我们来看看今天使用最广泛的两种模型:OSI(开放式系统互联)和 TCP/IP。

OSI 和 TCP/IP 模型

如果您是 IT 专业人士,您肯定听说过 OSI 模型或广泛使用的替代模型,即互联网协议套件(IPS)或 TCP/IP 模型。这些模型的基本概念是定义 ,这本质上是一种抽象。每一层建立在下一层之上;一层服务于其上的一层,并由其下一层服务。这意味着特定层的协议只能对直接下一层将提供什么做出假设。因此,每一层都专注于特定的问题集,并为上层抽象这个问题。

OSI 定义了七层,而 IPS 只列出了四层,如图 5.4 所示。figure 5.4。在这里,我们将重点介绍 IPS 模型,因为它是互联网的模型。详细讲解 IPS 模型的每一层超出了本书的范围^([1]),因此在这里我们只总结它们是什么以及它们的作用。

¹

《计算机网络》(第 5 版)由安德鲁·S·坦南鲍姆(Pearson,2010)著,是一本帮助您更好地掌握计算机网络核心概念细节的杰出书籍,包括分层模型。

图 5.4. OSI 模型(左侧)与互联网协议套件(又称 TCP/IP,右侧)的比较,以及一些与物联网(中心)最相关的协议和协议栈的示例

图片

  • 物理层(也称为链路层)关注网络的单个段之间的通信技术以及传输信息所需的接口。这里的要角是比特转换为数据包。这就是以太网或 Wi-Fi(IEEE 802.11)协议所在的地方。

  • 网络层(也称为互联网层)关注连接独立网络中的主机。这里的常见角色是 IP 地址;因此,这就是互联网协议(IP)所在的地方。

  • 传输层 处理两个主机之间的信息通信。这是两个著名的互联网协议 TCP 和 UDP 所在的地方。

  • 应用层 关注两个应用程序之间的数据交换。这是软件开发人员通常接触到的层。因为 HTTP 就生活在这里,所以网络浏览器或移动应用程序使用它来让我们做各种事情,从管理我们的在线日历到阅读电子邮件到检索维基百科内容。

正如你在本章后面将看到的,应用层是物联网架构所在的地方。显然,这意味着没有应用层以下的网络层,物联网就无法存在。为了从手机和浏览器上的应用程序中消费真实世界的数据,我们首先需要从模型较低层以某种方式获取这些数据。这里的好消息是,如果你抽象出应用层以下的层,那么使用哪些协议或接口并不重要。

当你将这些不同层的协议组合在一起时,层与层相结合,形成了一个协议栈。正如你稍后将会看到的,存在许多不同的协议栈;例如,蓝牙或 ZigBee,其中一些在图 5.4 中显示。

5.2. 物联网的联网协议

在第一章中,我们将物联网定义为“一个由物理对象组成的系统,这些对象可以通过电子设备发现、监控、控制或与之交互,这些电子设备通过各种网络接口进行通信,最终可以连接到更广泛的互联网。”但事物“连接到互联网”意味着什么呢?这意味着你可以使用图 5.4 中显示的互联网协议栈的通信协议与之交互。

更具体地说,这意味着事物应该使用第 2 层的 IP 和第 3 层的 TCP 或 UDP。如果是这样,那么事物“说”的就是互联网语言!

5.2.1. 空间考虑因素

在对网络进行分类时,我们考虑的最后因素是两个节点之间的距离(范围)。根据节点需要彼此多远或多近,以及它们是否可以有线连接或必须使用无线电信号,可以将协议进行分类。物联网应用的各个空间范围在表 5.1 中显示。

表 5.1. 各种物联网联网协议的空间范围和范围
空间范围 典型范围 示例
近场 (NFC) < 10 cm NFC 论坛
个人区域网络 (PAN) 1 m–50 m 蓝牙,ZigBee,Thread,IEEE 802.4.15
局域网 (LAN) 50 m–1 km Wi-Fi, 以太网
广域网 (WAN) 1 km–50 km SigFox,LoRa,5G,4G,GSM

这很好地说明了残酷的现实:物联网不是运行在单一的网络协议上,可能永远不会因为不同设备在范围方面的不同需求。为什么它们不都运行在广域网协议上呢?因为距离越远,你需要更多的电力,这对电池供电的设备来说是不可能的。正如你将在本章后面看到的那样,一些协议允许低功耗设备在长距离传输数据,但代价是你只能传输非常少量的数据,也称为低带宽。

5.2.2. 互联网协议与物联网

为了帮助您了解物联网协议之间的差异,让我们回顾一下 IP、TCP 和 UDP 协议的基本知识。如果您熟悉这些,欢迎您跳过这一节。

互联网协议:IPv4 和 IPv6

互联网协议与我们通常所说的互联网名称相同并非偶然:这个协议用于发送到互联网上的任何字节数据。IP 为网络中的任何节点提供唯一的 IP 地址,并负责使用它们的 IP 地址在任意节点之间路由信息包。当你在浏览器中输入 URL 时,IP 会找到你想要访问的网站的地址,并在许多子网络中检索页面——例如,你的本地网络、你国家的网络等等。

目前,IP 版本 4 (IPv4) 是最广泛使用的协议版本。IPv4 地址的大小为 32 位(见图 5.5),这意味着有 2³² 个唯一的 IP 地址,或大约 43 亿。当 IPv4 在 1974 年被发明时,这似乎已经足够满足所有将要连接到互联网的机器。当时没有人想象到 40 年后互联网会如此流行。今天,思科估计有超过 150 亿个物品连接到互联网。2 换句话说,我们已经很长时间以来一直在缺少 IPv4 地址。

²

blogs.cisco.com/news/cisco-connections-counter

图 5.5. 比较 IPv4 和 IPv6 的大小和寻址空间

图 5.5

随着物联网的发展,这种短缺已成为一个主要障碍。第一个对策是网络地址转换 (NAT) 的想法。NAT 允许本地网络中的多个主机共享一个公共 IP 地址。大多数路由器和防火墙都可以执行 NAT,并且它已成为今天物联网的基石。但 NAT 只是一个补丁,因为它给网络增加了许多复杂性。这导致了设计一个更长期的解决方案:IPv6。

IPv6 基于表示为一系列由冒号分隔的四组十六进制(基数为 16)字符的 128 位地址(参见图 5.5),这允许 2¹²⁸ 个唯一的地址——即 340,282,366,920,938,463,463,374,607,431,768,211,456 个不同的 IPv6 地址,以防你没有做心理计算。这能满足物联网对 IP 地址的需求吗?为了更清楚地说明这一点,这个数字意味着我们可以为地球上的每一个原子分配一个 IPv6 地址,并且我们仍然有足够的 IP 地址为另一个 100 个地球上的每一个原子分配!

IPv6 将对物联网的规模化成功起到关键作用。但将整个互联网升级到 IPv6 并非易事,因为这几乎需要升级所有连接到互联网的设备。从你的笔记本电脑或手机的操作系统到任何连接到互联网的路由器或防火墙的固件,所有这些都将必须支持 IPv6。

你的树莓派出厂时并没有启用 IPv6,但添加 IPv6 支持相当简单,如下面的列表所示。只需通过 SSH 连接到你的 Pi 并运行此代码。

列表 5.1. 在你的树莓派上启用 IPv6 支持

117fig01_alt.jpg

如果你第一次接触 IPv6,那么欢迎来到未来!

互联网的传输协议

正如你所看到的,网络层只负责在互联网上的两个主机之间路由数据包,而不是如何将数据交付给应用程序,这是上层传输层的责任。这一层引入了源端口和目的端口的观念,用于标识应用程序。例如,它给一个网页服务器分配一个端口,给一个邮件服务器分配另一个端口。你可以将端口想象成一个应用程序(如网页服务器)可以租用的邮箱,以接收数据包。源和目的 IP 地址以及端口号的组合形成了通常所说的网络套接字或简称套接字。

在互联网上,这一层有两个协议:传输控制协议(TCP)和用户数据报协议(UDP)。了解 TCP 和 UDP 以及它们之间的区别的最佳方式是从一个笑话开始;参见图 5.6。

图 5.6. 一个好的 UDP/TCP 笑话![来源:pcp-comics.com/,经许可使用]

05fig06_alt.jpg

开个玩笑,这强调了 UDP 和 TCP 之间的基本区别:UDP 不提供可靠性和顺序交付,这就是为什么你可能永远听不到笑话,而 TCP 则提供。缺点是,TCP 更复杂,因此更重,它需要同步和确认消息来保证数据交付,我们将在下面详细说明。

理想情况下,我们应该在这里停下来,忽略细节。但一些物联网协议基于 TCP,而另一些基于 UDP,因此为了了解物联网协议之间的区别——以及笑话——我们现在将简要介绍这两种协议。

用户数据报协议

我们先从 UDP 开始,它更容易理解。UDP 是一种无连接协议,这意味着你不需要在消息的发送者和接收者之间建立连接(握手)。你发送消息,然后祈祷它能够到达,因为交付是不保证的。它通常会被送达,但有时不会,这就是那个笑话的由来!

让我们看看一个具体的例子。一个“事物”想使用 UDP 向服务器发送温度更新。它需要发送一个包含温度值的 UDP 消息,以及接收者的 IP 地址和端口号。如果接收者想要回复,“事物”也会提供自己的 IP 地址和端口号。这就像你用经济邮资寄一封信,希望它能够到达。对于价值不大的物品来说,这已经足够好了。这种模型被称为单播,因为消息是通过提供地址发送给唯一接收者的。由于 UDP 是无连接的,它也可以用来多播消息。这意味着它也可以用来向 IP 地址范围内的多个接收者发送消息——即子网。3 多播依赖于路由器将消息发送给多个接收者,因此在本地网络中效果很好,但在全球互联网上效果不佳,因为多播消息通常会被路由器和网关阻塞。

³

zh.wikipedia.org/wiki/Subnetwork

传输控制协议

UDP 的简单性是以牺牲一些对某些应用程序很重要的功能为代价的,例如消息的保证交付。要将贵重物品发送到全球的某个人,经济邮资是不够的——你需要使用一个可信赖的服务,这不仅需要大量的文件工作,还会让你的钱包变薄。这就是保证交付所付出的代价。

TCP 是互联网上的联邦快递。它也处理套接字,但通过添加确认消息、序列号、丢失数据包的重传以及拥塞和流量控制来确保通信的可靠性和顺序,我们将在稍后解释这些。

图 5.7 给出了 TCP 连接的简化概述。想象一个“物”想要向互联网上的一个应用服务器发送一条超级重要的消息;例如,你的烟雾探测器想要告诉你你的房子着火了。与 UDP 不同,TCP 是一种面向连接的协议,它首先做的事情是建立与服务器的连接。还要注意使用的众多 ACK 消息,这些消息是确认消息已被接收的确认。一旦建立连接,客户端可以发送分割成块的消息(称为TCP 数据包)。这些数据包的大小将根据网络的拥塞程度而变化,这被称为 TCP 的控制流机制。数据包还会添加序列号,以便当发送者没有收到特定数据包的 ACK 时,它会再次发送该数据包,确保整个消息已被接收。最后,“物”终止连接。

图 5.7. TCP 通信:客户端与服务器建立连接(SYN)并开始向其推送数据。一旦数据可靠地传输完毕,客户端将关闭与服务器的连接(FIN)。

图 5.7

物联网中的 TCP 与 UDP 对比

正如你所见,当必须保证交付时,TCP 是必不可少的。因此,互联网上的大部分流量都依赖于 TCP。但这种可靠性是有代价的:由于需要发送更多的消息,并且数据包也显著更大,因为它们包含更多保证可靠性和顺序的信息,所以协议更重。这意味着客户端和服务器两端的额外开销以及由于消息重传而产生的延迟,这使得 TCP 通常比 UDP 慢。因此,UDP 更适合对时间敏感的应用,在这些应用中,丢失的数据包比延迟的数据包更可取。例如,UDP 常用于 VoIP 应用或通过互联网流音乐或视频。

在物联网中,这两种协议都很吸引人,没有明显的赢家。一些广泛使用的应用协议,如 HTTP 或 MQTT(消息队列遥测传输;见 5.3.2 节),使用 TCP,而其他如 CoAP(约束应用协议;见 5.3.3 节)则基于 UDP。

由于 UDP 更轻量级且速度更快,它似乎更适合资源有限的嵌入式设备。但某些物联网应用可能需要保证交付;因此,一些基于 UDP 的应用协议正在尝试重现 TCP 提供的一些保证。更重要的是,与 TCP 不同,路由器通常拒绝来自互联网到本地网络的 UDP 流量,除非在过去的几秒钟内请求了消息。这意味着如果你尝试通过互联网用你的手机控制你的智能恒温器,你的恒温器可能永远收不到这个笑话。嗯,我们是说这个消息。

当 IPv6 主导互联网时,这将发生显著变化,因为所有事物都将能够拥有一个 IP 地址,NAT 将不再需要。但在此之前,TCP 在大型异构网络中部署仍然更加容易,因此物联网传输协议的竞争仍在继续。

5.2.3. 物联网个人区域网络

个人区域网络(PAN)协议用于与附近的事物进行通信,从可穿戴健身追踪器到你家的智能恒温器,再到你建筑中的联网车库门。PAN 协议在物联网中非常受欢迎,因为它们在通信范围和功耗之间提供了一个有吸引力的权衡。让我们首先描述并比较最常用的 PAN 协议。

IEEE 802.15.4 和 6LoWPAN

当你在物联网社区结识新朋友时,你肯定会听到他们谈论“15.4”。他们真正指的是 IEEE 802.15.4,这是一种低功耗、低成本、低速率的无线通信协议,用于相邻设备之间的通信。这些特性使其成为物联网的绝佳候选者,尤其是在考虑室内使用的资源有限的智能家居设备时。因此,15.4 成为了许多物联网个人区域网络协议的基础。

IEEE 802.15.4 协议的一个缺点是它不能通过互联网直接与其他设备通信——也就是说,通过 TCP/IP 或 UDP。这一限制通过创建 6LoWPAN 得到了解决,它允许连接两个世界。6LoWPAN 代表“IPv6 over low-power wireless personal area networks”。它使你能够在基于 IEEE 802.15.4 的网络中发送和接收 IPv6 数据包。它不仅使用 IPv6 地址,还优化了用于资源受限设备的 IPv6 头部大小。

ZigBee

ZigBee 已成为基于 IEEE 802.15.4 的最受欢迎的协议之一。ZigBee 有两个有趣的特点:首先,它支持网状网络。其次,ZigBee 不仅仅是一个物理层协议。如图 5.4 所示,它从物理层到应用层。不幸的是,ZigBee 是一个由一组公司控制的专有标准,需要付费会员资格才能构建和运输使用此协议的产品。尽管一些工业和爱好者平台(例如 Arduino)支持 ZigBee,但你的 Pi 并不直接支持它。通过使用特殊的屏蔽板^([4])或将 Telegesis USB 等 ZigBee 适配器添加到你的 Pi 上,可以为你的 Pi 添加 ZigBee 支持。

例如,www.cooking-hacks.com/documentation/tutorials/raspberry-pi-xbee

Thread

Thread^([5])是最近出现的物联网网络协议之一。像 ZigBee 一样,Thread 基于 IEEE 802.15.4 并支持网状网络。不同之处在于 Thread 尽可能重用开放协议,并通过 6LoWPAN 实现 IP,以便可以直接与互联网上的设备通信。此外,在传输层它使用 UDP。与 ZigBee 或蓝牙不同,Thread 不提供显式的应用层,并支持各种互联网应用层协议(比如网络标准?)。

官方网站:threadgroup.org

Thread 允许创建由 250-300 个设备组成的大型网状网络,这些设备可以部署在房屋或建筑物中。延迟虽然不是实时的,但相当低,典型交互的延迟小于 100 毫秒。它还节能,并允许运行 Thread 的电池供电设备使用数年。

虽然需要付费会员资格才能创建和销售使用 Thread 的产品,但该协议建立在开放标准之上,这使得它比其他协议更容易集成。将 Thread 支持添加到您的 Pi 上并不简单,主要是因为 Thread 协议规范相对较新。一旦采用该协议,您只需将 IEEE 802.15.4 或 ZigBee 适配器添加到您的设备即可,因为 Thread 协议只需要对 ZigBee 控制器进行软件(固件)更新即可工作。

蓝牙

蓝牙是另一个有趣的物联网协议,并且与迄今为止我们检查的其他个人区域网络(PAN)协议不同,它不是基于 802.15.4 的。使蓝牙如此吸引人的是它的普及性:几乎任何手机(以及许多其他设备)都支持蓝牙。这使得蓝牙成为可穿戴设备的理想选择,因为大多数这些物联网设备使用手机作为通向其他设备和网络的网关。

蓝牙由一个由多个技术合作伙伴组成的非营利联盟管理,因此需要会员资格才能构建经过认证的蓝牙硬件和软件堆栈。但许多标准和文档是免费可获取的.^([6]) 类似于 ZigBee,蓝牙协议栈也跨越从物理层到应用层的多个层次(参见图 5.4),我们将在 5.3 节中更详细地讨论。

bluetooth.com

自从它首次作为连接无线耳机到手机的协议被引入以来,蓝牙已经显著发展,以支持许多其他设备。蓝牙 4.0,也称为蓝牙智能或蓝牙低功耗(BLE),将蓝牙定位为许多物联网应用的优秀候选者。BLE 专注于降低能耗,这使得它非常适合电池供电的设备。

在撰写本文时,蓝牙标准尚未包括对网状网络的支持,尽管许多研究人员已经展示了如何实现。尽管如此,蓝牙联盟宣布正在努力将网状网络支持添加到标准中,7],这使得基于蓝牙的网状网络可能在不久的将来出现。

阅读更多:blog.bluetooth.com/range-limitation-what-range-limitation-introducing-mesh-networks/

许多设备都自带即插即用的蓝牙连接功能。例如,我们在第四章中提到的英特尔 Edison 提供了 Wi-Fi 和蓝牙 4.0 连接功能。Pi 3 也支持蓝牙 4.0 BLE;对于 Pi 的其他版本,您可以通过 USB 外置网卡添加 BLE 支持。8 下面的列表显示了如何从您的 Pi 上扫描蓝牙设备。

www.raspberrypi.org/learning/robo-butler/bluetooth-setup/

列表 5.2. 在 Raspberry Pi 上测试蓝牙 4.0:BLE

122fig01_alt.jpg

Wi-Fi 和低功耗 Wi-Fi

Wi-Fi,技术上称为 IEEE 802.11,是谈论无线连接时首先想到的协议。由于其普遍性,Wi-Fi 似乎与物联网的物理层完美匹配,这就是为什么越来越多的消费电子产品,如电视、微波炉、音乐播放器等,都支持 Wi-Fi。

但 Wi-Fi 协议(802.11a–n)对于某些物联网应用来说有限制。最大的问题是能耗。Wi-Fi 的第二个问题是其范围。在 Wi-Fi 网络中,所有节点都必须在接入点的范围内:Wi-Fi 没有网状网络。是的,互联网是网状网络的最佳例子,但这是在路由器层面,而不是单个 Wi-Fi 节点作为客户端的层面。这些问题正在得到解决,一个新的针对物联网优化的 Wi-Fi 标准(IEEE 802.11ah^([9]))正在开发中,它将具有更好的范围和更低的功耗。

en.wikipedia.org/wiki/IEEE_802.11ah

自从 Pi 3 以来,Wi-Fi 已集成到板上。对于其他 Pi 版本,添加 Wi-Fi 很容易,因为 Raspbian Linux 操作系统默认支持大多数 Wi-Fi USB 外置网卡。10 我们在第四章、4.2.3 节中解释了如何在 Pi 上设置 Wi-Fi。

^(10)

更多详情:elinux.org/RPi_USB_Wi-Fi_Adapters

EnOcean

另一个值得在此提及的较少为人所知的协议是 EnOcean,^([11])它是一种能量收集无线技术,主要用于建筑和其他工业解决方案。尽管你可能从未听说过它,但 EnOcean 对于物联网特别相关,因为它优雅地解决了能源问题。这项技术允许设备从其环境中收集所需的能量。例如,当您轻触开关时产生的动能被开关捕获并用于传输信息。其他产品也使用电磁、太阳能或热电能量转换器。

¹¹

在线:www.enocean.com/.

EnOcean 规范涵盖了物理和网络层,但还提供了应用层的附加规范。该协议是专有的,需要在网关上使用专用无线收发器模块来接收来自各种 EnOcean 设备的信息。这项技术节省了时间和材料,因为它允许在没有电线的情况下快速安装无需电池的设备。有趣的是,element14 为 Raspberry Pi 提供了一种 EnOcean 扩展板,这样你就可以将其变成一个 EnOcean 网关.^([12])

¹²

在线:www.element14.com/community/community/raspberry-pi/raspberry-pi-accessories/enocean_pi/.

总结 PAN

我们讨论的各种 PAN 协议总结在表 5.2 中,该表根据与物联网相关的标准比较了它们的特性。在观察范围列中,我们考虑室内操作时两个节点之间的距离。请注意,这些协议的范围强烈依赖于环境变量,如厚墙或干扰的存在。此外,如果协议支持网状网络——例如,ZigBee——最大范围可以更大,因为消息可以在多个节点之间中继。

表 5.2. 最常见 PAN 协议的比较
名称 电池使用 观察到的最大范围(室内) 网状网络 开放性 易用性 互联网集成
EnOcean 非常低 <30 m 中等
ZigBee <50 m 困难
Thread <50 m 中等 中等
蓝牙 低^([a]) <50 m 2016 年推出 中等 中等 否(即将推出)
Wi-Fi 高(即将降低) <30 m 否(互联网) 容易

^a

使用蓝牙版本 4(蓝牙低功耗)。

易用性是一个基于我们对这些不同协议的实际使用经验的主体评价。它仅仅表达了我们在实验室和实际环境中使用这些不同协议时所经历的痛苦。

最后,如果你的设备将接入电网,那么你应该使用 Wi-Fi(很好)、以太网(更好),或者两者都使用(理想情况下)。如果设备将在固定位置部署而不是移动,并且可以安装有线基础设施,那么你应该使用以太网,理想情况下使用以太网供电,这样你只需要网络电缆。

显然,还有许多其他的 PAN 协议,从无线 Z-Wave^([13))到有线 KNX.([14))。寻找所有这些协议的好地方是维基百科([15))或 Postscapes.^([16))。

¹³

Z-Wave 联盟

¹⁴

KNX 官方网站

¹⁵

个人区域网络

¹⁶

物联网协议

5.2.4. 物联网广域网

对于某些物联网应用,上一节中提到的 PAN 协议可能并不适用。如果你想要部署数千个节点来监控大面积区域,例如田野、森林或城市,从理论上讲,你可以使用 ZigBee。然而,在实践中,部署和维护长期(数月或数年)和大规模的 PAN 网状网络证明是极其复杂且昂贵的。你需要部署网关、复制器和放大器。电池需要更换。PAN 主要适用于节点之间的小距离,因此适用于较小规模的部署。

这种限制导致了另一种类型物联网网络的出现:广域网。物联网 WAN 的典型共同特点是涉及低功耗节点直接与非常高功率的称为基站或天线的网关通信。一个 WAN 的好例子是移动电话网络。让我们更详细地看看与物联网最相关的 WAN 网络。

移动网络:从 GPRS 到 5G

在没有线的情况下将事物连接到 IP 网络的最常见方式是,如果可用,使用移动电话网络。你可以使用移动网络的数据通道——例如,GPRS、3G 或 4G——或者使用短信。你只需要在设备上安装一个蜂窝调制解调器,然后就可以出发了——至少,只要你别忘了支付电话费!

细胞连接的缺点是它并不是为物联网设计的。节点需要大量的能量——你多久充一次手机电,再次?——因此使用数据通道定期发送数据对于电池供电的设备来说并不可行。对于数据密集度较低的场景,你也可以定期发送短信并将设备大部分时间置于睡眠模式。这对于仅发送数据的传感用例是可行的,但当你需要向设备发送命令时则不可行,因为你必须等待设备醒来处理你的请求。另一个限制是,手机网络并不是为数十亿个设备设计的。在任何给定时间,只有有限数量的设备可以连接到网关,因此你需要部署更多的天线。最后,使用移动网络的成本相对较高,因为定价是基于一人一设备的商业模式,这与物联网的需求不符。这些限制意味着当前的移动网络仅仅是物联网的一个临时解决方案。

在未来几年,将有数以亿计的设备需要连接到互联网,因此需要一个可行的长期解决方案。考虑到这一点,NGMN(下一代移动网络)联盟,^([17]),一个由强大的移动运营商、供应商、制造商和研究机构组成的移动电信协会,正在开发一个专门针对物联网的移动网络,代号为 5G。这个网络预计将在 2020 年推出,并将有多个改进。首先,它将更加节能。其次,它将允许每个网关同时连接更多的客户端。第三,它将支持网状网络,这将提高覆盖范围和能源使用效率。最后,5G 订阅的成本将更适合物联网模式。

¹⁷

www.ngmn.org

使用移动网络进行物联网通信绝对不是不常见的事情,因此有许多嵌入式设备平台支持这种通信模式,无论是原生支持还是通过简单的扩展板。你的 Pi 也不例外;几个扩展板或 Wi-Fi 外置设备可以将它连接到移动网络,无论是通过短信还是数据网络.^([18])

¹⁸

Raspberry Pi 无线选项

物联网广域网:低功耗广域网

由于当前蜂窝移动网络不适合大多数物联网应用,在过去几年中,在“低功耗广域网络(LPWAN)”这个总称下,物联网专用 WAN 的数量激增。结果是出现了一些技术上令人信服的解决方案,与类似的提供方案竞争。大多数这些网络采用星型模型:许多低功耗节点直接与连接到电网和 IP 网络的强大基站通信。由于这种架构,LPWAN 运营商也面临着一项重大挑战:部署他们的基础设施。这些专用网络需要在野外部署额外的通信天线,就像移动运营商不得不在景观上布置天线一样。

该领域的领先网络是 SigFox.^([19]) 作为第一个专注于物联网的大规模 LPWAN 运营商,SigFox 在几个欧洲国家拥有广泛的覆盖范围。但那里的竞争非常激烈,其他网络(例如,LoRa^([20]) 或 nwave 和 Weightless 联盟^([21]))正在迅速追赶。

^(19)

www.sigfox.com/en/

^(20)

www.lora-alliance.org/

^(21)

www.nwave.iowww.weightless.org

这些网络有许多优点。首先,部署基础设施是运营商的业务,而不是你的。其次,基站的范围相当高,可达数公里。此外,每个基站可以处理来自可能连接到单个基站的数百万个设备的显著数量的并行流量。最后,它们的功耗远低于使用 WAN 或 PAN 协议的网络。让我们考虑一个由标准电池(2.5 Ah)供电的智能电表^([22]),每天发送几条信息。使用大多数 WAN 或 PAN 协议,电池的寿命可能只有几个月。而使用 LPWAN,同样的电池可以持续长达 20 年!

^(22)

智能电表是一种连接设备,用于测量电力消耗。

这些优势使得基于 LPWAN 的设备部署变得简单——至少在理论上是这样,因为与移动电话网络相比,LPWAN 的覆盖范围仍然很稀疏。此外,这些网络通常是专有和封闭的生态系统,你的设备将永远被锁定。

那有什么问题吗?嗯,LPWAN 非常适合低带宽传感场景,例如从智能电表中发送数据,但它不适用于执行操作,如打开你的车库门,因为向设备发送命令具有挑战性且非常缓慢。

由于它们专注于工业领域,将现成的嵌入式设备连接到 LPWAN 物联网比连接到 PANs 更复杂。幸运的是,随着 LPWAN 的普及,各种嵌入式设备模块已经出现.^([23])

^(23)

www.cooking-hacks.com/sx1272-lora-module-for-arduino-raspberry-pi-intel-galileo-900-mhz

总结广域网

本节中描述的大多数广域网协议具有或多或少相同的特性。唯一的例外是移动电话网络,它比其他物联网广域网协议需要更多的电力。随着 5G 网络的到来,这种情况可能会改变,但我们仍需等待几年。对于其他协议,主要的不同之处在于它们的覆盖范围。因为它们还远未达到移动电话网络的覆盖范围,使用这些协议部署物联网用例需要与网络提供商进行仔细规划,以确保目标区域得到充分覆盖。LPWAN 的覆盖范围预计在不久的将来将显著提高,但仍然难以预测哪个协议将占主导地位并统治物联网广域网的世界。参见表 5.3 以比较广域网协议。

表 5.3. 最常见广域网协议的比较。
名称 电池使用 最大范围 下行链路 开放性 覆盖范围
Weightless 非常低 20+ 公里 有限 中等 中等
SigFox 非常低 30+ 公里 有限 中高
LoRa 非常低 30+ 公里 有限 中等 中等
GPRS/3G/4G 50+ 公里
5G 非常低 ? ? (可能) 尚未部署

5.2.5. 那么,我应该选择哪一个呢?

为您的下一个物联网项目选择合适的网络协议栈可能是一项艰巨的任务。实际上,每一层都有数百种可能性,这些选择将更难逆转。与计算机不同,事物可能具有更长的生命周期,因为消费者不会仅仅为了支持新的物理层协议而更换冰箱的硬件。一位主要家用电器制造商的 CEO 曾告诉我们,他对物联网的主要担忧是持续的标准战以及选择错误标准的影响:“人们不想升级烤箱的协议栈,对吧?”

选择合适的网络协议栈没有秘密配方,因为我们所介绍的每个协议都有其各自的优势和劣势。在本节中,我们将提供一些考虑和权衡,以帮助您为您的应用或设备选择合适的协议。

电源

你首先需要考虑的是你的设备将如何供电。你的选择包括电池、有线电源和能量采集。这个决定将取决于设备将使用的环境;例如,电源线对于固定设备(如灯具或冰箱)来说是非常理想的。如果在森林等偏远环境中没有有线电源,电池或太阳能板将是一个更好的选择,除非你身处苏格兰。同样,移动设备(例如,便携式盖革计数器)将不得不使用电池或能量采集技术。

如果你的设备没有有线电源的便利,你需要使用低功耗协议,例如基于 IEEE 802.15.4 或 LPWAN 的协议。在这种情况下,蓝牙 LE、Thread 或 SigFox 将是不错的选择。在传输层,如果可以接受一些数据丢失,你可以使用 UDP 而不是 TCP 来进一步降低功耗。

如果你的设备可以连接到电源插座,Wi-Fi 是显然的选择,尤其是在建筑物内,Wi-Fi 可以很容易地安装。你也可能考虑以太网或电力线通信(PLC)。如果你只有以太网插座,你可能想使用以太网供电(PoE),因为这种协议通常更稳定。在传输层,你应该使用 TCP 的可靠性和良好的整体支持。

成本

成本是重要的因素,因为它与功耗一起决定了你可以将哪种嵌入式设备集成到你的设备中。如果你的每个嵌入式设备的预算低于 10 美元,你将不得不考虑资源受限的 SoC(系统芯片)平台,例如我们在第二章中提到的那些——例如,Marvell、Broadcom、TI 和 NXP。这些平台通常具有有限的 RAM、存储和处理能力,这直接影响了你可以使用的网络协议栈。如果你的每个字节都很重要,你可能更适合使用专为资源受限设备设计的协议,如 IEEE 802.15.4、ZigBee 和蓝牙 LE。在传输协议方面,UDP 比 TCP 使用更少的资源(RAM、CPU 和带宽)。如果你的目标成本高于每台设备 10-20 美元,你可以开始考虑基于 Linux 的平台,这将在物联网网络协议的使用上给你带来完全的自由。

范围和网络拓扑

另一个需要考虑的方面是设备与网关或其他设备之间的距离。这与你的设备能使用的电量紧密相关。粗略地说,你的设备电量越大,其可达到的距离就越远——LPWAN 除外。我们之前描述了不同协议的覆盖范围,所以你应该能够做出决定。别忘了网状网络可以帮助。它允许你使用低功耗的 PAN 网络,如 Thread 或 ZigBee,同时通过多跳通信来扩展它们的范围。

已经存在的设施也是重要的考虑因素。例如,如果存在星形拓扑,您不妨使用它。在家庭环境中,这通常是存在 Wi-Fi 路由器的情况,或者在 LPWAN 覆盖的区域外,这使得部署变得容易得多。

带宽、延迟、执行和传感

您需要清楚地了解交互类型和您的设备将需要的带宽。首先,这是一个传感应用,设备只需要将数据发送到其他设备或云吗?您会多久发送一次消息;也就是说,设备每分钟会发送几次数据?还是它大部分时间都在休眠,只在每天发送几次消息?此外,您是否需要向设备发送命令(执行),如果是的话,您的应用程序可以容忍多少延迟?最后,这些消息有多大?几字节?还是更多?

一些协议(如 LPWAN,除了移动电话网络外)与执行或需要大量带宽的使用案例不太兼容。对于这些,Wi-Fi 和以太网可能是最合适的物理协议。其他个人区域网络(PAN)协议栈,如 ZigBee、蓝牙和 Thread,在可用带宽方面介于两者之间,提供良好的通信,因此适合发送和执行,尽管随着消息必须穿越的跳数增加,端到端延迟可能会增加。

互联网集成和开放性

最后,您选择的协议栈的开放性可以是一个重要因素。这是一个开放标准还是专有标准?规范有多开放和可访问?该协议在现实世界中的支持情况如何?该协议与互联网协议栈的集成程度如何?如果一个协议栈不提供将数据转换为互联网协议(IP、TCP 或 UDP)的简单方法,那么在封闭网络(例如工厂中的工业机器)中可能还可以,但如果这些设备需要通过互联网访问,则可能不行。

由于物联网对协议栈施加的压力,使其必须与互联网兼容,因此我们之前描述的所有协议栈都可以以某种方式集成到互联网中。但真正的问题其实是:在哪个层次以及需要多少部署工作量?集成层次越高,就越难以重用已经存在的互联网基础设施。例如,仅在传输层(如通过实现 TCP)进行实现意味着您将无法使用已经存在的下层基础设施,例如互联网接入点、网桥、集线器、路由器和交换机,这些将需要由扮演类似角色的其他参与者来替换。

基本上,如果互联网集成发生在第 3 层,就需要有人部署硬件和软件来覆盖下面各层提供的服务,从而导致更复杂的部署。这里的关键问题是:谁将部署这些额外的组件?在基于现有基础设施的系统(比如,SigFox)的情况下,他们会为你这样做,所以负担最小,但你依赖于单一的公司(在这种情况下是 SigFox)。在家庭环境的情况下,情况则完全不同,因为你需要建立基础设施,如果你不打算依赖现有的互联网基础设施。

让我们来看一个具体的例子,如图 图 5.8 所示。想象一下,你想要制作一个用户可以通过手机控制的智能灯。你决定为你的灯选择 ZigBee 协议栈。因为手机不能直接使用 ZigBee,你可以决定使用 HTTP 作为应用协议。通信从 HTTP 层向下到 Wi-Fi 层。另一方面,你的灯使用 IEEE 802.15.4,这是 ZigBee 协议栈的物理协议。由于你的客户不太可能有支持 802.15.4 的接入点,并且这个协议在物理上与 Wi-Fi 不兼容,你需要新的硬件来支持 802.15.4,因此你还需要将 ZigBee 接入点包装在你的灯中。然后,你需要将 Wi-Fi 桥接到 ZigBee,并将 IP 地址转换为 ZigBee 地址,将 TCP 转换为 ZigBee 的传输协议,最后将 HTTP 转换为 ZigBee 的应用协议。然后你需要将这些转换回 HTTP、TCP、IP 和 Wi-Fi,以响应用户的手机应用程序。当然,其中许多转换是标准化的;然而,这增加了你系统部署的复杂性,这种复杂性甚至影响到你的客户,他们需要在自己的家中安装额外的硬件才能使你的系统工作。现在想象一下在几栋楼或整个城市部署这个系统:你以为你只是在部署智能灯,但突然你发现你正在部署一个全新的网络!ZigBee 本身并不是一个坏选择,但如果你的主要目标是建立一个大规模网络,其中互操作性是首要的,其他选项会使你的生活更轻松。

图 5.8。两个智能灯与网络应用程序通信。左:一个内置 Wi-Fi 并实现 IP 栈的智能灯。右:一个内置 ZigBee 并实现 ZigBee 栈的智能灯。在第二种情况下,需要在应用程序和灯之间使用翻译器。

图片

基于开放标准或大型联盟选择协议栈将有助于此。流行的和/或开放协议栈——如 Wi-Fi、Thread、蓝牙等——更有可能被未来一代的接入点、路由器和手机所支持。相比之下,像 EnOcean 和 ZigBee 这样的专有技术可能仍然需要额外的硬件和网关才能连接到互联网。

5.3. 物联网应用协议

到目前为止,我们只看了网络堆栈的底层:物理层、网络层和传输层。这些协议为物联网中的事物提供了可以在互联网上听到的声音。尽管这些事物现在可以讲互联网语言,但其他人无法理解这些事物在说什么——除非他们能说一种共同的语言。互联网的通用语言是什么?嗯,就像地球上不止一种语言一样,也有许多语言用于不同的目的。互联网的语言是应用层的协议(图 5.8 的第 4 层)。正如您在本节中将会看到的,许多应用层协议是为物联网设计的;不幸的是,其中很少能够与互联网无缝集成。

让我们从查看不基于互联网协议的应用层开始。这个领域充满了在家用自动化、楼宇管理和制造等领域使用过的标准,所以我们完全可以就这些内容再写一本书。相反,让我们专注于建立在之前查看过的两个 PAN 协议栈之上的两个应用:ZigBee 和蓝牙。

5.3.1. ZigBee 和蓝牙应用堆栈

ZigBee 和蓝牙在应用层提供了概念上相似的堆栈。关键思想是提供一套针对特定领域应用的规范;这些是非常具体的用例,例如管理安全系统或控制工业机器。在 ZigBee 的情况下,这些规范被称为应用配置文件,而在蓝牙的情况下则简单地称为配置文件。例如,蓝牙定义了一个用于健康监测设备的自定义配置文件,称为健康设备配置文件(HDP)。对于可以读取手机上消息的楼宇配件(如您的汽车),它定义了一个名为消息访问配置文件(MAP)的配置文件。对于 ZigBee,有针对楼宇照明系统、风扇、HVAC 系统或百叶窗的配置文件。配置文件定义了可以用来与使用特定配置文件的设备交互的协议、操作和有效载荷。

您不必使用这些配置文件来构建基于 ZigBee 或蓝牙构建的应用程序;您也可以构建自己的应用协议。但这些配置文件确保客户端应用程序理解 ZigBee 或蓝牙设备能提供什么,因此对于促进在物联网上运行的服务和应用程序之间的互操作性非常重要。

蓝牙和 ZigBee 的应用层也解决了其他应用层问题。例如,这两个标准都提供了一种唯一标识设备以及执行网络发现的方法。也就是说,它们提供了一种方式,让多个设备或多个应用程序和设备在最初不知道它们存在的情况下相互发现。例如,在蓝牙中,这是通过向所有监听设备广播一个蓝牙标识符和一个配置文件引用来实现的。ZigBee 也有类似的系统。

总的来说,蓝牙和 ZigBee 在应用层需要什么方面有很多可以教给我们。但正如你之前看到的,蓝牙和 ZigBee 并不原生支持互联网或 Web 协议。此外,对于常见语言缺乏 SDK 支持:虽然几乎任何编程语言都有 Web 客户端库可用,但这并不适用于 Zigbee 或蓝牙.^([24]) 因此,ZigBee 和蓝牙的应用协议使它们在很大程度上与其他使用其他协议栈的设备不兼容。

²⁴

尽管在 SDK 方面,蓝牙略好一些,因为 Android、iOS 或 Linux 都有标准的 SDK(参见www.bluez.org/

为了解决这个问题,已经建立了许多网桥(网关)。例如,许多蓝牙供应商提出了将蓝牙设备集成到 6LoWPAN^([25])中的方案,其中每个蓝牙设备都通过特殊的蓝牙智能网关获得一个 IPv6 地址。对于 ZigBee 也存在类似的倡议,并且相应的工作组正在忙于标准化这些网桥。

²⁵

一个很好的例子是 NordicSemic 的蓝牙物联网 SDK:www.nordicsemi.com/eng/Products/Bluetooth-Smart-Bluetooth-low-energy/nRF51-IoT-SDK.

有很大的可能性,ZigBee 和蓝牙将在不久的将来系统地实现互联网协议栈。这使得它们可以连接到互联网,解决这些事物的网络问题,但不是它们的应用层。尽管蓝牙或 ZigBee 的应用层不是建立在基于 IP 的协议之上,但有许多基于 IP 的应用协议可用。其中一些,如 XMPP^([26])(可扩展消息和存在协议)和 AMQP^([27])(高级消息队列协议),最初并不是为物联网用例设计的,但多年来人们开始适应它们。其他一些,如 CoAP(约束应用协议)和 MQTT(消息队列遥测传输),是专门为物联网创建的,并且建立在互联网协议之上,因此我们需要更详细地研究这两个。但首先,让我们看看今天最臭名昭著的技术巨头苹果和谷歌推动的两个其他协议。

²⁶

xmpp.org

²⁷

www.amqp.org

5.3.2. 苹果 HomeKit 和谷歌 Weave

2014 年 6 月,苹果宣布它将进入物联网市场,特别是智能家居市场,推出名为 HomeKit 的协议,^([28]) 将支持所有基于 iOS 的设备。HomeKit 协议栈涵盖了几个选项,如图 5.9 所示。有一个选项是使用蓝牙协议栈的蓝牙设备选项,还有一个选项是使用 IP、TCP、HTTP 和 JSON 的互联网设备选项。最后,桥接规范允许其他设备集成到 HomeKit 配件协议中。无论你选择使用哪个栈,你连接的设备最终都可以通过 iOS SDK 接口供应用程序使用。

²⁸

developer.apple.com/homekit/

图 5.9. HomeKit 栈:支持 IP 设备和蓝牙设备。基于其他栈的设备通过 HomeKit 桥接器集成。在应用层,HomeKit 通过 iOS 直接访问设备。

尽管 HomeKit 仍处于早期阶段,但许多家庭中苹果设备的存在意味着它可能扮演一个重要的角色。因为它基于多个开放标准,HomeKit 与其他设备的集成不会是一个重大的技术挑战。但是,HomeKit 协议本身并不是开放的,因此实现 HomeKit 设备或应用程序需要成为苹果的 MFi(为 iPhone 制造)计划的一部分。

毫不奇怪,谷歌决定反击,并在 2015 年宣布了 Google Weave。从概念上讲,Weave 与 HomeKit 类似,因为它为应用程序(例如,移动应用程序)提供了一个与基于网络技术的物联网设备通信的协议。但与 HomeKit 不同,它还指定了一套云 API,并支持 Android 和 iOS 设备。在撰写本文时,Google Weave 尚未公开发布,但可能也会扮演一个重要的角色,尤其是在智能家居领域.^([29])

²⁹

developers.google.com/weave/

5.3.3. 消息队列遥测传输

消息队列遥测传输(MQTT)应用层协议是在 1999 年由 Andy Stanford-Clark 和 Arlen Nipper 发明的。它旨在作为一种轻量级消息协议,建立在 TCP/IP 之上,允许带宽有限的受限设备相互通信。MQTT 在此方面做得非常好,其实施符合大多数嵌入式设备的限制。多年来,MQTT 已成为机器对机器(M2M)通信的重要应用层协议。参见 图 5.10。

图 5.10. MQTT、MQTT-SN 和 CoAP 的典型协议栈。MQTT 建立在 TCP 之上。CoAP 建立在 UDP 之上,通常使用 IPv6(6LoWPAN)。MQTT-SN 通常建立在 UDP 之上。

在下一章中,您将了解到 MQTT 是一个发布-订阅协议。现在,您需要知道的是,MQTT 客户端会订阅一个感兴趣的主题,并在为该主题发布新消息时接收通知。消息的发布者和订阅者不会直接交流,而是通过一个称为代理的中间件进行交流。如果只需要本地通信,代理可以部署在本地;如果发布者和订阅者不在同一本地网络中,则可以部署在互联网上。

服务质量

MQTT 的一个有趣特性是它提供了三个服务质量(QoS)级别,以确保客户端在消息投递方面可以期待什么。请注意,这与您之前看到的 TCP 传输层投递保证是互补的,因为它与订阅者和发布者之间的应用层投递相关。客户端可以从支持它们的代理请求以下 QoS 级别:

  • QoS 0:发射后即忘— 已发布的消息可能会被投递给订阅者,但这并不保证。接收者不会确认消息,而代理也不会存储或重新投递它们。

  • QoS 1:至少一次投递— 已发布的消息至少会投递一次给订阅者。这意味着如果订阅者暂时断开连接,它将在重新连接时立即收到消息。这种连接所需的示例消息如图 5.11 所示。

    图 5.11。具有 QoS 1 的客户端和代理之间的连接。通过发送定期的PINGREQ请求来保持连接打开。这些请求的频率取决于客户端设置的 keepalive 参数。由于 MQTT 是一个应在连接上保持打开的协议,这个参数确保了代理和客户端都处于连接状态。如果代理在 1.5 倍的 keepalive 间隔内没有收到PINGREQ或任何其他消息,它将关闭连接。

  • QoS 2:精确一次投递— 已发布的消息将只投递一次,并且仅投递一次给每个订阅者。

持久连接

如图 5.10 所示,MQTT 建立在 TCP 和 IP 之上。这意味着所有发布和订阅都通过 TCP/IP 进行。但 MQTT 的一个有趣方面是它尽可能长时间地保持客户端和代理之间的连接打开。为了保持这个打开的连接,客户端会定期向服务器发送 ping 请求(PINGREQ),如图 5.11 所示,其中客户端通过 QoS 1 连接到代理。这些请求的频率取决于客户端设置的 keepalive 参数。由于 MQTT 是一个应在连接上保持打开的协议,这个参数确保了代理和客户端都处于连接状态。如果代理在 1.5 倍的 keepalive 间隔内没有收到PINGREQ或任何其他消息,它将关闭连接。

安全性和加密

MQTT 中的安全是通过传输层安全性(TLS)实现的,TLS 是用于加密网络流量的 SSL 的后继者。在加密之上,代理可以请求用户名和密码来识别客户端。

用于小型设备的 MQTT—MQTT-SN

即使 MQTT 被设计成轻量级,它通过 TCP 维护永久连接的事实对于某些设备来说可能是个问题。这对你的 Pi 来说不是问题,但对于我们在第四章中提到的资源受限设备(如 RTOS 平台)来说可能是个问题。特别是,这对电池供电的设备来说显然是个问题,因为永久保持 TCP 连接开放会迅速耗尽电池。

MQTT 世界有一个具有类似目标的协议:用于传感器网络的 MQTT(MQTT-SN)。MQTT-SN 在概念上与 MQTT 类似,但有两个主要区别,如图 5.10 所示。首先,它不需要永久连接,并且建立在 UDP 而不是 TCP 之上,这节省了一些带宽——但引入了一些限制,正如你之前看到的。然后,MQTT-SN 代理索引主题名称,这些名称对于非常低带宽的网络来说有时太长。尽管这是一个有趣的协议,但 MQTT-SN 的普及度不如 MQTT 或 CoAP,迄今为止,唯一免费提供的功能齐全的代理是 Eclipse 基金会提供的 Really Small Message Broker.^([30])

³⁰

git.eclipse.org/c/mosquitto/org.eclipse.mosquitto.rsmb.git/

5.3.4. 限制应用协议

专注于适用于非常有限的嵌入式设备的占地面积以及节省电池电量是下一个我们关注的网络层协议(限制应用协议 CoAP)的几个原因之一。CoAP 是一组旨在在具有至少 10 KB RAM 的嵌入式设备上运行的协议规范,并产生大约 100 KB 的代码占用空间。CoAP 也是我们在这里看到的最新应用协议;它于 2014 年 6 月正式发布.^([31])

³¹

coap.technology/

非持久连接的 UDP

典型的 CoAP 栈,通常被称为“智能对象的 IP”,在三个方面与 MQTT 不同。首先,如图 5.10 所示,CoAP 通常在网络层与 6LoWPAN 一起使用。然后,在传输层,它使用 UDP(就像 MQTT-SN 一样),而不是 TCP。这意味着 CoAP 不维护一个开放的 TCP 连接,因此需要更少的电力。然而,在实践中,这也意味着 CoAP 在某些环境中部署起来更困难。正如你之前看到的,UDP 在与网络地址转换结合使用时带来了一系列挑战,并且这种情况在 IPv6 完全部署之前不太可能改变;参见第 5.2.1 节。

请求/响应和观察

CoAP 是基于 REST 原则实现的请求/响应协议。这些原则是 Web 和物联网的核心,将在下一章中详细讨论。在这方面,CoAP 是一个非常有趣的协议,你将在接下来的章节中学到的概念将对你理解 HTTP for Things 和 CoAP 非常有用。

在一个名为 CoAP Observe 的单独规范中,^([32]) 请求/响应范式通过支持“观察”资源得到了扩展。简而言之,这意味着你可以以类似 MQTT pub/sub 的方式订阅资源。但与 MQTT 不同,没有代理,事物本身将更新推送到客户端。不依赖于外部代理意味着每个事物都既是客户端也是服务器,这使得设备能够轻松直接地相互通信。

³²

datatracker.ietf.org/doc/draft-ietf-core-observe/

5.3.5. 那么,我应该使用哪一个呢?

HomeKit 是一个有潜力的协议,预计在未来几年内会有很大的发展。但它有一个明显的局限性,即只能在苹果生态系统的范围内工作。Google Weave 还未证明它能为物联网做些什么。MQTT 和 CoAP 是有趣的替代方案,但在物联网架构的背景下,它们面临两个主要挑战。

首先,它们不能直接与 Web(浏览器)集成。你不能直接与 CoAP 或 MQTT 交互构建客户端 JavaScript。这并不意味着故事就此结束。有方法可以将这两个协议直接集成到浏览器网络中,通过在 WebSocket 上中继 MQTT 请求或通过 HTTP 代理为 CoAP。这个主题将在第七章中更详细地介绍。

其次,MQTT 和 CoAP 都提出了一种实现物联网应用层协议的方法,但它们没有涉及具有固定语义和语法的数据模型以及可能的交互。换句话说,即使你使用 MQTT,你仍然需要为你的设备或应用程序创建自己的模型。因为这不是基于一个明确定义的标准,MQTT 客户端需要事先了解特定设备的自定义模型,这限制了即兴交互。

桥接这个差距正是物联网架构的目标:使用和重用 Web 标准和工具为事物创建一个应用协议。这正是物联网架构的全部内容,好消息是你在下一章中学到的知识也将适用于其他流行的应用层协议,如 MQTT 和 CoAP。

5.4. 物联网架构

正如你所见,现有的物联网应用层协议提供了各种对嵌入式设备部署有用的功能,从设备和服务发现到可靠的消息传递,再到即兴安全配对等等。与这些协议不同,HTTP 和 WebSockets 本身并不支持这些功能,因为它们并不是为嵌入式设备设计的。为了真正有用,物联网需要这些功能,并且还需要能够轻松扩展以支持在需要时添加额外功能。在本节中,我们提出了物联网的结构化架构,这将成为本书其余部分的基础。这个架构将实际展示如何扩展 Web 协议以支持我们构建任何类型 WoT 应用所需的所有功能,同时真正集成到网络中。

与 OSI 或互联网协议栈不同,WoT 架构栈并不是由严格意义上的层组成,而是由添加功能的水准组成,如图 5.12 所示。每一层都有助于将事物集成到网络中,从而使它们对应用程序和人类更加易于访问。WoT 架构栈从 OSI 和互联网协议栈结束的地方开始:它探讨所有位于应用层及其以上(第 7 层及以上)的协议和工具。这一强大的含义是,你不必担心底层(1-6 层),因为物联网只关注应用层协议以及你如何使用它们——而不是使用哪些底层协议。

图 5.12. 物联网架构堆栈及其各种层^([33])

³³

这个物联网架构是在 Dominique Guinard 的博士论文中提出的:webofthings.org/2011/12/01/phd-web-of-things-app-archi/

图片

本书接下来的章节将详细描述每一层,这样你将拥有所有使用这些特定于设备的特性为你的 WoT 产品和应用程序提供最大重用和互操作性的工具。现在,让我们回顾 WoT 架构的各个层,并描述它们的目的;参见图 5.12。

5.4.1. 层 1:访问

访问层也是最基础的,因为它通过提供 Web API 来探讨事物如何连接到网络。这一层负责将任何事物转变为可编程的 Web 事物,其他设备和应用程序可以轻松与之通信。

这一层的核心思想很简单:通过使用基于 TCP/IP 和 JSON 数据格式的 HTTP RESTful API 公开其服务,事物可以无缝集成到网络中。访问层还描述了如何使用 WebSockets 来适应许多物联网用例是实时或事件驱动的这一事实。我们将在第六章中探讨这些方面。第六章。

并非所有事物都能说网络协议,甚至可能无法连接到互联网,但这并不意味着这些事物不会成为物联网的一部分。因此,我们将探讨使用网关等集成模式将非网络和非互联网事物集成到网络中。这些访问层的方面将在第七章(chapter 7)中介绍。

5.4.2. 层次 2:查找

通过网络 API 访问事物并不意味着客户端可以“理解”事物是什么,它提供哪些数据或服务,等等。这是第二层的目标:查找。在这个层次上,我们提出一个基于 HTTP 的协议,包括一系列资源、数据模型、有效载荷语法和语义扩展,网络事物和应用程序应该遵循。这一层确保您的设备不仅可以被其他 HTTP 客户端轻松使用,还可以被其他 WoT 应用程序找到并自动使用。这里的方法是重用网络语义标准来描述事物及其服务。这使您可以通过搜索引擎和其他网络索引来搜索事物,以及自动生成用户界面或工具来与事物交互。在这个层面,我们还描述了语义网络技术,如 JSON-LD 和 HTML5 微数据,以及它们如何集成到事物 API 中。这些主题将在第八章(chapter 8)中介绍。

5.4.3. 层次 3:共享

物联网在很大程度上基于事物将数据推送到网络的理念,在那里可以应用更多的智能和大数据技术——例如,帮助我们管理健康或优化能源消耗。但只有当一些数据可以高效且安全地在服务之间共享时,这才能以大规模的方式发生。这是 Share 层的责任,它规定了事物生成数据如何在网络上以高效和安全的方式共享。

在这个层面,我们探讨在 RESTful API 之上应用细粒度共享机制。我们还研究了委托网络身份验证机制,并将 OAuth 集成到我们的事物 API 中。最后,我们讨论通过使用社交网络来共享事物及其资源来实现社交物联网。这些主题将在第九章(chapter 9)中介绍。

5.4.4. 层次 4:组合

最后,一旦事物被放置在网络上(层次 1),人类和机器都可以找到它们(层次 2),并且它们的资源可以安全地与其他人共享(层次 3),那么就是时候考虑如何构建大规模、有意义的物联网应用了。换句话说,我们需要理解来自异构事物的数据和服务如何整合到一个庞大的网络工具生态系统,例如分析软件和混合平台。Compose 层的目标是使创建涉及事物和虚拟网络服务的应用变得更加简单。

组合层上的工具从网络工具包——例如提供高级抽象的 JavaScript SDKs——到可编程小部件的仪表板,最后到物理混合工具,如 Node-RED。受到 Web 2.0 参与式服务和特别是 Web 混合的启发,物理混合工具提供了一个对经典互联网和物联网的统一视图,并赋予人们使用物联网服务构建应用程序的能力,而无需编程技能。我们将在第十章中探讨这一层。

5.4.5. 物联网为什么重要?

好消息是,在物联网的网络上,你不需要关心各种设备如何物理上相互通信,或者在网络层。就像在互联网上一样,你不需要担心你的手机是用 4G 还是 Wi-Fi 来获取网页。在物联网中,你只需要关心这些设备共享一套通用的语言,使得它们能够在应用层进行通信,无论它们的网络连接方式如何。通过在众多网络协议之上叠加已知的网络标准,你使得任何网络上的设备和应用都能以标准且有意义的方式相互通信和交换数据。简而言之,物联网对应用层以下的所有内容都是中立的,因此任何设备都可以成为通用物联网的一部分,无论它使用什么协议连接到互联网。

正如你所学到的,已经提出了许多应用层协议用于物联网,但没有一个可以被看作是“唯一”的。物联网的前提不同:与其从头开始创建另一个协议,为什么不重用和适应那些广泛流行且普遍支持的,比如互联网?我们在第 5.4 节中提出的物联网架构正是将这个想法付诸实践:为物联网提供一个单一的统一架构,任何物联网设备、服务或应用都可以使用它来相互通信。这本书的剩余部分将教你如何实现这一点。

5.4.6. 超越书籍

你肯定已经意识到,一个单一的通用协议栈来统治一切既不现实也不实用。某些协议栈在某些场景下比其他场景更适合,对这些妥协没有意义,尤其是在商业用例中。如果你要记住本章的一个要点,那就是确保你选择与互联网兼容的协议。当你在构建物联网时,这似乎是一个相当明显的声明,但集成越直接,开发和应用你的产品就会越容易。

物联网协议领域很可能会在许多年内继续是旧、新和不兼容协议的混合体——战斗远未结束!你应该关注这个领域的未来发展。一个好的起点是 IPSO(智能对象互联网协议)联盟^([34]),一个强大的参与者联盟,推广在嵌入式设备上使用互联网协议。还请确保关注 Thread Group^([35]),它正在积极研究创建一个干净且可操作的物联网协议栈,直至网络层。你还应该更仔细地研究 AllSeen Alliance^([36])和 OIC(开放互连联盟)^([37]),它们正变得越来越受欢迎。

³⁴

www.ipso-alliance.org/

³⁵

threadgroup.org/

³⁶

allseenalliance.org/

³⁷

openinterconnect.org/

5.5. 摘要

  • 存在着各种用于分类网络拓扑和协议的模型,它们帮助我们比较和对比物联网中常用的一系列工具和用例。

  • 今天的物联网是一个由各种协议组成的混合体,其中很少是基于互联网协议套件的。

  • 一些流行的物联网协议是专门为嵌入式设备的限制条件设计的——例如低功耗或带宽——因此需要特别设计的网关才能连接到互联网。

  • 对于物联网,也存在各种应用层协议,它们之间难以轻松集成。这些协议提供了物联网应用所期望的额外功能,例如实时推送、设备发现等。

  • 物联网帮助最大程度地提高各种物理网络之间的互操作性。

  • 网络技术非常流行,并为大多数物联网应用提供了所需的灵活性和功能,包括发现、安全和实时推送。

  • 物联网架构堆栈组织了在网络上常用的一系列工具、技术和标准,以便它们形成一个完整的框架,用于构建物联网系统,这些系统是网络的原生部分。

如果你刚接触物联网和网络,毫无疑问,这一章对你来说是一个挑战。它当然没有让你变成物联网网络协议专家,但至少它为你提供了构建网络设备所需的各种技术的坚实基础概述。如果你必须处理硬件和/或基础设施,了解哪种协议适用于哪种用例将肯定有助于你为下一个项目选择合适的工具。

第二部分. 构建物联网

在第二部分中,我们描述了如何构建物联网,并展示了如何实现第一部分中引入的物联网架构的各个层次。

第六章 是对现代网络架构的快速介绍,并描述了它如何应用于嵌入式设备和物联网。

第七章 展示了如何在各种设备上实现前一章中提出的概念。

第八章 介绍了可发现性和可查找性问题,并展示了如何使用 Web 技术来公开和共享关于网络连接设备的服务和功能元数据。

第九章 提供了关于网络安全以及如何以安全方式将设备连接到网络并共享其数据的快速介绍。

第十章 展示了如何使用前几章中介绍的技术和方法来快速构建名为物理混合应用(physical mashups)的混合 Web 物联网应用。

第六章. 访问:物件的 Web API

本章涵盖

  • 基于 REST 原则设计物件的 API

  • 使用 HTTP 和 WebSockets 实现 RESTful 物件

  • 使用 JSON 和 MessagePack 表示资源

  • 允许使用 CORS 进行跨站请求

  • 使用 WebSockets 和 Web 钩子实现与物件的实时通信

  • 探讨 HTTP/2,HTTP 的未来

到现在为止,应该已经很清楚,物联网背后的核心思想是通过使用与网络上的任何其他事物相似的模式和标准,使设备、服务和应用程序能够相互通信。

在本章中,我们将详细描述这些模式是什么,并展示如何使用它们来实现物理对象的 Web API。在深入代码之前,我们需要一点理论,因此我们将从探索现代网络架构的基本原理开始。首先,我们将介绍 REST,它定义了网络的核心理架。之后,我们将提出一套指南和方法来设计用于物理设备的 RESTful API,以便 HTTP 客户端可以轻松地从它们的传感器读取数据或将控制命令发送给它们。最后,我们将讨论 REST API 在 HTTP 上处理实时传感器数据和通知时的局限性,并描述如何利用 Web 技术如 WebSockets 的最新发展来为物联网提供推送通知。

6.1. 设备、资源和 Web 物件

让我们从上一章中介绍的物联网架构的第一层开始探索。这一层恰当地命名为“访问”,因为它涵盖了物联网拼图中最基本的部分:如何将一个“物”连接到互联网,以便可以使用标准的 Web 工具和库来访问它。到本章结束时,你将获得对 HTTP 和 WebSockets 以及如何用于物理对象的深刻理解。这将使你能够使用干净、RESTful 的 API 来建模你的“物”提供的服务和数据,其他开发者和设备可以轻松理解和使用。图 6.1展示了物联网的访问层。

图 6.1. 物联网的访问层。这一层假设“物”以某种方式连接到互联网,并关注设备及其资源(属性、服务、数据、传感器等)如何作为 Web API 公开。

图片

6.1.1. 表现性状态转移

如果你曾经使用过 Web API,你肯定遇到过 REST 或 RESTful 这个术语。表现性状态转移(REST)是一套架构原则,任何分布式系统都可以采用,并在 Roy Fielding 的博士论文中得到了正式化:^([1])

¹

来源:www.ics.uci.edu/~fielding/pubs/dissertation/top.htm.

REST 提供了一套架构约束,当整体应用时,强调组件交互的可扩展性、接口的通用性、组件的独立部署,以及中间件组件以减少交互延迟、加强安全性和封装遗留系统。

简而言之,如果任何分布式系统的架构遵循 REST 约束,那么该系统就被认为是 RESTful 的。其理念是,当系统的每个组件(服务器和客户端)都遵守这些约束时,组件之间的交互就得到了很好的定义,因此是相当可预测的。这最大化了系统的互操作性和可扩展性,这对于像互联网这样的全球系统至关重要。正是这些特性使得互联网如此成功,而这正是因为 HTTP——万维网核心的应用层协议——是基于 REST 的!另一个 RESTful 协议是 CoAP,我们在第五章(kindle_split_012.html#ch05)中介绍了它,并在第七章(kindle_split_015.html#ch07)中将进一步讨论。REST 被设计用来支持大规模的分布式多媒体内容(也称为超媒体)系统,正如互联网的成功所证明的那样,它运作得相当不错。让我们看看这些约束是什么。

约束#1——客户端-服务器

组件之间的交互基于请求-响应模式,其中客户端向服务器发送请求并获取响应。这种模式最大化了组件之间的解耦,因为客户端不需要了解服务器实现的任何信息,只需知道如何发送请求以获取所需的数据。同样,服务器也不需要了解客户端的状态或数据将如何被使用。这种在数据、控制逻辑和展示之间的关注点分离提高了可扩展性和可移植性,因为松散耦合意味着每个组件可以独立存在和演进。

约束 #2—统一接口

只有在系统中的所有组件都遵守统一接口的情况下,才能实现组件之间的松散耦合。明确、简单且易于扩展的接口(适用于各种内容和场景)在很大程度上促成了互联网作为一个开放和参与式系统的成功。这对于物联网同样至关重要,因为新的、未知的设备可以随时添加到系统中,也可以随时从系统中移除,与之交互将需要最少的努力。

约束 #3—无状态

客户端上下文和状态应仅保留在客户端,而不是服务器上。因为每个对服务器的请求都应该包含客户端状态,可见性(服务器的监控和调试)、健壮性(从网络或应用故障中恢复)和可扩展性都会得到改善。当然,服务器和应用程序可以是状态的,因为这一约束仅仅要求客户端和服务器之间的交互包含彼此状态的信息。

约束 #4—可缓存

缓存是当今网络性能(加载时间)的关键要素,因此也是其可用性的关键。客户端和中间件可以本地存储一些数据,这提高了它们的加载时间,因为每次请求都不需要从实际服务器获取这些数据。服务器可以定义策略,例如数据何时过期以及何时必须从服务器重新加载更新。这导致性能得到提升,因为减少客户端-服务器交互提高了服务器的可扩展性,并减少了延迟。

约束 #5—分层系统

统一接口使得设计分层系统变得容易,这意味着几个中间组件可以隐藏它们背后的内容。分层系统使得使用中间服务器进一步改善可扩展性和响应时间成为可能。例如,分布式缓存或内容分发网络(CDN)如 Akamai^([2])可以在全球各个位置缓存数据,以便客户端能够更快地检索某些数据。这是可能的,因为客户端很少需要知道它们是否与目标服务器或其他代理交互。分层系统的另一个好处是它使得封装旧协议和系统(例如,到专有协议的网关)成为可能,这使得执行各种安全策略变得简单。

²

www.akamai.com

6.1.2. 我们为什么需要一个统一的接口?

如你所见,这些约束在很大程度上是使 Web 工作起来的原因。没有它们,Web 就不会像今天这样开放、可扩展、灵活和高效,它将成为另一个封闭和专有系统天堂中的幽灵。还记得 CompuServe 吗?^([3]) 正是如此!

³

CompuServe 是美国第一个主要的商业在线服务。它主要使用专有协议在互联网(和其他网络)上提供服务。该服务与开放的万维网竞争了数年,但最终败北,并于 2011 年完全关闭:en.wikipedia.org/wiki/CompuServe

这些约束中最重要的就是统一的接口,因为将所有可能的交互限制在通用且定义良好的操作子集内提供了几个优点。首先,使用如 HTTP 定义的统一接口可以最小化组件之间的耦合,这有助于我们设计更可扩展和更健壮的应用程序。其次,我们可以使用类似 Web 的思维来设计应用程序:标记语言、基于事件的浏览器交互、脚本语言、URL 等。第三,80 端口的 HTTP 流量是大多数防火墙唯一允许的协议。第四,它使得在简单的高级抽象背后隐藏低级协议细节变得容易,这促进了服务和数据(无论它们实际如何存储或编码)的开放性、可编程性和可重用性。

我们在这里的要点是,REST 和 HTTP 为 Web 所做的一切,它们也可以为物联网做到。只要一个“物”遵循与 Web 其他部分相同的规则——即共享这个统一接口——那么这个“物”就真正是 Web 的一部分。最终,物联网的目标是:使任何物理对象都能通过与其他 Web 部分相同的统一接口访问。这正是访问层所能实现的,正如我们将在本节余下的部分所描述的,Web 的统一接口基于以下四个原则:

  • 可访问的资源— 资源是应用程序中需要引用或使用的任何概念或数据。每个资源都必须有一个唯一的标识符,并且应该使用唯一的引用机制进行访问。在网络上,这是通过为每个资源分配一个唯一的 URL 来实现的。

  • 通过表示形式操作资源— 客户端通过其资源的多个表示形式与服务交互。这些表示形式包括 HTML,用于在网络上浏览和查看内容,以及 JSON,更适合机器可读内容。

  • 自描述消息— 客户端必须仅使用协议提供的方法—GETPOSTPUTDELETEHEAD等,并且尽可能紧密地遵循其含义。对这些操作的响应必须仅使用众所周知的响应代码—例如 HTTP 状态代码 200、302、404 和 500。

  • 超媒体作为应用状态引擎(HATEOAS)— 服务器不应跟踪每个客户端的状态,因为无状态应用程序更容易扩展。相反,应用状态应通过其自身的 URL 进行访问,并且每个资源应包含关于在每种状态下可能进行的操作以及如何在不同状态之间导航的信息。HATEOAS 在查找层特别有用,因此我们将在第八章中更详细地讨论它。链接

多亏了这样一个简单、统一的接口以及 HTTP 客户端和库的广泛可用性,RESTful 服务可以轻松重用和组合,无需了解任何资源的具体知识,因为这些可以在运行时发现和理解,正如将在第 4 个原则:超媒体作为应用状态引擎中所示。在本节的其余部分,我们将更详细地描述如何使用 HTTP 将这些四个原则付诸实践,然后我们将展示如何设计 RESTful API 用于事物。最后,对于这些原则中的每一个,我们将提出一套规则,以帮助您为您的物品构建友好的 Web API。

6.1.3. 原则 1:可访问的资源

REST 是一种面向资源的架构(ROA),其中系统或应用程序的每个组件(一个传感器、其采样频率、一个变量等)都称为资源。资源被明确标识,并且可以单独访问。使用 HTTP,这是通过在 RFC 3986 中定义的众所周知的统一资源标识符(URI)标准方案来完成的。注 4] 使用与其他所有网络资源相同的精确标准命名方案,您可以将事物及其属性无缝集成到网络中,因为它们的函数、数据或传感器可以像网络上的任何其他内容一样链接、共享、书签和使用。

查看 tools.ietf.org/html/rfc3986

URI 是一系列字符,它明确地标识了一个抽象或物理资源。有许多可能的 URI 类型,但在这里我们关注的是那些 HTTP 用于在网络上标识和定位 Web 上的资源,这些资源被称为该资源的 URL(统一资源定位符)。由此,我们推广认为,Web of Things 中的任何资源 URL 都必须遵循以下语法:

<scheme> ":" <authority><path> [ "?" query ] [ "#" fragment ]
  • 在 Web of Things 中,<scheme> 总是 httphttps

  • <authority> 是一个带有可选端口或访问凭证的主机。

  • <path> 是指向资源的任何层次路径,必须以 / 开头。

  • 最后是可选的查询参数和/或片段。

这的一个重要且强大的后果是资源标识符的可寻址性和可移植性:它们变得唯一(互联网或内网范围内)并且可以被任何 HTTP 库或工具(例如,浏览器)解析,它们可以被书签、在电子邮件中交换、在即时通讯工具中使用、编码在二维码中、在 RFID 标签中使用,以及通过信标广播,正如你将在第七章中看到的那样。

技术角落:URL 与 URI

URL 是一种 URI 类型,通过其主访问机制(例如,其网络位置)的表示来标识资源,而不是通过它可能具有的其他属性。在网络上,URL 是以 scheme 开头的 URI,并且可以通过 HTTP 解析。此外,请注意,设备的根 URL 不需要设备连接并且可以通过互联网公开访问。URL 在局域网内同样有效。

Web of Things 上的每个设备都必须有一个与其网络地址相对应的根 URL,以下是一些各种设备的根 URL 示例:

http://gateway.api.com/devices/TV/
http://kitchen-raspberry.device-lab.co.uk/
https://192.168.10.10:9002/
https://kitchen:3000/fridge/

在 Web of Things 中,我们可以有几种类型的资源。尽管其中一些代表事物及其实际属性,但其他一些可以是完全虚拟的(例如,一个混合体、数据处理服务等等):

# User with the ID No. 12
https://webofthings.org/users/12

# Sample No. 77654 from october 2009
https://webofthings.org/samples/2009/10/77654

# Device called lamp14
https://devices.webofthings.io/lamp14

网上的资源通常按照路径定义的层次结构组织。这种组织和链接资源的方式在物理世界中尤其相关,因为它不仅可以用来标识事物的资源以及事物之间的关系,还可以用来标识事物与其物理位置之间的关系。我们还可以标识资源集合,这些集合本身也是资源:

# a list of sensors on a device (all the sensors on device ID 24)
http://devices.webofthings.io/24/sensors

# a list of devices in an area (building 4)
http://192.168.44.12/building4/devices/

# a list of sensor readings
https://webofthings.org/devices/4554/samples

有趣的是,退后一步思考一下,在我们之前查看的互联网协议的大背景下,这些 URL 有什么意义。如图 6.2 所示,从 Thing URL 中有很多东西可以学习!

图 6.2. 一个 Thing URL 及其指向的协议。URL 的第一部分指定了我们使用的协议,这里为 HTTP+TLS/SSL(HTTPS);然后通过 DNS 将域名解析为 IP 地址,端口由 TCP 使用以知道要重定向到哪个进程,最后显示 REST 资源。

让我们通过我们的 Raspberry Pi 的例子来使这个概念更具体。你可以在图 6.3 中看到不同的资源是如何相互关联的,以及你如何为 Pi 的任何元素构造 URL。从这个层次结构中,你可以首先看到任何设备都必须有一个根 URL(devices.webofthings.io/pi)。然后,它有各种传感器(如光、温度等)和执行器(如 LED)。每个元素的 URL 是通过将其名称附加到其层次结构中前一个元素的路径上构建的。例如,光传感器的 URL 如下:

图片

图 6.3. Raspberry Pi 上各种资源 URL 结构的示例。加粗的 X 轴加速度传感器的完整 URL 是devices.webofthings.io/pi/sensors/accel/x

图片

设备的所有组件都可以映射到一个类似的资源树中,其中每个传感器、执行器或系统属性都分配了自己的 URL。这样,你设备的每个组件都完全融入网络,成为一个独特的网络资源,任何人都可以通过网络对其进行寻址和交互。

在第八章中,我们将探讨语义并提出资源命名方案。现在,值得注意的是,关于资源标识符的语义没有官方规则。尽管如此,你应该遵守以下指南来处理所有资源:

  • 使用描述性的名称。 因为资源名称会出现在 URL 中,使用具有语义价值的名称对开发者和用户都有很大帮助。

  • 在 URL 中不要使用动词。 避免使用动词——例如,锁定或启动——并在可能的情况下使用名称:/garagedoor/openDoor 是不好的;/garagedoor/status 要好得多。动词用于 HTTP 方法,而不是用于 URL!

  • 对于聚合资源使用复数形式。 如果一个实体有多个传感器,它们应该可以通过一个名为/sensors 的父资源访问。但你会使用/accel 来表示加速度计,尽管它提供了三个维度的值,但只有一个这样的传感器。

设计规则 #1—可寻址资源

我们探讨的第一个原则强调,你网络中的每个元素,无论是网络实体、服务还是应用程序,都成为一个可寻址的网络资源。基于这一点,我们提出以下设计规则,以遵循实现网络实体时的规则:

  • Web Things 必须是一个 HTTP 服务器。 简而言之,如果你不能向设备发送 HTTP 请求,那么它就不是物联网的一部分。为了确保最大兼容性,Web Things 必须始终支持 HTTP 版本 1.1——理想情况下也支持 v2,但不仅仅是 v2——因为它是目前最广泛使用的协议版本。多亏了 REST 的分层架构,HTTP 服务器实际上不需要托管在设备本身上。我们将在第七章(kindle_split_015.html#ch07)中描述各种集成模式。

  • Web Things 应使用安全的 HTTP 连接(HTTPS)。 当可能时,事物应仅提供安全连接。如果事物可以从外部世界访问,这是至关重要的。

  • Web Things 必须有一个可通过 HTTP URL 访问的根资源。 客户端应用程序必须有一个 URL 来发送 HTTP 请求。该 URL 不需要对外公开或公开;它可以是您局域网中设备的 IP 地址。

  • Web Things 必须使用分层结构来公开其属性。 事物必须使用分层结构来公开其属性,以促进其资源的发现。在第八章(kindle_split_016.html#ch08)中将提出一个特定的模型和资源结构。

6.1.4. 原则 2:通过表示操纵资源

计算机通信中的一个挑战是如何编码信息,以便它可以被普遍解码和理解。在互联网上,多用途互联网邮件扩展(MIME)类型已被引入作为标准,用于描述通过互联网传输的各种数据格式,如图像、视频或音频。以 PNG 编码的图像的 MIME 类型表示为 image/png,MP3 音频文件为 audio/mp3。互联网数字分配机构(IANA)维护所有官方 MIME 媒体类型的列表.^([5])

在线:www.iana.org/assignments/media-types/.

如前所述,资源只是一个概念——一个事物的抽象想法——而不是事物本身。资源的有形实例称为表示,它使用 MIME 类型对资源进行标准编码。Web 浏览器通常支持相当多的表示,例如(HTML、GIF 和 MPEG;或者可以使用插件或外部应用程序来渲染它们,如 PDF、vCards 或 Flash)。

HTTP 定义了一种简单的机制,称为内容协商,允许客户端请求从特定服务接收的偏好数据格式。使用Accept头,客户端可以指定他们希望作为响应接收的表示格式的格式。同样,服务器使用Content-Type头指定它们返回的数据的格式。为了说明这个原则,让我们看看当你在浏览器中输入 Pi 的 URL 时会发生什么。以下列表显示了请求和响应消息。

列表 6.1. 简单的 HTTP 请求和响应

如您所见,这个请求是一个简化的版本,它实际上发送的内容,包含以下头信息:Accept: text/html,指示服务器返回 Pi 的 HTML 表示形式,即您在 第二章 中看到的 Pi 根页面。默认情况下,浏览器请求它们可以渲染的 HTML 文件,并允许人类用户与资源交互。但在某些情况下,HTTP 客户端不希望是 HTML,而更希望是机器可读的格式,例如 JSON 或 XML——例如,当客户端是一个应用程序而不是网页浏览器时。这可以通过使用 Accept: 头的另一个值来实现,如接下来的两个列表所示。

列表 6.2. 使用 Accept 头请求 XML 返回负载
`Request:`

GET /pi
Host: devices.webofthings.io
Accept: application/xml

`Response:`

200 OK
Content-Type: application/xml

<device>
  <name>Pi</name>
  ...
</device>
列表 6.3. 使用 Accept 头请求 JSON 返回负载
`Request:`

GET /pi
Host: devices.webofthings.io
Accept: application/json

`Response:`

200 OK
Content-Type: application/json

{
  "name" : "Pi"
  ...
}

您可以使用各种编码格式来描述传感器数据,以便其他应用程序可以理解和处理它,并且显然不是所有服务器都支持所有这些格式。

HTTP 请求的 Accept: 头可以包含客户端理解的一个或多个带权重的媒体类型列表——例如,application/json;q=1, application/xml;q=0.5。然后服务器会尝试提供它知道的最好可能的格式(根据客户端使用参数 q 作为质量因子请求的),并在 HTTP 响应的 Content-Type 中指定它。在我们的例子中,Pi 无法提供 XML,因此会返回 JSON 表示形式,并将 HTTP 头设置为 Content-Type: application/json

Web 物联网的 JSON 及其超越

我们建议用于 Web 物联网的格式是 JSON。JSON 特别适合 Web 应用程序,因为它轻量级、便携且自包含,并且可以使用 JavaScript 在浏览器中轻松解析,也可以由任何编程语言解析!它比 XML 更轻量级,因为它需要的处理能力和带宽更少,对开发者的眼睛也更友好。但即使 JSON 轻量级,它也不是二进制格式,因为它仍然是文本。当需要更有效的格式时,例如,由于设备的内存限制或它运行在电池上,替代表示格式才有意义。有许多格式可以将 JSON 转换为二进制格式。

MessagePack^([6]) 是我们最喜欢的替代方案之一。它支持所有流行的编程语言的库,包括客户端 JavaScript、Node.js 和 C。MessagePack 不是一个官方的 MIME 媒体类型,但在内容协商过程中您仍然可以请求它。处理非官方 MIME 类型的一种常见方式是使用 x- 扩展名,因此如果您想让您的客户端请求 MessagePack,请使用 Content-Type: application/x-msgpack

msgpack.org/

设计规则 #2–内容协商

基于您刚刚学到的内容,我们提出以下规则,供您在实现您的 Web Things 时遵循:

  • Web Things 必须支持 JSON 作为它们的默认表示形式。 您的实体可以支持它想要的任何表示形式,只要它至少接受 JSON 请求,并在请求时可以返回 JSON 表示。始终使用驼峰式命名法;例如,在 JSON 有效载荷中的对象名称应使用lastValue而不是Last-Valuelast_value

  • Web Things 支持 UTF8 编码的请求和响应。 Web Thing 可以支持许多其他编码格式(例如,它可以描述其提供的服务为中文或俄语),但至少它必须支持 UTF8 编码的任何资源。

  • Web Things 可能提供 HTML 界面/表示(UI)。 除了计算机友好的 API 之外,设备还应提供人类友好的用户界面,可以通过网络浏览器访问。这对于消费产品来说特别有用,因为它可以方便用户访问、控制和故障排除他们的设备。

6.1.5. 原则 3:自描述消息

REST 强调组件之间的统一接口,以减少操作与其实现之间的耦合。这要求每个资源都支持一个标准、通用的操作集,具有明确定义的语义和行为。HTTP 定义了一个固定的操作集,每个资源都可以支持,也称为动词或方法。其中最常用的有GETPOSTPUTDELETEHEAD。虽然看起来您似乎只需要GETPOST就能做所有事情,但正确使用所有四个动词对于避免应用程序中的意外或引入安全风险非常重要。

将操作限制在这些方法之一是启用服务松耦合的关键之一,因为客户端只需要支持处理这些方法的机制。在物联网中,这些操作映射得相当自然,因为事物通常提供相当简单和原子的服务,这些服务通常可以简化为四种基本的 CRUD 操作类型:创建、读取、更新和删除。

GET

GET是一个只读操作,如列表 6.4 所示。它既安全又幂等。安全意味着调用GET不会以任何方式改变服务器的状态(只读)。幂等意味着无论您应用此操作多少次,都不会对资源状态产生影响。使用 HTTP GET请求一次或十次读取 HTML 文档不会改变资源状态。

列表 6.4. GET 读取资源(我们的 Pi 的温度传感器)
`Request:`

GET /pi/sensors/temperature/value
Accept: application/json
Host: devices.webofthings.io

`Response:`

200 OK HTTP/1.1
Content-Type: application/json

{"temperature" : 37}

在这个列表中,我们想知道温度传感器的最新值;因此,这是一个只读操作。因为我们指定了编码为 JSON,所以响应有效载荷包含一个包含值 37 的 JSON 消息。您可能会想“37 是什么?”这个答案将在第八章中提供,当我们讨论语义时。

在某些情况下,HTTP 客户端可能不需要完整的响应有效负载,例如当客户端只想验证资源是否可用或最近是否已更新时。在这种情况下,客户端可能会使用HEAD动词而不是GET,它实际上做的是同样的事情,但只返回头部而不是有效负载。这在请求发送到资源受限的设备时尤其有用,因为每个字节都很宝贵。

POST

POST是 HTTP 中既非幂等又非安全的操作,这意味着它不仅会改变服务器状态,而且每次调用都会有不同的结果。POST应该仅用于创建尚未拥有自己的 URL 的新实例,例如系统中的新用户或银行账户。以下列表中的请求创建了一条将在 LCD 显示屏上显示 30 秒的消息。

列表 6.5. POST用于创建新资源

你刚刚创建的资源 URL,要显示的消息,应始终通过Location头部在答案中返回。这个 URL 现在允许你与刚刚创建的资源进行交互,你可以稍后更新或删除它。

正如你在下一章中将会看到的,在某些情况下,POST请求的结果可能不会立即出现。例如,当你想要控制一个执行器,比如移动机器人的手臂时,操作可能需要几秒钟或几分钟才能执行。同样,如果你的请求被缓冲(例如,当消息被排队而不是立即显示时),你的请求将被异步处理。对于立即处理的同步请求,例如资源创建,你应该返回201 Created。对于所有异步操作,你应该返回202 Accepted,这意味着资源最终将被创建.^([7])

RFC 2616,第 10.2.3 节:www.ietf.org/rfc/rfc2616.

PUT

PUT通常被建模为一个幂等但不安全的更新方法。你应该使用PUT来更新已经存在并拥有自己的 URL 的东西,例如当你更改用户的姓名或向他们的银行账户添加存款时,但不用于创建新资源。与POST不同,它是幂等的,因为发送相同的PUT消息一次或十次会产生相同的效果,而POST会创建 10 个不同的资源。在下一个示例中,我们使用新的 RGB 值作为参数编码为 JSON 对象来更改 LED 4 的颜色。

列表 6.6. PUT用于更新现有资源(更改 LED 的颜色)
`Request:`

PUT /pi/actuators/leds/4 HTTP/1.1
Host: devices.webofthings.io
Content-Type: application/json

{"red" : 0, "green" : 128, "blue" : 128}

`Response:`

200 OK HTTP/1.1

您应仅使用PUT来更改已存在的东西,而不是创建新资源——为此应使用POST。在物联网中,这意味着PUT应用于更改某物的状态(例如 LED 4),打开/关闭车库门等。您将在后面看到,在决定是否通过PUTPOST向设备发送命令时,有一条很细的界限。一般来说,如果命令将立即执行而不会被缓冲,那么您应使用PUT。但如果需要缓冲请求,通常当多个用户同时访问同一资源时(例如,我们第二章中的 Pi 的 LCD 屏幕),您正在创建一个将在以后改变状态的资源(等待列表中的项目),因此您应使用POST

DELETE

DELETE是一个幂等且不安全的方法,应仅用于删除资源。通常,您会使用这个动词永久地从实体中删除资源,例如,当您删除对主题的订阅或设备上的规则时,如下一个列表所示。

列表 6.7. 使用DELETE删除现有资源
`Request:`

DELETE /rules/24 HTTP/1.1
Host: devices.webofthings.io

`Response:`

200 OK HTTP/1.1

由于您使用DELETE从对象中删除资源,因此您发送请求的资源 URL 在请求执行后将不再可访问。如果您想从网关中删除设备,应使用DELETE。但如果您想禁用传感器,这将是一个状态变化;因此您应使用PUT

错误代码

HTTP 还提供了一种表达错误和异常的方式。HTTP 响应的状态由作为 HTTP 响应消息头一部分发送的标准状态码表示。有几十个这样的代码,每个代码对 HTTP 客户端都有众所周知的含义;这些代码及其含义列在 HTTP 1.1 规范中。([8])

RFC2616 的第十部分。

在物联网中,这些代码非常有价值,因为它们提供了一种轻量级但强大的方式来通知异常和成功的请求执行。例如,在上一个示例中,对/pi/sensors/humidity/POST请求将返回405 方法不允许状态码。客户端从该状态码中理解,它不能向该资源发送POST动词,因此未来再尝试也没有意义。

HTTP 定义了一个标准状态码列表,服务器在接收到每个请求时返回。最常用的包括以下这些:

  • 200 正常—请求成功完成时返回。此代码的另一种常见形式是204 无内容

  • 201 已创建—当新资源成功创建时返回。头部Location包含已创建资源的 URI。

  • 202 已接受—当请求已被接受但资源尚未创建时,用于异步操作返回。

  • 401 未授权——请求需要用户身份验证,或者使用提供的凭证授权失败。

  • 404 未找到——请求的资源或文档在服务器上未找到。

  • 500 内部服务器错误——服务器遇到错误,无法完成请求。

  • 501 服务不可用——服务器由于维护或临时过载而无法处理请求。

CORS——启用客户端 JavaScript 访问资源

虽然在服务器端应用程序中从不同源的服务器访问网络资源不会引起任何问题,但由于安全原因,运行在浏览器中的 JavaScript 应用程序不能轻易地跨源访问资源。我们这里所说的意思是,从 apples.com 域加载的一小段客户端 JavaScript 代码不会被浏览器允许使用特定的动词从 oranges.com 域检索特定的资源表示。

通常来说,浏览器只能执行简单的跨站请求,例如从另一个网站加载图片,但不能请求其他类型的表示,如 JSON 或 JavaScript。例如,列表 6.6 中的PUT /pi/actuators/leds/4请求不会被浏览器授权。这种安全机制被称为同源策略,其目的是确保一个网站不能从另一个域名加载任何脚本。特别是,它确保一个网站不能滥用 cookies 来使用您的凭证登录到另一个网站。让我们通过一个例子来说明这一点。您登录到 facebook.com,这会在您的浏览器中创建一个 cookie,每次向 facebook.com 发送请求时都会随请求一起发送。如果浏览器允许跨站请求,从 apples.com 加载的脚本就可以使用您的 Facebook cookie 向 facebook.com 发送请求,从而假装成您在 Facebook 上!

显然,这种安全机制是好事,但也意味着从浏览器中的 JavaScript 代码直接与 Web Things 的 API 交互默认是不被允许的。等等!那么您是如何在第二章中访问devices.webofthings.io并与我们的 WoT Pi 通信的呢?幸运的是,一个新的标准机制跨源资源共享(CORS)[9]已经被开发出来,并且得到了大多数现代浏览器和 Web 服务器的良好支持。

查看enable-cors.org/www.w3.org/TR/cors/

当浏览器中的脚本想要执行跨站请求时,它需要包含一个包含源域的Origin头。服务器会回复一个包含允许的源域列表(或*以允许所有源域)的Access-Control-Allow-Origin头。下一个列表提供了一个 CORS 操作的示例,对应于第二章的 2.1 练习的请求和响应。

列表 6.8. 使用 CORS 对资源进行GET请求

当浏览器接收到回复时,它将检查Access-Control-Allow-Origin是否与源对应,如果是,它将允许跨站请求。

对于除GET/HEAD之外的动词,或者当使用除application/x-www-form-urlencodedmultipart/form-datatext/plain之外的表示进行POST时,需要一个额外的请求,称为预检请求。预检请求是一个带有OPTIONS动词的 HTTP 请求,浏览器使用它来询问目标服务器是否安全地发送跨源请求。一个预检请求的示例显示在图 6.4 中。

图 6.4. 对应于第二章练习 5 的预检请求。JavaScript 需要向另一个服务器发送带有Content-Type头的POST请求。服务器回复允许的源、方法和头。因为它们与脚本想要的匹配,所以调用将被授权。

虽然 CORS 规范中有很多额外的选项,但我们将不会深入探讨,因为这需要整本书的内容。10 但这应该足以让你理解为什么了解 CORS 是什么以及如何在 WoT 中应用它很重要。

^(10)

关于这个主题,有一本名为《CORS in Action》的好书,作者是 Monsur Hossain(Manning Publications,2014 年)。

设计规则 #3–自描述消息

总结一下,我们可以根据 REST 的自描述消息原则定义四个简单的设计规则:

  • Web Things 必须支持GETPOSTPUTDELETEHTTP 动词。 为了从 RESTful 架构提供的好处中受益,统一的接口约束是至关重要的。在物联网的上下文中,GET用于检索传感器资源,例如温度读数;POST用于创建一个将获得新 URL 的新资源——例如创建规则;PUT用于更新给定 URL 的执行器资源——例如更新 LED;DELETE用于删除资源,例如规则。

  • Web Things 必须实现 HTTP 状态码 20x、40x、50x。 如前所述,按照预期使用 HTTP 动词非常重要。当然,每个 Web Things 都支持所有这些是不现实的,但设备至少应该支持每组中的一个——例如,如果请求成功,则为 200;对于客户端错误,即请求无效,则为 400;对于服务器错误,即请求有效但服务器无法完成,则为 500。

  • Web Things 必须支持根 URL 上的GET操作。 理想情况下,每个资源都应该支持GET动词,以便客户端可以始终检索其表示。但至少,根 URL 必须支持GET,以便客户端始终能够访问设备信息。

  • Web 实体应支持 CORS。 应支持简单和预检 CORS 请求,以允许网络浏览器中的应用程序直接访问。

6.1.6. 原则 4:超媒体作为应用状态引擎

REST 的第四个原则被称为 超媒体作为应用状态引擎 (HATEOAS)。尽管这可能是有史以来计算机科学中最糟糕的缩写,但这个原则并不像听起来那么糟糕。它包含两个子概念:超媒体和应用状态。

超媒体

这个第四个原则围绕着 超媒体 的概念,即使用链接作为相关想法之间的连接。超媒体在 20 世纪 60 年代初期由 Ted Nelson 提出,作为超文本的推广,除了文本之外还包括各种媒体格式,如视频、图像或声音。由于网络浏览器的普及,链接变得非常流行,但它们并不仅限于人类使用。例如,用于识别 RFID 标签的 UUID 也是一种链接。考虑以下列表中的简化的 HTML 片段。

列表 6.9. Raspberry Pi 根资源的 HTML 表示
<html><body>
  <h1 class="device-name">Raspberry Pi</h1>
  <a href="http://devices.webofthings.io/pi" class="self">Root URL</a> of this device.
  View the list of <a href="sensors/" class="wot-sensors">sensors</a> and <a href="actuators/" class="actuators">actuators</a> on this device.

  You can also view all <a href="links/" class="links">links</a> available on this device.

  Or read the <a href="about/" class="help">documentation</a>.
</body></html>

基于此设备的表示,您可以轻松地通过这些链接检索有关设备子资源的更多信息,例如如何找到其文档。而不是将整个网络实体的结构和所有子资源描述在一个单独的文档中,该文档需要单独维护,这种基于树的模型方便地映射到 图 6.3 中显示的实体的资源树,因为树中的每一层都充当一个代理,隐藏其下的层。方便的是,这也使我们能够在结构更改时检索和解析这样一个大文件,因为我们只需要检索我们感兴趣的资源。

HATEOAS

应用状态——HATEOAS 中的 AS——指的是一个过程或工作流中的一个步骤,类似于状态机,REST 要求应用状态引擎由超媒体驱动。11 好的,很好,但这意味着什么呢?简单地说,您的设备或应用程序的每个可能状态都需要是一个具有自己唯一 URL 的 RESTful 资源,任何客户端都可以检索当前状态的表示以及转换到其他状态的可能的过渡。资源状态,如 LED 的状态,保留在服务器上,每个请求都通过当前状态的表示以及如何更改资源状态(如关闭 LED 或打开车库门)的必要信息来回答。

^(11)

roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

换句话说,只要客户端状态不在服务器上保持,并且应用程序内部的状态变化是通过跟随链接来实现的,那么应用程序就可以是有状态的,这符合自包含消息的约束。在物联网中,链接非常重要,因为它们使客户端能够 发现 相关资源,无论是对于人类用户在网页上跟随链接进行浏览的情况,还是对于机器进行爬取的情况。

简而言之,通过链接资源,它们可以动态地被发现和重新排列,而无需在某个地方保持站点地图。我们将在第八章中详细解释这一点,并对其进行实验,但现在让我们使用 列表 6.9 中所示的示例。从 HTML 表示形式中,你可以看到有一个指向名为“links”的资源的链接。正如我们稍后将要详细说明的,你可以向该资源发送一个 GET 请求,以检索包含设备提供的所有资源的 JSON 对象,这将在下一列表中展示。

列表 6.10. 树莓派“links”资源的 JSON 表示形式
"links":{
  "sensors": {
    "link": "http://devices.webofthings.io/pi/sensors/",
    "title": "List of Sensors"
  },
  "actions": {
    "link": " http://devices.webofthings.io/pi/actions/",
    "title": "List of actions"
  },
  "meta": {
    "link": "http://w3c.org/schemas/webofthings/",
    "title": "Metadata"
  },
  "self": {
    "link": " http://devices.webofthings.io/pi/",
    "title": "Self"
  },
  "help": {
    "link": "http://webofthings.io/docs/pi/",
    "title": "Documentation"
  },
  "ui": {
    "link": " http://devices.webofthings.io/pi/",
    "title": "User Interface"
  }
}

多亏了这份文档,任何 HTTP 客户端都将能够使用第八章中描述的方法来查找该设备提供的各种资源和服务,它们的意义以及如何与之交互。当客户端请求该资源的 HTML 表示形式时,例如你的浏览器,你会看到相同的内容,但带有你可以点击的实际链接。

OPTIONS

在上一节中,我们讨论了 HTTP 动词。大多数 HTTP 服务器实现的一个不太为人所知的 HTTP 动词在发现可以在资源上执行的操作方面非常有用。OPTIONS 动词可以用来检索资源允许的操作列表,以及关于在此资源上调用元数据。在可编程的物联网中,这是一个非常有用的功能,因为它允许应用程序通过仅知道资源的 URL 就能在运行时找出资源允许的操作;请参见下一列表。

列表 6.11. 使用 OPTIONS 获取资源支持的动词
`Request:`

OPTIONS pi/sensors/humidity/ HTTP/1.1
Host: devices.webofthings.io

`Response:`

204 No Content HTTP/1.1
Content-Length: 0
Allow: GET, OPTIONS
Accept-Ranges: bytes

例如,列表 6.10 是对 pi/sensors/humidity/OPTIONS 请求,返回 GET, OPTIONS 以告知客户端该资源只支持这两个动词。将链接与 OPTIONS 动词结合意味着客户端可以发现事物的可用资源,以及可以对新发现的资源执行的操作。

设计规则 #4–HATEOAS

第四组设计规则强调逻辑上链接资源的能力,以便客户端可以发现事物的资源及其之间的链接、操作和参数:

  • Web Things 应支持通过链接进行浏览性。 这意味着 Web Things 应始终提供指向与其相关的资源在资源层次结构中的链接,尤其是父资源和子资源。这可以通过浏览人类和应用(爬虫)都可以使用的资源来发现一个事物的所有资源。理想情况下,链接应存在于所有表示中。

  • Web Things 可能支持其每个资源的 OPTIONS 当可能时,向任何资源发送 HTTP 请求应返回该资源支持的动词列表。这是 HATEOAS 拼图中的一个有用部分,因为客户端只需知道资源的 URL 就可以自动确定他们可以使用该资源做什么。

6.1.7. 摘要—Web 事物设计过程

在本节中,我们已表明可以通过重用 Web 架构中的模式来构建事物的 API。我们不是仅仅将 Web 用作传输协议,而是通过使用 HTTP 作为其预期用途:作为应用层协议,将事物作为 Web 及其基础设施的组成部分。

Web Things API 的五步设计过程

我们描述了如何使用 RESTful 架构使 HTTP 成为连接到网络的设备的通用协议。我们描述了使事物网络化的过程,该过程总结为 Web Things 设计过程的五个主要步骤:

1.  集成策略—选择一种模式将事物集成到互联网和 Web 中,无论是直接还是通过代理或网关。这将在第七章(kindle_split_015.html#ch07)中介绍,所以我们现在将跳过这一步骤。

2.  资源设计—识别事物的功能或服务,并组织这些服务的层次结构。这就是我们应用设计规则 #1:可寻址资源的地方。

3.  表示设计—决定为每个资源提供哪些表示。正确的表示将由客户端选择,归功于设计规则 #2:内容协商。

4.  界面设计—决定每个服务可能的命令以及相应的错误代码。在这里,我们应用设计规则 #3:自描述消息。

5.  资源链接设计—决定不同资源之间的链接方式,特别是如何暴露这些资源及其链接,以及它们可以使用哪些操作和参数。在这一最后步骤中,我们使用设计规则 #4:超媒体作为应用状态引擎。

6.2. 超越 REST:实时 Web 事物

到目前为止,我们的界面仅通过 HTTP 提供访问。使用此协议,客户端通过发送请求并期望获得响应来与服务器进行通信;这被称为 请求-响应 通信。

在物联网中,当客户端只需要向设备发送请求时,这种模式效果很好。例如,当移动应用想要检索传感器的读数值,或者当 Web 应用用于解锁门时。不幸的是,请求-响应模型对于许多物联网用例来说是不够的。更确切地说,它不适用于需要将事件(推送)作为事件发生时与客户端通信的事件驱动用例。在本节中,我们将更详细地探讨这个问题,并提出如何通过使用另一个 Web 友好的应用协议——WebSocket 来扩展 HTTP 的请求-响应模型!

6.2.1. WoT 需要事件!

对于需要设备异步地将通知发送给客户端的应用来说,客户端发起的模型并不实用。例如,安全摄像头或烟雾报警器必须在检测到任何异常时立即发送警报,而不必等到客户端请求此信息。考虑我们在第四章中添加到我们的 Pi 上的 PIR 传感器。作为回顾,PIR 传感器可以检测到有人经过。使用通过 HTTP 的 REST 请求-响应模式并不高效,因为我们必须不断轮询 Pi 以获取 PIR 传感器的最新值。这不仅效率低下,而且如果我们没有在正确的时间轮询,我们可能会错过入侵者。如图 6.5 所示,轮询是绕过这个问题的方法之一。

图 6.5. 基本轮询:客户端应用以固定时间间隔向 Web 设备发送请求。客户端应用获取的结果与传感器新可用的值不同步。

理念是客户端可以通过定期向设备发送GET请求,从 Web 设备请求更新。尽管客户端可以通过连续发送相同的请求来模拟接近实时行为——例如,每秒一次——但对于大多数应用来说,这种方法效率低下,因为它消耗了不必要的带宽和处理时间。大多数请求最终会得到空响应(304 Not Modified)或与观察到的值保持不变的相同响应。这有两个原因不够理想。首先,它产生了大量的 HTTP 调用,其中很大一部分是无效的。因为减少对 Web 应用进行扩展时 HTTP 调用的数量是关键,所以当客户端数量增加时,这种模型扩展性不好。其次,大量的 HTTP 调用对于仅应发送严格必要数据的电池供电设备来说是个问题。

6.2.2. 发布/订阅

对于物联网(Web of Things)的交互式和反应式应用,需要一个简单灵活的机制来发送事件或接收通知。在请求-响应模式之上真正需要的是一个称为发布/订阅(pub/sub)的模型,它允许进一步解耦数据消费者(subscribers)和数据生产者(publishers)。生产者向一个称为代理(broker)的中心服务器发送消息,该代理负责根据消息的类型或内容将消息路由和分发到各个订阅者。最简单的类比是聊天室——有的公开,有的私密。有时你只和一个人聊天,有时和成千上万的人。对于设备来说,情况也是一样的。一个生产者可以向一个主题(想象成聊天室)发送通知。感兴趣的消费者可以订阅一个或多个频道,以接收该频道中生产者推送的所有通知。在 pub/sub 协议中,主题通常指定为任意字符串,这样我们就可以轻松地将我们的 Web Things 的 REST 资源映射到 pub/sub 主题上。

让我们看看一个实际例子,看看这对我们的树莓派(Pi)是如何工作的。如图 6.6 所示,许多客户端订阅了一个由代理(broker)管理的主题。每当一个客户端想要更新一个主题时,它会向代理发送一个消息——也就是说,它通过代理发布消息。代理随后将消息发送给所有订阅该主题的客户端。请注意,代理可以是事物本身,也可以是网络上某个地方的外部代理。在图 6.6 的左侧,客户端 A 订阅了以下主题:devices.webofthings.io/pi/sensors/temperature

图 6.6。左:pub/sub 中的订阅模式。客户端 A 订阅温度更新,客户端 B 和 C 订阅 PIR 更新。他们都通过代理订阅,代理维护着一个订阅者列表,记录了谁订阅了什么主题。右:发布模式。检测到入侵者,因此事物向代理发布 PIR 更新。代理将更新传递给客户端 B 和 C。

图 6.6

为了便于阅读,我们将主题缩短为/temperature。客户端 B 和 C 通过代理订阅/pir。图 6.6 的右侧显示了发布机制。有人经过传感器,因此事物生成 PIR 传感器更新通知,通知所有听众。代理知道哪些客户端正在监听/pir主题,因此它立即将此更新发送给客户端 B 和 C。

现在我们已经拥有了请求-响应交互所需的上层模式,问题是如何利用网络支持的技术来实现它,并且希望也能被常见的网络浏览器所支持。从层次结构的角度来说,我们需要一个能够支持实现发布/订阅系统的网络应用协议。为此,有多个候选方案,我们将探讨三种技术:webhooks、Comet 和 WebSockets。

6.2.3. Webhooks—HTTP 回调

在不破坏 REST 模型的情况下,通过 HTTP 实现发布/订阅系统的最简单方法是将每个实体都视为客户端和服务器。这样,Web 设备和 Web 应用都可以作为 HTTP 客户端,通过向其他服务器发起请求来行动,并且它们可以同时托管一个服务器来响应其他请求。这种模式被称为webhooksHTTP 回调,在网络上变得流行,用于使不同的服务器能够相互通信。例如,这是 PayPal 用来向 eBay 或任何其他购物网站确认您的付款已被接受所使用的机制。

这种模型的实现相当简单。我们只需要在设备和客户端上实现一个 REST API,这样客户端也就变成了服务器。这意味着当设备有更新时,它通过 HTTP POST到客户端,如图 6.7 所示。这意味着客户端必须实现一个 HTTP 服务器,并带有可以由设备访问的 REST API——例如,通过拥有一个公开的 URL。必要的调用细节如下所示。

图 6.7. 在设备和客户端之间实现的 webhook 机制。客户端通过在设备 API 上POST来订阅湿度资源。设备随后通过在客户端 API 上POST来通知服务器湿度变化。

列表 6.12. 通过 webhook 进行订阅

Webhooks 是一种通过将所有内容转换为服务器来实现客户端和服务器之间双向通信的概念上简单的方法。正如您刚才看到的,webhooks 也可以用来实现物联网的发布/订阅。但 webhooks 有一个很大的缺点:因为它们需要订阅者拥有一个 HTTP 服务器来推送通知,所以这只有在订阅者有一个公开可访问的 URL 或 IP 地址时才有效。在现实世界中,这非常有限,因为除了服务器到服务器(或设备到设备!)通信之外,很少会有这种情况。考虑一个运行在您的浏览器中的 JavaScript 应用程序,它想要从设备获取通知的情况:即使应用程序有在您的网络浏览器内部启动 HTTP 服务器的方法,^(12),但您的网络防火墙不太可能允许来自互联网的请求到达您的机器。

^(12)

注意,这可以通过编写一个自定义插件来实现,该插件允许浏览器与事物交互。但在现实世界中,使用这种非标准的扩展是不可行的,因为它严重限制了无缝访问物联网的愿景。

6.2.4. Comet—为实时 Web 破解 HTTP

当涉及到浏览器应用程序时,Webhooks 的限制导致了许多解决方案来处理网页上实时事件的问题。"Comet"是一个总称,指的是一系列通过在 HTTP 上引入基于事件的通信来绕过 HTTP 轮询和 Webhooks 限制的技术。这种模型使得 Web 服务器能够在客户端没有明确请求的情况下将数据推送到浏览器。由于浏览器最初并没有考虑到服务器发送事件,因此 Web 应用程序开发人员已经利用了几个规范漏洞来实现类似 Comet 的行为,每种方法都有其不同的优点和缺点。

其中包括一种称为长轮询的技术,如图 6.8 所示。使用长轮询,客户端向服务器发送一个标准的 HTTP 请求,但服务器不会立即响应,而是保留该请求,直到从传感器接收到事件,然后将其注入到返回给客户端的空闲请求的响应中。一旦客户端收到响应,它立即发送一个新的请求以获取更新,该请求将被保留,直到从传感器收到下一个更新,依此类推。因此,事件到达客户端的延迟被最小化。但是,客户端必须保持在一个打开的 HTTP 请求中,等待答案,并且在每次响应后都必须发送一个请求。这增加了服务器的负载,并迫使客户端发送不必要的消息。

图 6.8. 长轮询:客户端发送一个请求,该请求被保留,直到从传感器接收到事件,此时它被 Web Thing 转发给客户端作为其初始请求的响应。之后,它们重新发起一个新的请求,该请求将以相同的方式保持打开状态,直到从传感器检索到新的值。

6.2.5. WebSocket

尽管像 Comet 这样的解决方案有助于推进事物的发展,但它们只是修补,而不是真正的解决方案。Comet 和其他长轮询解决方案效率低下。在 Web 浏览器的情况下,Webhooks 不切实际。

但并非所有的希望都破灭了!一个更近期的、真正的推送通信 Web 协议已经出现:WebSocket!WebSocket^([13])是 HTML5 规范的一部分。HTML5 在大多数最新的 Web 和移动 Web 浏览器中得到越来越多的支持,这意味着 WebSocket 对所有 Web 应用程序都是普遍可用的。就像 HTTP 上的 REST 一样,这种普遍的支持使得 WebSocket 成为在物联网中实现 pub/sub 支持的相当好的候选者。

¹³

参考:www.websocket.org/.

在第二章中,你尝试构建了一个 WebSocket 客户端,并接触到了 WebSocket 的简单客户端 API,该 API 通过浏览器中运行的 JavaScript 直接访问协议。在这里,我们将更详细地关注 WebSocket 协议以及如何使用它来实现 Web Things 的 pub/sub 机制。

WebSocket 协议握手

WebSocket 在单个 TCP 连接上启用全双工通信通道。用简单的话说,这意味着它创建了一个客户端和服务器之间的永久链接,客户端和服务器都可以使用它来互相发送消息。与之前我们看到的技术不同,如 Comet,WebSocket 是标准的,并打开一个 TCP 套接字。这意味着它不需要在 HTTP 消息中封装自定义的非网页内容,也不需要像 Comet 实现那样人工保持连接活跃。

WebSocket 连接通过三个步骤初始化,就像网络爱好者所说的那样,创建了一个 握手,如图 6.9 所示。第一步是向服务器发送一个带有特殊头部的 HTTP 请求,要求将协议升级到 WebSocket。如果 web 服务器支持 WebSocket,它将以 101 Switching Protocols 状态码回复,确认全双工 TCP 套接字的打开。

图 6.9. WebSocket 协议握手。首先,通过一个用于协议升级的 GET 请求打开连接。然后打开持久的 TCP 连接,客户端和服务器可以交换数据帧。最终,一方(服务器或客户端)发送一个控制帧来表示通信结束并可以关闭。

图片

让我们看看一个具体的例子,使用我们在第四章中设置的 PIR 传感器,该传感器在我们的 Pi 上运行在 devices.webofthings.io/pi/sensors/pir。如果你用浏览器访问这个地址,你会得到 PIR 传感器的 HTML 表示形式。同样,如果你访问它并使用之前显示的 Content-Type: application/json 头部请求它,你会得到 PIR 传感器的值,这是通过 HTTP 实现的 REST;正如你之前所看到的。现在,使用相同的资源,你也可以通过请求协议升级来请求 WebSocket 内容,如下一个列表所示。

列表 6.13. WebSocket 握手中的客户端请求

图片

这里真正有趣的是,升级协议的调用是通过 HTTP 发起的。这意味着想要实时数据的客户端始终可以请求通过 WebSocket 交付 REST 资源,如果服务器不支持 WebSocket,则回退到纯 HTTP。在我们的 devices.webofthings.io 服务器的情况下,它支持 WebSocket,并将回复协议升级的确认,如下面的列表所示。

列表 6.14. WebSocket 握手中的服务器响应

图片

客户端和服务器通过 WebSockets 提供 PIR 传感器值的完整握手过程在图 6.10 中展示。

图 6.10。Firefox 网络工具中看到的 WebSocket 协议握手动作。客户端请求协议升级,服务器接受。从那时起,客户端和服务器可以通过 TCP 连接互相发送消息,该连接将保持打开状态,直到客户端或服务器决定关闭它。

一旦完成初始握手,客户端和服务器将能够通过开放的 TCP 连接来回发送消息;这些消息不是 HTTP 消息,而是 WebSockets 数据帧。文本和二进制数据帧可以双向同时发送。每个 WebSockets 数据帧的开销是 2 字节,与 HTTP 消息元数据(如标题等)的 871 字节开销相比很小。再加上 IP、TCP 和 TLS 的开销(见第九章),每条消息将额外增加 60-100 字节!这使得 WebSockets 通信比 HTTP 消耗的带宽少得多。

他们交换的消息的语法和语义是开放的,但可以使用Sec-WebSocket-Protocol请求头来指定数据帧内部使用的协议(在这个例子中,我们使用“wot”,这是一个虚构的子协议)。已注册的子协议由互联网数字分配机构(IANA)管理.^([14])

¹⁴

www.iana.org/assignments/websocket/websocket.xml

物联网的 WebSockets

对于物联网的 WebSockets 来说,真正有趣的是它们使用了标准的互联网和 Web 技术。因为它们通过 80 端口打开 TCP 连接,所以 WebSockets 不会被防火墙阻止,并且可以穿越代理。然后,因为它们在浏览器中工作,并且通过 HTTP 启动,这使得我们可以使用在探索 HTTP 和 REST 时考虑到的许多原则。首先,事物的分层结构和它们的资源作为 URL 可以原样重用于 WebSockets。正如你在列表 6.14 中看到的,我们可以通过使用相应的 URL 并请求将协议升级到 WebSockets 来订阅事物的资源事件。此外,WebSockets 不指定来回发送的消息的格式。这意味着我们可以愉快地使用 JSON,并为消息赋予我们在第八章中将工作的结构和语义。

此外,由于 WebSocket 由一个初始握手和随后在 TCP 上层叠的基本消息帧组成,它们可以直接在许多支持 TCP/IP 的平台上实现——而不仅仅是网页浏览器。它们还可以用来封装其他与互联网兼容的协议,使它们成为网页兼容。一个例子是 MQTT,这是一个著名的物联网 pub/sub 协议,可以通过 WebSocket 集成到网页浏览器中。你将在下一章中更详细地了解这一点。

在物联网的背景下,WebSocket 通信创建的永久链接很有趣,尤其是在考虑希望观察或订阅现实世界属性(如环境传感器)的应用程序时。最后,与 HTTP 轮询相比,WebSocket 显著降低了带宽消耗。然而,缺点是保持 TCP 连接始终开启可能会导致电池消耗增加,并且比 HTTP 在服务器端更难扩展。

6.2.6. 未来:从 HTTP/1.1 到 HTTP/2

当正确使用时,HTTP/1.1 是构建网络服务的一个优秀协议,正如你在本章中看到的。但它可以追溯到 1999 年。你还记得那个时代吗?当时,我们使用 Windows 95,用有线电话互相通话,物联网这个术语才刚刚被提出!我们主要使用 HTTP 来显示点击计数器、网站地图和动画的“建设中”gif,但并不是用来显示很多实时数据,更不是用来与设备交互。

显然,自那时以来,网络已经发生了巨大的变化,对可扩展性、性能、实时消息传递和安全性的需求显著增加。因此,互联网即将全面拥抱一场名为 IPv6 的革命(见第五章),以及另一场名为 HTTP/2 的革命.^([15]) 新协议并非专门为物联网设计,但新协议的创造者显然考虑到了物联网的一些需求。HTTP/2 专注于在 HTTP/1.1 上进行多项改进,并且可以轻松运行在你的 Pi 上!^([16])

¹⁵

http2.github.io

¹⁶

已经有几种 HTTP/2 的 Node.js 实现。如果你想在你 Pi 上尝试 HTTP/2,你应该尝试在 github.com/molnarg/node-http2 上可用的 node-http2 模块。

性能改进

这个新的 HTTP 版本允许多路复用响应——也就是说,并行发送响应。这解决了 HTTP/1.x 的首部阻塞问题,当时在 TCP/IP 连接上一次只能有一个请求是挂起的^([17])。此外,它鼓励客户端和服务器使用单个 TCP 连接,在该连接上请求和响应以流的形式发送。

¹⁷

在 HTTP/1.1 中提出了流水线技术来解决这个问题,但它并没有完全解决这个问题,并且难以部署;参见devcentral.f5.com/articles/http-pipelining-a-security-risk-without-real-performance-benefits

这对于 WoT 来说是一个有趣的功能,因为它导致连接的更高效使用,从而减少了 HTTP 的开销。它还导致传输速度更快,因此有可能在通信所需的电池功率方面节省能源.^([18])

¹⁸

Akamai 开发了一个简单的页面,让您可以尝试这些改进:http2.akamai.com/demo

更高效的格式

HTTP/2 还引入了使用高效且低内存压缩格式的压缩头,^([19])与在 HTTP 之上最常用的压缩格式 GZIP 不同。这减少了每个 HTTP 请求和响应的大小。此外,HTTP/1.1 是一个 ASCII 协议——即传输 ASCII 字符的协议——而 HTTP/2 使用二进制帧,这意味着它传输二进制数据流。二进制协议在解析上更高效,且更紧凑。

¹⁹

称为 HPACK:HTTP/2 的头压缩。参见:datatracker.ietf.org/doc/rfc7541/

所有这些都特别有趣,因为“资源受限的物联网”意味着数据包的大小显著减小,允许具有有限 RAM 的设备愉快地处理 HTTP/2。

服务器推送

最后,HTTP/2 引入了服务器推送的概念。具体来说,这意味着服务器可以在不需要等待客户端发送请求的情况下向客户端提供内容。从长远来看,HTTP/2 上服务器推送的广泛应用甚至可能消除像 WebSocket 或 webhooks 这样的推送协议的需求。

HTTP/2 与物联网

这对 HTTP/2 的功能及其对物联网的影响的概述远非完整,但它表明网络的未来将使物联网变得更加出色。有趣的是,您不需要改变太多方法:HTTP 的实现将改变,但您构建在其上的 API 不会改变,因为 HTTP/2 的语义保持不变。因此,您在本书的剩余部分学到的任何内容都将适用于 HTTP/2!

HTTP/2 规范于 2015 年 2 月正式通过,Chrome、Firefox 和 Opera 等几个浏览器已经开始支持它。这意味着该标准即将在全球范围内部署,并将很快使物联网变得更加高效!

6.3. 摘要

  • 当正确应用时,REST 架构是创建大规模和灵活分布式系统的优秀基础。

  • REST API 非常有趣,并且易于应用,可以启用对物理对象和其他设备的访问。

  • 各种机制,如内容协商和作为应用状态引擎的 Hypermedia(HATEOAS)的缓存,有助于创建出色的物联 API。

  • 一个五步设计过程(集成策略、资源设计、表示设计、界面设计和资源链接)允许任何人根据行业最佳实践为物联创建有意义的 REST API。

  • 实时 Web 的最新发展,如 WebSockets,允许创建高度可扩展、分布式和异构的实时数据处理应用。直接与网络通信的设备可以轻松使用基于 Web 的推送消息来高效地流式传输其传感器数据。

  • HTTP/2 将为物联带来许多有趣的优化,例如多路复用和压缩。

你已经深入了解了 REST 和 HTTP,但如果你还想了解更多,你可能想看看 Leonard Richardson 和 Sam Ruby 所著的非常好的RESTful Web Services(O’Reilly Media,2007)[20],或者浏览到 WoT Publications 页面[21],在那里你可以找到许多关于使用 REST 为现实世界设备提供资源的方法。

^(20)

shop.oreilly.com/product/9780596529260.do

^(21)

webofthings.org/publications/

在了解了这么多关于 REST、HTTP 和 WebSockets 的理论之后,你可能想知道如何在你的物联设备上实际实现这些内容。准备好吧——这就是下一章将关注的内容。你将学习如何实现本章中你所学到的模式,以便你可以访问任何设备,无论它是否连接到互联网。准备好你的编码手指;在下一章中,你将需要它们更多!

第七章. 实现 Web 物联

本章涵盖

  • 探索实现 Web 物联的三个可能模式

  • 通过 Web 协议提供传感器和执行器的访问

  • 在你的 Pi 上使用 Node.js 和 Express 构建 REST 和 WebSockets API

  • 构建 CoAP 设备并将它们连接到网络

  • 在你的 Pi 上使用 MQTT 连接到 EVRYTHNG API

在上一章中,我们关注了如何为物理物联设计一个干净的 Web API。本章基于你所学到的原则,描述了如何实际实现这些 API。

下文详细介绍了实现 Web Thing API 的三个不同方法,以处理实现策略。在这里,我们关注将事物集成到网络中的集成模式,回答问题:“我们实际上在哪里实现事物的 API 以将其集成到网络中?”这个问题很重要,因为并非所有事物都是平等的!正如你在第五章中看到的,一些事物可以访问互联网并原生实现 Web 协议。但对于计算能力或功耗受限的其他事物,Web 协议可能具有挑战性。

7.1. 将设备连接到网络

最直接的集成模式是直接集成模式。它可以用于支持 HTTP 和 TCP/IP 的设备,因此可以直接暴露 Web API。当设备可以直接连接到互联网时,这种模式特别有用;例如,它使用 Wi-Fi 或以太网。其次,我们探讨网关集成模式,资源受限的设备可以使用非 Web 协议与更强大的设备(网关)通信,然后该网关为这些非 Web 设备暴露 REST API。这种模式对于无法直接连接到互联网的设备特别有用;例如,它们只支持蓝牙或 ZigBee,或者它们资源有限,无法直接处理 HTTP 请求。第三,云集成模式允许强大的可扩展 Web 平台充当网关。这对于任何可以通过互联网连接到云服务器的设备都很有用,无论它是否使用 HTTP,并且需要比它单独提供更多的功能。

在第六章中介绍的 Web Things 设计流程的第一步是选择这些模式之一。之后,我们可以应用设计流程的步骤 2-4。第 5 步将在第八章中更详细地介绍:

1. 集成策略—选择将事物集成到互联网和 Web 的模式。这些模式在本章中介绍。

2. 资源设计—识别事物的功能或服务,并组织这些服务的层次结构。

3. 表示设计—决定为每个资源提供哪些表示。

4. 接口设计—决定每个服务可能的命令,以及相应的错误代码。

5. 资源链接设计—决定不同的资源如何相互链接。

参考 WoT 服务器—webofthings.js

在接下来的几章中,您将学习如何实现一个功能齐全的物联网服务器,该服务器允许根据本书中介绍的 WoT 架构和概念将任何设备连接到网络。每一章都将扩展前一章中编写的代码,因此您可以通过按顺序阅读下一章来从本书中获得最大收益。通过这样做,到本书结束时,您将从头开始构建一个完整、可扩展和安全的框架来实现网络事物。

如果您迫不及待,也可以下载物联网参考实现的最新版本:webofthings.js。您可以在我们的 GitHub^([a])或直接在 npm^([b])上找到它,并在您自己的项目、应用程序或设备中使用它。并且,请向我们发送您的反馈和拉取请求——我们希望这个框架能够超越本书的内容。

^a

github.com/webofthings/webofthings.js

^b

www.npmjs.com/package/webofthings

7.2. 直接集成模式——设备上的 REST

实现网络事物 API 的第一种也是最简单的方法是在事物上直接实现,如图 7.1 所示。这要求事物可以通过互联网协议(TCP/IP)访问,并且能够直接托管 HTTP 服务器。疯狂的想法,我们听到您这么说?好吧,Web 服务器并不大,可以轻松地安装在最小的设备上。一些最小的 HTTP 服务器可以在少于 50 字节的 RAM 上运行,包括 TCP/IP 堆栈,这使得即使是微型且廉价的 8 位设备也能使用 HTTP。这也意味着您的 Pi——或您使用的任何其他 Linux 设备——肯定可以实现 Web 服务器,通过 REST API 提供对资源的访问。

¹

MiniWeb 是一个极小的 Web 服务器的例子;请参阅miniweb.sourceforge.net/

图 7.1. 直接集成模式。在这个模式中,网络事物是 Wi-Fi 连接的灯具,运行嵌入式 HTTP 服务器,因此可以直接提供物联网 API。这允许网络事物客户端,如移动应用程序或其他网络事物,直接通过 HTTP 与灯具通信。图片经model.webofthings.io许可使用。

尽管在大多数嵌入式设备上都可以实现网络协议,但当设备不是由电池供电且需要客户端如移动网页应用直接访问时,直接集成模式是最佳选择。一个很好的例子是家庭自动化,通常电力供应充足,低延迟的本地交互很重要——例如,开关灯。在本节的其余部分,我们将展示如何在您的 Pi 上直接实现 WoT 服务器,以便它可以直接使用网络协议并加入物联网俱乐部。

7.2.1. 创建 WoT 服务器

让我们从在 Pi 上设置一个 Web 服务器开始。Node.js 主要用于构建 Web 应用程序,所以你可以像在第三章中做的那样从头开始构建一个。但因为我们正在转换方向,并希望实现一个完整的 REST API,大量的 Node.js 框架可以帮助我们。^([2)] 为了简化起见,我们将使用最受欢迎的:Express。^([3)]

²

这里列出了 Node.js 最受欢迎的 Web/REST 框架:nodeframework.com/

³

expressjs.com/

你将构建的服务器架构在 图 7.2 中显示。它围绕一个中央模型构建,插件可以更新和观察。此外,所有传感器和执行器都作为 Web 资源可用,这得益于 Express 框架的使用。

图 7.2. 我们 WoT 服务器的组件架构:服务器使用 Express 框架。系统的其余部分围绕一个插件可以观察和更改的模型构建。插件建立在其他 Node 库之上,通过 GPIO 提供对物理资源的访问。

我们将在 Pi 上部署此代码,但如果你还没有 Pi,不要担心;除了少数例外,你将能够在你的计算机上运行本章中的所有示例。但用 Pi 来做会更有趣!^([4)]

查看我们合作伙伴提供的特别优惠:book.webofthings.io

Express:Node.js Web 框架

Express 不仅仅是一个 Web 服务器;它是一个完整的框架,几乎可以处理现代 Web 应用程序需要的所有功能,从 RESTful API 到 HTML 和 CSS 模板引擎、数据库连接器、cookie 管理,甚至社交网络集成。Express 还拥有庞大的开发者社区和多种插件。

虽然 Express 在 Pi 和大多数其他 Linux 设备上运行顺畅,但值得注意的是,Express 并不是为物联网设备实现 Web API 的最轻量级方式。但正如你将在许多场合看到的那样,这种灵活的框架使我们能够快速扩展我们的 Web Thing API 并实现我们将遇到的多种模式。

现在我们将展示如何基于 Express 创建一个 WoT 服务器。^([5)] 项目的结构如下所示。你可以复制这个结构并通过 NPM 安装 Express。或者,你可以从书籍的 GitHub 仓库克隆项目,book.webofthings.io,它位于文件夹 chapter7-implementation/part1-2-direct-gateway/ 中。^([6)]

虽然你当然可以直接在 Pi 上运行这些命令,但更方便、更实际的做法是在你的笔记本电脑或台式计算机上开发应用程序,然后通过 Git 在 Pi 上拉取代码,正如第四章所述。book.webofthings.io

查看 book.webofthings.io

列表 7.1. Pi 项目 WoT 服务器目录结构
wot-pi
—— middleware
   —— converter.js
—— plugins
   —— external
      —— coapPlugin.js
   —— internal
       —— DHT22SensorPlugin.js
       —— ledsPlugin.js
       —— pirPlugin.js
—— public
   —— websocketsClient.html
—— resources
   —— model.js
   —— resources.json
—— routes
   —— actuators.js
   —— sensors.js
   —— things.js
—— servers
   —— coap.js
   —— http.js
   —— websockets.js
—— utils
   —— utils.js
—— package.json
—— wot-server.js

7.2.2. 资源设计

您现在已经拥有了实现 API 所需的所有元素,让我们从我们流程的第一步开始:资源设计。首先,您需要考虑设备上的物理资源,并将它们映射到 REST 资源中。从您在第四章中配置的 Pi 开始,您的设备至少应该有一个 LED、一个被动红外(PIR)传感器,可能还有一个温度传感器和一个湿度传感器。如果您没有所有这些传感器,不要担心。我们将向您展示如何模拟它们!这些传感器和执行器可以映射到图 7.3 中显示的资源树中。

图 7.3. 您的 Pi 资源树,包含多个传感器和执行器及其层次结构。每个资源都通过遵循到该资源的路径获得一个 URL。例如,被动红外传感器的 URL 将是 http://localhost:8484/pi/sensors/pir。

07fig03_alt.jpg

第 1 步:创建资源模型

您现在可以将此树映射到您的应用程序将使用的 JSON 文件中,以公开您希望的结构化的 URL。创建或打开包含下一列表中对象的 resources/resources.json 文件。

列表 7.2. /resources/resources.json: Pi 资源的 JSON 模型
{
  "pi": {
    "name": "WoT Pi",
    "description": "A simple WoT-connected Raspberry PI for the WoT book.",
    "port": 8484,
    "sensors": {
      "temperature": {
        "name": "Temperature Sensor",
        "description": "An ambient temperature sensor.",
        "unit": "celsius",
        "value": 0,
        "gpio": 12
      },

      "humidity": {
        "name": "Humidity Sensor",
        "description": "An ambient humidity sensor.",
        "unit": "%",
        "value": 0,
        "gpio": 12
      },
      "pir": {
        "name": "Passive Infrared",
        "description": "A passive infrared sensor.
            When 'true' someone is present.",
        "value": true,
        "gpio": 17
      }
    },
    "actuators": {
      "leds": {
        "1": {
          "name": "LED 1",
          "value": false,
          "gpio": 4
        },
        "2": {
          "name": "LED 2",
          "value": false,
          "gpio": 9
        }
      }
    }
  }
}

接下来,您创建 resources/model.js 文件,如下所示导入我们的 JSON 模型:

var resources = require('./resources.json');
module.exports = resources;

此文件从 resources.json 文件加载我们的 Pi 的 JSON 模型,exports将此对象作为您可以在应用程序中使用的 node 模块提供。

第 2 步:创建 Express 路由

您现在可以将这些资源绑定到您的 Web 服务器将响应的 URL 上。在 Express 和许多其他 Web 框架中,资源的 URL 由一个路由定义。您在 routes/文件夹中的两个文件(actuators.js 和 sensors.js)中定义这些路由,如下一两个列表所示。

列表 7.3. /routes/sensors.js: 传感器路由

181fig01_alt.jpg

第 3 步:创建 Express 应用程序

现在路由已经准备好了,您需要在 HTTP 服务器中加载它们,这通过 servers/http.js 文件完成。该文件的内容如下所示,本质上是一个包裹在 Express 框架中的 HTTP 服务器。

列表 7.4. /servers/http.js: Express 应用程序

182fig01_alt.jpg

在您测试实现之前,需要再添加一个文件:wot-server.js,如下一列表所示。这是您的 WoT Pi 服务器的入口点,负责以正确的配置启动服务器。

列表 7.5. /wot-server.js: 应用程序入口点

182fig02_alt.jpg

您现在可以通过在终端中启动应用程序来测试您的服务器,就像通常一样.^([7]) 一旦启动,您将能够使用浏览器访问资源——例如,温度传感器在 http://localhost:8484/pi/sensors/temperature 或执行器列表在 http://localhost:8484/pi/actuators。在这两种情况下,您将获得与您的请求相对应的 JSON 有效负载。显然,如果您在 Pi 上运行代码,请将 localhost 替换为 Pi 的 IP 地址或名称(raspberrypi.local)。

wot-pi/ 文件夹中运行 npm install 后,运行 node wot-server.jsnodemon wot-server.js,如 第三章 中所示。

第 4 步:将传感器绑定到服务器

这听起来不错,但目前我们返回的只是 列表 7.2 中的 JSON 模型的一部分,以及缺失的实际世界数据——实际的温度!您需要将 Pi 上的某些传感器数据放入服务器中。您将通过创建多个插件每个传感器或执行器一个插件来完成此操作。每个传感器插件应在从传感器读取新数据时更新模型。传感器插件至少应具有以下列表中显示的函数。所有插件都在 plugins/ 目录中。PIR 传感器插件的实现如下一列表所示,它本质上是对 第四章 中编写的 pir.js 代码的扩展。

列表 7.6. /plugins/internal/pirPlugin.js: PIR 传感器插件

温湿度和传感器代码如下一列表所示,除了一个新函数:connectHardware(),它使用在 第四章 中介绍的 node-dht-sensor 库。

列表 7.7. /plugins/internal/DHT22SensorPlugin.js: 温湿度传感器插件

两个插件的代码明显共享了许多公共函数,因此作为练习,您可能希望将公共功能提取到一个 JavaScript 原型中。如果您不知道如何做,不要担心;我们将在 第八章 改进代码时说明这一点。

您现在可以通过从 wot-server.js 文件中引入它们并使用正确的参数启动每个插件来在您的服务器上安装您的插件,如下所示。

列表 7.8. 将插件集成到 wot-server.js 中

再次运行您的服务器并打开湿度传感器页面,网址为 http://localhost:8484/pi/sensors/humidity。刷新页面几次;每次应该得到不同的值。

在您的 Pi 上使用真实硬件进行测试

模拟传感器是不错的,但使用真实的传感器则更好!要在你的 Pi 上使用真实传感器,你首先需要安装连接传感器的库。问题是这些库不支持像你的笔记本电脑这样的非 IoT 平台,所以如果你通过npm install --save添加依赖项,你将无法再在 PC 上安装你的代码。别担心:有解决办法!NPM 允许你为 package.json 文件设置一个optionalDependencies对象。想法是,如果optionalDependencies中的依赖项无法安装,npm install不会失败。继续在你的 package.json 文件中添加以下代码;第一个依赖项支持 PIR 传感器和 LED,第二个依赖项支持温度和湿度传感器(如果你有):

"optionalDependencies": {
  "onoff": "¹.0.4",
  "node-dht-sensor": "⁰.0.8"
}

最后,在你的 Pi 上运行npm install来安装这些依赖项。现在,使用{'simulate': false}修改你想要启用的插件的参数,并在 Pi 上运行你的应用程序;^([8]) 这将连接到物理驱动器。现在,你的 Pi 正通过 Web API 向世界公开其实际传感器数据和执行器。你可以通过 REST 使用你的 Pi 地址访问它们,例如,raspberrypi.local:8484/pi/sensors/pir

如果你已经设置了温度和湿度传感器,你需要使用 sudo 运行代码:sudo node wot-server.js

7.2.3. 表示设计

设计过程的下一步是表示设计。REST 对特定的数据格式或表示是中立的。我们提到 JSON 是保证互操作性的必需品,但它并不是唯一有趣的数据表示。在本节中,你将添加对两种更多表示的支持。你将添加 HTML 支持,因为这允许你以人性化的方式浏览你的 Pi 并发现其资源。为此第一步,你将使用一个简单的 JSON 到 HTML 库,称为json2html

当你忙于这些时,让我们也添加对 MessagePack 的支持,这是一种比 JSON 更紧凑的二进制替代品,在第六章中简要介绍过。MessagePack 可以轻松映射到 JSON,但比 JSON 更紧凑,这使得它在通过低带宽网络通信的资源受限的设备中很有趣。大多数流行编程语言都有用于编码和解码 MessagePack 的库,因此在你的服务器中添加对它的支持只是安装 Node 的msgpack5模块的问题。

实现表示转换中间件

在 Express 中支持其他表示的方法有很多,但我们提出了一种基于中间件模式的模块化方法。许多 Node 库,包括 Express,都支持在请求-响应周期中访问请求(req)和响应(res)对象的函数链式调用。这种模式允许扩展性,同时保持代码干净和模块化。本质上,一个中间件可以执行更改请求或响应对象的代码,然后可以使用 next() 函数决定响应客户端或调用堆栈中的下一个中间件。我们将要实现的中间件链在图 7.4 中显示。

图 7.4. 为 WoT 服务器实现的中间件链:每个中间件都是通过 app.use() 函数添加到 Express 应用程序的。然后,每个中间件都会获得对请求对象、响应对象和链中下一个中间件的引用。

图片

典型中间件的存根看起来如下:

function myMiddleware(req, res, next) {
  // do something with the request
  // AND/OR add something to the response
  res.send("something");
  next();
}

我们使用这种模式来实现一个支持 MessagePack 和 HTML 表示的表示转换器。首先,通过 NPM 安装这两个库(npm install node-json2html msgpack5)。中间件代码位于 middleware/converter.js 中,如列表 7.9 所示。本质上,我们的表示转换器中间件将实现第六章中描述的内容协商模式,其中它会在请求中查找 Accept 头,并尝试提供客户端请求的格式表示。如果它不识别该格式,则默认返回 JSON。

列表 7.9. /middleware/converter.js: 实现表示中间件

图片

图片

现在中间件已经准备好了,你需要修改服务器的路由,使它们调用链中的下一个中间件,即表示转换器,而不是直接响应请求。你可以在两个路由文件 sensors.js 和 actuators.js 中轻松实现这一点,通过将每个 res.send(resource) 替换为 req.result = resourcenext()。以下列表显示了传感器的路由更改,所以请确保也将这些更改应用到执行器的路由上。

列表 7.10. 在 sensors.js 中调用下一个中间件

图片

最后,你需要将中间件添加到 Express 应用程序中,以便它在中间件链中被调用。在 http.js 中,首先使用 converter = require('./../middleware/converter') 引入中间件,并通过调用 app.use(converter()) 将其添加到链中。因为你的转换中间件响应客户端,确保你在 app.get('pi') 之后添加它,否则它将绕过任何其他中间件。从你的浏览器中测试你的新服务器(或 PC):raspberrypi.local:8484/pi/sensors/pir。你现在应该看到一个 PIR 传感器的简约 HTML 表示。现在使用 cURL 通过设置 Accept 头到所需的 MIME 类型(JSON 为 application/json,MessagePack 为 application/x-msgpack)来请求其他类型的表示,如下所示:

curl -i -H "Accept: application/json" \
-X GET 'http://raspberrypi.local:8484/pi/sensors/pir'

curl -i -H "Accept: application/x-msgpack" \
-X GET 'http://raspberrypi.local:8484/pi/sensors/pir'

你的服务器现在可以为它提供的所有资源提供三种不同的表示——JSON、HTML 和 MessagePack。所有这些都要归功于一个 20 行长的中间件!这是对开放网络标准和 Node.js 力量的一个很好的说明吗?

7.2.4. 接口设计

到目前为止一切顺利,但你只能通过 HTTP 从你的 Pi 上获取资源。其他动词如 PUTPOST 呢?HTTP 状态码呢?使用 WebSockets 订阅传感器数据呢?这是接口设计步骤的目标。

添加一个主体解析器

对于传感器来说,GET 动词就足够了,因为你只需要读取它们,但如果你想要改变执行器——例如,打开/关闭一个 LED 或改变其颜色呢?第一步是通知 Express 你愿意接收来自客户端的 JSON 负载。在中间件链中添加一个 HTTP 主体解析器就可以做到这一点。在 http.js 中,引入 body-parser 模块:bodyParser = require('body-parser')。同时,将中间件添加到链的起始位置。为什么是起始位置?你想要首先解析 HTTP 消息的主体,以便所有其他中间件都可以使用:app.use(bodyParser.json())

支持其他 HTTP 动词

你的服务器现在也可以处理传入的 JSON 消息,所以让我们添加对使用 PUT 请求更新 Pi LED 状态的支持。要添加对 PUT 的支持,你需要再次扩展 actuators.js 中的路由。将 /leds/:id 的路由转换为以下列表。

列表 7.11. 在 /routes/actuators.js 中为 LED 添加 PUT 支持

你现在可以通过在 LED 资源上运行带有适当 JSON 负载的 PUT 请求来更新 LED 的状态。在 cURL 中,这看起来像以下内容:

curl -i -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -X PUT 'http://localhost:8484/pi/actuators/leds/1' \
  -d '{"value":true}'

如果你成功了,你将在 Node 控制台中看到以下内容:Changed LED 1 value to true

将执行器绑定到服务器

这听起来不错,但实际上它只更新了您的模型;它并没有激活现实世界!要改变这种行为,您需要编写一个具有类似结构但有一个显著区别的 LED 插件:它有一个 observe() 函数。observe() 函数对于执行器插件是必需的,其目标是观察模型的变化并将它们报告给物理世界。

这个实现的核心理念是使用 Object.observe() 函数.^([9]) 这允许您通过注册一个回调函数来异步观察对象的变化,每当检测到观察对象的变化时,该回调函数将被调用:

注意,这个功能自 Node 0.11.13 版本以来就已经被支持,并且在我们这本书中使用的当前 Node LTS v4.X 中可用,但它在未来的 LTS 版本中可能会被移除,所以请确保您使用 node 4.X LTS 来确保其正常工作。请参阅 www.infoq.com/news/2015/11/object-observe-withdrawn

Object.observe(obj, function(changes) {
  console.log(changes);
});

让我们利用这个特性来实现您的 LED 插件。它使用 on/off 库来改变连接到 GPIO 的 LED 的状态。请注意,为了保持简单,此代码仅支持一个 LED(LED #1),但请随意将其扩展以支持更多作为练习。在 observe 中,您注册一个回调函数,以便在您的 LED 模型发生变化时触发。实际上,您是在这个回调函数中改变 GPIO 的状态,如下一列表所示。

列表 7.12. /plugins/internal/ledsPlugin.js: LED 插件

将此插件添加到 wot-server.js 中初始化插件的列表中,并将模拟设置为 false:ledsPlugin.start({'simulate': false})。在您的 Pi 上运行它,并再次尝试针对您的 Pi 的 URL 进行最后的 cURL 请求;这次 LED 应该会亮起!是的,我们知道。这只是个 LED,但,哦,这感觉真好。欢迎来到物联网!

7.2.5. 通过 WebSocket 的 Pub/sub 接口

我们界面设计步骤的最后一部分是支持通过 WebSocket 进行发布/订阅。我们的目标是提供一个简单的协议升级到 WebSocket 的能力,以便通过 WebSocket 订阅我们所有的资源。

对于 Node,有几种 WebSocket 的实现。最完整和最知名的一个可能是 Socket.io,它不仅仅是一个 WebSocket 服务器。它还支持不支持 WebSocket 的客户端的回退;例如,如第六章中所示的长轮询。我们建议您更仔细地查看 Socket.io,但为了我们的 Pi 服务器,我们将选择一个纯且高性能的 WebSocket 实现,称为 WS。

WS 集成的实现如图 7.5 所示。你创建一个 WebSocket 服务器,并将其监听器附加到 Express 的主要 HTTP 服务器上,以监听 WebSocket 协议升级请求;参见第五章。然后,你接收这些升级请求,并使用请求的 URL 来观察模型中的相应资源。每当检测到变化时,你通过开放的 WebSocket 连接将变化传播给客户端。

图 7.5. WoT 服务器 WebSocket 实现的序列图:WebSocket 服务器通过观察相应的资源(例如,/temp)来响应客户端通过 HTTP 升级请求打开 WebSocket 连接;每当资源的可用新值出现时,WebSocket 服务器通过它与客户端保持的开放 TCP 连接发送它。

07fig05_alt.jpg

要将此集成到 Pi 上的 WoT 服务器中,首先安装 WS 模块(npm install --save ws)。然后按照以下列表实现 WebSocket 服务器。

列表 7.13. /server/websockets.js: WebSockets 服务器

ch07ex13-0.jpg

ch07ex13-1.jpg

启用 WebSocket 支持的最后一步是在 wot-server.js 中启动 HTTP 服务器后初始化 WebSocket 服务器。修改对 listen() 的调用,包括一个回调,以启动 WebSocket 服务器,如下列所示。

列表 7.14. 在 wot-server.js 中启用 WebSockets 服务器
var httpServer = require('./servers/http'),
  wsServer = require('./servers/websockets'),
  resources = require('./resources/model');
[...]
var server = httpServer.listen(resources.pi.port, function () {
  console.log('HTTP server started...');
  wsServer.listen(server);
  console.info('Your WoT Pi is up and running on port %s', resources.pi.port);
})

就这样!你现在可以通过 WebSocket 订阅 Web 上的所有资源,并在资源状态发生变化时得到通知。你可以使用任何 WebSocket 客户端——例如,一个网页,如你在第二章中看到的示例。尝试将其修改为订阅你的 WoT Pi,或者你可以在任何最近的浏览器中使用 /public/websocketsClient.html 文件。当你在这个文件中打开浏览器时,你将在你的开发者工具的 JavaScript 控制台中看到 WebSocket 消息,如图 7.6 所示。

图 7.6. 使用 Firefox 中的简单 WebSocket 客户端通过 WebSocket 订阅温度更新。上半部分显示协议升级过程,下半部分显示从设备(这里是一个 Pi)直接推送到基于浏览器的客户端的消息。

07fig06_alt.jpg

技术角落——我想更好的 JavaScript!

我们在本节中构建的 WoT 服务器在代码方面保持极其简单,以确保每个人都能跟上。通过应用一些 JavaScript 良好实践和模式,代码可以变得更加简洁和可重用。如果你了解 JavaScript,一个很好的练习是将代码模块化;例如,从原型开始,以分解大量插件代码。在第八章中,你将看到我们在这里展示的框架的改进版本,但你现在就可以不犹豫地构建自己的版本!

7.2.6. 摘要——直接集成模式

在本节中,我们展示了如何快速构建一个完整的物联网 API,它不仅可以在实际设备上运行并与实际传感器和执行器通信,而且还支持许多高级功能,如内容协商和通过 WebSockets 的推送支持。但是,有一系列设备,你无法享受在本地运行 Node 的奢侈。下一节将描述这些情况下的解决方案,我们将提供另一种非 HTTP/WebSockets 设备的模式:网关集成模式。

7.3. 网关集成模式——CoAP 示例

在上一节中,你通过为你的树莓派传感器创建 HTTP 和 WebSockets API 将其转换为一个 Web 设备。这很有效,因为你的树莓派不是电池供电的,有足够的带宽(Wi-Fi/Ethernet),并且有足够的 RAM 和存储空间来运行 Node。但并非所有设备都这么幸运。对 HTTP/WS 或甚至 TCP/IP 的原生支持并不总是可能,甚至可能不是所希望的。对于电池供电的设备,Wi-Fi 或以太网可能会消耗太多的电量,因此它们需要依赖于低功耗协议,如 ZigBee 或蓝牙。这意味着这些设备不能成为物联网的一部分吗?当然不是,如图 7.7 所示。

图 7.7. 网关集成模式。在这种情况下,Web 设备不能直接提供 Web API,因为设备可能不支持 HTTP。一个应用网关通过以设备的名义提供 Web API 作为设备的代理。这个 API 可以托管在路由器上,例如在蓝牙的情况下,或者在其他暴露 Web 设备 API 的设备上;例如,通过 CoAP。

07fig07_alt.jpg

这些设备也可以成为物联网的一部分,只要某个中间设备能够通过我们之前描述的 WoT API(如我们之前描述的那样)暴露设备的函数。这些中间设备被称为应用网关(在此之后我们将它们称为 WoT 网关),它们可以使用任何非 Web 应用协议与设备通信,然后将这些协议转换为任何 HTTP 客户端都可以使用的干净 RESTful WoT API。一些网关还能执行比简单协议转换更多的功能。它们可以添加一层安全或认证,临时聚合和存储数据,为没有语义描述的设备提供描述,等等。

为了更好地理解 WoT 网关是什么以及它能为非网络设备做什么,让我们看看一个具体的例子,其中我们使用 HTTP 和 WebSocket API 暴露一个 CoAP 设备。正如你之前所看到的,CoAP 是一个基于 REST 的有趣协议,但由于它不是 HTTP,并且使用 UDP 而不是 TCP,因此需要一个网关来将 CoAP 消息从/到 HTTP 进行转换。因此,它非常适合在低功耗无线电通信中进行设备到设备的通信,但你不能在没有安装特殊插件或浏览器扩展的情况下从你的浏览器中的 JavaScript 应用程序与 CoAP 设备进行通信。让我们通过使用你的 Pi 作为 WoT 网关到 CoAP 设备来解决这个问题。

7.3.1. 运行 CoAP 服务器

首先,创建一个简单的 CoAP 资源。为了简化——并且为了控制你的预算——你将在你的计算机上模拟一个 CoAP 设备。当然,你也可以购买一个资源受限的设备,如 Arduino Uno,并在其上安装 CoAP.^([10])

¹⁰

例如,使用这里可用的 microcoap 实现:github.com/1248/microcoap

Node 上有多个 CoAP 库,但我们最喜欢的是 coap(运行 npm install coap 来安装它)。在 /servers/coap.js 中提供了一个最小 CoAP 服务器的实现,如下所示。

列表 7.15. coap.js:一个简单的 CoAP 服务器

你可能会意识到这段代码与你在前两章中学到的并没有太大区别。这是因为 CoAP 受 HTTP 和 REST 的启发很大,但为了适应低功耗嵌入式系统的需求而进行了调整。由于 CoAP 使用 UDP 而不是 HTTP/TCP,你不能直接通过 CoAP URI:coap://localhost:5683/co2 从你的浏览器中直接访问这些资源。

但你可以使用一个名为 Copper 的优秀 Firefox 插件.^([11]) 一旦安装了插件,启动 CoAP 服务器(在服务器目录中运行 node coap.js)并你可以通过在 Firefox 的地址栏中输入 CoAP URI 来访问你刚刚创建的资源;例如,coap://localhost:5683/co2。

¹¹

addons.mozilla.org/en-US/firefox/addon/copper-270430/

7.3.2. 通过网关代理 CoAP

下一步是将 CoAP 请求代理到 HTTP,这样你就可以通过网页浏览器访问 CoAP 设备。在这里,你将扩展你的 WoT 网关以通过你的 Pi 提供对 CoAP 设备的访问。

将你的 Pi 转换为 WoT 网关需要两个简单的步骤。首先,你创建一个 CoAP 插件,这本质上是在我们的模型中创建一个新的 CoAP 客户端。其次,你为 CoAP 设备必须提供的资源创建路由。CoAP 插件的代码在下一列表中显示。由于大部分代码与其他温度/湿度传感器插件类似,我们关注的是不同之处。

列表 7.16. /plugins/external/coapPlugin.js:一个简单的 CoAP 插件

图片

然后您需要为您的 CoAP 设备添加资源路由。为了简化,您只需连接一个 CO2 传感器,但您可以自由扩展以支持您想要的任何其他资源。您需要在 /routes/things.js 中添加这些路由:

router.route('/coapDevice/sensors/co2').get(function (req, res, next) {
  req.result = resources.things.coapDevice.sensors.co2
  next();
});

您将路由器作为中间件加载到 /servers/http.js 中,并将其绑定到 /things。这意味着 CoAP 设备将在 /things/coapDevice/sensors/co2 上可访问,这是有意义的,因为这是一个由 Pi 管理的实体,Pi 充当代理,而不是 Pi 本身。最后,在 wot-server.js 中引入并启动 CoAP 插件:

var coapPlugin = require('./plugins/external/coapPlugin');
coapPlugin.start({'simulate': false, 'frequency': 10000});

如果一切正常,您应该能够通过 Pi 网关使用 HTTP 访问模拟的 CoAP 设备。首先,在您的 PC 上启动 CoAP 服务器(node coap.js),然后启动 Pi 上的网关(node wot-server.js),最后,尝试运行以下 cURL 命令:

curl -i -H "Accept: application/x-msgpack" \
  -X GET 'http://raspberrypi.local:8484/things/coapDevice/sensors/co2'

您应该以 MessagePack 表示形式获取传感器读数。您还可以尝试使用一段 JavaScript 代码通过 WebSockets 连接到 CoAP CO2 传感器(例如,使用文件 /public/websocketsClient.html)或甚至将浏览器指向 raspberrypi.local:8484/things/coapDevice/sensors/co2。所有这些方法都将有效,即使 CoAP 设备无法提供 MessagePack 或 HTML 表示,也无法使用 WebSockets。这是 网关模式 的实际应用:它无缝地将 CoAP 设备集成到浏览器网络中!

这是对 CoAP 的一个非常简短的介绍。如果您想了解更多关于这个年轻且不断发展的受网络启发的协议的信息,请从探索 CoAP 技术门户开始.^([12])

¹²

coap.technology/

技术角落—I 想要一个通用的 CoAP 代理

尽管 CoAP 不被浏览器网络支持,但它为资源或电池受限的设备提供了一个有趣的权衡,尤其是在考虑设备间通信时。它与 HTTP 类似,因此翻译更容易,因为我们不需要在不同数据模型之间进行映射,并且可以在 HTTP 和 CoAP 上使用相同的 JSON 模型。我们在这里展示的翻译很简单,但不是通用的:我们需要手动将 CoAP 资源映射到 HTTP 路由。为 CoAP 设备构建通用的 HTTP 代理是可能的,并且并不过于复杂。如果您想尝试,您将在网上找到许多示例.^([a])

^a

例如,这里:github.com/morkai/h5.coap/blob/master/example/http-proxy.js

7.3.3. 摘要——网关集成模式

对于某些设备,直接支持 HTTP 或 WebSocket 可能没有意义,或者甚至不可能,例如当它们具有非常有限的资源,如内存或处理能力时,当它们无法直接连接到互联网(例如您的蓝牙活动追踪器)时,或者当它们是电池供电时。这些设备将使用更优化的通信或应用协议,因此需要依赖更强大的网关来将它们连接到物联网,例如您的手机通过桥接/翻译各种协议上传您的蓝牙手环中的数据。在这里,我们从头开始使用 Express 实现了一个简单的网关,但您也可以使用其他开源替代方案,例如 OpenHab^([13])或 The Thing System.^([14])

¹³

www.openhab.org/

¹⁴

github.com/TheThingSystem/steward

7.4. 云集成模式—通过 EVRYTHNG 的 MQTT

正如前几节所看到的,可以在设备或网关上直接实现 WoT 服务器。这在许多情况下是足够的,但一旦需要管理大量设备和数据,就需要一个更强大、可扩展的平台来存储和处理数据。图 7.8 所示的云集成模式是网关模式的扩展,其中网关是一个远程服务器,设备和应用程序通过互联网访问。

图 7.8. 云集成模式。在这个模式中,实体无法直接提供 Web API。但云服务作为强大的应用网关,以实体的名义提供更多功能。在这个特定的例子中,Web 实体通过 MQTT 连接到云服务,该服务通过 HTTP 和 WebSocket API 公开 Web 实体 API。云服务还可以提供许多附加功能,例如无限数据存储、用户管理、数据可视化、流处理、支持大量并发请求等。

使用云服务器有几个优点。首先,因为它没有设备和网关的物理限制,所以它具有更高的可扩展性,可以处理和存储几乎无限量的数据。这也使得云平台能够同时支持许多协议,高效地处理协议转换,并作为可扩展的中介,支持比物联网设备更多的并发客户端。其次,这些平台可以拥有许多可能需要相当长时间从头开始构建的功能,从行业级安全到专门的分析能力,再到灵活的数据可视化工具和用户及访问管理。这意味着你可以用极短的时间开始构建复杂的应用程序。第三,因为这些平台与互联网原生连接,所以你的设备的数据和服务可以轻松集成到第三方系统中,以扩展你的设备。

近年来,出现了许多云平台,它们通常具有相似的特征:在云中创建虚拟设备,这些设备可以永久与其物理对应设备通信,存储设备生成所有数据,并以多种方式可视化这些数据。Xively^([15])(前 Cosm,前 Pachube)是其中之一;它简单的 API 允许全球的开发者快速将设备连接到云。你可以为你的项目使用许多其他平台,例如 ThingWorx^([16]), ThingSpeak^([17]), Carriots^([18]), 和 thethings.io^([19])。

¹⁵

xively.com/

¹⁶

www.thingworx.com/

¹⁷

thingspeak.com/

¹⁸

www.carriots.com/

¹⁹

thethings.io/

在本节中,我们将教你如何连接到我们最喜欢的物联网平台:EVRYTHNG。好吧,我们可能有点偏见,因为我们就是构建它的!但至少它将帮助你快速了解云集成模式的优势。EVRYTHNG 始于 2011 年,也是第一个应用物联网原则的物联网平台,所以它应该感觉熟悉!在本节中,你将学习如何实现一个基本的网络连接电源插头,如图 7.9 所示。该插头监控连接到它的设备的能源消耗,并将这些数据永久发送到云中。这些读数将永久存储在云中。此外,该插头通过 MQTT(我们在第五章中描述过)永久连接到云,这意味着云可以在任何时间以最小的延迟向它发送命令。

图 7.9。一个物理设备通过 TCP/IP 使用 MQTT 连接到云平台,并通过其 Web 上的代理(称为 Thng)进行通信。然后,外部应用程序可以使用简单的 HTTP 客户端与 Thng 通信。

之后,我们将实现一个简单的 Web 应用程序,使用 HTML 和 JavaScript 订阅插头,实时显示当前消耗,并允许你打开和关闭插头。你需要的所有源代码都可以在本书 GitHub 仓库的 chapter7-implementation/part3-cloud 文件夹中找到。

7.4.1. 设置你的 EVRYTHNG 账户

你需要做的第一件事是创建一个 EVRYTHNG 账户并登录进去.^([20]) 别担心;这很快,而且实施这个练习不会花你一分钱。EVRYTHNG 服务对非商业项目是完全免费的!为了让你快速上手,你可以查看我们关于如何使用它的快速教程.^([21])

²⁰

你可以在这里完成:dashboard.evrythng.com/signup

²¹

EVRYTHNG 快速入门:developers.evrythng.com

一旦你创建了你的账户,你将需要你的账户 API 密钥(称为操作员 API 密钥^([22]))。这个密钥将允许你在云中创建和管理你的设备的数字身份,并与它们交互。绝对不要与他人分享它,因为这个 API 密钥非常强大,它可以删除你账户中的每一个 Thng——这不是一个打字错误;EVRYTHNG 喜欢省略元音——这很重要!甚至不要与你的女朋友/男朋友或最好的朋友分享!除非他真的非常棒,在这种情况下,可能可以——或者也可能不行!

²²

在这里找到它:dashboard.evrythng.com/account

在本节中,你设置账户以便可以将你的设备和应用程序连接到引擎。你也可以从 EVRYTHNG 仪表板或通过 POSTman 执行所有以下步骤。在本节中,我们将向你展示如何使用 cURL,这样你就可以轻松地从你的终端运行这些请求,并查看各种 HTTP 请求的详细信息。你可以在请求中添加--verbose标志来查看更多关于你的请求的详细信息。

自动设置你的账户数据

你将不得不手动在你的 EVRYTHNG 账户中创建一些实体,通过对我们云的多个 HTTP 请求来实现,如步骤 1–4 所述。如果你遇到任何问题或者你不想手动完成所有这些,我们为你准备了一个很酷的脚本,它会自动完成步骤 1–4,这个脚本在本节的末尾有描述。这个脚本叫做 setup.sh,你可以从你的 Pi 的终端运行它。

$ sh setup.sh XXX

其中XXX是你的操作员 API 密钥。你会看到一个类似矩阵的效果,它不仅运行你看到的所有命令为你创建这些实体,还将它们保存到一个 config.json 文件中,你将在下一节中需要这个文件。现在你已经准备好体验云实现模式的全部功能了。

第 1 步——创建一个项目和应用程序

在我们开始之前,你必须复制并粘贴以下两行到你的终端中,这将定义两个 shell 环境变量,这些 cURL 请求在本节中将会使用:

SERVER="https://api.evrythng.com"
EVRYTHNG_API_KEY="YOUR-API-KEY"

显然,在运行之前,你需要将YOUR-API-KEY替换成你自己的操作员 API 密钥。在这个部分,你将不得不以同样的方式定义相当多的其他环境变量。

你要做的第一件事是在你的账户中创建一个项目。一个项目是一个占位符(一个范围),它允许你分离你将生成的各种元素(Thngs 和数据)。此外,你需要一个项目来创建可以访问你一些数据的应用程序,因为你不想在客户端应用程序中使用你的操作员 API 密钥。将以下请求粘贴到你的终端中:

curl -X POST "$SERVER/projects" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "name": "Web of Things Book", "description": "My First WoT
     Project" }' --verbose

如果这个请求没有成功,请确保SERVEREVRYTHNG_API_KEY环境变量是正确的,你可以通过在终端中运行echo $SERVER来测试它们。如果成功了,你应该会收到一个201 Created响应,看起来像这样:

HTTP/1.1 201 Created
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Link, X-Result-Count
Content-type: application/json
Date: Sun, 14 Jun 2015 18:41:52 GMT
location: https://api.evrythng.com/projects/UCkWEEKnPe5wWhgbdhSfwnGc
Content-Length: 150
Connection: keep-alive
{
  "id":"UCkWEEKnPe5wWhgbdhSfwnGc",
  "createdAt":1434810298365,
  "updatedAt":1434810298365,
  "name":"Web of Things Book",
  "description":"My First WoT Project"
}

注意现在对象中有一个id字段,它包含你创建的项目 EVRYTHNG ID。像之前粘贴EVRYTHNG_API_KEY="XXX"时那样,将这个值保存到环境变量$PROJECT_ID中,通过在你的终端中粘贴PROJECT_ID=YYY,其中YYY是你刚刚创建的项目的 ID——在这个例子中是PROJECT_ID=UCkWEEKnPe5wWhgbdhSfwnGc

一旦你创建了你的第一个项目,你就可以在其中创建一个应用程序。应用程序会为你提供一个额外的 API 密钥(一个应用程序 API 密钥),你可以在我们稍后构建的客户端应用程序中使用它。这个密钥并不那么危险,因为它能做的事情非常有限;基本上它只允许你创建用户。要创建一个应用程序,请在你的终端中发送以下 cURL 请求:

curl -X POST "$SERVER/projects/$PROJECT_ID/applications" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "name": "My Awesome WoT App", "description": "My First WoT Client
     Application","tags":["WoT","device","plug","energy"], "socialNetworks":
     {} }'

同样,就像你为项目做的那样,将响应中返回的 ID 存储在$APP_ID环境变量中。还要注意响应中的 API 密钥;你将在稍后构建你的 Web 应用程序时使用它。现在你已准备好在我们的云中创建一个 Web Thing。

第 2 步——创建你的第一个产品和 Thngs

一个产品是一类物理对象(比如电视型号或汽车类型)的类别,而不是一个独特的实例(比如序列号)。你不应该使用产品来存储有关物理对象的数据。它们是一个概念实体,一个物理对象的模型,应该只包含许多此类物理对象共享的信息——如大小、图像、重量和颜色——但不包含实时信息,如位置、传感器读数或当前状态。使用以下请求创建一个产品:

curl -X POST "$SERVER/products?project=$PROJECT_ID" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "fn": "WoT Smart Plug", "description": "A Web-connected Smart
     Plug","tags":["WoT","device","energy"],"photos":["https://
     webofthings.github.io/files/plug.jpg"] }'

注意?project=$PROJECT_ID查询参数,它告诉 EVRYTHNG 将此产品存储在我们之前创建的项目中。将产品 ID 存储在$PRODUCT_ID变量中。

另一方面,Thngs是物理对象独特实例的数字表示:你手中的事物!对于你想要网络化的每个独特设备或对象,你都需要创建一个独特的 Thng。你可以这样做:

curl -X POST "$SERVER/thngs?project=$PROJECT_ID" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "name": "My WoT Plug", "product":"'$PRODUCT_ID'", "description":
     "My own Smart Plug","tags":["WoT","device","plug","energy"] }'

你可以看到,我们也在这个请求中发送了产品 ID。请跟踪在 payload 中返回的 Thng ID,并存储在$THNG_ID变量中。

第 3 步—创建设备 API 密钥

从技术上讲,你可以使用你的操作员 API 密钥来更新你的 Thng 属性,但由于各种安全原因,在产品设备上使用此方法是不明智的。相反,你可以轻松生成一个Thng API 密钥,允许你的设备仅查看和编辑自身。使用你的操作员 API 密钥发送一个POST请求(使用你的操作员 API 密钥)到端点api.evrythng.com/auth/evrythng/thngs,并带上你的 Thng ID,如下所示:

curl -X POST "$SERVER/auth/evrythng/thngs" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "thngId": "'$THNG_ID'" }'

你将得到以下内容:

{
  "thngId": "UCE7qfbK8VKwdt8kAfqtbwmd",
  "thngApiKey": "M1ST3RP0TAT0H3ADROCKS"
}

thngApiKey字段包含一个 API 密钥,允许设备查看和更新自身。将其值存储在$THNG_API_KEY环境变量中。

第 4 步—更改属性

如果你成功完成了前面的行,你现在将有一个项目、一个应用程序、一个产品以及该产品的一个实例在你的账户中。你可以在仪表板上看到它们。现在你将更新你的智能插头的属性。属性是一个数据数组,你可以随时更新并永久存储在引擎内部。你可以连续更新每个属性,并在任何时候检索它。

要更新一个或多个属性,使用设备 API 密钥,并发送一个POST请求到thngs/$THNG_ID/properties端点,使用此请求:

curl -X POST "$SERVER/thngs/$THNG_ID/properties" \
     -H "Authorization: $THNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '[{"key": "status","value": true},
          {"key": "energyConsumption","value": 71}]'

现在,你可以导航到 EVRYTHNG 仪表板中你的 Thng 的页面,如图 7.10 所示。在那里,你会看到有关这个插头及其属性的信息随着你多次运行此请求并使用不同的值而实时变化。

图 7.10. 从仪表板直接查看智能插头及其属性实时更新。

7.4.2. 创建你的 MQTT 客户端应用程序

在上一节中,你学习了如何设置你的 EVRYTHNG 账户以创建任何你想要连接到网络的物理对象的网络事物。你是通过使用 cURL 命令来了解端点和它们的工作方式来做到这一点的,但显然这不是你的设备与引擎通信的方式。

对于这个项目,你将编写一个简单的 Node 应用程序,模拟一个智能插头,你可以在你的 Pi 或 PC 上运行它。该应用程序使用 Thng API 密钥(在上一练习的第 3 步中创建)通过 MQTT 打开到 EVRYTHNG 引擎的永久连接,并每五秒更新其属性。

打开 simple-plug.js 文件并查看代码。在你可以执行它之前,你需要一个包含你的 EVRYTHNG 账户 ID 和 API 密钥的 config.json 文件。如果你运行了 setup.sh bash 脚本而不是通过四个手动步骤进行,它将为你生成此文件。否则,创建一个名为 config.json 的 config-sample.json 文件的副本,并将 Thng ID(上一节的步骤 2)和 Thng API 密钥(步骤 3)放入其中,如下所示。

列表 7.17. simple-plug.js:通过 MQTT 连接到 EVRYTHNG 的模拟电源插头

图片

图片

你可以看到这个示例通过安全的 MQTTS 连接到 EVRYTHNG,并订阅了所有属性更新。然后,它每五秒钟调用一次updateProperties()函数,模拟实际插头可能测量的典型电流和电压读数,并最终通过更新 Thng 的属性将读数发送到云,就像你在上一节步骤 4 中所做的那样。

然后,你可以回到仪表板中的 Thngs 页面,通过运行 Node 代码来启动模拟设备:npm installnode simple-plug.js。现在,你可以回到仪表板选项卡,查看插头的属性正在实时更新,如图 7.10 所示 figure 7.10。

7.4.3. 使用操作来控制电源插头

你已经构建了一个始终连接到互联网并通过 MQTT 推送其更新的设备。这说明了使用云引擎如何允许你快速构建网络连接设备,而无需实现本地网关。

在这个阶段,你可能正在想,“好吧,很好,但我该如何向插头发送命令来打开或关闭它?”完全合理!最简单的答案是:因为这个设备已经通过 MQTT 订阅了所有属性,所以每当 EVRYTHNG 云中的任何属性发生变化时,它都会收到一条消息。确实,你在终端中看到的文本是由那个回调函数中的console.log()语句显示的。当 Node 应用程序运行时,转到仪表板,点击 Thng 的status属性,并将其设置为false。你将立即在终端中看到这个变化:

[{"timestamp":1434823136116,"key":"voltage","value":220.259}]
[{"timestamp":1434823136116,"key":"current","value":0.839}]
[{"timestamp":1434823136898,"key":"*status*","value":false}]
[{"timestamp":1434823137065,"key":"voltage","value":219.919}]
[{"timestamp":1434823138184,"key":"power","value":913.355}]

然后,你可以修改那个回调处理程序,以便在发生这种情况时在代码中触发其他操作,这将工作得很好。

但这不是最佳选择,因为你必须仔细跟踪设备应该设置的属性以及哪些属性应该只由应用程序更改。此外,属性只是一个单一值;因此,如果你想向设备发送具有多个输入参数的命令,你将不得不使用多个属性;例如,设置几个 LED 的 RGB 值。

因此,您将使用 actions 发送包含多个输入参数的更复杂的命令。为此,您可以查看更高级的 plug-with-control.js。如果您已运行 7.4.1 节 中的 bash 脚本,它将为您创建一个名为 _setStatus动作类型。如果没有,您将必须使用以下命令自行创建:

curl -X POST "$SERVER/actions?project=$PROJECT_ID" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "name": "_setStatus", "description": "Changes the Status of the
     Thng","tags":["WoT","device"] }'

在创建动作之前,您需要更改您的设备应用程序,使其也订阅 actions/ 资源,并在每次接收到特定动作时执行某些操作。您可以打开文件 plug-with-control.js,其中回调函数已被修改,如下一列表所示。

列表 7.18. plug-with-control.js: 订阅并处理从服务器推送的动作

您现在可以运行此第二段代码,您仍然会在属性更新时看到它。但到目前为止,请打开第二个终端,并使用以下请求向您的引擎中的 Thng 发送 _setStatus 命令:

curl -X POST "$SERVER/actions/_setStatus?project=$PROJECT_ID" \
     -H "Authorization: $EVRYTHNG_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "type": "_setStatus", "thng":"'$THNG_ID'", "customFields":
     {"status":"false"} }'

仔细研究响应负载的内容,因为它包含的信息比您发送的要多得多。我们的云使用您的 IP 地址来确定动作的位置。如果您刷新页面,您将在仪表板中的 Thng 页面上看到此动作,以及它的地图。

7.4.4. 创建一个简单的网络控制应用程序

到目前为止,您已经编写了一个 Node 应用程序,允许 Pi 连接到云服务并立即接收推送通知,即使您的设备位于防火墙或 NAT 网关之后。使用云服务的优点是它允许您使用统一的接口,如 REST API、属性或动作,与连接到云的任何设备进行通信,无论该设备是否使用 REST API、MQTT 或其他协议进行通信。这意味着您可以构建一个通用的客户端应用程序,并且它将与平台上的任何 Thng 一起工作。它还使对您的“事物”的访问更具可扩展性,因为客户端不是直接与您的“事物”交谈,而是通过代表“事物”状态的云中介进行交谈,就像一面镜子或一个影子。

使用云的一个简单网络应用程序

下一步是构建一个简单的网络应用程序,它可以与设备通过其 Thng 交互,使用 WebSockets 订阅其属性,并在设备发送它们时立即显示。此应用程序还应能够通过云平台的 REST API 向设备推送命令。

我们为您构建了这样一个应用程序,所以请继续在您的编辑器中打开文件 part3-cloud/client/plug.html——或者,当然,您也可以自由地构建自己的应用程序!在这里,将 Thng ID 和您的操作者 API 密钥作为 URL 中的查询参数提供:

file:///.../plug.html?key=XXX&thngId=YYY

你现在可以启动我们在上一节中使用的 plug-with-control.js Node 应用程序,并在你的浏览器中打开 plug.html。在几秒钟内,你应该会看到属性在实时变化,并且图表也在更新。这个应用程序连接到你的 EVRYTHNG 中的 Thng,使用 WebSocket 订阅其属性,并在从引擎接收到属性更新时立即更新页面。由于代码很长,并且使用了相当多的优秀库来完成繁重的工作,我们在这里只展示最有趣的片段。首先,你创建一个切换按钮,每次使用时都会发送一个 _setStatus 动作,如下面的列表所示。

列表 7.19. client/plug.html: 将处理程序附加到切换按钮
<input type="checkbox" id="toggle-status"/>
<script>
  $(function() {
    $('#toggle-status').bootstrapToggle({
      on: 'On',
      off: 'Off'
    });
  });

  $(function() {
    $('#toggle-status').change(function() {
      sendAction("_setStatus",{"status": $(this).prop('checked')});
    });
  });
</script>

你创建一个按钮,并使用 Bootstrap Toggle^([23]) 库来让它看起来更美观。每次这个按钮被触发时,sendAction() 函数将被调用,其代码如下所示。

²³

www.bootstraptoggle.com/

列表 7.20. client/plugs.html: sendAction() 函数

每次这个函数被调用时,它将在引擎中POST一个动作,这个动作将被你的设备接收——正如你在上一节中看到的那样。

哦,别忘了用你刚刚创建的按钮打开插头;否则,你不会在图表上看到任何东西!

通过 WebSocket 订阅

第二,你需要使用 WebSocket 订阅你的客户端应用程序的所有属性更新。原则上,这与你在上一节中使用的 MQTT 类似。想法是创建一些 HTML 占位符来显示当前的属性值。对于你想要显示的每个属性,你创建一个看起来像这样的列表项:

<li class="list-group-item">
  <span id="value-status" class="badge">false</span>
  status
</li>

注意,id 必须设置为 value-status,并且与引擎中的属性名称相同。现在让我们看看如何连接和处理 WebSocket 消息,如下面的列表所示。

列表 7.21. 连接到 WebSocket 并处理属性更新

这与 MQTT 类似,但完全是基于 Web 的,并在你的 Web 浏览器中运行,无需安装任何依赖项。每次任何属性更新时,你将通过 WebSocket 通道接收到相应的 JSON 消息,因为你已经订阅了属性主题。

使用二维码来识别物品

在我们完成本节之前,让我们来做一个最后的技巧。为此,您需要将 plug.html 和 ui/文件夹文件部署到云中的某个位置——例如,GitHub pages.^([24]) 如果您想走捷径,我们已为您部署了这个文件,您可以通过以下链接访问它(将 XXX 替换为您的运营商 API 密钥,将 YYY 替换为您的 Thng ID):webofthings.github.io/wot-book/plug.html?key=XXX&thngId=YYY. 如果您在上一节中提前运行了脚本,它将自动为您创建一个 Thng 的重定向;扫描二维码或点击短 URL,您将自动跳转到该链接。否则,您可以使用图 7.11 中所示的设置重定向按钮来创建它。

²⁴

pages.github.com/

图 7.11. 如何创建一个重定向到您设备用户界面的短 URL

在重定向表达式中输入文件的完整路径——带有您自己凭证的先前 URL——然后点击创建重定向。现在您有一个短 URL 和一个相应的二维码,您可以在任何时候更新它们,并通过扫描手机上的二维码来查看和控制您的设备。二维码或 NFC 标签是序列化事物 URL 的绝佳方式,使它们可以通过手机物理发现。

注意,您不应该与任何人分享这个二维码或 URL:它包含您的运营商 API 密钥!这只是为了说明问题,但您永远不应该以这种方式暴露您的 API 密钥。

7.4.5. 摘要——云集成模式

让我们花点时间来反思一下在本节中您所做的一切。在这么短的时间内完成这么多事情感觉如何?多亏了像 EVRYTHNG 这样的 Web of Things 云平台的开放和灵活 API,您已经编写了一个简单的 Node 应用程序,它可以使用 MQTT 与其云中的数字身份进行通信。您还构建了一个基本的 HTML/JavaScript 应用程序,它可以实时显示来自您的设备的数据,并且即使它没有 REST API,也可以向它发送命令。

这就是云集成模式大放异彩的地方。因为这个系统的复杂性大部分都存在于云平台内部,您就不必过多担心可扩展性、可靠性或安全性。云平台允许您,一个数字产品开发者,大大缩短构建网络连接设备所需的时间,并提供各种强大的功能,如可视化、数据存储和访问控制。因为前端应用程序只使用 HTML/JS/CSS,您有一个统一的接口来控制任何设备,无论它是否支持 HTTP,因为云系统可以无缝处理协议之间的转换。

为什么你会选择使用直接连接模式呢?仍然在“事物”上原生提供 API 有几个很好的理由。例如,考虑延迟:本地调用几乎总是比通过云的调用快。或者考虑在互联网断开时进行监控和控制。因此,对于现实世界的 WoT 产品来说,最佳折衷方案通常是提供本地和云访问。

7.5. 概述

  • 连接“事物”到 Web 的主要集成模式有三个:直接、网关和云。

  • 无论你选择哪种模式,你都需要完成以下步骤:资源设计、表示设计和接口设计。

  • 直接集成允许对“事物”的 Web API 进行本地访问。你通过使用 Express Node 框架为 Pi 构建 API 来尝试了这种方法。

  • 在 Express 中,资源设计步骤是通过路由实现的,每个路由代表 Pi 资源路径。

  • 我们在表示设计步骤中使用了中间件的概念来实现对不同表示的支持——例如,JSON、MessagePack 和 HTML。

  • 接口设计步骤是通过在路由上使用 HTTP 动词以及通过集成使用wsNode 模块的 WebSockets 服务器来实现的。

  • 网关集成允许通过为它们提供 API 将没有 Web API(或不支持 Web 甚至互联网协议)的“事物”集成到 WoT 中。你通过在云上通过网关集成 CoAP 设备来尝试了这种方法。

  • 云集成使用网络上的服务器作为“事物”的影子或代理。它们通过可扩展性、分析和安全性等特性增强了“事物”的 API。你通过使用 EVRYTHNG 云来尝试了这种方法。

现在我们已经为“事物”创建了 Web API,接下来的几章将介绍如何使这些 API 更加强大和有趣。在下一章中,我们将探讨资源链接设计步骤以及可查找性和可发现性问题。本质上,我们将研究 Web“事物”如何以易于被其他应用程序、Web“事物”甚至人类找到、理解和使用的方式公开其 API!

第八章:查找:描述和发现 Web“事物”

本章涵盖

  • 学习可发现性的基础知识(方法和协议)

  • 理解如何进行 Web 级发现(链接/爬取)

  • 提出一个模型来描述 Web“事物”及其功能

  • 通过添加额外的语义网格式扩展基本模型

在前两章中,我们广泛探讨了将你的 Things 连接到 Web 的各种集成模式,这是我们在第六章中引入的 WoT 架构的第一层。我们说明了如何使用 Web 标准作为异构设备之间的连接组织,这显著提高了互联网规模系统中组件之间的互操作性,因此是物联网的核心基础。尽管如此,如果没有一个通用的格式来描述 Web Things 及其功能,集成 Web Things 和应用程序仍然需要开发者付出持续的努力。拥有一个所有 Web Things 都可以共享的单个和通用数据模型将进一步通过使应用程序和服务能够无需为每个特定设备手动调整应用程序来提高互操作性和易于集成。这是 WoT 的一个基本基石,因为它意味着我们第一章中引入的酒店控制中心示例可以无缝地发现、理解和读取 Web of Things 上的任何设备的数据,并发送命令,无论其功能或制造商如何。能够轻松发现和理解 Web of Things 中的任何实体——它是什么以及它能做什么——的能力被称为可发现性

如何实现这样的互操作性水平——使 Web Things 可被发现——是 WoT 架构中“查找”这一第二层的目的,也是本章的重点所在。查找层的目的是提供一个统一的数据模型,所有 Web Things 都可以使用它来仅通过 Web 标准和最佳实践来暴露其元数据。“元数据”指的是 Web Things 的描述,包括 URL、名称、当前位置和状态,以及它提供的各种服务,如传感器、执行器、命令和属性。首先,这对于发现连接到本地网络或网络的 Web Things 非常有用。其次,它允许应用程序、服务和其他 Web Things 在无需为该设备安装驱动程序的情况下搜索和找到新设备。到本章结束时,你将了解如何使用网络发现协议(如 mDNS)、轻量级数据模型(如 Web Thing Model)和语义 Web 标准(如 JSON-LD)以通用和互操作的方式暴露任何 Web Things 的元数据。

8.1. 可发现性问题

一旦设备使用我们在前两章中介绍的方法成为 Web Things,它就可以通过 HTTP 和 WebSocket 请求进行交互。这在理论上听起来很棒,但要使其在实践中也能工作,我们必须首先解决三个基本问题,如图 8.1 所示:

图 8.1. 物联网中可发现性的三个问题。客户端应用程序如何找到附近的 Web Things,与之交互,并理解这些事物是什么以及它们能做什么?

图 8.1

1. 我们如何知道发送请求的位置,例如一个 Web Thing 的根 URL/资源?

2. 我们如何知道要发送什么请求以及如何发送;例如,动词和有效载荷的格式?

3. 我们如何知道我们发送的请求和收到的响应的意义,即语义?

为了更好地理解这些问题,让我们回到第一章中的智能酒店场景。章节 1。想象一下 Lena,一位住在酒店 202 号房的爱沙尼亚客人。Lena 想要打开手机来打开加热。第一个问题是 Lena——或者她的手机,或者她手机上的一个应用——如何找到加热器的根 URL?这通常被称为引导问题。这个问题关注的是如何在物联网中建立两个实体之间的初始链接。解决这个问题最简单的办法是将根 URL 写在桌面上或房间的墙上。另一个解决办法是将 URL 编码到卡片上打印的 QR 码或使用 NFC 标签,这样 Lena 就可以用手机扫描它。一个更复杂的解决办法是在她的手机上安装一个应用程序,该应用程序可以搜索附近具有加热功能的设备。这些方法将是第 8.2 节的主题。最后,一个友好的 Web 解决方案是让她在 Google 上搜索附近的加热器;我们将在第 8.4 节中探讨这一点。

假设现在 Lena 在她的手机上输入了加热器的根 URL。理想情况下,她会看到一个漂亮的以爱沙尼亚语为母语的用户界面,这让她能够立即知道哪个按钮可以打开加热。在这种情况下,一个干净且以用户为中心的 Web 界面可以解决第三个问题,因为人类能够阅读并理解如何操作。第二个问题也会由网页解决,它会硬编码发送到哪个端点的请求。

但如果加热器没有用户界面,只有 RESTful API 呢?^([1]) 因为 Lena 是一位经验丰富的前端开发者,而且从不看电视,她决定构建一个简单的 JavaScript 应用程序来控制加热器。现在她面临第二个问题:尽管她知道加热器的 URL,但她如何找到加热器 API 的结构?有哪些资源(端点)可用?她可以向哪个资源发送哪些动词?她如何指定她想要设置的温度?她如何知道这些参数是否需要以摄氏度或华氏度表示?

¹

如果经理发现了这一点,他可能应该解雇最初负责选择这个加热器的人,因为这个加热器没有提供用户界面,未能满足第六章中提到的设计规则#2。

通常,应用开发者依赖于描述事物上可用的各种 API 端点和资源的书面文档(问题 2)以及它们的含义(问题 3)。但在某些情况下,一种更自动化的方式在运行时发现 REST API 的资源可能是有用的。如果 Lena——或者她编写的应用程序——能够即时查询任何 Web 事物并找出它提供的服务/数据,而不必阅读文档,那么她的应用程序就可以与任何加热设备一起工作,无论其制造商如何。

为这三个问题提供基于 Web 的解决方案是查找层的目标,如图 8.2 所示。在本章的其余部分,我们将提出一套工具和技术,以展示 Web 事物如何暴露其数据资源,使用户、应用程序和事物能够轻松找到并与之交互。

图 8.2. 物联网的查找层。这一层涉及如何轻松理解事物的本质,它们之间的关系,如何访问它们的文档,它们的 API 端点是什么,以及如何访问这些端点(包括参数及其类型)。它还涉及以标准方式解释这些属性的意义。

8.2. 事物发现

我们从比较几个用于引导问题的解决方案开始我们的查找之旅。简而言之,一个应用或事物如何找到它之前从未遇到过的 Web 事物的根 URL?这个问题涉及两个范围:首先,如何找到物理上附近(例如,在同一本地网络内)的 Web 事物;其次,如何找到不在同一本地网络中的 Web 事物——例如,通过互联网找到设备。在本地网络中查找 Web 事物可以使用第 8.2.1 节中描述的网络发现方法来完成。为了找到同一本地网络之外的 Web 事物,我们将依赖于资源发现和搜索,如第 8.2.2 节所述。现在让我们更详细地看看这些方法。

8.2.1. 网络发现

在计算机网络中,自动发现新参与者的能力很常见。在你家里的局域网中,一旦设备连接到网络,它就会自动使用 DHCP^([2]) (动态主机配置协议) 获取一个 IP 地址。但只有 DHCP 服务器知道设备在你的网络中,那么你的网络中的其他主机怎么办?一旦设备有了 IP 地址,它就可以广播数据包,这些数据包可以被同一网络上的其他机器捕获。正如你在第五章中看到的,消息的广播或多播意味着这个消息不是发送到特定的 IP 地址,而是发送到一组地址(多播)或发送给所有人(广播),这是通过 UDP 完成的。这个过程被称为网络发现协议,它允许设备和应用程序在本地网络中找到彼此。这个过程通常被各种发现协议使用,如多播域名系统 (mDNS)^([3]), 数字生活网络联盟 (DLNA)^([4]), 和通用即插即用 (UPnP)^([5]). 例如,大多数互联网连接的电视和媒体播放器可以使用 DLNA 来发现网络附加存储 (NAS) 并从中读取媒体文件。同样,你的笔记本电脑可以通过内置在 iOS 和 OSX 中的网络级发现协议,如 Apple Bonjour,以最小的努力找到并配置网络中的打印机。

²

DHCP: 动态主机配置协议.

³

多播 DNS

数字生活网络联盟

通用即插即用

mDNS

在 mDNS 中,客户端可以通过监听如以下列表中的 mDNS 消息来发现网络上的新设备。随着消息的到来,客户端填充本地 DNS 表,因此一旦发现,新的服务——这里是一个打印机的网页——可以通过其本地 IP 地址或通过通常以 .local 域名结尾的 URI 来使用。在这个例子中,它将是 evt-bw-brother.local

列表 8.1. 来自打印机的 mDNS 消息

这也是你的 Pi 使用来广播其 raspberrypi.local URL 的协议(见第四章),向所有使用 mDNS 客户端监听的附近计算机。

mDNS 和大多数网络级发现协议的限制在于,网络级信息不能直接从网络中访问。当然,你可以编写依赖于预定义的.local 域名的 JavaScript 代码,但这仅仅是一个所有浏览器都不支持的 hack。这也是为什么许多移动浏览器无法解析这些地址的原因:它们没有 mDNS 客户端在后台填充本地 DNS 记录。

技术角落——我想让我的 Pi 说“Bonjour!”

您的 Pi 已经通过 Avahi 库启用 mDNS 来广播其.local URL,但您可以使用 mDNS 做更多的事情,例如描述您的 WoT 服务器提供的 HTTP 服务(就像列表 8.1 中的打印机一样)。实验性的node_mdns Node 库^([a])建立在 Avahi 之上,让您能够以编程方式实现这一点以及更多。要开始使用此库,请查看我们在 GitHub 上此章节的 mdns 文件夹中提供的代码示例。

^a

github.com/agnat/node_mdns.

注意:此模块在 Pi 上不一定运行顺畅,因此您可能需要回退到您的 PC。如果您仍然想在 Pi 上尝试,请确保通过apt-get install libavahi-compat-libdnssd-dev安装所需的额外 Debian 软件包。

网络发现

如果 mDNS 在所有浏览器上都不工作,那么运行在您的手机或平板电脑上的 Web 应用程序如何找到附近的 Web 设备?或者,为什么您不能通过页面上的链接找到家中的 Web 设备?一个简单的解决方案是为 Firefox 或 Chrome 编写一个自定义插件,使其能够与这些网络级发现协议通信。但这并不能解决问题,因为代替使用基于 Web 标准的基于 Web 的资源发现,设备仍然需要实现一个或多个非 Web 网络发现协议。结果,Web 设备客户端应用程序也需要能够使用和了解这些协议,这违背了物联网的目的。

因为 HTTP 是一个应用层协议,它对底层的网络协议一无所知——这些协议用于在网络中移动 HTTP 请求。它也不需要关心这一点——除非一个网络设备或应用程序需要了解同一网络中的其他资源。真正的问题是,为什么路由器的配置和状态只能通过网页供人类访问,而不能通过 REST API 访问。简单来说,为什么不是所有的路由器都提供一种安全的 API,允许网络中的其他设备和应用程序查看和更改其配置?

提供这样的 API 做起来很简单.^([6]) 例如,你可以安装一个针对路由器的开源操作系统,如 OpenWrt^([7)),并修改软件以将路由器 DHCP 服务器分配的 IP 地址作为 JSON 文档暴露出来。这样,你就可以利用路由器现有的 HTTP 服务器创建一个 API,该 API 暴露了你网络中所有设备的 IP 地址。这样做是有意义的,因为今天几乎所有联网设备,从打印机到路由器,都已经配备了 Web 用户界面。其他设备和应用程序可以通过简单的 HTTP 调用(图 8.3 中的步骤 2)检索网络中的 IP 地址列表,然后通过使用它们的 IP 地址检索网络中每个设备的元数据(图 8.3 中的步骤 3)。

这一想法在 Vlad 的博士论文中提出:Vlad Trifa 在苏黎世联邦理工学院最终博士论文答辩

OpenWrt 官网

图 8.3. 局域网级别的资源发现。假设所有 Web Things 都在端口 80 上暴露它们的根资源,Web Things 客户端可以从路由器获取它们的 IP 地址,然后查询每个设备以提取它们的元数据。

由于路由器通常具有本地网络的基址,你可以轻松编写一个 Web 应用程序,该应用程序定期查询路由表,跟踪连接到网络的新的设备,并在网络中注册设备。相同的模式可以用于网络上的任何其他设备,其中任何 Web Things——比如机顶盒或 NAS——都可以使用各种协议连续搜索网络中的新设备,理解它们提供的服务,然后通过动态生成针对这些设备的新 WoT API 来作为这些设备的桥梁。

8.2.2. 网络资源发现

虽然网络发现可以在本地完成任务,但它不会传播到本地网络之外。从更广泛的角度来看,还有一些问题悬而未决:在一个有数十亿设备可以通过万维网访问的物联网中,当新设备连接时,我们如何找到它们,我们如何理解它们提供的服务,我们能否在复合应用程序中搜索正确的设备和它们的数据?

当网络在九十年代初从仅有几千页包含文本和图像的目录转变为指数级增长的包含网络应用、文档和多媒体内容的集合,包括电影、音乐、游戏和其他服务类型时,它面临了类似的挑战。在那些早期,AltaVista 和 Yahoo 成功地整理了这一不断增长的文档集合。但随着网络的指数级增长,很明显,手动管理网络上的资源列表是一条死胡同。大约在这个时候(~1998 年),Google 突然出现,几乎消灭了任何其他搜索引擎,因为它能够自动索引数百万页,并允许用户快速准确地在这个庞大的目录中找到相关内容。

在网络上,新资源(页面)是通过超链接发现的。搜索引擎定期解析其数据库中的所有页面,以找到指向其他页面的出站链接。一旦发现指向尚未索引的页面的链接,该新页面就会被解析并添加到目录中。这个过程被称为网络爬虫

爬取网络事物的 API

我们可以将网络爬虫的过程应用到事物上:在第二章中,你使用了基于 HTML 的用户界面来为 WoT Pi,而在第五章中,你看到了如何创建资源的 HTML 表示。通过在 HTML 代码中添加子资源的链接,我们使得使用下一列表中所示简单伪代码来爬取网络事物成为可能。

列表 8.2. 爬取事物 HTML 表示的伪代码
crawl(Link currentLink) {
  r = new Resource();
  r.setUri = currentLink.getURI();
  r.setShortDescription = currentLink.text();
  r.setLongDescription =
    currentLink.invokeVerb(GET).extractDescriptionFromResults();
  r.setOperations = currentLink.invokeVerb(OPTIONS).getVerbs();
  foreach (Format currentFormat: formats) {
    r.setAcceptedFormats =
      currentLink.invokeVerb(GET).setAcceptHeader(currentFormat);
  }
  if (currentLink.hasNext()) crawl(currentLink.getNext());
}
foreach(Link currentLink: currentPage.extractLinks())
{ crawl(currentLink); }

从网络事物的根 HTML 页面开始,爬虫可以通过发现出站链接来找到子资源,例如传感器和执行器,然后可以创建网络事物及其所有资源的资源树。然后,爬虫使用 HTTP OPTIONS方法检索网络事物每个资源支持的动词。最后,爬虫使用内容协商来了解每个资源可用的格式。作为一个练习,我们建议你尝试实现用于你在第七章中创建的 Pi 的 API 的此爬虫。

HATEOAS 和网页链接

这种简单的爬取方法是一个良好的开始,但它也有几个局限性。首先,所有链接都被同等对待,因为没有关于链接性质的概念;用户界面和执行器资源的链接看起来相同——它们只是 URL。然后,它要求网络事物提供一个 HTML 界面,这可能对资源受限的设备来说太重了。最后,这也意味着客户端需要理解 HTML 和 JSON 才能与我们的网络事物一起工作。

发现任何 REST API 资源的更好解决方案是使用我们在第 6.1.6 节中提出的 HATEOAS 原则来描述 Web Thing 各种资源之间的关系。实现 HATEOAS 的简单方法是在 RFC 5988 中定义的Web 链接机制。其思想是,对任何资源发出的 HTTP 请求的响应总是包含一组相关资源的链接——例如,包含搜索结果的上一页下一页最后一页。这些链接将包含在响应的Link: HTTP 头中。尽管 HTML 4 规范中的LINK元素已经支持了类似的机制,但将链接编码为 HTTP 头引入了一个更通用的框架来定义资源表示之外的资源之间的关系——直接在 HTTP 级别。因此,无论客户端请求的媒体类型如何,如 JSON 或 HTML,链接都可以始终以相同的方式进行描述。这种类型的链接也是我们在前几章中讨论的约束应用协议所支持的。^([8])

tools.ietf.org/html/rfc5988

www.w3.org/TR/html401/struct/links.html#edef-LINK

¹⁰

tools.ietf.org/html/rfc6690

当对任何 Web Thing 执行 HTTP GET 操作时,响应应包含一个包含相关资源链接的Link头。特别是,你应该能够仅通过Link头获取有关设备、其资源(API 端点)和 API 文档的信息。以下是一个发送到 WoT 网关的示例 HTTP 查询:

HTTP 1.1 GET /
Host: gateway.webofthings.io
Accept: application/html

200 OK
Link: </model/>; rel="model", </properties/>; rel="properties", </actions/>;
     rel="actions", </things/>; rel="things", <http://model.webofthings.io/>;
     rel="type", </help>; rel="help", </>; rel="ui"

在本例中,响应包含了一组链接到 Web Thing 资源的Link头。每个资源的 URL 包含在尖括号<URL>之间,链接的类型由rel="X"表示,其中X是关系的类型。如果 URL 不是绝对 URL——也就是说,它不以 http://或 https://开头——它将在当前请求路径的上下文中被解释,相对 URL 将被附加到该路径。在本例中,Web Thing 的文档因此将成为devices.webofthings.io/help。请注意,链接元素可以是任何有效的 URI,因此它可能托管在设备本身、网关或网络上的任何其他地方。一些保留和标准化的关系类型由 IANA 定义,但这些主要与经典的多媒体文档网络相关。因为尚未为物理对象和物联网提出一套关系类型,所以我们将在本章中提出一套。在前面的例子中,你可以看到物联网网关的根页面包含了对以下四个资源的链接。

rel=“model”

这是一个指向 Web Thing 模型资源的链接;参见第 8.3.1 节。

rel=“type”

这是一个包含关于此 Web Thing 的额外元数据的资源的链接。

rel=“help”

这种关系类型是一个指向文档的链接,这意味着对devices.webofthings.io/help的 GET 请求将返回 API 的文档,以人类友好(HTML)或机器可读(JSON)的格式。文档不需要在设备本身上托管,但可以托管在任何地方——例如,在制造商的网站上,在这种情况下,标题将如下所示:

Link: <http://webofthings.io/doc/v/1.1>; rel="help"

这允许维护和持续更新在野外部署的多个设备及其各种固件版本的文档,而无需直接在设备上托管,而是在云端托管。

rel=“ui”

这种关系类型是一个指向与 Web Thing 交互的图形用户界面(GUI)的链接。用户界面必须使用 HTML 实现,以便任何浏览器都可以访问,并且它应该能够响应,以便各种设备类型可以与 Web Thing 交互。请注意,GUI 可以(但不一定)在设备本身上托管,只要 GUI 应用程序可以访问 Web Thing 及其资源。在以下示例中,GUI 托管在 GitHub 上,并接受要控制的 Web Thing 的根 URL 作为参数:

Link: <http://webofthings.github.io/ui?url=devices.webofthings.io>; rel="ui"

在某些情况下,您可能无法修改 Web Thing 返回的响应的 HTTP 头。如果是这种情况,您需要在资源的 HTML 或 JSON 表示中插入它们。我们将在第 8.3.3 节(针对 JSON)和第 8.4.1 节(针对 HTML)中展示如何这样做。

8.3. 描述 Web Thing

发现 Web Thing 的根 URL 和资源的能力解决了可发现问题的第一部分,如果它提供了一个用户界面,那么仅此就足以与 Web Thing 交互——根 URL 返回一个 HTML 页面。但只知道根 URL 是不够的,因为我们需要解决本章开头提到的第二个问题:应用程序如何知道应该向 Web Thing 的哪些资源发送哪些有效负载?换句话说,每个端点支持哪些可能的参数及其类型,给定的请求会产生什么效果,将返回哪些可能的错误/成功消息,以及这些意味着什么?

这个问题可以总结如下:我们如何正式描述任何 Web Thing 提供的 API?正如您在图 8.4 中看到的那样,有各种方法可以做到这一点,从 Web Thing API 之间没有共享数据模型(1)到语义上定义与 Web Thing 的每一种可能的交互(4)。语义 WebThing 通过确保客户端应用程序可以自动发现新的 Thing 并在运行时使用它们,从而最大化互操作性,无需人工介入。

图 8.4. 描述 Web Thing 的不同级别。任何设备都可以有一个 HTTP API(1)。Web Thing(2)是遵循第六章中提出的要求的 HTTP 服务器;因此,API 更一致、可预测且易于使用。使用共享模型将使 Web Thing 更具互操作性(3)。最后,添加语义注解将确保 Web Thing 之间有更强的合同,同时也为正式定义 Web Thing API 的每个元素提供更多灵活性(4)。

图片

最简单的解决方案是为您的 Web Thing 的 API 提供书面文档,以便开发者可以使用它(图 8.4 中的 1 和 2)。这意味着开发者必须阅读有关您的 Web Thing 的文档,了解他们可以向其发送哪些请求以及每个请求的作用,并最终使用正确的参数实现各种 API 调用。然而,这种方法不足以自动发现新设备,了解它们是什么以及它们提供的服务。此外,手动实现有效载荷更容易出错,因为开发者需要确保他们发送的所有请求都是有效的。当 API 文档与设备上实际运行的 API 不同时,这尤其棘手,这可能发生在 API 发生变化但文档没有变化的情况下。或者简单地说,当文档一开始就……嗯……不够友好。遗憾的是,物联网中的大多数 API 都处于这种状况,因为它们没有使编写仅通过了解其根 URL 就能动态生成设备用户界面的应用程序变得容易或甚至可能。

正如本章其余部分将要展示的,并非所有的希望都已丧失——恰恰相反!通过使用一个独特的数据模型来正式定义任何 Web Thing 的 API(如 8.3.2 节所述的 Web Thing 模型),我们将拥有一个强大的基础来以标准方式描述任何 Web Thing 的元数据和操作(图 8.4 中的案例 3 和 4)。这是物联网的基石:创建一个模型来描述物理事物,在表达性——模型有多灵活——和可用性——使用该模型描述任何 Web Thing 有多容易——之间取得适当的平衡。实现这种平衡对于实现全球规模的互操作性和采用是必要的,这也是本章剩余部分我们将要做的。

8.3.1. 介绍 Web Thing 模型

一旦我们找到一个 Web Thing 并了解其 API 结构,我们仍然需要一个方法来描述该设备是什么以及它能做什么。换句话说,我们需要一个 Web Thing 的概念模型,它可以使用一组公认的概念来描述 Web Thing 的资源。

在前几章中,我们展示了如何使用/sensors/actuators端点来组织 Web Thing 的资源。但这只适用于实际上具有传感器和执行器的设备,不适用于现实世界中常见的复杂对象和场景,这些对象和场景无法映射到执行器或传感器。为了实现这一点,物联网的核心模型必须易于应用于现实世界中的任何实体,从卡车上的包裹到收藏卡牌游戏,再到橙汁瓶。本节提供的就是这样一个模型,被称为Web Thing 模型^(11))

^(11)

在撰写本文时,Web Thing 模型(model.webofthings.io)也是 W3C 的一个官方成员提交。这并不意味着它是一个标准,但它的意思是它将影响 Web of Things 兴趣小组(www.w3.org/WoT/IG/)周围的物联网标准化工作。EVRYTHNG(因此是 Vlad 和 Dom)是 W3C 物联网兴趣小组的一部分。

由于这个模型比我们在前几章中使用的模型更抽象,覆盖了更多的用例,因此它也稍微复杂一些,理解和使用起来也更困难,这就是为什么我们只在这里介绍它。但不用担心——到本章结束时,一切都会对你来说变得有意义,你也会看到你可以轻松地将其适应任何你能想到的物联网场景。不仅如此,通过本章中提供的这个模型的参考实现,你还将能够实现真正互操作的物联网和 WoT 应用,充分发挥物联网的潜力。让我们开始吧!

注意,为了使你更容易发现 Web Thing 模型并尝试本节中的示例,我们在云中部署了一个 Web Thing:gateway.webofthings.io。在下一节中,你将学习如何在你自己的 Pi 或笔记本电脑上实现和运行相同的 Web Thing 服务器,所以你可以随时在自己的物联网上回顾这些示例。

实体

正如我们之前所描述的,物联网由 Web Thing 组成。但具体来说,什么是 Web Thing 呢?Web Thing 是物理对象——即实体——的数字表示,可以在网络上访问。可以这样想:你的 Facebook 个人资料是你自己的数字表示,所以 Web Thing 就是一个物理对象的“Facebook 个人资料”。Web Thing 的例子包括车库门的虚拟表示、一瓶汽水、一套公寓、一台电视等等。Web Thing 是一个可以直接在设备上托管或托管在网络中的中间件(如网关或云服务,它将非网络设备连接到网络)的 Web 资源。所有 Web Thing 都应该具有以下资源,如图 8.5 所示:

图 8.5。网络实体的资源。网络实体客户端可以与网络实体的各种资源进行交互。模型资源提供了用于发现的元数据,属性是实体的变量(数据、传感器、状态等),而动作是网络实体支持的功能调用(命令)。当网络实体也是其他(非网络)实体的网关时,实体的资源是代理到非网络实体的。

08fig05_alt.jpg

  • 模型— 网络实体始终有一组元数据,用于定义其各个方面,如名称、描述或配置。

  • 属性— 属性是网络实体中的一个变量。属性代表网络实体的内部状态。客户端可以订阅属性,以便在满足特定条件时接收通知消息;例如,一个或多个属性值发生变化。

  • 动作— 动作是网络实体提供的一个函数。客户端可以通过向网络实体发送动作来在网络上调用一个函数。动作的例子包括车库门的“打开”或“关闭”,烟雾报警器的“启用”或“禁用”,以及苏打水瓶或地点的“扫描”或“签到”。动作的方向是从客户端到网络实体。

  • 实体— 网络实体可以是连接到没有互联网连接的其他设备的网关。这个资源包含所有由这个网络实体代理的网络实体。这主要用于云或网关,因为它们可以代理其他设备。

每个网络实体都可以使用这个模型来展示其功能。在下一节中,我们将更详细地探讨这些内容,特别是它们的外观。在本书中描述整个模型需要几章的内容,所以我们只限于理解它是什么以及如何使用它的严格必要要素。我们邀请您参考网络实体模型的在线描述,以查看包含您可以使用的各种实体和字段的完整描述。您在跟随本章内容时不需要这些信息,但在您想要为自己的设备和产品调整模型时,这些信息将有所帮助。此外,这个模型在很大程度上建立在您在第六章和第七章中学到的概念之上,所以您绝对不是从零开始!

8.3.2。元数据

在网络实体模型中,所有网络实体都必须有一些关联的元数据来描述它们是什么。这是一组关于网络实体的基本字段,包括其标识符、名称、描述和标签,以及它拥有的资源集合,如动作和属性。对任何网络实体的根 URL(以下列表中的{WT})的 GET 请求始终以这种格式返回元数据,默认情况下是 JSON 格式。

列表 8.3。GET {WT}:检索网络实体的元数据
GET / HTTP/1.1
Host: gateway.webofthings.io
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: </model/>; rel="model", </properties/>; rel="properties",
  </actions/>; rel="actions", </things/>; rel="things",
  <http://model.webofthings.io/>; rel="type"

{
  "id": "http://gateway.webofthings.io",
  "name": "My WoT Raspberry PI",
  "description": "A simple WoT-connected Raspberry Pi for the WoT
    book.",
  "tags": ["raspberry","pi","WoT"],
  "customFields": {...}
}

如您在此处所见,返回的有效负载包含 Web 事物的基本信息。此 Web 事物的各种资源的链接包含在响应的 Link: 标头中;参见第 8.2.2 节。然后您可以通过每个链接获取有关每个资源的更多信息。GET {WT}/model 将返回 Web 事物的整个模型,包括可用动作或属性的详细信息。

8.3.3. 属性

Web 事物也可以有属性。属性是一组与 Web 事物某些方面相关的数据值。通常,您会使用属性来模拟 Web 事物公开的任何动态时间序列数据,例如 Web 事物的当前和过去状态或其传感器值——例如,温度或湿度传感器的读数。由于属性应始终捕获 Web 事物的最新状态,因此它们通常在值更改时由 Web 事物本身更新,而不是由 Web 事物客户端或应用程序更新。让我们通过在 {WT}/properties 资源上执行 GET 请求来查看我们 Web 事物的属性,如下面的列表所示。

列表 8.4. GET {WT}/properties: 获取 Web 事物的属性
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <http://model.webofthings.io/#properties-resource>; rel="type"

[
  {
    "id": "temperature",
    "name": "Temperature Sensor",
    "values": {
      "t": 9,
      "timestamp": "2016-01-31T18:25:04.679Z"
    }
  },
  {
    "id": "humidity",
    "name": "Humidity Sensor",
    "values": {
      "h": 70,
      "timestamp": "2016-01-31T18:25:04.679Z"
    }
  },
  {
    "id": "pir",
    "name": "Passive Infrared",
    "values": {
      "presence": false,
      "timestamp": "2016-01-31T18:25:04.678Z"
    }
  },
  {
    "id": "leds",
    "name": "LEDs",
    "values": {
      "1": false,
      "2": false,
      "timestamp": "2016-01-31T18:25:04.679Z"
    }
  }
]

您可以在 Raspberry Pi 上查看各种传感器的当前值,例如温度、PIR 以及它们最后一次更改的时间。现在让我们在下一个列表中更详细地查看其中之一。

列表 8.5. 获取温度属性
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <http://model.webofthings.io/#properties-resource>; rel="type"

[
    {"t":21.1,"timestamp":"2015-06-14T15:00:00.000Z"},
    {"t":21.4,"timestamp":"2015-06-14T14:30:00.000Z"},
    {"t":21.6,"timestamp":"2015-06-14T14:00:00.000Z"},
  ...
]

对特定属性的 GET 请求将返回一个值对象数组,如此处所示。每个值对象有一个或多个字段,例如 t 用于实际的温度传感器读数,以及记录值时的 timestamp。某些传感器可能有多个维度;例如,加速度传感器将有三个维度,称为值,每个轴一个:X、Y 和 Z。

8.3.4. 动作

操作 是 Web Thing 的另一种重要资源类型,因为它们代表了可以发送到该 Web Thing 的各种命令。操作的例子包括“打开/关闭车库门”、“打开客厅灯光,将其亮度设置为 50%,并将颜色设置为红色”,以及“30 分钟后关闭电视”。理论上,您也可以使用属性来更改 Web Thing 的状态,但当一个应用程序和 Web Thing 本身都想要编辑相同的属性时,这可能会成为一个问题。这就是操作可以发挥作用的地方。让我们通过类比来更好地理解这个概念:操作代表了 Web Thing 的公共接口,而属性则是私有部分。就像在任何编程语言中一样,您可以访问公共接口,而任何私有部分仅对特权方(如实例本身或在这种情况下,Web Thing)可访问。但限制对操作的访问——即公共接口——也允许您实现各种控制机制,例如访问控制、数据验证、原子性地更新多个属性等。

当您想要发送到 Web Thing 的命令比设置简单值更复杂时,操作尤其有用;例如,当您想要向打印机发送 PDF 或当操作可能不会自动执行时。您可以通过发送 GET {WT}/actions 请求来找到给定 Web Thing 支持的操作列表,如下一列表所示。

列表 8.6. GET {WT}/actions:检索 Web Thing 支持的操作
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <http://model.webofthings.io/#actions-resource>; rel="type"

[{"id":"ledState","name":"Changes the status of the LEDs"}]

响应有效负载包含一个数组,其中包含 Web Thing 支持的每个操作的名称和 ID。关于这些操作的更多详细信息可在 {WT}/model 资源中找到,该资源描述了每个操作的作用以及如何调用它(使用哪些参数,它们的值应该是多少等)。让我们在下一列表中检查模型中 ledState 操作的详细信息。

列表 8.7. GET {WT}/model:Web Thing 模型的 actions 对象

图片

Web Thing Model 的 actions 对象包含一个名为 resources 的对象,其中包含此 Web Thing 支持的所有类型的操作(命令)。在本例中,仅支持一个操作:"ledState":{} 对象,其中 ledState 是此操作的 ID。values 对象包含在创建操作时可以发送的可能参数。在此处,操作接受两个值:ledId(要更改的 LED 的 ID,作为字符串)和 state(目标状态,作为布尔值),两者都是必需的。操作通过向操作的 URL 发送 POST 请求发送到 Web Thing,该 URL 为 {WT}/actions/{id},其中 id 是操作的 ID (ledState),如下一列表所示。

列表 8.8. POST {WT}/actions/ledState:打开 LED 3
POST {WT}/actions/ledState
Content-Type: application/json

{"ledId":"3","state":true}

HTTP/1.1 204 NO CONTENT

你可以看到,有效载荷是一个对象,其中不同的字段对应于该动作的values对象(参见列表 8.7)。如果立即执行该请求,通常响应将是204 NO CONTENT,如果动作将在稍后执行,则响应将是202 ACCEPTED。如果 Web Thing 跟踪它接收到的所有动作,你可以通过在{WT}/actions/{actionId}资源上执行 GET 来查看所有动作的列表。你可以在 Web Thing 模型在线参考中找到有关动作及其在 Web Thing 模型中使用方式的更多详细信息。

8.3.5. Things

如图 8.5 所示,Web Thing 可以作为互联网未连接的设备和网络之间的网关。在这种情况下,网关可以使用 Web Thing 公开那些非 Web Thing 的资源——属性、动作和元数据。然后,Web Thing 作为非 Web Thing 的应用层网关,将针对设备的传入 HTTP 请求转换为它们原生支持的协议或接口。例如,如果你的 WoT Pi 有一个蓝牙适配器,它可以找到并桥接附近的蓝牙设备,并将它们公开为 Web Thing。

包含所有由 Web Thing 网关代理的 Web Thing 的资源是{WT}/things,对该资源执行 GET 将返回当前所有可用的 Web Thing 列表,如下所示。

列表 8.9. GET {WT}/things:Web Thing 模型的things对象
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Link: <model.webofthings.io/things>; rel="meta"

[
  {
    "id":"http://devices.webofthings.io/pi",
    "name":"Raspberry Pi",
    "description":"A WoT-enabled Raspberry Pi"
  },
  {
    "id":"http://devices.webofthings.io/camera",
    "name":"Fooscam Camera",
    "description":"LAN-connected camera."
  },
  {
    "id":"http://devices.webofthings.io/hue",
    "name":"Philips Hue",
    "description":"A WoT-enabled Philips Hue Lamp."
  }
]

然后,你可以通过访问其 ID(如果它是绝对 URL)或将其附加到 Things 资源 URL({WT}/things/{id})来访问每个资源的 Web Thing,就像访问任何其他 Web Thing 一样发送动作或检索其属性。当 Web Thing 是一个网关或云服务时,Things 资源主要相关,但如果 Web Thing 连接了多个其他设备,例如通过 USB、蓝牙或任何其他类型的接口,它也相关。

8.3.6. 在 Pi 上实现 Web Thing 模型

现在你已经了解了 Web Thing 模型的基础,是时候深入其实现的最重要部分了。

如何获取代码

我们刚才介绍的 Web Thing 模型实现背后有大量的代码,所以我们不会描述每一行代码,而是会关注最重要的或最棘手的部分。你可以在 GitHub 上找到完整的代码;请参阅book.webofthings.io。本章的示例位于 chapter8-semantics 文件夹中。转到 webofthingsjs-unsecure 文件夹,运行npm install,然后运行node wot.js

因为代码使用了 webofthings.js 项目(Web Thing 模型的参考实现),你必须使用–-recursive选项克隆 Git 仓库,以确保检索到本章的所有子模块。

WoT Pi 模型

现在我们想要做的第一件事是使用 Web Thing 模型来描述 Pi 及其功能。这意味着扩展我们在第七章中编写的简单传感器/执行器模型。使用 Web Thing 模型建模的 Pi 的树结构如图 8.6 所示,相应的 JSON 模型可以在 /resources/piNoLd.json 文件中找到。

图 8.6. 实现 Web Thing 模型的 Pi 的资源树。传感器和执行器的概念被属性(变量)和操作(函数)的概念所取代。一些资源,如类型或产品,可以是外部引用。

下面的列表显示了 列表 8.5 中所示的温度属性模型。

列表 8.10. Pi 的温度属性

记住,我们模型中的属性是网络实体的变量或私有接口,因此不应该由外部客户端更改,而应由设备本身更改。属性可以通过操作进行修改,您可以将操作视为函数或公开接口,网络客户端可以在网络实体上调用。

操作是客户端和网络实体之间的合同。当创建操作时,网络实体必须知道如何处理它;您将很快看到操作的实现。同样,客户端必须知道操作的格式和语义,例如可以发送哪些参数。

为了使客户端能够轻松访问网络实体的资源,整个实体的模型应该能够被客户端轻松检索。一旦模型准备就绪,我们通过 /model 资源使其可访问,该资源返回整个 piNoLd.json 文件。

使用 JSON 模式验证您的模型

创建您的模型文件以使其符合 Web Thing 模型可能是一项艰巨的任务,因为此模型比我们在第七章中使用的模型复杂得多。遗憾的是,这是我们为了更好的互操作性和实际应用准备所付出的代价。幸运的是,有一个工具可以帮助我们:JSON 模式。12 JSON 模式是正式化 JSON 有效负载模型的一种方式;它基本上是 JSON 的 XML 模式(XSD)。Web Thing 模型提供了一个符合 Web Thing Model 的 JSON 模式,您可以使用它来验证您的实体的 JSON 模型。要使用它,请下载它 13,然后使用 Node.js 的 JSONSchema 库 14,或者使用在线验证器,如良好的 JSON Schema Lint15。

¹²

json-schema.org/

¹³

model.webofthings.io/models/wot-schema.json

¹⁴

github.com/tdegrunt/jsonschema

¹⁵

jsonschemalint.com/

扩展 WoT 服务器以进行发现——架构

在 Pi 上实现此模型有许多方法,但最简单的方法是在您在第七章中实现的基础上扩展架构。关键思想是将 Web Thing Model 放在中间。模型的属性将由连接到传感器的不同插件更新;例如,温度或 PIR 插件。管理执行器的插件将监听模型中的传入动作。最后,客户端请求资源,服务器将模型的一个子集作为响应发送给它们。查看图 8.7 以了解此实现的关键部分。

图 8.7. 我们 Pi 网络事物的实现策略:模型位于中间。它被路由创建者用来创建 REST 资源及其对应的端点。传感器插件(例如,PIR)在从传感器读取新数据时更新模型。执行器插件监听客户端发送的动作,执行动作,并在动作成功执行后最终更新模型;例如,它们更新因动作而改变的性质。

动态路由

在第七章中,我们手动创建了 Express 路由。在这里,因为我们实现了一个知名合同(Web Thing Model),所以我们能够轻松地自动生成路由。为此,我们首先加载模型,并在/route/routesCreator.js 文件中相应地创建路由。下一个列表中的代码展示了根资源的创建。

列表 8.11. /routes/routesCreator.js: 根资源路由

事物(/)、模型(/model)、属性((/properties/...))和动作((/actions/...))资源的代码类似。下一个列表展示了如何创建与动作相关的路由。

列表 8.12. /routes/routesCreator.js: 动作资源路由

您可以看到,路由是通过两个辅助函数创建的,这些函数定义在 utils.js 中,它们将模型映射到 Web Thing Model 中指定的资源表示:

  • extractFields(fields, model) 通过仅从模型中复制必要的字段来创建一个新的对象。

  • modelToResources(subModel, withValue) 将模型的一个子集转换为一个资源数组;例如,它从模型中提取所有属性及其最新值以创建 /properties 资源。

插件

由于 Web Thing 模型基于动作的概念,而不仅仅是属性(就像我们在第七章中的实现),我们需要调整插件以对传入的动作做出反应。基本概念在图 8.7 中显示:传感器插件(例如,温度和湿度插件、PIR 插件)仍然像在第七章的代码中一样更新属性。但执行器插件将通过观察模型来监听传入的动作,并在动作执行后改变其状态时更新属性。

你可以在 /plugins/internal 目录中找到新插件的代码。你会注意到,与第七章不同,现在所有插件都从 corePlugin.js 模块继承。这有助于我们将所有插件共有的代码分组到一个抽象插件中,其他具体插件将从这个插件继承并扩展。这可以通过使用名为 原型继承 的 JavaScript 功能来实现。如果你对我们在这里谈论的内容毫无头绪,不要担心。你只需要记住的是,所有插件共享的代码都实现在 corePlugin.js 中,而特定于插件的全部功能都实现在具体的插件模块中;例如,pirPlugin.js.^([16]) corePlugin.js 文件最重要的部分在下一列表中显示。

^((16))

如果你想了解更多关于 JavaScript 中原型继承的信息,Mozilla JavaScript 站点是开始的好地方:developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain。或者,你可以使用我们在第三章中推荐的所有 JavaScript 或 Node.js 书籍中的任何一本。第三章。

列表 8.13. /plugins/corePlugin.js: 通用功能的插件

图片

图片

因此,具体的插件要短得多,也简单得多,因为它们可以使用 corePlugin.js 模块的功能。现在所有这些插件需要做的只是注册它们将更新哪个属性以及它们将监听哪些操作(观察)。显然,它们还必须实现硬件连接部分(GPIOs),以及当它们通过 REST API 监听到的操作执行时,对硬件的处理。所有插件都在 /plugins 目录下。要了解这一切是如何工作的,请仔细查看下一列表中所示的 LED 插件。

列表 8.14. /plugins/ledsPlugin.js: 与 Web Thing 模型一起工作的 LED 插件

图片

图片

如果你家中有其他设备,我们邀请你扩展你的设备的 Web Thing 模型,并调整此实现,以便你可以将这些设备作为 Web Thing 暴露出来,这样它们就可以成为物联网的一部分。

8.3.7. 摘要——Web Thing 模型

在本节中,我们介绍了 Web Thing 模型,这是一个基于 JSON 的简单数据模型,用于描述 Web Thing 及其资源。我们还展示了如何使用 Node.js 实现此模型,并在 Raspberry Pi 上运行它。我们展示了这个模型既易于理解和使用,又足够灵活,可以通过一组属性和动作来表示各种设备和产品。目标是提出一种统一的方式来描述 Web Thing 及其功能,以便任何 HTTP 客户端都可以找到 Web Thing 并与之交互。这对于大多数用例来说已经足够了,这个模型包含了你生成 Web Thing 用户界面所需的所有内容,正如我们将在第十章中展示的那样。如果我们的爱沙尼亚朋友 Lena 所住的酒店房间只提供这样的 Web Thing 模型和 API,她将会很高兴,并且可以立即构建她的梦想应用!遗憾的是,物联网的 Web 还远未达到这个愿景,因为这样的物联网模型一直缺失。直到现在,情况才有所改变!

8.4. 物联网的语义网

在一个理想的世界里,搜索引擎和任何其他网络应用都能够理解 Web Thing 模型。给定一个 Web Thing 的根 URL,任何应用都可以检索其 JSON 模型,并理解这个 Web Thing 是什么以及如何与之交互。但这还不是现实,因为我们提出的 Web Thing 模型不是一个标准。现在的问题是,如何使用现有的网络标准来暴露 Web Thing 模型,使得资源以对其他客户端有意义的方式进行描述。答案在于语义网的概念,以及更精确地说,本节中我们引入的链接数据概念。

语义网指的是对网络的扩展,它通过推广通用的数据格式来促进机器之间的有意义数据交换。得益于万维网联盟(W3C)定义的一套标准,网页可以提供一种标准化的方式来表达它们之间的关系,以便机器可以理解这些页面的意义和内容。换句话说,语义网通过一个通用和可扩展的数据描述和交换格式,使得从任何网络内容中查找、共享、重用和处理信息变得更加容易。

8.4.1. 链接数据和 RDFa

当搜索引擎找到并索引网络上的内容时,网页上的大部分数据都是非结构的。这使得很难理解网页的主题。这个页面是关于某人的吗?还是关于一家餐厅、一部电影、一个生日派对或一个产品?HTML 页面只有有限的描述能力,告诉网络客户端或搜索引擎它们在谈论什么。你所能做的只是定义一个摘要和一组关键词。仅 HTML 规范本身并没有定义一个共享词汇表,允许你以标准和非歧义的方式描述页面上的元素及其相关内容。

链接数据

进入链接数据的概念,^([17)) 这是一套在网络上发布和连接结构化数据的最佳实践,以便网络资源可以以允许计算机自动理解每个资源类型和数据的方式相互链接。这一点尤其吸引人,因为任何理解资源类型的应用程序都可以收集、处理和统一聚合来自不同来源的数据,无论这些数据在哪里发布。

¹⁷

linkeddata.org/

这一愿景受到了围绕资源描述框架^([18)) (RDF) 和各种称为本体论的控制词汇的复杂和重型标准和工具的强烈推动。尽管 RDF 功能强大且表达能力强,但对于大多数简单场景来说,它可能过于冗余,这就是为什么需要一个更简单的方法来在网络上结构化内容。

¹⁸

www.w3.org/RDF/

为了克服没有 RDF(资源描述框架)重型机器的情况下,网络描述能力的局限性,RDFa^([19]) 提供了一种有趣的权衡。这个标准作为一个更轻量级的 RDF 版本出现,可以嵌入到 HTML 代码中。RDFa 旨在服务于人类和机器,是一种简单且轻量级的方法,可以直接在 HTML 页面中注释结构化信息,如产品、人物、地点和事件。大多数搜索引擎都可以使用这些注释来生成更好的搜索列表,并使查找您的网站变得更加容易。

¹⁹

rdfa.info/

使用 RDFa 直接在您的设备的 HTML 表示中注释 Web Things 模型元素特别有用,因为搜索引擎可以找到并理解您的 Web Things,而无需理解 Web Thing 模型的 JSON 表示。直白地说,使用 RDFa 描述 Web Things 的元数据将使该 Web Things 可由 Google 找到和搜索。尽管 Google 支持多种数据类型,如产品、食谱和事件,^([20)) 但 Web of Things 没有特定的类型。让我们看看我们如何创建自己的数据类型并在 RDFa 中使用它们。

²⁰

了解更多关于 Google 对标记的支持:developers.google.com/structured-data/rich-snippets/

RDFa 入门

要使用 RDFa 注释任何内容,我们必须重用现有的词汇表或创建一个新的。词汇表,也称为分类法,是一组可以用来注释特定类型元素的术语(字段),以及每个字段所指内容的定义。例如,如果我们只想公开有关树莓派的基本信息,如其名称、描述或图片,我们可以使用 Google 支持的产品词汇表.^([22])

²¹

RDFa 提供了一个简单的解释:www.w3.org/TR/rdfa-lite/#vocab-typeof-and-property

²²

谷歌的产品注释格式:developers.google.com/structured-data/rich-snippets/products

不幸的是,此格式不允许公开我们的 Web Thing 模型的属性或操作,因为我们没有可以重用的 Web Thing 词汇。但我们可以根据此处找到的 Web Thing 模型参考定义自己的:model.webofthings.io

在以下列表中^([23]),我们展示了如何使用 RDFa 和我们自己的物联网词汇来公开 WoT Pi 的 JSON 模型。从我们的 GitHub 仓库启动 WoT Pi 服务器,如第 8.3.6 节所示。通过使用您的浏览器访问您的 WoT Pi 的根资源,您将看到以下 HTML 代码。

²³

注意,为了提高可读性,列表 8.15 中显示的提取是实际由您在本章使用的 Web Thing 实现返回的 HTML 代码的简短版本。

列表 8.15. 带有 RDFa 注释的根资源 HTML 表示形式
<div vocab="http://model.webofthings.io/" typeof="WebThing">
  <h1 property="name">Raspberry Pi</h1>
  <div property="description">
    <p>A simple WoT-connected Raspberry PI for the WoT book.</p>
  </div>
  <p>ID:<span property="id">1</span></p>
  <p>Root URL:<a property="url" href="http://devices.webofthings.io">http://devices.webofthings.io</a></p>
  Resources:
  <div property="links" typeof="Product">
    <a property="url" href="https://www.raspberrypi.org/products/raspberry-pi-2-model-b/">
     Product this Web Thing is based on.</a>
  </div>
  <div property="links" typeof="Properties">
    <a property="url" href="properties/">
     Properties of this Web Thing.</a>
  </div>
  <div property="links" typeof="Actions">
    <a property="url" href="actions/">
    Actions of this Web Thing.</a>
  </div>
  <div property="links" typeof="UI">
    <a property="url" href="ui/">
    User Interface for this Web Thing.</a>
  </div>
</div>

您可以看到,大多数 HTML 标签都有一些由 RDFa 定义的未知属性^([24)):

²⁴

在这里了解更多关于 HTML 属性的信息:www.w3schools.com/html/html_attributes.asp

  • vocab 定义了该元素使用的词汇,在本例中是之前定义的物联网模型词汇。

  • property 定义了模型的各个字段,例如名称、ID 或描述。

  • typeof 定义了这些元素相对于元素词汇的类型。

这允许其他应用程序解析设备的 HTML 表示形式,并自动理解哪些资源可用以及它们如何工作。特别是,由于物联网搜索引擎将越来越受欢迎(或者当谷歌支持并理解 Web Thing 模型时),物理设备、其数据和服务的实时索引和搜索将变得容易。

为您的 WoT Pi 添加 RDFa

要为您的 WoT Pi 提供 RDFa 注释,您需要扩展其资源的 HTML 表示形式。在第七章中,您看到了基于转换中间件返回 HTML 的简单方法。这种方法的问题是在转换中间件内部创建 HTML 代码,这并不很干净。在 Express 中,一个更好的方法是使用 模板引擎。这些模块提供了在请求 HTML 表示形式时动态填充数据的 HTML 模板创建能力。我们在第八章的项目中安装了一个名为 Handlebars^([25]) 的模板引擎,但您也可以按照随后的“极客角落”中描述的方式自行安装它。

²⁵

github.com/wycats/handlebars.js/

一旦安装了模板引擎,你所需要做的就是创建包含你的 RDFa 代码的 HTML 模板。例如,列表 8.16 是 Pi 根资源的 HTML 模板片段。

极客角落——安装模板引擎

要为你的 WoT Pi 安装模板引擎,请安装consolidate模块^([a]) (npm install –-save consolidate);此模块便于将多个模板引擎集成到 Express 中。在我们的例子中,我们将使用 Handlebars 模板模块,你也可以通过 NPM 安装它(npm install –-save handlebars)。一旦安装完成,你需要通过向 http.js 文件中添加以下代码来告诉你的 Express 应用使用它:

^a

github.com/tj/consolidate.js

app.engine('html', cons.handlebars);
app.set('view engine', 'html');
app.set('views', __dirname + '/../views');
列表 8.16. 在 Express 中使用 RDFa 标签模板 HTML 视图

然后,扩展 converter.js 中间件以注入你的 RDFa 所需的变量,并调用模板引擎,如以下列表所示。

列表 8.17. /middleware/converter.js: 扩展转换器

就这样!你的 Pi 的 HTML 页面现在提供了 RDFa 注释,可供语义网的参与者(例如,客户端和搜索引擎)消费这些数据。

8.4.2. 约定的语义:Schema.org

语义网工具可以用来描述几乎所有的事物。例如,我们可以使用 RDFa 在我们的 Web Thing 模型上添加更多的语义描述。我们可以创建一个词汇表,描述一个网络事物是洗衣机或智能门锁。这种方法的问题在于,只有我们生态系统中的应用程序才能理解这些特定的词汇表。我们可以更进一步,将这些词汇表变成标准。但这很耗时,并且通常会因每个制造商都希望有自己的词汇表而导致竞争标准。

一种更近的方法是依赖更轻量级的协作仓库。这些仓库为特定的语义描述提供了简单的模式。它们提供了描述简单概念(如事物、人物和地点)的既定方式。

Schema.org^([26])已成为最受欢迎的这些协作仓库之一。它托管了一组在互联网上所有种类的结构化数据的高定义模式。用他们自己的话说,

²⁶

schema.org/

Schema.org 是一个协作的社区活动,其使命是创建、维护和推广互联网上、网页上、电子邮件消息中以及更多地方的标准化数据模式。Schema.org 词汇可以与许多不同的编码一起使用,包括 RDFa、Microdata 和 JSON-LD。这些词汇涵盖了实体、实体之间的关系和动作,并且可以通过一个良好记录的扩展模型轻松扩展。超过 1000 万个网站使用 Schema.org 来标记他们的网页和电子邮件消息。许多来自 Google、Microsoft、Pinterest、Yandex 和其他公司的应用程序已经使用这些词汇来提供丰富、可扩展的体验。

摘自schema.org/

换句话说,不仅任何人都可以直接重用 schema.org 中的模型以更标准的方式描述他们的网络资源,而且这样做还将使它们自动被许多其他网站和服务发现和理解。例如,Google、Yahoo!和 Microsoft Bing 可以解析 schema.org 词汇以供人类使用。如果您使用这种词汇的序列化(例如,使用 RDFa)来描述一个产品,搜索引擎将知道您在谈论一个产品,并将相应地呈现结果。同样,Person 词汇用于识别描述人类的页面,而 Place 词汇用于将物理位置附加到网页上,当使用基于位置的搜索查询时(例如通过 Google Maps),这些位置会被考虑在内。使用这些词汇的客户端不仅仅是搜索引擎;邮件客户端如 Gmail、网络浏览器和其他基于网络的发现工具也开始理解它们。

²⁷

developers.google.com/gmail/markup/overview

在物联网中,这些达成一致的词汇可以很容易地用来提高事物的可发现性,正如我们接下来将通过一个使用日益增长的格式 JSON-LD 的小例子来展示的。

8.4.3. JSON-LD

schema.org 上可用的模式不受特定格式的限制。显然,您可以使用它们在 RDFa 中使用,也可以在 Microdata 中使用,作为在 HTML 中表示链接数据的另一种方式。除此之外,这些模式还以一个更新的格式提供,称为 JSON-LD(基于 JSON 的链接数据序列化)。

²⁸

html.spec.whatwg.org/multipage/microdata.html

JSON-LD 是一种有趣且轻量级的语义注释格式,用于链接数据,与 RDFa 和 Microdata 不同,它基于 JSON。这是一种通过添加上下文信息和超链接来描述 JSON 对象不同元素语义的简单方法,以语义增强 JSON 文档。

²⁹

JSON-LD 也可以嵌入到 HTML 中:www.w3.org/TR/json-ld/#embedding-json-ld-in-html-documents

开始使用 JSON-LD 可能会有点棘手,因为撰写本文时,JSON-LD 还不是官方标准,而是一个不断发展的 W3C 推荐。30 一个好的起点是官方 JSON-LD 页面,31 在那里您可以找到许多教程、示例和一个用于测试您的 JSON-LD 负载的沙盒。在本节中,我们只关注您将需要了解如何使用它的基本内容,以便理解我们提供的示例。

³⁰

推荐的最新版本可在以下位置找到:www.w3.org/TR/json-ld/

³¹

json-ld.org

JSON-LD 通过使用以@符号开头的特殊名称表示的 JSON 属性关键字扩展了 JSON 语言。最重要的关键字总结在表 8.1 中。

表 8.1. JSON-LD 语言添加到 JSON 中的三个主要保留关键字
关键字 描述 示例
@context 指向特定模式的 URL schema.org/Person
@id 唯一标识符(通常是 URI) dbpedia.org/page/Mahatma_Gandhi
@type 指向值类型的 URL www.w3.org/2001/XMLSchema#dateTime

JSON-LD 本身只是为数据添加语义的另一种格式。但与标准模式一起使用时,例如在 schema.org 上可用的模式,它可以非常强大,因为它允许您引用一个已商定的上下文来语义描述您的数据。

JSON-LD 用于物联网

让我们看看一个简单的例子。我们将使用 schema.org 上描述的产品模式 32 来为我们 Pi 添加一些语义数据。毕竟,我们的 Pi 也是一个产品,所以这样做是有意义的!以下列表显示了pi.json模型的修改版本,它使用产品词汇表中的 JSON-LD。

³²

schema.org/Product

列表 8.18. resources/piJsonLd.json:将 JSON-LD 添加到我们的 JSON 模型中

JSON-LD 使用的 MIME 或媒体类型与 JSON 不同。多亏了您之前看到的 HTTP 内容协商机制,您只需在 converter.js 中间件中添加一小段代码,就像下一个列表中所示,就可以开始提供 JSON-LD。

列表 8.19. middleware/converter.js:添加对 JSON-LD 表示的支持

现在请尝试使用Accept: application/ld+json头在您的 Pi 的根资源上请求 JSON-LD,您将得到返回的 JSON-LD 数据。

可发现性及其超越

这个简单的例子已经说明了 JSON-LD 的本质,它为 JSON 文档的内容提供了上下文。因此,所有理解 schema.org/Product 上下文的客户端都能够以有意义的方式自动处理这些信息。例如,搜索引擎就是这样做的。谷歌和雅虎使用产品模式处理 JSON-LD 有效载荷,以呈现特殊的搜索结果;一旦被索引,我们的 Pi 就会被谷歌和雅虎识别为 Raspberry Pi 产品。这意味着我们为 Pi 添加的语义数据越多,它就越容易被找到。例如,尝试使用位置模式为您的 Pi 添加位置^([33)),它最终将可以通过位置找到。

³³

schema.org/Place

我们还可以使用这种方法在 Web Thing Model 上创建更具体的模式;例如,为洗衣机或智能锁提供的数据和功能达成一致的模式。这将促进发现,并使与越来越多的网络客户端自动集成成为可能。

8.4.4. 超越书籍

正如您所意识到的那样,一个通用的应用层协议对于实现互操作性是必要的,但并不足够。我们需要一个更高层次的模型来描述物联网的元数据和功能,以及一组标准 API,以构建互操作的应用程序和设备。我们在第 8.3 节中引入的 Web Thing Model 桥接了这一差距,并且是构建您下一个物联网设备、网关或云的绝佳起点。

在撰写本文时,此模型已作为 W3C 会员提交出版。尽管它不是官方标准,但它可能作为工作组的基础,我们邀请您关注 W3C Web of Things 协同体内的即将到来的标准化努力。^([34)]

³⁴

www.w3.org/WoT

物联网的语义和模型之战是战略性的,不仅会涉及开放标准。在家庭自动化领域,Apple HomeKit 和 Google Weave 可能会扮演重要角色。我们在物联网的发展中处于一个关键的转折点,依赖于大公司创建的标准可能不是个人消费者最佳的选择。因此,像 W3C 这样的独立机构将在未来网络和 WoT 的未来中发挥至关重要的作用。

8.5. 摘要

  • 在物联网中找到附近设备和服务的功能至关重要,这被称为引导问题。几个协议可以帮助发现事物的根 URL,例如 mDNS/Bonjour、二维码或 NFC 标签。

  • 网络事物设计过程的最后一步,资源链接设计(在 REST 术语中也称为 HATEOAS),可以使用 HTTP 头中的网络链接机制来实现。

  • 除了找到根 URL 和子资源之外,客户端应用程序还需要一种机制来发现和理解 Web Thing 提供的数据或服务。

  • 物联网的服务可以被建模为属性(变量)、动作(函数)和链接。Web Thing 模型提供了一个简单、灵活、完全兼容 Web、可扩展的数据模型,用于描述任何 Web Thing 的详细信息。这个模型很容易适应你的设备,并且对你的产品和应用来说易于使用。

  • Web Thing 模型可以通过更多具体的语义描述进行扩展,例如基于 JSON-LD 的描述,这些描述可以从 Schema.org 仓库中获取。

虽然互联网接入是成为 Web of Things 的一部分所必需的最低要求,但你已经看到,一个共享和开放的数据模型来描述 Web Thing 将最大化互操作性,而不会牺牲灵活性和易用性。

现在你已经学会了如何在万维网上打开、公开、查找和使用 Web Thing,你已经准备好迎接下一个挑战——Web of Things 的下一层:如何在开放的网络上如 Web 安全地共享 Web Thing。在下一章中,我们将首先向你展示如何使用最先进的方法和最佳实践来保护你的 Web Thing。之后,你将学习如何使用你现有的社交网络账户来与你的朋友分享你的设备。最后,我们将展示如何在你的 WoT Pi 上实施 Web 安全和数据共享的最佳实践。

第九章:分享:保护与共享 Web Thing

本章涵盖

  • Web of Things 上的安全风险和问题的简要概述

  • HTTPS、证书和加密的简要理论介绍

  • 基于 Web 的授权和访问控制的最佳实践和技术

  • 学习如何在你的树莓派上实施这些最佳实践和工具

  • 在 WoT 网关中实现社交 Web of Things

在大多数情况下,物联网部署涉及一组设备,它们在封闭网络中相互通信或与各种应用程序通信——很少通过如互联网这样的开放网络。这样的部署可以公平地被称为“物联网的内部网络”,因为它们本质上是被隔离的、私有的网络,只有少数实体可以访问。但 Web of Things 的真正力量在于打开这些孤立的孤岛,并促进大规模的设备和应用程序之间的互联。

你为什么会想要这样做呢?当涉及到一个关键的物联网系统,比如深圳一家大型工厂的工业机器网络、大英博物馆的安全系统,或者仅仅是家中的智能设备集合时,你当然不希望这些网络对任何人开放。但是,当涉及到公共数据,如 data.gov 倡议、城市中的实时交通/天气/污染状况,或者部署在丛林或火山中的传感器群组时,确保全球的公众或研究人员能够访问这些数据将是非常好的。这将使任何人都能利用这些数据创建新的创新应用,并可能产生巨大的经济、环境和社交价值。另一个用例是第一章中提到的智能酒店场景第一章,其中酒店客人(只有他们)在其入住期间(只有那时)应该能够访问其房间中的一些服务和设备(只有那里)。因为公共基础设施正在变得不仅数字化,而且无处不在,我们越早构建、部署和扩展这些系统,同时最大化设备、用户和应用程序之间共享数据的能力,对我们所有人来说就越好。如何在安全且灵活的方式下共享这些数据,这正是第三层所提供的,如图 9.1 图 9.1 所示。

图 9.1. 物联网的共享层。此层关注的是如何确保设备和它们的资源得到保护,以便只有授权用户和应用能够访问。

图 9.1

此处的先决条件是在设备和应用程序之间使用通用的协议和数据格式,这在之前的章节中我们已经进行了详细阐述。但是,一旦设备连接到公共网络,最重要的待解决问题是如何确保只有特定的一组用户可以在特定时间以特定方式访问特定的一组资源。在接下来的几节中,我们将通过构建你已经了解的概念和工具来展示如何实现这一点。首先,我们将展示 WoT 架构的第三层如何覆盖设备安全:如何确保只有授权方可以访问特定的资源。然后,我们将展示如何利用现有的可信系统通过互联网共享物理资源。

9.1. 确保设备安全

目前,物联网世界最热门的话题(或者更准确地说,是“土豆”)无疑是安全问题。¹ 我们在新闻中不断听到关于物联网和全连接世界的无限可能性。遗憾的是,这一愿景也不断受到重大安全漏洞的玷污:数百万用户的个人信息、信用卡数据、敏感文件或密码被黑客窃取。此类事件不仅可能严重损害公司的声誉,而且可能对用户造成灾难性的影响。最终,每一次安全漏洞都会损害整个网络,因为它侵蚀了用户对技术的整体信任。没有人希望他们的智能冰箱发送关于可疑药品、遗产或未领取的彩票收益的垃圾邮件。²

¹

venturebeat.com/2016/01/16/ces-2016-the-largest-collection-of-insecure-devices-in-the-world

²

www.theguardian.com/technology/2014/jan/21/fridge-spam-security-phishing-campaign

物联网中的安全问题甚至比网络中的安全问题更为关键。因为物联网设备是将在现实世界的各个地方部署的物理对象,与物联网攻击相关的风险可能是灾难性的。数字增强设备可以以细粒度收集关于人们的大量信息,例如你上次注射胰岛素的时间、你跑步的时间和地点等等。但更重要的是,对物理对象的未经授权访问可能是危险的——远程控制你的全新宝马³或房屋⁴,这可能吗?尽管存在这些风险,但最近的报告显示物联网安全领域的情况令人沮丧。⁵ 尽管许多漏洞——在黑客术语中被称为“漏洞”——是众所周知的,并且针对它们的补丁也容易获得,但据报道,大多数物联网解决方案甚至不遵守最基本的最佳安全实践;想想明文密码和通信、无效证书、带有可利用漏洞的旧软件版本等等。换句话说,你甚至不需要是安全专家,就可以利用许多服务或设备中现有的弱点,获取未经授权的内容。

³

www.wired.com/2015/08/bmw-benz-also-vulnerable-gm-onstar-hack/

www.forbes.com/sites/kashmirhill/2013/07/26/smart-homes-hack/

参见“物联网中的不安全因素”,www.symantec.com/iot/.

本书不是关于网络安全,所以不要期望在本章结束时成为这个领域的专家。但是,由于这对于任何连接到互联网的生产系统或消费产品来说都是一个关键问题,我们将以构建安全可靠设备和应用程序的最佳实践的形式,介绍您在构建物联网解决方案时需要了解的基本知识。如果您迫不及待,一个极好的资源是开放网络应用安全项目(OWASP)的物联网项目^([6]),它包含有关如何构建更安全的物联网应用程序和系统的有用、实用和实际信息。

OWASP 物联网项目

大体来说,确保物联网的安全归结为解决三个主要问题,这些问题在图 9.2 中总结:

图 9.2. 确保物联网安全的三个主要挑战。首先,通信必须加密,以防止未经授权的实体读取客户端和服务器之间的消息。其次,客户端必须确保他们确实在与他们交谈的人交流。第三,服务器必须确保消息来自被允许发送该请求的授权客户端。

图片

  • 首先,我们必须考虑如何加密两个实体之间的通信(例如,应用程序和 Web 物品之间的通信),以便恶意拦截者——“中间人”——无法访问以明文形式传输的数据。这被称为保护通道,将在第 9.1.1 节中讨论。

  • 第二,我们必须找到一种方法来确保当客户端与主机通信时,它可以确保主机确实是“他自己”,这是第 9.1.2 节中讨论的主题。例如,在第四章中,您从我们的网站上下载并安装了 NOOBS。但是您是通过 HTTP 而不是 HTTPS 来做的,我们没有提供通过 HTTPS 可用的 SHA 校验和。本质上,您必须相信您下载的确实是我们所提供的,而不是攻击者插入的损坏的操作系统镜像。

  • 第三,我们必须确保正确的访问控制措施得到实施。我们需要设置一种方法来控制哪个用户可以访问哪个服务器或物品的资源,以及何时访问,然后确保用户确实是他们声称的那个人。这个主题将在第 9.2 节中讨论。

在探索了这三个问题和它们的解决方案之后,在第 9.3 节中,我们将模糊另一条界限:社交网络和物联网之间的界限。我们将把迄今为止所学的一切整合起来,构建一个应用程序,允许您使用第三方社交网络身份与您的朋友分享物品。

9.1.1. 加密基础

如您之前所见,安全不仅仅是加密。尽管如此,加密是任何安全系统的基本要素。没有加密,任何尝试保护事物的尝试都将徒劳无功,因为攻击者可以嗅探通信并理解所实施的安全机制。

使用未加密的 Web 协议可以比作通过蜗牛邮件发送明信片:任何人都可以在任何阶段阅读明信片的内容。将加密添加到 Web 协议中就像将明信片放入厚厚的密封信封中:即使你能看到信封,你也无法阅读卡片!

对称加密

信息编码的最古老形式是对称加密。其理念是发送者和接收者共享一个秘密密钥,该密钥可以用来以特定方式编码和解码信息;例如,通过替换或移位某些字符。这种加密方式对于资源有限的物联网设备来说最容易实施,但问题是,一旦有人发现了密钥,他们就可以解码和编码任何信息。要成功使用对称密钥,必须安全地将密钥与受信任的各方共享,例如亲自将密钥交给接收者。

非对称加密

在互联网时代,另一种称为非对称加密的方法变得流行,因为它不需要在各方之间共享秘密。这种方法使用两个相关的密钥,一个是公钥,另一个是私钥(秘密),如图 9.3 所示。图 9.3。主机可以自由地将其公钥与互联网上的任何人分享。当任何客户端想要向主机发送消息时,它可以使用公钥在发送之前对消息进行编码。一旦消息用公钥编码,就只能用只有主机知道的私钥来解码。这样,任何客户端(例如,Web 应用)发送给服务器(例如,Web 设备)的消息只能由服务器打开,而不能由窃听者打开。

图 9.3。物联网环境中的非对称加密。加热器将其公钥与 Lena 分享。然后,由 Lena 的移动应用来加密发送给加热器的消息。多亏了密码学的力量,解密消息的唯一方法就是使用加热器的私钥。

图片

9.1.2. 使用 TLS 的 Web 安全:HTTPS 中的 S!

幸运的是,存在标准协议,可以在客户端和服务器之间安全地加密数据。其中最著名的协议是安全套接字层(SSL)。SSL 长期以来一直是 HTTPS 中“S”背后的技术,这是浏览器和网站服务器之间加密所有通信的方法。但多年来,SSL 协议中发现了许多重要漏洞,使得攻击者能够破解 SSL 提供的安全。2014 年,SSL 3.0 协议中发现了重大漏洞;例如,POODLE^([7]), Heartbleed^([8]), 和 Shellshock^([9]). 这些事件标志着该协议的终结,它被更安全但概念上相似的传输层安全性(TLS)所取代.^([10])

blog.mozilla.org/security/2014/10/14/the-poodle-attack-and-the-end-of-ssl-3-0/

en.wikipedia.org/wiki/Heartbleed

en.wikipedia.org/wiki/Shellshock_(software_bug)

¹⁰

SSL 的长期历史意味着今天,SSL 这个缩写被用作 TLS 和 SSL 的总称。

这突出了两个重要观点。首先,没有任何方法或系统是永远安全的。其次,开放协议——尤其是网络协议——一旦发现漏洞,就会受到密切监控并及时修复。因此,所有在物联网上的通信都应该使用 TLS 加密。在这里,我们不会对 TLS 进行完整描述,因为它需要单独的一章——或者整本书,但我们会回顾 TLS 的基本知识,并关注关键概念,同时简化复杂部分。

TLS 101

尽管名为 TLS,但它是一种应用层协议(参见第五章)。TLS 不仅保障了 HTTP(HTTPS)通信的安全,也是安全 WebSocket(WSS)和安全 MQTT(MQTTS)的基础。TLS 有两个主要角色。首先,它帮助客户端确保服务器就是它所声称的那样;这就是 SSL/TLS 认证。其次,它保证通过通信通道发送的数据只能被参与交易的客户和服务器读取(也称为 SSL/TLS 加密)。

客户端和服务器之间典型的 TLS 交换如图 9.4 所示。图 9.4.^([11]) 这就是当你使用浏览器连接到 HTTPS 网站,如manning.com时会发生的情况。以下是最重要的步骤总结:

¹¹

如果你想用更简单的方式向你的猫解释 TLS,请查看“垫锁背后的秘密”:casecurity.org/wp-content/uploads/2013/01/ssl-1200.jpg

图 9.4. SSL/TLS 握手:客户端和服务器首先协商协议和加密算法,然后服务器将其证书链发送给客户端以证明其身份。最后,客户端发送一个 preMasterSecret,客户端和服务器从中推导出一个 masterSecret,用于加密所有未来的消息。

09fig04_alt.jpg

1. 客户端,例如一个移动应用,会告诉服务器,例如一个 Web 设备,它支持哪些协议和加密算法。这有点类似于我们在第六章中描述的内容协商过程。

2. 服务器将其证书的公钥发送给客户端。这里的目的是让客户端确保它知道服务器的身份。所有网络客户端都有一个它们信任的证书列表。12 在你的 Pi 上,你可以在/etc/ssl/certs 中找到它们。SSL 证书形成了一个信任链,这意味着如果客户端不相信服务器发送的证书 S1,但它信任用于签署 S1 的证书 S2,那么网络客户端也可以接受 S1。

^(12)

Firefox 和 Chrome 等浏览器信任由这些 CA 签发的证书;请参阅mozillacaprogram.secure.force.com/CA/IncludedCACertificateReport

3. 其余的过程从公钥证书生成一个密钥。然后,这个密钥被用来以安全的方式加密服务器和客户端之间来回传输的数据。因为这个过程是动态的,所以只有客户端和服务器知道如何解密他们在会话期间交换的数据。这意味着数据现在被安全地加密了:如果攻击者设法捕获数据包,它们将毫无意义。

9.1.3. 在你的 Pi 上启用带有 TLS 的 HTTPS 和 WSS

现在你已经了解了理论,是时候进行一些实践了!让我们确保你的 WoT Pi 的 API 安全,以确保 Pi 和其客户端之间的流量被加密。请注意,我们在这里定义的过程同样适用于我们讨论的所有其他 Linux 设备——例如,Intel Edison 或 BeagleBone——以及任何基于 Linux 或 Unix 的机器。继续生成一个证书。首先,你需要确保 OpenSSL 库已安装。在你的 Pi 上,前往/resources 目录并运行

sudo apt-get install openssl

这应该会告诉你类似“openssl 已经是最新版本。”的消息。如果没有安装,它将被安装。现在,要生成证书,请运行

openssl req -sha256 -newkey rsa:2048 -keyout privateKey.pem -out caCert.pem
     -days 1095 -x509

由于此命令是自我解释的,我们不会详细说明。不?好吧,让我们深入探讨!该命令在一件事中做了两件事。首先,它生成一个私钥(-newkey rsa:2048 -keyout privateKey.pem),该私钥将用于使用 sha256 哈希算法签署证书。在此过程中,你会看到一个“生成 2048 位 RSA 私钥”的消息,然后是一个提示提供密码短语,实际上是一个用于保护你的私钥的密码。确保你将其安全保存,因为你很快就会需要它!

其次,它将生成一个新的证书(-out caCert.pem),使用 x509 数据格式,有效期将长达 1,095 天,并且会提示你几个问题,如列表 9.1 所示。通用名称是此证书应有效的主机名;例如,如果你在 Pi 上,则为raspberrypi.local,如果你在这些示例中在自己的机器上运行,则为localhost。你在此处提供的信息将在证书中公开,并且对所有客户端可见。

列表 9.1. 生成自签名证书时请求的信息

图片

在此过程结束时,将生成两个文件:

caCert.pem是 Pi 服务器在通过 TLS 连接到它时发送给客户端的证书的公共部分。

privateKey.pem是 Pi 服务器的私钥,因此应该保密。

现在,你已经准备好将你的 Pi 未加密的 HTTP 和 WS API 转换为安全的 HTTPS 和 WSS API。你需要做的只是修改 WoT PI 项目根目录下的 wot-server.js 文件的代码(参见第七章和第八章)。将 wot-server.js 的内容复制到一个新的 wot-server-secure.js 文件中,并按以下列表修改它,以启用 HTTPS 和 WSS。

列表 9.2. 修改 WoT Pi 服务器以提供 HTTPS 和 WSS 内容

图片

图片

最后,修改 wot.js 文件以要求加载 wot-server-secure.js,并通过运行 nodewot.js 来启动服务器。现在,请在浏览器中访问 https://localhost:8484/properties/pir。你应该会收到一个警告,说明连接不是私有的。这实际上意味着什么在细小字迹中显示出来:ERR_CERT_AUTHORITY_INVALID。这意味着证书是由你生成的,而不是由你的浏览器信任的证书颁发机构(CA)生成的。有两种方法可以解决这个问题:你可以从受信任的 CA 购买证书,如下一节所述,或者你可以告诉你的计算机信任你刚刚创建的证书。最好的方法是将其添加到浏览器信任存储中。操作将取决于你使用的环境,但以下是将其添加到 Firefox 的方法:点击“我了解风险”(因为你现在确实了解了,不是吗?),添加异常,最后确认安全异常。其他浏览器如 Chrome 使用底层操作系统的信任存储。因此,为了确保 Chrome 接受你的证书,请转到“首选项”>“设置”>“显示高级设置”;在 HTTPS/SSL 中点击“管理证书”。这应该会打开操作系统的信任存储,在那里你可以导入证书。直接将自签名 SSL 证书添加到你的操作系统^([13]) 将使你更容易为你的 Pi 开发安全的应用程序。

¹³

blog.getpostman.com/2014/01/28/using-self-signed-certificates-with-postman/

一旦你的浏览器信任了你的 WoT Pi 的证书,你应该能够获取返回的内容,并且浏览器应该在地址栏上显示通常的锁图标。如果你点击它,你会看到你的 TLS 证书的详细信息,如图 9.5 所示。

图 9.5. 现在可以通过 HTTPS 访问 WoT Pi 的服务器。可以通过点击地址栏上的小锁图标来查看安全连接和证书的详细信息。

有关自签名证书之外的

显然,不得不处理所有这些安全异常并不愉快,但这些异常存在是有原因的:为了警告客户端,通常由 SSL/TLS 覆盖的部分安全性不能保证使用你生成的证书。基本上,尽管消息的加密将使用自签名证书(你使用上一条命令创建的证书)工作,但服务器(Pi)的真实性无法保证。因此,信任链被打破——这是 图 9.2 中的问题 2。在物联网环境中,这意味着攻击者可以假装成你认为是你正在与之交谈的设备。当你的设备仅限于本地网络时,这并不是什么大问题,但一旦你将它们放在网络上,这就会变得至关重要。

生成保证服务器真实性的证书的常见方式是从知名且受信任的证书颁发机构(CA)获取。网上有许多这样的 CA,例如 Thawte、Symantec 和 GeoTrust。这类 CA 颁发的证书的好处是,它们会验证谁创建了证书,尽管严谨程度各不相同。这意味着客户端对其所交谈的服务器有更大的确定性(认证)。因此,这些证书或使用这些证书生成的证书被许多客户端(如网页浏览器)所信任。更具体地说,这意味着网页浏览器和操作系统将它们存储在信任存储中。

问题在于,由知名 CA(证书颁发机构)颁发的证书当然不是免费的。出售网络安全业务是一项有利可图的业务!这种做法的直接且不幸的后果是,许多网站使用成本较低的 CA,这些 CA 在检查证书发放对象方面做得较差,或者他们决定完全不使用加密连接。但这种状况正在迅速改变:网络上的许多主要参与者,如 Mozilla、Akamai、Cisco 和互联网安全研究小组,共同发起创建了 Let’s Encrypt^([14])项目,这是一个为公众利益提供免费和安全的 CA 自动化项目。甚至可以使用运行 Express 的 Node 服务器从 Raspberry Pi 上自动生成 Let’s Encrypt 证书.^([15]) 现在你已经了解了 TLS 的基础知识,当你将你的 Pi 迁移到万维网时,你应该考虑这一点。

¹⁴

letsencrypt.org

¹⁵

github.com/DylanPiercey/auto-sni

技术角落——我想让我的 Pi 上网!

当你的 WoT Pi 的开发和测试阶段完成时,你可能会想要通过自己的公共域名使其可通过网络访问;例如,mypi.webofthings.io。为此,你可以使用 Yaler,^([a]) 这是一个出色的服务和开源项目,它提供了一个中继,可以安全地通过防火墙访问你的嵌入式设备,并支持移动事物连接到不同的网络。或者,如果你想走 DIY 路线,你可以使用动态 DNS 服务——除非你已经有一个固定的 IP 地址——它会持续监控你的家庭路由器的 IP 地址以确定何时发生变化。有许多这样的服务,但 Duck DNS 简单且免费。此外,它提供了如何在 Pi 上安装它的清晰说明.^([b]) 一旦设置好,你还需要在你的家庭路由器上设置端口转发.^([c]) 然后,你可能还需要生成(或购买)一个与 Pi 的新 Duck DNS 子域对应的通用名称证书;例如,mypi.duckdns.org。一旦完成所有这些,你的 Pi 应该真正地连接到全球物联网。但你的 Pi 也将准备好让攻击者尝试攻击它,所以请确保你很好地保护它,至少要阅读到本章的结尾并实施我们描述的概念!

^a

www.yaler.net/raspberrypi

^b

www.duckdns.org/install.jsp#pi

^c

portforward.com/

9.2. 身份验证和访问控制

一旦我们将事物与客户端之间的通信加密,如前节所示,我们只想允许某些应用程序访问它。让我们回到我们的酒店场景来理解这个问题。酒店控制中心应用程序需要完全访问网络中的所有设备,并具有配置和管理它们的能力。但是,住在 212 号房的 Lena 只需要访问那个房间的设备和服务。此外,她不应该能够配置它们,而只能发送一组有限的命令。首先,这意味着事物或连接到事物的网关需要能够知道每个请求的发送者(识别)。其次,设备需要信任发送者确实是他们声称的那个人(身份验证)。第三,设备还需要知道它们是否应该根据发送者的身份和发送的请求来接受或拒绝每个请求(授权)。如果加密就像在密封的信封中发送明信片一样,那么身份验证和授权就像通过挂号信发送那个信封:邮递员只有在他们能够用有效的身份证证明自己的身份的情况下,才会将信件递送给正确的收件人。

9.2.1. 使用 REST 和 API 令牌进行访问控制

现在,我们在网络上经常经历这个认证过程,即每次我们在网站上输入用户名和密码时。当我们使用用户名/密码登录网站时,我们与服务器启动一个安全的会话,该会话存储在服务器应用程序的内存中或本地浏览器 cookie 中,有效期为有限时间。在此期间,我们可以发送其他请求到服务器而无需再次认证。这种方法(称为基于服务器的认证)通常是状态性的,因为客户端的状态存储在服务器上。但正如您在第六章中看到的,HTTP 是一个无状态协议;因此,使用基于服务器的认证方法违反了这一原则,并带来了一定的问题。首先,整个系统的性能和可扩展性受到限制,因为每个会话都必须存储在内存中,当有大量认证用户时,开销会增加。其次,这种认证方法存在一定的安全风险——例如,跨站请求伪造.^([16])

¹⁶

此方法利用了恶意网站可以使用您的浏览器代表您向已登录的另一个网站发送请求的事实。请参阅en.wikipedia.org/wiki/Cross-site_request_forgery

为了规避这些问题,一种称为基于令牌的认证的替代方法已经变得流行,并被大多数 Web API 所使用。其想法是,一个秘密令牌——一个对每个客户端唯一的字符长字符串——可以用来认证该客户端发送的每个请求。因为此令牌被添加到发送到服务器的每个 HTTP 请求的头部或查询参数中,所以所有交互都保持无状态。因为不需要在服务器(s)上保留会话或状态,应用程序可以在水平扩展时无需担心每个用户的会话存储位置。

显然,API 令牌应该使用密码学安全的伪随机数生成器生成^([17]),并且应该像密码一样处理:以加密方式存储。

¹⁷

en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator

要使用 Node.js 生成 API 令牌,您可以使用crypto.randomBytes()函数.^([18]) 您可以在下一列表中显示的/utils/utils.js 文件中找到此函数。

¹⁸

nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback

列表 9.3. utils/utils.js: 生成加密安全的 API 令牌
exports.generateApiToken = function(length, chars) {
  if (!length) length = 32;
  if (!chars) chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  var randomBytes = crypto.randomBytes(length);
  var result = new Array(length);

  var cursor = 0;
  for (var i = 0; i < length; i++) {
    cursor += randomBytes[i];
    result[i] = chars[cursor % chars.length];
  }

  return result.join('');
};

您可以通过在 http.js 文件中取消注释以下行来调用此函数:

console.info('Here is a crypto-secure API Key: ' + utils.generateApiToken());

当你启动 WoT Pi 服务器时,你将在终端看到一个新 API 令牌,你可以将其复制并粘贴到 resources/auth.json 文件中apiToken键的值。这将是你需要发送到 Pi 的 API 令牌。

你现在需要修改 WoT Pi 应用程序,以便对于每个进入的请求,你都要检查该请求是否使用有效的 API 令牌进行签名;请参阅以下列表。最好的做法是使用前一个章节中展示的中间件模式。你将在中间件文件夹中创建一个 auth.js 文件,该文件包含一个函数,每次有新的请求到达你的 API 时都会被调用,并检查其是否已签名且有效。

列表 9.4. auth.js: 认证中间件

最后,你需要将这个中间件函数添加到 servers/http.js 中的中间件链中。首先使用auth = require('./../middleware/auth'),引入中间件,然后使用app.use(auth());将其添加到链中,紧随 CORS 中间件之后。现在,再次运行 WoT 服务器,然后尝试访问 https://localhost:8484/properties/pir。你现在应该会收到一个错误消息。再次尝试使用 https://localhost:8484/properties/pir?token=YOUR_TOKEN(或者通过 Postman 添加带有你的令牌作为值的Authorization头)应该可以工作:你的 API 现在需要有效的令牌!

在这个最小示例中,你将手动检查每个请求是否与硬编码的 API 令牌匹配。我们想要向你展示基于令牌的认证的基本工作原理,因此这不是一个健壮且可扩展的解决方案,不适合生产应用。你需要使用一个更复杂的解决方案,以适应你的用例和设备。你会有很多不同的用户,每个用户都需要自己的 API 令牌吗?或者只有一个令牌就足够了?你的访问控制需要多细粒度?你需要多久更改一次这些权限?作为一个练习,你可以扩展这个简单的基于令牌的实现,以支持多个用户和令牌,以及你的设备(包括 WebSocket 交互;参见/servers/websocket.js 以获取解决方案)。

技术角落——我想要更好的令牌!

人工生成令牌并从头开始实现一个基于令牌的最小认证系统,就像之前展示的那样,是一个很好的练习,可以帮助你理解它是如何工作的。但对于任何超出这个范围的事情,你最好使用一个实际的标准。JSON Web Tokens^([a])(JWT)在这里特别有趣,因为它不仅生成安全的令牌,还提供了一个标准机制来在非安全连接上发送加密的有效负载。换句话说,JWT 使得在 HTTP 和 WebSocket 数据包上发送安全内容成为可能,而无需使用 TLS。这对于 WoT 来说尤其有吸引力,因为它消除了你之前在浏览器中遇到的自动生成的证书警告,因为在本地网络中应用程序与事物之间的交互不需要证书。它当然不如 TLS 标准化和经过实战检验,但我们在自己的测试中已经取得了一些有希望的结果。JWT 库支持许多语言,包括 Node.js,所以你可以尝试一下!

^a

jwt.io

9.2.2. OAuth:一个 Web 授权框架

在上一节中,我们简要介绍了 API 令牌,它们的工作原理以及如何在 Web Things 上实现它们。API 令牌是一个良好的起点,并且与加密(TLS)一起,它们可以说是 WoT 设备在安全方面应该提供的最基本功能。但是,一旦我们需要将设备的资源与具有不同授权权限的多个用户共享,我们之前介绍的简单 API 令牌就面临两个挑战。

首先,我们需要一个流程,让 Web 应用程序能够动态地生成和检索令牌,理想情况下是通过 API。显然,我们不能仅仅创建一个返回令牌的 API 端点。这将是不安全的,我们又会回到起点,因为我们也需要保护这个 API。此外,创建一个获取令牌的定制机制不会促进互操作性;它会使过程变得复杂,并为每个设备/或 API 定制。

其次,API 令牌不应永久有效。API 令牌,就像密码一样,应该定期更换。我们还应该能够在需要时手动使任何令牌失效。这确保了当 API 令牌泄露时,我们可以禁用它。但再次强调,创建一个用于续订令牌的定制 API 不会促进 Web 客户端和 Web Things 之间的互操作性。

该怎么办?事实证明,有一个网络标准即将来拯救我们:OAuth.^([19]) OAuth 是一个开放标准,用于授权,本质上是一种机制,允许 Web 或移动应用将用户的认证委托给第三方可信服务例如,Facebook、LinkedIn 或 Google。OAuth 通过仅使用 Web 协议动态生成访问令牌,使委托认证过程既安全又简单。OAuth 还允许应用程序之间共享资源。例如,您可以允许一些 Facebook 朋友安全地访问您在 Google 上的一些文档。

¹⁹

tools.ietf.org/html/rfc6749

简而言之,OAuth 标准化了如何在网络上以安全且标准的方式验证用户、生成带有过期日期的令牌、更新令牌以及提供对资源的访问。听起来这正是我们所需要的,不是吗?让我们看看如何使用最新的 OAuth 标准:OAuth 2.0 来实现这一点。

OAuth 角色

典型的 OAuth 场景涉及四个角色:

  • 资源所有者— 这是想要授权应用程序访问其信任账户之一的用户;例如,您的 Facebook 账户。

  • 资源服务器— 是提供用户想要共享的资源访问权限的服务器吗?本质上,这是一个接受 OAuth 令牌作为凭证的 Web API。

  • 授权服务器— 这是管理访问资源授权的 OAuth 服务器。它是一个提供 OAuth API 以验证和授权用户的 Web 服务器。在某些情况下,资源服务器和授权服务器可以是同一个,例如在 Facebook 的情况下。

  • 应用程序— 这是想要访问用户资源的 Web 或移动应用程序。为了保持信任链,应用程序必须在授权服务器中预先知道,并必须使用一个秘密令牌进行身份验证,这个令牌是一个只有授权服务器和应用程序知道的 API 密钥。

典型的 OAuth 委托认证机制的流程如图 9.6 所示。在令牌交换过程结束时,应用程序将知道用户是谁,并将能够代表用户访问资源服务器上的资源。然后,应用程序还可以使用可选的刷新令牌或重新运行授权过程来在令牌过期之前更新令牌。

图 9.6。OAuth 委托认证和访问流程。应用程序会询问用户是否希望授予它访问第三方可信服务(资源服务器)上的资源的权限。如果用户接受,将生成一个授权授予代码。此代码可以与授权服务器交换以获取访问令牌。为了确保授权服务器知道应用程序,应用程序必须发送一个应用程序 ID 和应用程序密钥,以及授权授予代码。然后可以使用访问令牌从资源服务器访问一定范围内的受保护资源。

图片

OAuth 已经成为一个成功的协议,因此,许多网络服务,如社交网络(例如,Facebook、Google+、LinkedIn 和 Twitter)、开发者服务(例如,GitHub 和 BitBucket)以及许多其他网站(如 TripAdvisor 和 Meetup)都支持 OAuth。但是,物联网呢?OAuth 如何与我们的网络事物相关?

OAuth 和网络事物

首先,如果所有事物都成为 OAuth 服务器而不是生成 API 令牌,网络客户端将有一个标准的方式来获取令牌以访问设备的资源。

让我们再次回到我们的酒店场景。Lena 是图 9.6 中的用户,她在图 9.2 中的暖气单元上有一个用户账户,该单元既是授权服务器也是资源服务器。Lena 使用一个移动应用程序来控制暖气,如图 9.6 所示。应用程序要求 Lena 使用她的用户账户登录暖气,然后应用程序将生成的授权授予交换为暖气单元的访问令牌。暖气单元接受访问令牌,并代表 Lena 向应用程序提供暖气访问权限。

如果 Lena 在与她家的暖气互动,这将是一个实际场景。但在酒店的情况下,这意味着暖气和其他所有设备都需要了解 Lena 和其他所有酒店客户。此外,所有设备还需要了解所有将与它们交互的应用程序,并且需要为每个应用程序生成一个秘密令牌。很明显,这种方法维护起来将会是一场噩梦!

在基于 Linux 的嵌入式设备上实现 OAuth 服务器,如 Pi 或 Intel Edison 并不困难,因为该协议并不真正复杂。但是,维护每个事物上所有应用程序、用户及其访问范围的列表显然不会适用于物联网。我们将在下一节中探讨一个更好的方法。

极客角落——我想让我的 Pi 成为一个 OAuth 服务器!

如果你真的想将你的 Pi 转换成一个 OAuth 服务器,那就去做吧!这将是一个很好的练习,帮助你更好地理解协议,实际上也会使下一节的实现更加安全。一个不错的起点是 Express 的 node-oauth2-server Node.js 模块,它应该可以在你的 Pi、Edison 或 BeagleBone 上无缝运行。

9.3. 物联网的社会网络

使用 OAuth 来管理对 Things 的访问控制很有吸引力,但如果每个 Thing 都必须维护自己的用户和应用列表,那就不是这样了。这就是我们在第七章 chapter 7 中发现的网关集成模式能帮到的地方。如果你只有一个代理,它不仅知道你家里(或整个酒店)的 Things,还知道所有相关的用户,那么它就可以代替这些 Things 来管理访问控制,这会怎么样呢?“但是我还是得在这个代理上为每个用户创建用户账户,”我们听到你这么说。当然,你可以这样做,但更好的方法是用 OAuth 提供的委托认证概念,这允许你使用你在信任的 OAuth 提供商(如 Facebook、Twitter 或 LinkedIn)上已有的账户。

这种方法不仅允许你重用你在其他网络服务中已有的用户账户,还允许你通过现有的社交网络关系共享对设备的访问。这些概念通常被称为物联网的社会网络.^([20]) 让我们更详细地看看这会是什么样子。就像所有与安全相关的事情一样,这不会是最容易的旅程,但绝对是一次值得的旅程。

²⁰

物联网的社会网络是 Dom 在他的论文 webofthings.org/2011/12/01/phd-web-of-things-app-archi/ 中提出的一个概念,基于 Friends and Things 项目:webofthings.org/2010/02/02/sharing-in-a-web-of-things/

9.3.1. 物联网的社会网络认证代理

物联网的社会网络的想法是创建一个认证代理,通过识别客户端应用程序的用户使用受信任的第三方服务来控制它所代理的所有 Things 的访问。这个工作流程的详细步骤在 图 9.7 中展示。

图 9.7. 物联网的社会网络认证代理:认证代理首先通过安全通道与 Thing 建立一个密钥。然后,客户端应用程序通过认证代理请求访问资源。它通过 OAuth 服务器(这里为 Facebook)进行身份验证,并获取一个访问令牌。然后,这个令牌被用来通过认证代理访问 Thing 上的资源。例如,客户端应用程序请求 /temp 资源,并通过认证代理将请求转发到 Thing,并将响应转达到客户端应用程序。

再次,我们有四个参与者:一个设备、一个使用客户端应用的用户、一个认证代理和一个社交网络(或任何具有 OAuth 服务器的其他服务)。客户端应用可以使用认证代理和社交网络来访问设备上的资源。这个概念可以在三个阶段中实现:

1. 第一阶段是设备代理信任阶段。这里的目的是确保代理可以安全地访问设备的资源。如果设备受 API 令牌(设备令牌)保护,那么这可以简单地将此令牌存储在代理上。如果设备也是 OAuth 服务器,则此步骤遵循 OAuth 认证流程,如图 9.6 所示。无论使用哪种认证方法,在这个阶段之后,认证代理都有一个秘密,允许它访问设备的资源。

2. 第二阶段是委托认证步骤。在这里,客户端应用中的用户通过 OAuth 授权服务器进行认证,如图 9.6 所示。认证代理使用授权服务器返回的访问令牌来识别客户端应用的用户,并检查用户是否有权访问该设备。如果有,代理将访问令牌或生成一个新的令牌返回给客户端应用。

3. 最后阶段是代理访问步骤。一旦客户端应用获得令牌,它就可以使用它通过认证代理访问设备的资源。如果令牌有效,认证代理将使用在第一阶段获得的秘密(设备令牌)将请求转发到设备,并将响应发送回客户端应用。

为了不在任何步骤中泄露任何令牌,所有通信都必须使用 TLS 进行加密。每个阶段的详细信息总结在图 9.7 中。

利用社交网络

你可能已经注意到,我们在过程中遗漏了一步:认证代理如何知道用户可以访问哪些资源,甚至他们是否可以访问任何资源?有人需要配置代理,包括与可以访问系统的用户相对应的一组用户标识符以及他们可以访问的资源列表。在我们的酒店案例中,我们可以要求客人使用他们的 Facebook 账户登录,或者更好的是,使用他们最初预订酒店房间时使用的 Booking.com 个人资料!然后我们可以在认证代理中保存他们的社交标识符,以及他们房间中设备的路径。在家庭自动化系统的案例中,甚至可以想象授予朋友或亲戚访问列表的权限。图 9.8 是认证代理上的一个用户界面示例,可以让你与朋友共享资源。

图 9.8. 社交物联网授权代理的用户界面:首先(左上角),UI 允许用户选择要共享的设备以及(左下角)应共享的设备资源;例如,/temperature。然后(右上角)它允许设备所有者登录到他们的社交网络,例如 Facebook,以及(右下角)选择要共享的朋友或朋友列表。在这里,我们通过 Facebook 与 Dom 的妹妹共享 Spot1 设备的温度传感器。来源:朋友和事物社交物联网项目^([[21])]

²¹

webofthings.org/2010/02/02/sharing-in-a-web-of-things/

好消息是这里不需要硬编码任何内容。多亏了我们的设备都支持 Web(参见第五章和第六章),我们可以发现它们的资源(参见第七章),并将它们映射到我们在各种 OAuth 兼容社交网络上的连接!这正是社交物联网的核心理念:我们不是创建抽象的访问控制列表,而是可以重用现有的社交结构作为共享我们设备的基础。因为社交网络越来越多地反映了我们的社会关系,我们可以重用这些知识,通过 Facebook 与朋友共享我们设备的访问权限,或者通过 LinkedIn 与工作同事共享。

9.3.2. 实现一个社交 WoT 认证代理

现在您已经了解了理论,让我们将其付诸实践,并实现一个简单的社交物联网认证代理,如图 9.9 所示。

图 9.9. 为您的 Pi 设计的社交物联网认证代理:客户端应用通过 Facebook 的 OAuth 获取令牌;然后可以使用此令牌通过认证代理访问 Pi 资源。认证代理必须在网络上可访问,或者至少与客户端应用在同一个网络中,但只要认证代理可以访问,Pi 可以位于本地网络中。

本部分完整的代码位于第九章-sharing/social-auth 文件夹中,但在这里我们只查看一些部分的细节。代理可以直接建立在我们在前几章中构建的 WoT Pi 代码之上,但正如我们之前所说的,将其实现为一个独立的代理,可以在 Pi 上或其它地方部署,这更有意义,因为它可能代理多个设备的访问。

创建 Facebook 应用程序

在我们开始编码之前,我们需要确保 Facebook 知道我们的身份验证代理是一个授权的 Facebook 应用程序。要创建一个 Facebook 应用,你需要一个 Facebook 账户并申请一个 Facebook 开发者账户。如果你不感兴趣于猫咪视频或假日自拍,因此没有 Facebook 账户,你可以自由选择其他 OAuth 提供商,例如 Google、Twitter 或 GitHub,并在所有后续章节中将“Facebook”替换为你选择的 OAuth 提供商。我们不会详细说明如何实现对其他提供商的支持,但原则将是相似的,所以你在这项练习中不应该遇到太多麻烦。

如果你还没有 Facebook 开发者账户,请访问 developers.facebook.com 并申请一个。在“我的应用”下选择“注册为开发者”。然后你可以选择“我的应用”>“添加新应用”。选择“网站”,给你的应用起一个名字,并选择跳过快速入门。现在你应该有一个新的 Facebook 应用;通过点击你的应用名称下的“设置”来填写字段,如图 9.10 所示。

图 9.10. 为我们的 Social WoT 身份验证代理设置新的 Facebook 应用程序。应用 ID 和密钥将由 Facebook 用于验证我们的应用。

完成这些步骤后,你可以记录下你需要的信息:你的 Facebook 应用的应用 ID 和应用密钥。你需要将这些信息发送给 Facebook 以验证你的客户端应用。请注意,直到你的应用公开发布,只有你和你邀请的开发者/管理员才能通过这个 Facebook 应用登录。

Passport.js:Express 的身份验证中间件

现在你的 Facebook 应用已经准备好了,你需要将其集成到你的代码中。你首先创建一个简单的 Express 应用程序,并带有 HTTPS 服务器。你可以在第九章的 sharing/social-auth/authProxy.js 中找到这个应用程序,但在这里我们不会详细说明,因为它与你在第八章和前几节中创建的 Express 应用程序类似。接下来,你将创建一个通过 Facebook 验证用户的组件。你可以使用最流行的 Node.js 模块之一来实现它,即 Passport.js.^([22]) Passport 是一个令人印象深刻的身份验证中间件,它提供了一系列简单的身份验证技术集成——超过 300 种!——包括 OAuth,因此也涵盖了所有实现 OAuth 的社交网络。

²²

passportjs.org/

在通过 npm install --s passport 安装 Passport 之后,你将通过 npm install --s passport-facebook 安装 Passport 的 Facebook 身份验证模块,称为策略。如果你想通过 Twitter、LinkedIn 或 GitHub 进行身份验证,你需要安装相应的 Passport 策略;例如,passport-twitterpassport-linkedin。只要选择一个支持 OAuth 的网络,使用你选择的身份验证策略实现的代理将与 Facebook 的实现几乎相同。

实现一个 Facebook 身份验证策略

现在你已经准备好为你的代理添加 Facebook 身份验证支持了。providers/facebook.js 文件展示了如何实现这一点。如下所示,你需要实现一系列函数来与 Passport 策略一起工作。

列表 9.5. providers/facebook.js: 一个 Facebook 身份验证策略

图片

图片

初看之下,这个流程可能看起来有些复杂。它包括一系列路由,将用户重定向到 Facebook 登录页面,并从 Facebook 返回到你的代理,同时还有一个可以用来交换令牌的代码。Passport 会为你处理所有琐碎的细节。好消息是,所有身份验证策略都必须实现相同的方法,所以在这里学到的知识也可以应用到其他社交网络上!

这曾是 Facebook 身份验证机制的核心,现在你还需要确保用户有一个用户界面(HTML 视图)来访问你创建的所有路由。当然,你可以从头开始编写 HTML 页面,但使用我们在前几章中使用的模板引擎 Handlebars 会更容易一些。我们创建的页面位于 /views 文件夹中。至少,你需要一个 login.html 页面,其中包含指向 /auth/facebook 的链接以触发身份验证过程。你还需要一个 account.html 页面,用户在成功完成 Facebook 身份验证后将被重定向到该页面。

实现访问控制列表

现在你的应用程序允许用户通过 OAuth 使用 Facebook 进行身份验证,你需要决定哪些用户可以访问哪些 Thing 上的哪些资源。本质上,你需要创建一个访问控制列表(ACL)。实现 ACL 的方法有很多,例如将它们存储在本地数据库中。为了简化问题,你将使用一个 JSON 配置文件,该文件位于 config/acl.json 中,并在下一列表中展示。该文件跟踪哪些用户可以访问你的 Pi 上的哪些资源。

列表 9.6. config/acl.json: 访问控制列表 JSON 文件

图片

图片

可能的困难之一是找到你想要通过社交网络标识符共享的用户 ID。一个好方法是请他们先登录,因为这将在从 Facebook 返回的账户页面上显示他们的社交网络 ID。或者,你也可以使用 Facebook Graph API 探索器^([23]) 工具。确保你在 ACL 中添加了自己的 ID!

²³

developers.facebook.com/tools/explorer/

现在 ACL 已经设置好了,你需要将 Facebook 返回的内容与你设置的内容进行比对,以确保尝试登录的用户确实是受欢迎的。同样,你需要检查他们是否有权访问请求的实体资源。你可以通过在/middleware/auth.js中使用中间件来实现这一点,如下一列表所示。

列表 9.7. 授权用户请求:/middleware/auth.js

实体的资源代理

最后,你需要实现实际的代理功能:一旦中间件认为请求有效,你需要联系提供该资源的实体,并将结果代理回客户端。这部分与其他任何 HTTP 代理没有区别。要实现它,你将使用一个用于构建代理的快速 Node 模块,名为node-http-proxy。^([24]) 通过npm install --save http-proxy来安装它。然后使用此模块在/middleware/proxy.js中构建另一个中间件,如下一列表所示。

²⁴

github.com/nodejitsu/node-http-proxy

列表 9.8. 代理请求到实体:/middleware/proxy.js

就这样!你现在应该已经拥有了一个完整的社交物联网认证代理。要测试它,运行node authProxy.js。然后,使用简单令牌认证启动 WoT Pi,如第 9.2.1 节中所示,或者如果你实现了 OAuth,则使用 OAuth。

尝试使用无效令牌通过代理访问你的树莓派资源;例如,raspberrypi.local:5050/properties/pir?token=1234。正如预期的那样,这将返回一个错误:未授权访问此资源!

现在,让我们获取一个访问令牌以发出有效请求:首先浏览到IP:5050/login。这应该会提示你在 Facebook 上登录(如果你的浏览器中没有放置在橱柜中的 Facebook 登录 cookie),然后会询问你是否授权代理 Facebook 应用程序访问你的个人资料。如果你接受,你将到达你的个人资料页面,如图 9.11 所示,在那里你可以看到你的访问令牌。复制它,然后再次打开raspberrypi.local:5050/properties/pir?token=YOUR-TOKEN,但这次使用你的新令牌。如果一切正常,你应该会得到 PIR 传感器的 HTML 表示。深吸一口气,思考你刚才所做的事情:你将社交网络与物理世界合并了!

图 9.11。首先,Facebook 将提示用户接受应用程序请求。在成功完成 Facebook 身份验证后,用户将被重定向到 auth 代理上的账户页面,在那里他们可以检索用于后续调用代理后事物的访问令牌。

技术角落——我想要更多这样的内容!

如同往常,由于我们保持了实现的简单性,因此有许多可能的方式来扩展这个例子。以下是一些扩展想法:你可以使用你在第八章中学到的知识来实现一个系统,使代理能够自动发现符合 Web Thing 模型规范的设备。你也可以通过实现通配符来使 ACL 更容易处理;例如,/properties/*。或者,你可以为代理创建一个用户界面,让你可以与朋友分享,或者让你可以动态添加授权用户(在我们的酒店场景中)。如果你仍然渴望更多,你也可以实现 WebSocket 的代理;node-http-proxy也支持它。最后,你可以在 Pi 上实现 OAuth 服务器——例如,使用node-oauth2-server——并将代理更改为从 Pi 动态获取 OAuth 访问令牌,而不是简单的令牌;这将使流程更加安全,并且更加灵活。

9.4. 超越书本

在本章中,你学习了如何将社交网络和物联网融合,以实现社交物联网。这对于一个章节来说,无疑是一个不小的成就!尽管你应该尽情享受这一刻,但你也应该意识到,我们对物联网和物联网安全只是触及了皮毛。我们没有涵盖许多方面,从确保隐私到保护设备免受分布式拒绝服务攻击,或确保软件和固件更新的安全。

按照定义,完美的安全是无法实现的。保护计算机网络是安全专家和黑客之间的持续战斗,其中安全系统始终需要领先一步,因为我们的机器和工具越好,就越容易破解安全系统。网络安全应该是一种持续的纪律,而不是一次性的事件,在你追求物联网冒险的过程中,你需要保持信息灵通和更新。

²⁵

一些好的床头阅读材料:- HP 研究揭示 70% 的物联网设备存在安全漏洞- OWASP 主页- 如何在物联网中搜索睡眠婴儿的照片- 为什么物联网安全如此关键

随着物联网从青少年时期步入成年,不同的焦点将出现。首先,安全性将变得无处不在,成为必需品,而不仅仅是锦上添花。但正如 HTTP 可能对资源有限的设备来说过于沉重一样,TLS 和其底层加密套件对于资源最受限的设备来说也太沉重了。这就是为什么正在开发更轻量级的 TLS 版本,例如 DTLS,^([26]) 它与 TLS 类似,但运行在 UDP 而不是 TCP 上,并且具有更小的内存占用。尽管这些协议代表了有趣的演变,但一些研究人员正在寻找革命!例如,一些研究人员开始研究他们称之为 设备民主 的概念。^([27)) 在这个模型中,设备变得更加自主,并倾向于点对点交互而不是集中式云服务。安全性通过区块链机制得到保证:类似于比特币交易在比特币网络中由多个独立方验证的方式,设备都可以参与使物联网安全。毫无疑问,物联网安全将在未来几年发生巨大变化,因为网络本身也将演变以适应今天的需要。

²⁶

数据报传输层安全

²⁷

IBM 物联网服务

技术角落——我想要安全应用管理的未来!

如前所述,在嵌入式设备上管理应用程序或固件更新可能很难正确且安全地进行:如果您没有正确操作,例如使用不安全的 HTTP 服务器,攻击者可能会利用您的更新机制在您的客户设备上注入他们想要的任何内容!幸运的是,随着物联网的成熟,出现了有趣、安全且可扩展的解决方案,以帮助您在您的“物”上部署代码。例如,resin.io 允许您使用 Git 将新版本的代码推送到您的所有“物”或其中的一部分。它还使用 Docker 容器在嵌入式设备上独立打包和运行多个应用程序,这提高了可移植性、安全性和稳定性。最后,它与 Node.js 和 Pi 兼容,如果您拥有少量设备,它是免费的,所以您可以尝试一下.^([a])

^a

resin.io

9.5. 摘要

  • 您必须涵盖四个基本原理来确保物联网系统的安全:加密通信、服务器认证、客户端认证和访问控制。

  • 加密通信确保攻击者不能读取消息的内容。它使用基于对称或非对称密钥的加密机制。

  • 您应该在网络上使用 TLS 加密消息。TLS 基于非对称密钥:一个公钥和一个私有的服务器密钥。

  • 服务器认证确保攻击者不能伪装成服务器。在网络上,这通过使用 SSL(TLS)证书来实现。这些证书的发放通过信任链来控制,只有被称为证书授权机构的可信方才能发放证书以识别网络服务器。

  • 您可以在 Raspberry Pi 上创建自签名的 TLS 证书,而不是从可信的第三方购买证书。缺点是,由于它们没有在信任存储中拥有 CA 证书,网络浏览器会将通信标记为不安全。

  • 您可以使用简单的 API 令牌实现客户端认证。令牌应定期轮换,并且应使用加密安全的随机算法生成,以确保其序列无法被猜测。

  • OAuth 协议可以以动态、标准和安全的方式生成 API 令牌,并且被许多嵌入式 Linux 设备(如 Raspberry Pi)支持。

  • OAuth 的委托认证机制依赖于其他 OAuth 提供者来认证用户并创建 API 令牌。例如,一个“物”的用户可以通过 OAuth 使用 Facebook 来识别。

  • 您可以通过创建用于客户端认证和社交网络联系人的认证代理来实现对“物”的访问控制,以反映您的社交联系。

现在你已经看到了如何确保你的 Web 连接事物可以安全地共享和通过 Web 访问其数据和服务的安全性,现在是时候转向 WoT 架构的最后一层:组合层。在下一章中,你将看到如何将本书中你所学到的所有组件结合起来,构建新一代的 Web 应用程序:物理混合应用。在 Web 应用程序和服务中直接集成来自众多物理源的真实时间数据无疑是 Web 的未来。我们想确保你拥有所需的工具,以便迅速实现这一点!

第十章. 组合:物理混合应用

本章涵盖

  • 使用模型自动生成 Web Things 的用户界面

  • 使用框和箭头混合编辑器组合 Web 事物和 Web 资源。

  • 使用向导混合编辑器在几分钟内为 Web Things 创建复杂的工作流程

自从本书开始以来,我们已经走了很长的路!我们在事物上实现了 Web 协议,使它们可以通过 Web 访问。我们使用 Web 友好的格式和语言对事物进行建模和语义描述,以促进它们的发现和互操作性。我们使用最先进的 Web 安全协议和最佳实践来保护事物,然后在 Web 上共享它们,使我们的朋友可以在各种社交网络上访问它们。现在可能是回顾并理解我们为什么通过所有这些不同层次的好时机。通过这些层次可访问的事物现在可以无缝且轻松地集成到任何 Web 应用程序或服务中,因为 Web 事物已经成为 Web 的第一公民!

最后一层——组合层,如图 10.1 所示——主要是利用你迄今为止所学和构建的内容来创建新的应用程序。你将事物变成了 Web 乐高积木;现在是你释放内心艺术家的时候,创作一系列惊人的雕塑!在本章中,我们将首先向你展示如何使用事物的 API 构建用户界面,使其能够适应和调整它们发现的事物。

图 10.1. 物联网架构的组合层。这一层专注于构建可以控制事物或从各种来源组合数据和服务的 Web 应用程序,以提供复杂的过程。它将 Web 混合应用的概念引入物联网。

图片

然后,我们将探讨物理混合应用:结合事物和 Web 服务的复合 Web 应用程序。你将学习如何使用混合工具通过连接来自各种来源的数据和服务来快速构建复杂的工作流程。因为你的应用程序的所有组件都是 Web API,你不必担心数据集成,可以专注于应用程序的连接和逻辑。

10.1. 构建简单应用程序——自动 UI 生成

物联网架构各层的最终目标是尽可能减少努力,使尽可能多的应用程序能够发现、理解和与其他事物交互。许多物联网场景涉及用户使用各种应用程序与各种设备交互。正如您之前所看到的,物联网的一个问题是每个设备都需要一个定制的应用程序,这对用户来说很不方便。如果我们能有一个能够控制任何设备的通用应用程序,那就更有趣了。物联网通过使用 Web Thing 模型实现了这一点,本节将向您展示如何开始构建这个通用遥控器。

10.1.1. 物联网的通用用户界面

您在第八章中实现的 Web Thing 模型,并在第九章中进行了安全保护,以标准格式描述了设备支持的各种动作,以及其属性和附加元数据。基于此,您可以轻松编写一个客户端应用程序,该应用程序可以自动为任何 Web Thing 生成用户界面,并在实时显示属性的同时发送命令(动作)到 Web Thing。该应用程序的架构如图 10.2 所示,这是构建物联网通用遥控器的第一步:即使没有关于它刚刚发现的特定 Web Thing 的任何先验知识,它也可以使用该事物的 Web Thing 模型文档来生成自定义用户界面,并将其绑定到事物上以控制它和/或实时可视化其数据。

图 10.2. 物联网的通用遥控器。一个 Web Thing 客户端应用程序(纯 JavaScript/HTML)可以找到附近的设备,检索它们的 Web Thing 模型描述,并使用它来生成针对该特定设备的定制用户界面。

图片

您可以在本书的 GitHub 仓库中找到这样一个简单应用程序,位于 chapter10-mashups/UI 文件夹中,因此让我们打开文件 UI.html 并分析其内容。

这个 Web 应用程序首先做的事情是使用以下列表中所示的getModel()函数检索一个事物的模型。当页面加载并给出模型 URL 作为参数时,会调用此函数。

列表 10.1. 使用 jQuery 检索 Web Thing 的 JSON 模型

图片

一旦您检索到 Web Thing 的 JSON 模型,您就使用其内容来生成它的用户界面。首先,您使用 Web Thing 的元数据——名称、描述、主机名、端口等——来生成 Web Thing 及其用途的人性化描述。然后,您使用动作的描述来创建 UI 元素,这些元素会向 Web Thing 发送命令,如图 10.3 所示。最后,您使用属性的描述来生成渲染 Web Thing 数据的 UI 元素(您可以在图 10.4 中看到这一过程)。

图 10.3. 基于 Raspberry Pi 的 Web Thing 模型的ledState动作的 HTML 表单

图片

在我们一头扎进生成 HTML 表单的代码之前,按照前几章的做法,使用node wot.js启动 WoT Pi 服务器,并确保启动了安全版本(wot.js 的第一行应该 require './wot-server-secure')。你现在可以在浏览器中打开 UI.html 文件,并将你的 WoT Pi 的令牌作为 URL 的查询参数传递(UI.html?token=YOUR_TOKEN)。

现在,获取你的 WoT Pi 的模型,并查看模型中的Action元素,如下一列表所示。你可以看到,Pi 只有一个动作,称为ledState。此动作需要两个输入参数或值:ledId(一个可以是12ALL的枚举)和state(一个表示 LED 所需状态的布尔值)。

列表 10.2. 我们 Pi 的 Web Thing 模型的actions对象

图片

图片

创建此类动作的相应 Web 表单如图 10.3 所示。

现在,让我们看看这个 HTML 表单背后的相应 HTML 代码,如下一列表所示。这当然不是最简单的代码片段,因为我们使用了Bootstrap库来使其看起来更美观(因此有复杂的 HTML 元素树),所以请耐心等待。

列表 10.3. 创建ledState动作的表单的 HTML 代码
<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Change LED state</h3>
  </div>
  <div class="panel-body">Change the state of an LED
    <form class="form-horizontal" id="wt-actions-ledState">
      <div class="form-group" id="wt-actions-ledState-ledId-f">
        <label class="col-sm-2 control-label">ledId</label>
        <div class="col-sm-4" id="wt-actions-ledState-ledId-field">
          <select class="form-control" name="ledId" id="wt-actions
            -ledState-ledId-value">
            <option value="1">LED 1</option>
            <option value="2">LED 2</option>
            <option value="ALL">All LEDs</option>
          </select>
        </div>
      </div>
      <div class="form-group" id="wt-actions-ledState-state-f">
        <label class="col-sm-2 control-label">state</label>
        <div class="col-sm-4" id="wt-actions-ledState-state-field">
          <select class="form-control" name="state" id="wt-actions
            -ledState-state-value">
            <option value="true">True</option>
            <option value="false">False</option>
          </select>
        </div>
      </div>
      <p>
        <a class="col-sm-offset-2 col-sm-4 btn btn-primary btn-lg"
          id="send-ledState" href="#" role="button">Create Action:
          ledState</a>
      </p>
    </form>
  </div>
</div>

总结一下,这里有一个简单的 HTML 表单,包含两个输入——LED ID 和所需状态——以及一个发送动作的按钮。对于任何 Web Thing 的每个动作,我们都需要生成这样的表单,包含它们支持的适当输入参数。现在让我们分析下一列表中显示的generateActions()函数,它正是这样做的。

列表 10.4. generateActions()函数

图片

图片

一旦这段代码为 Web Thing 的每个动作生成了一个表单,我们现在可以为它的属性做同样的事情。看看下一列表中我们 Pi 模型的 PIR 和 LED 属性。

列表 10.5. Pi Web Thing 模型的 PIR 和 LED 属性
...
"pir": {
  "name": "Passive Infrared",
  "description": "A passive infrared sensor.",
  "values": {
    "presence": {
      "name": "Presence",
      "description": "Current sensor value (true=motion detected)",
      "type": "boolean",
      "customFields": {"gpio": 20}
    }
  },
  "tags": ["sensor","public"]
},
"leds": {
  "name": "LEDs",
  "description": "The LEDs of this device.",
  "values": {
    "1": {
      "name": "LED 1",
      "customFields": {"gpio": 17}
    },
    "2": {
      "name": "LED 2",
      "customFields": {"gpio": 19}
    }
  },
  "tags": ["sensor","public"]
}
...

我们希望我们的生成器创建如图 10.4 所示的 HTML 输出以显示这些属性。

图 10.4. WoT Pi 的 PIR 和 LED 属性的 HTML 视图

图片

正如我们对动作所做的那样,我们将生成 HTML 元素来显示属性,如图 10.4 所示,然后为每个属性创建一个 WebSocket 订阅,以便我们可以显示每个属性的最新值,就像 Web Thing 发送的那样。所有这些都是在下一列表中显示的generateProperties()函数中完成的。

列表 10.6. generateProperties()函数

图片

图片

启动安全的 WoT Pi 服务器并打开raspberrypi.local:8484/?token=X。确保您用实际的 WoT Pi API 令牌替换 X,就像在第 9.2.1 节中做的那样。一旦您通过添加安全异常(见第 9.1.3 节)接受证书,您应该能看到 Pi 的根页面。之后,您可以打开 UI.html 文件,并将?token=X(再次,X 是您的 Pi 令牌)追加到 URL 中,您应该能看到自动生成的 WoT UI 页面,包括操作和属性。

那又如何?

在本节中,我们探讨了可以即时生成自定义用户界面以与任何 Web Things 交互的简单 Web 应用程序,而无需了解除了 Web Things 的 URL 之外的其他任何信息。我们希望这足以说明我们在第八章中介绍的 Find 层所扮演的重要角色。到目前为止,应该很清楚为什么拥有一个统一的格式——Web Things 模型——来描述物理对象及其能力是物联网规模化的关键推动力。

使用不到 200 行代码,我们提供的示例只是一个起点,绝对不是一个真正的、生产级别的应用程序。我们还有很多可以改进的地方,但我们将这部分留作练习,以激发您的想象力。例如,您可以扩展generateActions()函数以显示用于数值输入的滑块,或者您可以在发送操作之前验证是否提供了所有必需的参数。此外,您还可以探索更多有趣的方式来实时显示属性。告诉我们您的想法,不要犹豫向我们发送包含您改进和想法的拉取请求——我们期待您的反馈!

10.2. 物理混合应用

实现 WoT 架构的第 1 至 3 层不仅允许您自动创建与 Web Things 交互的 UI 和应用程序,而且可以无缝地将 Web Things 与网络上的任何其他服务和数据源融合在一起。

第 4 层专注于这个问题:如何轻松地将来自各种来源的数据组合起来以创建更复杂的应用程序。Web 混合应用是一种将多个 Web 资源结合起来创建新的、混合应用的应用程序。与传统的集成方式不同,混合应用主要关注为最终用户个人使用在网络上发生的偶然性集成。混合应用的概念也可以应用于物联网,以物理混合应用的形式。这些是结合了物理 Things 的服务和虚拟 Web 资源服务的 Web 应用程序。

¹

我们在名为“迈向物联网:嵌入式设备的 Web 混合”的研究论文中首次描述了物理混合的概念,这成为了 Dom 博士论文的核心主题之一。两者都可以在webofthings.org/publications/找到。

如果你阅读了第二章,你应该已经熟悉了物理混合的概念。在第 2.6 节(练习 5)中,你将 Pi 上的温度传感器与来自网络的天气数据、LCD 屏幕和摄像头结合起来——所有这些操作都是通过 JavaScript 和 Web API 完成的。

现在既然你的 Pi 有了 Web API,你就可以使用支持 HTTP、WebSockets、JSON 和 TLS(提示:几乎所有的语言都支持)的任何编程语言来创建混合。在本章中,你将看到你也可以不写一行代码就创建物理混合!多亏了像盒子和电线或基于向导的编辑器这样的优秀 Web 工具,你将在几分钟内就能创建复杂而强大的复合应用程序!

10.2.1. 物理网中的盒子和电线混合:Node-RED

使用 JavaScript 创建物理混合提供了最大的灵活性。由于你混合中包含的所有事物都使用 Web 协议,这使得操作变得简单。但是,与我们将要探讨的下一项混合技术(盒子和电线编辑器)相比,它仍然需要大量的工作和编程技能。这些工具的想法是通过将编程转化为一个视觉体验,即通过连接盒子来形成一个工作流程,从而使混合创建过程变得更加容易。

基本上,盒子是预先制作好的模块,它们抽象操作,例如从 REST API 获取数据,而模块之间的电线代表数据或控制的流动。你可以在图 10.5 中看到一个示例。网上有许多盒子和电线混合工具,但其中之一特别适合创建物联网混合:Node-RED.^([2]) Node-RED 是一个用于连接物联网的视觉工具。它是一个开源项目,支持多种协议,但重点在于 Web 协议,并受益于一个庞大的开发者社区,他们几乎每天都在创建新的模块。

²

nodered.org/

图 10.5. Node-RED 用户界面:节点(左侧)通过电线连接拖放以创建工作流程,将数据从一个节点发送到另一个节点。

技术角落——让我们在浏览器中完成它!

Node-RED 建立在 Node.js 之上,因此是一个服务器端 JavaScript 混合应用工具。这有一个很大的优点,即即使在浏览器关闭的情况下也能运行混合应用工作流程,但它需要安装和运行更多的工具。完全在浏览器中运行混合应用,无需服务器代码,也是可能的。在 Node-RED 还不存在的时候,我们创建了自己的客户端物理混合应用编辑器,名为wot-a-mashup。这个工具建立在 ClickScript 之上,^([a])一个运行在浏览器中的不错的可视化编程语言环境。你完全可以在 GitHub 上克隆它来尝试使用.^([b])

^a

clickscript.ch

^b

github.com/webofthings/wot-a-mashup

Node-RED 入门

好消息是,Node-RED 是一个 Node.js 应用程序,所以你应该熟悉它周围的工具。Node-RED 只能通过 NPM 在你的计算机或云中安装,^([3])但它也直接在你的树莓派上可用。如果你想创建的混合应用能够持续运行,那么在树莓派上部署 Node-RED 是有意义的。这样,你的树莓派将变成一个始终在线的网关,可以协调你家中/酒店/商业场所的所有混合应用,同时比 PC 消耗的电量少得多。还有许多专为树莓派提供的 Node-RED 社区模块,例如,用于与树莓派硬件模块接口的模块。

³

nodered.org/docs/getting-started/installation.html

让我们看看你如何可以使用 Node-RED 在你的 WoT Pi 或任何其他网络设备上创建物理混合应用。要在你的树莓派上启动它,请使用以下命令:

$ node-red-start
> Once Node-RED has started, point a browser ...

就这样!请注意,Node-RED 作为服务启动,这意味着关闭你启动它的 SSH 窗口不会停止 Node-RED。要停止它,请使用node-red-stop。如果一切正常,你现在应该能够通过访问raspberrypi.local:1880使用任何较新版本的 Firefox 或 Chrome 来使用 Node-RED 的 Web 用户界面。

“极客角落——我想保护 Node-RED!

如果你直接将你的树莓派连接到网络上,你绝对应该确保 Node-RED 需要认证才能访问它;否则,任何人都可以随意重新编程你的树莓派!这个过程很简单。它需要你生成一个密码并更改一个配置文件。这个过程在网上有很好的文档说明.^([a])

^a

nodered.org/docs/security.html

Node-RED 界面由三个主要部分组成,如图 10.5 所示。首先,在左侧有一个节点库。这是您可以找到所有代表您混合应用功能块框的地方。其次,在流程设计器(中央)中,通过连接不同的节点来创建您的流程(或只是流程)。您可以通过在流程设计器中双击节点来配置节点。第三,最右侧的部分是您可以点击节点查看节点文档的地方,也是您查看调试控制台的地方。

Hello World Node-RED

为了测试所有这些功能,让我们先创建一个简单的流程,显示传统的“Hello World”消息。该流程如图 10.5 所示;要构建它,首先将inject输入节点和debug输出节点拖放到流程设计器中。然后,将两个节点的末端连接起来。双击inject节点以更改此节点注入的内容。将Payload属性更改为字符串,并包含“Hello World!”,如图 10.6 所示。

图 10.6. 编辑节点:当双击一个节点时,会弹出编辑对话框,可以在其中配置节点。此编辑对话框显示了inject输出节点的配置。

图 10.6

就这样。您的第一个流程已经准备好了!现在通过点击右上角的 Deploy 按钮来部署它。这将把您的流程转换为 Node.js 代码,并在 Node-RED 进程中运行。现在,为了测试您的流程,请确保您已经打开了 Node-RED 调试控制台(在 Node-RED 窗口的右侧),并点击inject模块左侧的蓝色点。

当您这样做时,“Hello World”字符串会被添加到一个称为msg的特殊对象中。此对象持有要从一个节点传递到另一个节点的数据,可以是对象或msg对象的数组,每个节点在流程中的结果对应一个msg对象。在这种情况下,msg是一个包含msg.payload属性中的“Hello World”字符串的单个对象。在点击蓝色 Inject 按钮后,工作流程引擎将msg对象传递给下一个节点:debug节点,该节点在调试控制台中显示它接收到的msg.payload中的数据。这个系统基本上允许您在节点之间传递数据以构建工作流程。想象一下,使用这个来将代表您的 Pi 的节点中的温度数据可视地传递到代表数据库的节点。

保存工作流程

Node-RED 流程可以表示并保存为 JSON 文档,以便以后重用或在网上共享。要保存流程,通过拖动一个选择框包围您想要包含在导出中的所有节点来选择流程,然后点击屏幕最右侧的 Deploy 旁边的菜单。选择 Export 然后 Clipboard。您将获得与您的流程对应的 JSON 文档,您可以在任何您想要的地方保存它。然后,您可以使用此 JSON 表示来在网络上共享您的混合应用^([4]),或者通过使用导入菜单将混合应用重新导入 Node-RED。

例如,在 Node-RED 社区网站上:flows.nodered.org/

基于 Node-RED 的物理 mashup

现在你已经成功使用 Node-RED 创建了一个简单的虚拟 mashup,让我们看看如何使用 Pi 的 Web API 将物理世界包含进来。

你将要构建的流程如图 10.7 所示,并在第十章的源代码中提供,位于 chapter10-mashups/node-red/pir-websockets-twitter.json。这个想法是创建一个智能警报 mashup,当 PIR 的状态改变时接收通知,相应地更新 LED 的状态,并通过 Twitter 发送入侵警报。如果你决定从第二章的购物清单中购买建议的摄像头之一,它还应该通过摄像头捕捉入侵者的快照并将其附加到推文中。所有这些通过连接盒子和电线完成!

图 10.7. 使用 Node-RED 构建的物理 mashup 流程。这个物理 mashup 是一个智能入侵警报,通过 WebSocket API 监听 PIR 传感器的状态变化![num-01.jpg]。当事件到达时,Node-RED 通过 POST 一个ledState动作来改变 LED 的状态![num-02.jpg]–![num-03.jpg],如果事件包含 PIR 传感器的值为true![num-04.jpg],它通过 GET 从摄像头获取快照![num-05.jpg]并将其 POST 到 Twitter![num-06.jpg]–![num-07.jpg]。

让我们从参考图 10.7 的步骤开始构建你的工作流程。首先,拖放一个websocket输入节点(步骤 1),并通过双击它进行配置。这个节点的配置如图 10.8 所示;基本上,你让它监听 Pi 的 PIR WebSocket 资源。为此,选择添加一个新的 websocket-client,并配置为 ws://raspberrypi.local:8484/properties/pir^([5])(PIR 传感器的 WS 地址),并确保你选择了发送/接收整个消息选项。

使用自签名证书的 Secure WebSockets 在 Node-RED 上工作得不好,这就是为什么我们在这个例子中使用 WoT Pi 的非安全版本。如果你为你的 Pi 有一个非自签名的证书,你可以使用安全的 URL:wss://PI_URL/properties/pir。

图 10.8. WebSocket 输入节点配置和新的 WebSocket 客户端配置

接下来,你将创建一个函数节点(步骤 2)。这些节点包含一些 JavaScript 代码,可以根据某些条件将一个节点传递给另一个节点的消息进行转换。在步骤 2 中,你使用函数节点来准备相应的 POST 请求,根据通过 WebSocket 发送的 PIR 的值来打开或关闭 LED。这个函数节点的代码如下所示。

列表 10.7. 准备 LED 消息函数节点

在第 3 步中,您创建了一个http request函数节点,它通过向raspberrypi.local:8484/actions/ledState发送 POST 请求在 Pi 上创建一个动作。这个动作的有效负载作为由前一个节点准备好的内容在msg.payload中可用;见列表 10.7。

您应该能够测试工作流程的这一部分。点击部署,观察当改变 PIR 状态时会发生什么。您刚刚使用外部混合工具在 Pi 上连接了一个传感器(PIR)和一个执行器(LED)!

我们不要止步于此,而是构建工作流程的第二部分。第 4 步的函数节点是一个条件节点:如果 PIR 传感器的presence值为true,它将消息传递给下一个节点;否则,它中断这个流程分支。相应的代码很简单,技巧在于当一个函数节点返回null时,其余的流程停止,如下所示:

return msg.presence ? msg: null;

如果presence值是true,您将进入第 5 步,在那里您使用支持 WoT 的摄像头拍摄快照。如前所述,如果您没有提供 HTTP API 的摄像头,您可以简单地删除第 5 步和第 6 步的节点。如果您有摄像头,创建一个http request函数节点。使用 GET 方法调用摄像头的快照 URL(例如,在 Foscam 的情况下,http://[IP]/snapshot.cgi?user=USER&pwd=PWD),并选择二进制缓冲区作为返回类型。在第 6 步中,创建一个准备推文的函数节点:

msg.media = msg.payload;
msg.payload = 'Intruder Detected!';
return msg;

您必须复制msg.media中的图片,因为这是twitter节点期望图片所在的位置。第 7 步是您放置“点睛之笔”的地方:您通过 Twitter 发送入侵警报。为此,使用一个twitter输出节点,双击它以配置您的 Twitter 账户,选择添加新的 Twitter 凭据,它使用 OAuth(见第九章)为您的账户获取 Twitter API 令牌。

就这样!您现在可以测试您的入侵警报工作流程。请注意,关闭浏览器不会中断工作流程。只要 Node-RED 服务器运行,您的流程就会运行,等待下一个入侵者。

技术角落——我想要更多的节点!

Node-RED 的功能远不止我们在这里实现的。尝试使用您的 Pi 和所有可用的节点创建其他混合应用;例如,连接到 MQTT 客户端的MQTT节点或分析输入字符串是正面还是负面的sentiment节点。还有来自社区的数百个节点,从数据库集成如node-red-node-redis到物联网设备集成如node-red-node-arduino或物联网云集成如node-red-contrib-evrythng(由我维护)。所有这些节点都在网上可用.^([a]) 您可以通过 npm 安装它们,就像安装任何 Node.js 模块一样。您可能还想创建自己的节点;Node-RED 也允许您轻松地做到这一点.^([b])

^a

flows.nodered.org/

^b

nodered.org/docs/creating-nodes/

10.3. 使用向导进行物理混合应用:IFTTT

如 Node-RED 之类的视觉混合编辑器极大地缩短了构建物联网原型所需的时间。它们还显著简化了编程过程,但你仍然需要编程你的混合应用。我们接下来要探讨的下一个混合应用技术进一步将抽象推向简化混合应用创建的方向。基于向导的混合应用工具由一个用户界面组成,该界面引导你通过一系列步骤创建一个定制的工作流程。如果你曾经使用过你的邮件客户端的过滤器编辑器,你必须知道向导界面是什么样的:一系列步骤来创建一个规则。

有许多基于向导的混合应用工具可用,其中 Zapier^([6])和 IFTTT^([7])是最受欢迎的。这两个工具都与大量网络服务集成,从 Google Drive 到 Instagram 或 Facebook。IFTTT 代表“如果这个,那么那个”,在这里特别相关,因为它致力于整合许多物联网设备和平台,所以让我们更详细地看看这个工具。

zapier.com

ifttt.com

基本上,这个工具让你能够创建一个if (conditions) then (actions)语句,而无需编写任何代码。conditionsactions必须从从 Twitter 到 Google Drive 等预包装的网页集成列表中选择,以及如 Nest 恒温器、Philips Hue 照明系统、SmartThings 智能家居设备和 Misfit 可穿戴设备等物联网集成。为了创建这些if-then语句,IFTTT 会带你通过一个七步向导过程。一旦过程完成,新创建的工作流程将在云端的 IFTTT 服务器上运行,直到你想要它停止。

IFTTT 是一个简单而强大的系统,但它不允许公众以任何方式在提供的渠道之外进行任何集成。然而,它确实提供了制造商渠道,^([8]) 这允许你与任意的 REST API 进行集成。

ifttt.com/maker

10.3.1. 将入侵警报推文推送到谷歌电子表格

让我们通过 IFTTT 进行实验,创建我们智能入侵检测系统的简单集成。这是我们想要创建的第一个工作流程:

  • 如果发布了一条新的“入侵检测到”推文...

  • 然后在谷歌电子表格文档中记录一条条目。

首先,你应该创建一个免费的 IFTTT 账户^([9]) 并登录到你的账户。在 IFTTT 的世界里,工作流程被称为食谱。首先创建一个新的食谱,如图 10.9 所示。

ifttt.com/join

图 10.9. IFTTT 工作流程被称为配方。它们由两部分组成:一个条件(this)和一个反应(that),当条件满足时触发。

图 10.09

通过点击“this”链接,你设置条件。在 IFTTT 中,条件和反应都被称为“通道”。通道基本上是我们之前提到的预包装集成。选择 Twitter 通道,然后选择特定用户的推文触发器,当被提示输入 Twitter 用户时,提供你想要使用的 Twitter 账户的昵称——在这个例子中是@wotbook。这就是你的条件设置。基本上,你到目前为止创建的是一个if (new Tweet by @TwitterAccount)

接下来,你需要创建反应。点击“that”链接。同样的情况:选择一个用于反应的通道。我们选择了 Google Drive 通道和添加行到电子表格选项。这会提示你连接你的 Google 账户,然后设置电子表格和你要写入电子表格的内容。这一步如图 10.10 所示。

图 10.10. 设置电子表格。IFTTT 允许你通过点击 Erlenmeyer 烧瓶图标来添加变量。在这里,我们使用诸如 Twitter 用户名(UserName)、推文内容(Text)和推文的创建时间戳(CreatedAt)等变量。所有这些字段都将出现在位于 IFTTT/Twitter 文件夹中的 Google 电子表格中创建的新行中。

图 10.10

最后,在向导的第 7 步,你准备好通过选择创建配方来创建你的工作流程,如图 10.11 所示。这将工作流程保存到你的账户,并在云中为你持续运行。

图 10.11. 第 7 步是工作流程创建过程的最后一步。它总结了您刚刚创建的工作流程:如果推文被推送,则向 Google Drive 电子表格添加一行。

图 10.11

要测试它,运行第 10.2 节的 Node-RED 代码并触发 PIR 传感器,或者通过你选择的账户发推文来模拟这一过程。如果一切按计划进行,你应该在你的 Google 电子表格文档的 IFTTT/Twitter 文件夹中看到一个新的条目,如图 10.12 所示。

图 10.12. 当你的 Pi 上的 PIR 传感器检测到入侵者时,它通过 Node-RED 发布推文。IFTTT 检测到这一点,并在你的 Google 电子表格中添加相应的条目。在这里,你可以看到三个入侵检测事件。

图 10.12

10.3.2. 通过制造商通道向一个“物”发送请求

我们刚刚创建的混合应用相当令人印象深刻,但它只影响虚拟世界,即电子表格。如果它还能影响物理世界,比如改变 WoT Pi 上执行器的状态呢?这不会很强大吗?实际上,你可以通过 IFTTT 制造商通道做到这一点。这个对 WoT 友好的通道允许你在工作流程的“then”部分通过 HTTP 发送 REST 请求,如下所示:

  • 如果—— 新的“入侵检测”推文被发布

  • 然后—— 打开您的树莓派的 LED 灯。

首先,创建一个新的食谱。工作流程的“如果”部分与之前完全相同:选择 Twitter 通道,然后选择“由特定用户发送的新推文”。对于“然后”部分,使用 Maker 通道,并按照图 10.13 中的说明进行配置。

图 10.13. 通过 HTTP 向您的 WoT Pi 发送 REST 请求。通过 POST 相应的 JSON 有效负载,在您的 Pi 上创建一个新的ledState动作。

图片

注意,为了创建这个食谱,您的树莓派需要在网络上安全可访问,因为 IFTTT 在网络上运行,幸运的是(或者希望如此!)它无法访问您的本地网络。有关如何做到这一点的更多信息,请参阅第九章。如果您没有将树莓派放在网络上或者不想这样做,您可以使用第二章中使用的连接树莓派,并在检测到入侵者时在其屏幕上显示警报。您可以通过向以下 URL devices.webofthings.io/pi/actuators/display/content 发送 POST 请求来实现,发送包含要显示的消息的 JSON 对象;例如,{"value":"Intruder Detected!"}。最后,通过选择创建食谱来创建您的流程。现在,每当发送一条推文时,您将看到您的树莓派的 LED 灯亮起——或者会在树莓派上显示一条消息。

10.3.3. 将入侵警报推文推送到 Google 表格

让我们稍微退后一步,回顾一下您刚刚构建的内容。当您的树莓派通过 PIR 传感器检测到入侵者时,Node-RED 会通过您的网络摄像头发送一张图片。IFTTT 监听这样的推文,并在 Google 表格中添加相应的条目。第二条规则更进一步,使用 REST 在网络上打开您的树莓派的 LED 灯。我们能够在几分钟内构建如此复杂的流程,这要归功于 Web API 的力量和普及:我们流程中的所有参与者都可以通过 Web 协议相互通信。Node-RED 使用树莓派的 WoT WebSocket API 和 Twitter 的 HTTP API。IFTTT 也使用 Twitter 的 HTTP API 和 Google Drive 的 HTTP API。最后,IFTTT 使用您的树莓派的 RESTful HTTP API 通过动作来激活它。这就是物联网的真正美和力量!

极客角落——更多混合应用!

有许多方法可以调整您刚刚创建的工作流程。例如,您可以使用 Twitter 的条件“您的新推文带有特定#标签”,这样只有带有特定#标签的推文才会触发警报。为什么止步于此?IFTTT 为您打开了更多创建有趣工作流程的大门!例如,我们创建了一个新的食谱,每当检测到入侵时都会给我们发送短信。如果您想创建更多混合应用,请浏览ifttt.com/channels,寻找有启发性的通道。需要灵感吗?尝试使用您的 Pi 的其他传感器,如温度或湿度传感器,在文档中记录值,创建可视化,或发送天气警报。发挥创意;云是唯一的限制!请注意,您不必使用 Twitter 来集成 Pi 的数据到 IFTTT。您还可以使用 IFTTT 制造商通道的反向通信,通过 REST 与 IFTTT 通信并触发工作流程。最后,还有我们在这里查看的混合工具之外的世界,其中一些真的值得一试。例如,Freeboard^([a])允许您创建将来自各种设备的混合数据可视仪表板,并且与物联网很好地集成。一些物联网平台也方便地内置了混合工具。例如,EVRYTHNG 提供了一个名为 Reactor 的工具,它允许您编写在云中运行的 Node.js 脚本,并且每次连接的设备状态发生变化时都可以触发这些脚本.^([b])

^a

freeboard.io/

^b

developers.evrythng.com/docs/reactor

那又如何?

本节的关键要点是,通过将事物及其服务更靠近网络,我们可以无缝地将它们集成到庞大的网络工具生态系统中。如果我们不是基于网络标准而是基于封闭协议构建我们的 Pi API,那么创建这种集成将需要更多的时间。我们使用的两个工具提供了不同级别的集成。Node-RED 是开放的,并且默认支持许多标准网络协议,但它主要是一个面向开发者和制造商的原型设计工具。另一方面,IFTTT 采取了一种更加受控的方法,针对技术能力较低的用户,但并不提供相同级别的灵活性和可配置性。因此,设备和服务需要经过 IFTTT 团队的选择性筛选过程,才能获得自己的通道。

尽管如此,这两个平台共同的基础是网络。使用物联网方法极大地减少了原型设计时间,并为物联网的新想法和集成机会打开了大门。设备不再被锁定在自己的封闭世界中,而是可以开放进行集成。

10.4. 超越本书

混合应用对于物联网网络非常重要,因为它们展示了物联网方法提供的简单性。让我们来谈谈物联网复合应用的未来看起来像什么。

10.4.1. 从简单的混合应用到大数据混合应用

未来将是大数据混合应用的时代。非常巨大的数据!连接到互联网的事物将产生前所未有的数据量。想想看:一百万个连接设备每秒向物联网云发送一个传感器读数意味着每天有 8640 亿条消息(是的,十亿!)这大约是全球当天发布的所有推文的 170 倍^([10])!

¹⁰

参见“一分钟的互联网发生了什么”:www.intel.co.uk/content/www/uk/en/communications/internet-minute-infographic.html

将此与预测到 2020 年将有 200 亿到 500 亿个连接设备(见第一章)进行比较,我们面前有一个重大的数据挑战。然而,数据是新的黄金,所以我们不会愚蠢地不利用它!尽管目前对物联网的关注主要集中在原始连接上,但我们预计利用这些数据将是下一个大潮。物联网数据有潜力使我们的世界变得更智能、更有意识,但它将产生的大量数据将压垮我们过去使用的所有传统数据工具。

许多新的大数据技术和方法将有所帮助。例如,将事件视为它们发生时,并在事件流上运行查询而不是在数据库上,应该有助于我们处理更多的物联网数据。像 Spark^([11]), Storm^([12]), Flink,^([13]) 或 Samza^([14]) 这样的大规模和实时流数据处理系统如今如雨后春笋般涌现,帮助我们应对这些挑战。实时分析技术将帮助我们创建能够在异常事件发生时立即触发警报的应用程序。

¹¹

spark.apache.org/

¹²

storm.apache.org/

¹³

flink.apache.org/

¹⁴

samza.apache.org/

机器学习方法和工具(人工智能的现代术语)将帮助我们创建更智能的应用程序,这些应用程序可以分析并从大量非结构化数据中学习,并允许应用程序——从物流到公共交通,再到城市管理——实时调整其行为并做出最优决策。一些设备已经使用这些技术。例如,Nest^([15]) 恒温器使用数据模式来预测你可能在特定时间特定房间中可能喜欢的温度。

¹⁵

nest.com/

大多数此类数据集成和处理可能发生在云端,因此将设备连接到网络以有效地传输这些数据是有意义的。话虽如此,设备上也需要一定程度的过滤和智能。我们还有很多工具和技术需要构建和改进,以便最有效地使用物联网将产生的大量数据!

10.4.2. 更好的用户体验

数据不仅会使设备更智能,还会改善我们与物理世界的互动方式。目前,许多消费产品,尤其是电子产品,使用起来仍然相当繁琐。如果日常产品要变得更智能,我们与数字世界的互动方式也必须改变。我们与智能产品的互动方式尤其需要更加直观和不太干扰。因为感知技术将持续发展,与数字世界互动的新方式将变得可能——远远超出键盘、触摸屏或鼠标。

物联网的用户体验和交互设计还处于初级阶段,但其革命我们与机器互动方式——以及很快的物理世界——的潜力绝对是巨大的。

20 年前听起来奇怪或未来派的仪式,对我们中的一些人来说已经成为日常惯例。从用钱包刷公交回家,到用手机(甚至 RFID 身体植入物)解锁门,或者当你离家 20 分钟时自动打开暖气——这只是我们正在变得智能的世界的一个开始。然而,记住,更好的用户体验最终也意味着更安全,通过这本书成为 WoT 先锋的你不应忘记将安全性放在比纯可用性更高的位置。不要在现实世界中胡来!

10.5. 摘要

  • 由于 Web Thing Model 的语义层与 Web API 的结合,可以使用简单的 JavaScript 代码创建一个通用的远程控制网页应用,该代码检索模型并爬取资源。

  • 物理混合是将虚拟服务与设备提供的服务相结合的复合网页应用。

  • 混合工具主要有两大类:盒子和电线,以及巫师。

  • 盒子和电线编辑器允许你直观地连接虚拟世界和物理世界中的不同参与者。物理混合编辑器的一个好例子是 Node-RED。

  • 巫师编辑器引导你通过多个步骤来创建组合应用程序。IFTTT 是基于巫师原则的物理混合编辑器;它允许你将各种物理设备连接到各种虚拟服务,以创建简单的触发和反应。

你已经通过了 WoT 架构的所有层级。恭喜!现在你可以开始构建你的物联网络了!为了让你在未来几年里保持灵感,我们将留下著名人物马克·魏斯勒的一句话,他是物联网的奠基人之一:

最深刻的技术是那些消失的技术。它们融入日常生活的织锦中,直到它们与日常生活无法区分.

马克·魏斯勒,在《21 世纪的计算机》(1991 年 9 月)一文中

附录 A. Arduino, BeagleBone, Intel Edison, 和物联网

如前所述,尽管我们在这本书中一直以 Raspberry Pi 作为参考设备,但您在这本书中学到的所有概念、架构和模式绝对不局限于 Pi:它们适用于任何嵌入式设备或任何其他事物。如果您决定选择 Pi 以外的设备,这个附录将帮助您设置它并构建必要的代码,以将您选择的设备集成到物联网中。我们不会提供每个平台的详细信息,但会提供三个流行嵌入式系统的所有必要指针:BeagleBone,^([1]) Intel Edison,^([2]) 和 Arduino.^([3]) 对于每一个,我们将查看以下这些点:

¹

BeagleBoard 官网

²

Intel Edison 官网

³

Arduino 官网

  • 安装软件

  • 安装 Node.js

  • GPIO 布局和库

A.1. 将 BeagleBone 集成到物联网中

让我们从 Pi 最接近的亲戚开始:BeagleBone。这个设备由 BeagleBoard.org 制造,这是一家美国非营利性组织,提供嵌入式计算中的开源软件和硬件的教育和推广。该基金会与德州仪器等合作伙伴合作,发布易于使用的嵌入式设备,称为 BeagleBones。尽管在这个附录中我们专注于他们的最佳卖家 BeagleBone Black (BBB),但本节的大部分内容也适用于所有 BeagleBone 型号。

A.1.1. 认识 BeagleBone Black

BeagleBone Black 是 Linux 驱动的嵌入式设备家族的一员,其功能和尺寸与 Raspberry Pi B+类似:

  • ARM Cortex-A8

  • 512 MB 的 RAM

  • 板载 4 GB 闪存存储

  • 板载以太网适配器

  • 一个 3D 图形加速器

BeagleBone 因其坚固性和稳定性而闻名,这使得它在生产应用中也是一个极佳的选择。此外,BeagleBone 提供 SD 卡和基于闪存的存储,这使得从原型到实际试验的迁移变得简单(最好不要依赖 SD 卡,因为它们的寿命有限,它们会移动,而且它们通常速度较慢,以及其他问题)。BBB 的价格大约为 50 美元,这比 Pi 略高。如果您想购买一个,您可以在本书的网站上找到推荐卖家的列表.^([4])

物联网之书

A.1.2. 准备 BeagleBone Black 用于本书

如我们之前所说,BBB 的硬件和软件与 Pi 类似,因此这里展示的大多数设置步骤都将非常直接。

Linux

BeagleBone Black 预装了 Debian Linux,因此您几乎不需要做什么就可以让它启动运行。如果您想尝试冒险,BB 还可以支持其他 Linux 发行版,如 Ubuntu.^([5])

elinux.org/BeagleBoardUbuntu

SSH

要通过 SSH 连接到您的 BBB,首先遵循在线入门指南,^([6]) 这将教您如何为 BBB 提供电以及如何访问默认的板载网络服务器。是的,BeagleBone 是一个非常友好的 WoT 设备!然后,使用以太网线将 BBB 连接到您的路由器,并按照维基百科中的步骤设置和使用 BBB 的 IP 地址,并通过 SSH 访问它.^([7])

beagleboard.org/getting-started

elinux.org/Beagleboard:Terminal_Shells

Node.js

在这里,BBB 与 WoT 的兼容性很好,因为 Node 是该设备的默认编程语言.^([8]) 如果您想升级 Node 的版本,可以按照 第 4.2.5 节 中列出的步骤进行,但请确保选择适合您的 BBB 处理器(ARMv7 架构)的 Node 版本。BeagleBone 还配备了 Cloud9 IDE,它允许您方便地在板上直接编辑 Node.js 程序.^([9])

beagleboard.org/support/bonescript

beagleboard.org/Support/bone101/

WoT 服务器代码

本书中的所有代码示例,包括完整的 WoT 服务器示例,都可以在 BBB 上直接使用,无需任何更改。就像 Pi 一样,BBB 支持 Git,因此您可以从 GitHub 上分叉本书的源代码(见 第七章)。

GPIOs

我们用来与 Pi 的 GPIO 交互的 onoff 库也与 BBB 兼容,因此您在代码中不需要做太多更改。唯一会不同的是 GPIO 的布局;因此,您需要更改代码中的 GPIO 编号。例如,对于 第四章 中的 blink.js 示例(列表 4.6),您需要将 led = new Gpio(4, 'out') 更改为使用有效的 GPIO 引脚,例如引脚 11:led = new Gpio(11, 'out')。这也意味着您必须将您的电路连接到 BBB 上的不同 GPIO、GND 或 PWR 引脚,因此请务必仔细研究您的设备布局.^([10])

¹⁰

更多关于 BBB 引脚布局的详细信息,请参阅此处:beagleboard.org/support/bone101/.

A.2. 将英特尔爱迪生集成到 WoT 中

与 Pi 和 BeagleBone——以及大多数嵌入式平台——不同,Intel Edison 不是由 ARM 处理器供电,而是由 Intel 处理器供电(惊讶,惊讶!)。它主要是一个 Linux Yocto 设备,但它还有一个运行 Viper OS 的第二个微控制器。Edison 的尺寸不比邮票大多少,大约是 Pi Zero 的一半大小,使其成为本书中介绍的最小设备。尽管如此,它集成了令人印象深刻的功能集:

  • 用于 Linux 的 500 MHz Intel Atom 双核 x86 CPU

  • 用于 RTOS 的 100 MHz Intel Quark

  • 1 GB 的 RAM

  • 4 GB 板载闪存存储

  • 板载 Wi-Fi a/b/g/n 模块

  • 板载蓝牙 4.0 模块

所有这些功能都有一个代价;Edison 及其迷你断路板大约需要 70 美元。如果你想要购买,请查看本书网站上的推荐卖家列表。

A.2.1. 为本书准备 Edison

因为 Edison 也是一个 Linux 设备,为 Web of Things 做准备与 Pi 或 BBB 并没有本质的不同。

Linux

Edison 预装了 Yocto Linux 版本,因此可以直接使用。

SSH

要通过 SSH 访问你的 Edison,你首先需要通过 USB 建立一个串行连接来配置板载 Wi-Fi。一旦建立了 Wi-Fi 连接,你就可以直接 SSH 到 Edison。这个过程在在线入门指南中有详细说明.^([11])

¹¹

software.intel.com/en-us/iot/library/edison-getting-started

Node.js

Edison 喜欢 Node.js,因为它预装了。就像 BeagleBone 一样,Edison 也有自己的 IDE,允许你编写在板上运行的 Node.js 代码。它被称为 Intel XDK IoT Edition,可以在 Mac OS、Linux 和 Windows 上安装.^([12])

¹²

software.intel.com/en-us/getting-started-with-the-intel-xdk-iot-edition

WoT 服务器代码

这本书中你编写的完整 WoT 服务器将在 Edison 上运行,除了与 GPIOs 交互的部分;请参见下一项。就像 Pi 一样,Edison 支持 Git,因此你可以从 GitHub 上 fork 本书的代码;请参见第七章。

GPIOs

不幸的是,本书中使用onoff库访问 GPIO 的代码在 Edison 上无法直接运行。但不用担心;Edison 板也自带了一个名为MRAA GPIO的 Node GPIO 访问抽象库。^([13]) 你应该能够将onoff替换为 MRAA,并让不同的传感器和执行器正常运行。不过,就像 BBB 一样,Edison 上的 GPIO 布局与 Pi 不同,所以你必须确保连接正确的引脚.^([14])

¹³

github.com/intel-iot-devkit/mraa

¹⁴

Intel Edison 扩展板的引脚布局:bit.ly/1Kjc7mj

A.3. 集成 Arduino 到 WoT

Arduino 是一个基于易于使用的硬件和软件的开源电子平台。它旨在为任何制作交互式项目的人提供服务,可能是最流行——也是最古老!——的硬件原型平台之一。Arduino 不仅仅是一个,而是有数十个 Arduino 设备,或者如 Arduino 世界中所称的板。价格从 80 美元到仅几美元不等,板从一直以来的最佳畅销品 Arduino Uno15 到美丽而简约的 LilyPad16 不等。

15

www.arduino.cc/en/Main/ArduinoBoardUno

16

www.arduino.cc/en/Main/ArduinoBoardLilyPadUSB

与 Pi、BeagleBoard 和 Edison 平台不同,Arduino 板属于 RTOS 设备家族,而不是 Linux。Arduino 板也比我们迄今为止看到的其他平台资源受限得多。结果是,您将无法在 Arduino 板上运行 Node.js(除非是某些特殊板;见下一节)。这也意味着本书中的代码示例无法在 Arduino 上运行,您必须使用基于 C/C++语言的 Arduino 编程语言重写它们。

好消息是,本书中描述的所有概念都可以轻松地移植到 Arduino 平台,因为您的设备 API 将非常相似,如果不是完全相同。如果这是您在物联网世界的第一次游泳,并且直接在 Arduino 上学习这些概念并实现它们将很困难,我们建议您首先获取一个 Pi。但一旦您掌握了其中的精髓,您将能够进一步探索嵌入式系统世界。为此,Arduino 平台是一个很好的起点,尤其是如果您正在寻找低功耗设备。

A.3.1. Linux、SSH、Node.js

如前所述,Arduino 板运行在 RTOS 环境中,其中 C 编程语言占主导地位。在 Arduino 板上无法安装 Linux、SSH 或 Node。不过,有一个例外:Arduino Yún 支持 Linux 和 Arduino RTOS 环境,尽管这需要超出本书范围的多个步骤,但在 Yún 上安装 Node 是可能的。17

17

blog.arduino.cc/2014/05/06/time-to-expand-your-yun-disk-space-and-install-node-js/

如果你不能通过 SSH 编程其他 Arduino 板,你将使用一个名为 Arduino Integrated Development Environment (Arduino IDE)的开发环境。这个 IDE 在你的电脑上运行,并允许你在机器上开发程序,然后再上传到 Arduino 板。如何做到这一点也超出了本书的范围,但你会在网上找到很多可以帮助你的资源。或者,你可能会想获取 Martin Evans 等人所著的Arduino in Action(Manning Publications,2013)的副本.^([18])

¹⁸

Arduino in Action, Manning Publications: www.manning.com/books/arduino-in-action?a_aid=wot

WoT 服务器代码

尽管你无法重用本书中提供的代码示例,但一些优秀的 Arduino 库可以帮助你实现符合 WoT 的 Arduino。

尤其是使用Webduino^([19])库,你可以在 Arduino 板上实现 REST API。ArdunioWebsocketServer^([20])库可以用来实现设备 WoT API 的 WebSocket 部分。这应该让你能够理解从第四章到第八章中提出的所有概念。第九章有些复杂,因为 Arduino 平台对 TLS 的支持不是很好。这是因为底层加密算法资源消耗大,导致加密/解密消息需要显著的时间.^([21])

¹⁹

github.com/sirleech/Webduino

²⁰

github.com/ejeklint/ArduinoWebsocketServer

²¹

在这里可以找到关于在非常资源受限的设备上安全性的实际考虑的精彩阅读:tools.ietf.org/html/draft-aks-lwig-crypto-sensors-00

网关模式

将 Arduino 板集成到物联网中还有另一种方法:使用我们在第七章中探讨的网关模式。例如,你可以在 Arduino 板上使用 MQTT 或 CoAP,然后使用更强大的嵌入式设备,如 Pi,作为网关。如果你想这样做,有一个优秀的MQTT^([22)) Arduino 库,还有一个用于 CoAP^([23))的库。

²²

github.com/knolleary/pubsubclient

²³

github.com/1248/microcoap

GPIOs

Arduino 板旨在用于实验大量传感器和执行器,因此它们提供了许多 GPIO。好消息是 GPIO 直接标记在板上,所以不需要解释布局的图片。我们在第四章 chapter 4 中安装的所有传感器和执行器也可以与 Arduino 板一起使用,但再次强调,代码将会有很大不同。尽管描述 Arduino 代码超出了本书的范围,但我们为本书中使用的每个传感器和执行器提供了良好的链接:

A.4. 将其他嵌入式系统集成到物联网

实际上,任何嵌入式设备都可以通过遵循物联网架构集成到网络中。这只需要一点搜索或者选择你设备的优秀书籍!如果你还想尝试其他平台,Linux 设备的好起点是嵌入式 Linux Wiki^([27)),而对于 RTOS 设备,Element14 社区^([28))是一个不错的选择。如果你在其他平台上移植这些概念,请通过我们的 GitHub 联系我们,因为我们很乐意了解并从我们的书籍网站上链接到你的项目。

²⁷

elinux.org/Main_Page

²⁸

www.element14.com/community


  1. (13) ↩︎

  2. [c] ↩︎

  3. [d] ↩︎

  4. [7] ↩︎

  5. [8] ↩︎

posted @ 2025-11-20 09:29  绝不原创的飞龙  阅读(16)  评论(0)    收藏  举报