[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战

前言

  • 本文是“从物联到万联,Node.js与树莓派万维物联网构建实战”一书的读书笔记,该书翻译自“Building the Web of Things with examples in Node.js and Raspberry Pi (by Guinard &Trifa)”。

  • 书中相关源码可以通过@github访问,读者可以通过@webofthings来访问作者搭建好的web物联网应用。

  • 本文重点关注书中相关实例的实现及其依赖的一些关键概念,对一些基础知识和简单代码逻辑不做过多解释。主要总结实际操作碰到的问题和解决问题的思路。


第一个物联网聚合应用

1) 无法加载jquery.min.js

出现错误:”Failed to load resource: net::ERR_CONNECTION_RESET jquery.min.js:1“

考虑国内无法访问ajax.googleapis.com,我们可以将jquery下载到本地再修改src指向本地路径,也可以搜索网络上可用的jquery。以下我们替换这一行代码为:

重新打开html文件即可,注意进入浏览器可以在设置里面打开“开发人员模式”,切换到console选项卡查看打印的log。以下是该物联网聚合应用运行成功后的截图:

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第1张图片


2)源码分析

//#A First, get the temperature in the user location from Yahoo

//#B Then get the temperature from the WoT Pi in London

//#C Prepare the text to publish and use it to update the content of the LCD screen

//#D POST the message to the LCD actuator

//#E Set a timer that will call the takePicture() function in N seconds (after the LCD content has been updated)

//#F Generates the text to display with the user name, location and Pi temperature

//#G Retrieve the current image from the Webcam in our office

//#H Update the HTML img tag with the image URL

以上英文还是比较好翻译的,如果不太确定,可以借助有道词典翻译下:

//#A首先,从雅虎获取用户位置的温度

//#B然后从伦敦的WoT Pi得到温度

//#C准备好要发布的文本并使用它来更新LCD屏幕的内容

//#D将信息发送到LCD驱动器

//#设置一个计时器,在N秒内调用takePicture()函数(LCD内容更新后)

//#F生成显示用户名、位置和Pi温度的文本

//#G从我们办公室的摄像头中获取当前的图像

//#H用图像URL更新HTML标签

这样我们就大概理清了这个web程序“ex-5-mashup.html”的大致逻辑就是:分别从雅虎和WOT Pi得到温度数据,经过处理之后把要发送的数据通过Web API发送给树莓派,等待一段时间,通过摄像头查看LCD数据的响应结果。


开始异步编程

关于IO模型的相关知识,可以参考“UNIX高级编程”的相关议题。

1)使用request库发起http请求

npm init

npm install request --save

node *.js

代码清单 3.7

var request = require('request');

request('http://webofthings.org', function (error, response, body) { //#A

if (!error && response.statusCode === 200) {

console.log(body); //#B

}

});

//#A This is an anonymous callback that will be invoked when the request library did fetch the webofthings homepage from the Web

//#B This will display the HTML code of the page

以上看出,request库的第二个参数是一个匿名回调函数,当request库从第一个参数指定的URL获取到web网络数据后,该匿名函数被调用;这里的function有三个参数,当这个异步操作过程出现任何错误时,会将错误内容返回给error,response返回响应头,body返回响应正文。匿名函数体我们直接通过console.log将body内容打印处理。


2018.09.22更新

第7章 实现Web智能产品

本章涵盖的内容如下:

  • 探索Web智能产品的三种可能模式

  • 通过Web协议访问传感器和执行器

  • 使用Node.js和Express 在树莓派上面构建REST和WebSocket API

  • 构建COPA设备并连接到Web

  • 在树莓派上面使用MQTT来连接EVERYTHNG API

前言

本章围绕物联网设备接入Web的三种集成模式展开,它们分别是:

  • 直接连接模式——在设备上面实现REST

  • 网关集成模式——COAP

  • 云端集成模式——EVRYTHNG的MQTT

7.1 连接设备到Web

设计智能web产品的过程:

  1. 集成策略——选择一个模型将智能产品集成到Web。本章将讲解这些模型。

  2. 资源设计——确定智能产品的功能或服务,然后组织它们的层次结构。

  3. 表述设计——决定每个资源服务采用哪一种表述。

  4. 接口设计——决定每个服务可能接收哪些命令,以及处理哪些异常。

  5. 资源链接设计——决定不同资源之间如何相互链接。

7.2 直接集成模式——在设备上实现REST

  • 要想实现一套成熟的REST API,有许多的Node.js框架可以选择。这里列举一些最流行的Node.js的Web/REST框架:<1>

  • EXPRESS: A NODE.JS WEB FRAMEWORK

    虽然Express 可以在树莓派和大部分linux设备上面平稳运行,但是我们也要明白,Express并不是实现IOT设备Web API的最轻量方式。<2>

7.2.2 资源设计

我们要将硬件资源映射为REST接口,那么一个最基本的问题就是,如何来描述这些资源呢?答案就是通过JSON来保存我们的资源树。

步骤1:创建资源模型

新建resources/resources.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,代码如下:

var resources = require('./resources.json');

module.exports = resources;

这个文件加载resources.json文件,然后exports将object转变为node模块,这样就可以在应用里面使用它了。

步骤2:创建Express路由

在Express和其他许多Web框架中,资源的URL通过路由来定义

var express = require('express'),

router = express.Router(),

resources = require('./../resources/model');

 

router.route('/').get(function (req, res, next) {

req.result = resources.pi.actuators;

next();

});

....

module.exports = router;

创建完路由后,export router,这样其他模块就可以require它了。

步骤3:创建Express应用

通过Express framwork封装一个HTTP server,在servers/http.js文件实现,在这个文件中加载在我们之前创建的路由。

// Final version

var express = require('express'),

actuatorsRoutes = require('./../routes/actuators'),

sensorRoutes = require('./../routes/sensors'),

thingsRoutes = require('./../routes/things'),

resources = require('./../resources/model'),

converter = require('./../middleware/converter'),

cors = require('cors'),

bodyParser = require('body-parser');

...

var app = express();

...

//绑定路由到Express应用

app.use('/pi/actuators', actuatorsRoutes);

app.use('/pi/sensors', sensorRoutes);

app.use('/things', thingsRoutes);

 

app.get('/pi', function (req, res) {

res.send('This is the WoT-Pi!')

});

...

module.exports = app;

在可以测试功能之前,还需要wot-server.js文件,这是WOT服务器的入口,负责以正确的配置启动服务。

// Final version

var httpServer = require('./servers/http'),

wsServer = require('./servers/websockets'),

resources = require('./resources/model');

...

// HTTP Server

var server = httpServer.listen(resources.pi.port, function () {

console.log('HTTP server started...');

 

// Websockets server

wsServer.listen(server);

 

console.info('Your WoT Pi is up and running on port %s', resources.pi.port);

});

步骤4:将传感器绑定到服务器上

来到这里就比较有趣了,因为涉及到和硬件的交互,直接看代码:

var model = resources.pi.sensors.pir;

var resources = require('./../../resources/model');

var model = resources.pi.sensors.pir;

...

function connectHardware() { //#B

var Gpio = require('onoff').Gpio;

sensor = new Gpio(model.gpio, 'in', 'both'); //#C

sensor.watch(function (err, value) { //#D

if (err) exit(err);

model.value = !!value;

showValue();

});

console.info('Hardware %s sensor started!', pluginName);

};

model.gpio获取硬件的GPIO配置信息,这个信息保存在我们前面提到的resources.json文件中。最终通过watch来监听GPIO事件,触发事件时回调函数会被执行。

有了插件后,在wot-server.js当然要把它用起来啦:

// Internal Plugins

var ledsPlugin = require('./plugins/internal/ledsPlugin'), //#A

pirPlugin = require('./plugins/internal/pirPlugin'), //#A

dhtPlugin = require('./plugins/internal/DHT22SensorPlugin'); //#A

 

// Internal Plugins for sensors/actuators connected to the PI GPIOs

// If you test this with real sensors do not forget to set simulate to 'false'

pirPlugin.start({'simulate': true, 'frequency': 2000}); //#B

ledsPlugin.start({'simulate': true, 'frequency': 10000}); //#B

dhtPlugin.start({'simulate': true, 'frequency': 10000}); //#B

在树莓派上用真实硬件测试

在树莓派上面运行我们前面创建好的项目

  • npm install –save 添加依赖

  • 修改启动服务的参数为{‘simulate’:false}

  • node wot-server.js

7.2.3 表述设计

实现一个表述转换中间件

/middleware/converter.js : 实现html、json、msgpack 表述转换

7.2.4 接口设计

添加一个BODY PARSER

body-parser模块:用来接收客户端的JSON数据,在http.js中require它,在中间链开头添加app.use(bodyParser.json()),因为必须在其他中间件处理之前先处理HTTP消息的body部分。

支持其他的HTTP动作

为routes/actuators.js中的LED添加PUT支持

router.route('/leds/:id').get(function (req, res, next) { //#A

req.result = resources.pi.actuators.leds[req.params.id];

next();

}).put(function(req, res, next) { //#B

var selectedLed = resources.pi.actuators.leds[req.params.id];

selectedLed.value = req.body.value; //#C

req.result = selectedLed;

next();

});

将执行器绑定到服务器

/pligins/internal/ledsPlugin.js : 用于执行用户修改更新硬件状态。

7.2.5 通过WebSocket实现pub/sub接口

启动WebSocket服务用于监听HTTP服务器上面升级为WebSocket协议的请求

// HTTP Server

var server = httpServer.listen(resources.pi.port, function () {

console.log('HTTP server started...');

 

// Websockets server

wsServer.listen(server);

 

console.info('Your WoT Pi is up and running on port %s', resources.pi.port);

});

7.2.6 小结——直接集成模式

这小节我们直接在真实的设备上面构建WOT服务器,并且实现一些高级的功能如内容协商和WebSocket推送功能,也有大部分设备无法运行Node原生环境,后面会介绍另一种模式来支持非HTTP/WebSocket设备,即网关集成模式。

7.3 网关集成模式——CoAP

7.3.1 运行一个CoAP服务器

npm install coap

var coap = require('coap')

coap.createServer

github 上面的一个tiny coap server <3>

7.3.2 通过网关代理CoAP

我们知道,CoAP基于REST,但是由于它不是使用HTTP而是使用UDP代替TCP的,因此需要一个网关将CoAP消息转换成HTTP,它是理想的设备到设备低功耗无线电通信方式。我们需要为CoAP设备创建WOT网关,才能通过浏览器运行javascript和CoAP设备通信。

COAP技术门户网站<4>

为CoAP设备构建通用的HTTP代理<5>

7.3.3 小结——网关集成模式

对于一些设备来说,直接支持HTTP或者WebSocket是不太现实的,需要依靠更强大的网关来连接到万维物联网。除了Express外,还有其他开源的可选网关,比如OpenHab或The Thing System。

OpenHab<5>

The Thing System<6>


7.4 云端集成模式——EVRYTHNG的MQTT

云服务不仅能将智能产品的API通过HTTP和WebSocket暴露出来,还能提供很多额外的特性,如无限制的数据存储、用户管理界面、数据可视化、流处理、支持多种并发请求等。

云服务提供商

Xively<1>

ThingWorx<2>

ThingSpeak<3>

Carriots<4>

thethings.io<5>

7.4.1设置EVRYTHNG账号

步骤1——创建项目和应用程序

步骤2——创建第一个产品和智能物件

步骤3——创建设备API KEY

步骤4——改变属性

7.4.2 创建MQTT客户端应用程序

代码路径:chapter7/part3-clound

cp config-sample.json config.json

change "thngId":"U5ngAdV6HfQQmpwaw2ywcfWt"

"thngApiKey":"6iVK8UUzwOlvdtQYfqwLjYmAVWkEbhhusbGEFIPwFN2fzfzDjASkpBbUHF9q1iG6ulNsqkdM7Wn4ENUN"

npm install

node simple-plug.js

成功运行MQTT客户端打印如下,该客户端与云引擎建立一个持久的连接,这个连接基于MQTT,每隔5秒更新一次属性值。

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第2张图片

同时登陆EVEYTHNG平台,以下Thngs相关的Properties会同步刷新。

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第3张图片

思考:MQTT推送数据改变云引擎模型中的数据,那么这个云引擎是如何告知浏览器javascript的呢?websocket吗?回想前面我们学习的,WebSocket:用来推送消息的实质的Web协议。

7.4.3 使用action来控制智能插座

上面成功运行MQTT客户端打印其实在一个回调函数中打印的,这个客户端的回调函数将会在EVRYTHNG云端属性发生变化时被调用。代码逻辑如下:

client.on('message', function (topic, message) { // #G

console.log(message.toString());

});

我们可以修改这里的逻辑,在云端属性变化的时候去触发动作,但这并不是最好的选择,因为这样必须分清那些属性是设备设置的,那些属性是应用程序里面改变的。这里我们使用action来发送更复杂的命令,可以带多个输入的参数。

首先我们要创建一个叫做_setStatus的action类型,猜测应该是管理平台的如下:

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第4张图片

同样我们也可以使用curl来创建

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"]}'

 

果然,以上命令执行成功后,多出如下一项,正是我们在body里面填的信息:

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第5张图片

7.4.4 创建一个简单的Web控制应用

使用云服务创建简单的Web应用

需求:创建一个简单的web应用,使用户能够通过云端智能物件与设备交互。

  • 使用WebSocket订阅它的属性,并在设备属性改变的时候及时显示出来;

  • 这个应用也能通过云平台支持的REST API将命令推送到设备;

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第6张图片

[读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战_第7张图片

使用二维码来标识智能产品

  • GitHub pages 可以用来部署自己的网页或者搭建自己的网站

这里我们使用EVRYTHNG为我们提供的二维码访问地址:

https://webofthings.github.io/wot-book/plug.html?key=(your API key)&thngId=(your Thng ID)

这是我的设备二维码:

https://webofthings.github.io/wot-book/plug.html?key=6iVK8UUzwOlvdtQYfqwLjYmAVWkEbhhusbGEFIPwFN2fzfzDjASkpBbUHF9q1iG6ulNsqkdM7Wn4ENUN&thngId=U5ngAdV6HfQQmpwaw2ywcfWt

注意:前面章节是自己手敲curl命令来实现的,这些命令统一包含在part3-cloud下面的setup.sh脚本中

7.4.5 小结——云端集成模式

实时WOT产品最佳的方式是提供直接访问和云端访问两种模式。

7.5 总结

本章讲解了物联网设备连接到万维物联网的三种模式,它们分别是:直连模式,网关模式和云服务模式。并详细介绍这三种模式的应用场景和使用的技术:Express 路由,COAP服务,MQTT客户端,Websocket订阅,action post等。在第八章我们将探索资源-链接设计步骤,实现资源的可探索与可发现。


第八章 发现层:描述和发现Web智能产品

8.1 可发现性问题

8.2 发现智能设备

8.2.1 网络发现(Network discovery)

  • 常见的网络发现协议有:组播DNS(mDNS)、DLNA以及UPnP等。例如,大部分网络电视和多媒体播放器能够使用DLNA来发现局域网中的网络连接存储设备(NAS),并且从中读取媒体文件。

8.2.2 Web上的资源发现

爬取Web智能产品的API

HATEOAS和Web链接

8.3 描述Web智能产品

8.3.1 Web智能产品模型简介

8.3.2 元数据

8.3.3 属性

8.3.4 行为

8.3.5 智能产品

8.3.6 在树莓派上面实现Web智能产品模型

8.3.7 小结——Web智能产品模型

你可能感兴趣的:([读书笔记]从物联到万联——Node.js与树莓派万维物联网构建实战)