上一篇,纯粹玩 ESP8266,写入了 init.lua 能收发 UDP。这次拿 BBB 开刀,用 BBB host 一个 web server ,用于与用户交互,数据来自 ESP8266 的 UDP 交互结果。本来,ESP8266 能直接用 TCP,但我希望广播 UDP 来做自动发现,那服务端和设备端统一全部用 UDP 交互吧,服务端再通过 HTTP 与客户端交互。

以下过程,与 Linux 上面搭 web 没有区别。我选择用 node.js,没有什么特殊原因,只是因为它本来就跟着 BBB debian distro 一起装好了的。为求快捷,也搭着 Express 一起用。我要用最高速度完成这个东西来,试试而已,Node + Express 很快能搞定。

安装

首先,BBB 上面要有 node,确认一下在不在:

node –v

当然在。然后当前看看端口

netstate –tlpn

这里看到,80,是 systemd 用掉,就是 bone101 那一页介绍页面,3000 也是,Cloud9 IDE 的。两者都可以关掉,关掉对应的服务即可(bonescript.socket 和 bonescript.service)。8080 端口,是 apache2 。那我用 4001 吧。也是没有原因的。好,继续。

在某某文件夹里面创建一个子文件夹 /root/lasapp,然后 npm init,按需输入一些参数,它会帮我生成 package 档,然后 npm install express –-save,其后等它安装就好了。具体方法请参看这里:http://www.expressjs.com.cn/starter/installing.html

热身, Hello World 一下,app.js:

var express = require('express');
var app = express();
app.get('/', function (req, res) {res.send('Hello World!');
});
var server = app.listen(4001, function () {var host = server.address().address;  var port = server.address().port;
});

然后 node app.js。用电脑打开浏览器输入对应地址 http://192.168.7.2:4001/ 就会看到 Hello World,十行代码不到,够快了吧。

我准备做的,整个过程,是由一个网页上的点击,触发服务器发送 UDP 广播,然后接上一篇的 ESP8266 UDP 接收。然后ESP8266,或者多个不同的 ESP8266,响应后把它们回传报上来的身份标识,服务负责处理回传保存到数据库,页面定时刷新从数据库取值。一个人项目,蓝图在心中。简单写一下的话,是分开前端,后台两个,后台分开静态页、UDP、和 web service 三个部分。随便从哪里开始,那就从前端那里,页面吧。

前端页面

IDE 我用 webstorm,在 windows 写好 cp 过去 BBB 上,随便拿个 Bootstrap 模板改就是:

模板来自一个什么二十分钟搭好 bootstrap 的博文,其实不需要二十分钟,Copy & Paste 然后改文字而已。任何模板都能做,甚至是直接手敲 HTML 也不会有问题。关键是中间的部分,将会用 JS 从 web service 获得 JSON (设备列表)把它填上。中间还有一个绿色按钮“重新搜索设备”,要有 web service 响应处理设备搜索(就是 UDP 广播)。

页面样式大概弄好了就拷过去 BBB 先。把档案打包成 lasapp.tar (在 windows 用 7z),然后 pscp (putty 自带的远程 copy、cp 工具)去 BBB 上。

pscp lasapp.tar root@192.168.7.2:/root/lasapp.tar

然后到 BBB 上 在 lasapp文件夹内,创建 public 文件夹:

mkdir public

在 public 文件夹内解压:

tar –xf ~/lasapp.tar

最后修改刚才hello world 那个例子的 app.js 加入静态文件把 public 文件夹放出来,和根目录 GET 时候传送 index.html:

var express = require('express');
var app = express();
app.use(express.static('public')); // 配置静态文件路径
app.get('/', function (req, res) {res.sendFile('index.html'); // 之前是 send(‘Hello World!’)
});
var server = app.listen(4001, function () {var host = '192.168.7.2';var port = server.address().port;
});

然后运行测试一下,没问题就下一步,web service。

后台服务

BBB 空间有限,UDP、网页服务器、Web Service 三者都能在 node 实现的话,那就不装其他,就用 node 。快速做一遍三个分别是怎样在 node 实现。

测试 Web Service 与发出 UDP

写个 api.js 先,创建一个 api 文件夹然后在里面 vim api.js :

exports.udpService = function(port,bc_addr){ var dgram = require('dgram'); var port = port; var bc_addr = bc_addr; var queryTxt = '{"cmd":"0"}'; var queryMsg = new Buffer(queryTxt); var client = dgram.createSocket('udp4'); client.bind(port, function(){ this.setBroadcast(true); this.setMulticastTTL(128); }); return { query : function(req,res){ client.send(queryMsg,0,queryMsg.length,port,bc_addr); res.sendStatus(200); } };
}; 

node 可以发 datagram(UDP),API 请参看这里:
https://nodejs.org/docs/latest-v0.10.x/api/dgram.html

代码 api.js 的 query 方法是接受到请求时候,对广播地址(bc_addr)的特定端口(port)以 UDP 包方式发出一个字符 {“cmd”:”0”}。注意 setBroadcast 和 setMulticastTTL 两个方法都必须在 bind 绑定完成后才能操作,所以我放了它在 callback 内。

完成需要告诉客户端,搞定了没问题,STATUS 200 OK。

关于广播

IPv4 中,掩码 subnet mask,是指定子网的方式。一个 192.168.7.0  作为 network prefix 指定了掩码 255.255.255.252,等于 2^8 – 252 = 4个地址,这四个 192.168.7.0 至 192.168.7.3 之中,第一个 192.168.7.0是 network prefix,最后一个 192.168.7.3 是 broadcast address 广播地址,只有余下的 192.168.7.1 和 192.168.7.2 两个地址可以用作 host 主机。博文中 BBB 插着 USB 不插网线默认就是这个网段,BBB 用 USB 共享网络时本身 IP 用 192.168.7.2,电脑这时候应该设置为 192.168.7.1,因为不改 BBB 地址网段的情况下,你别无选择,余下只有一个主机地址可用。用这个子网,要发广播,这子网的广播地址是 192.168.7.3 了。而 255.255.255.255 就是公网以外,全物理网段广播,不区分割开了多少个子网。

Multicast TLL 这 Multicast 这个字是来自 IPv6,IPv6地址分三类,Unicast、Anycast、Multicast。Unicast 是给单独一个主机接收,Anycast 是给最近的一个主机接收,Multicast 是给网段所有主机接收,Multicast 意义上就是 IPv4 的 Broadcast。TTL 全写是 Time To Live,意义是封包的存活时间,实际上实现的时候,它是每到达一个节点就会减一,直到 0 时候它就会不再被传送。所以它并不是一个实际时间值(多少毫秒等等)。直接插 USB 连然后对一个只有两个主机地址的网段广播而且设置 TTL 128 其实是没有任何意义,这里面没有 128 个节点。看不惯就把上面代码那句删掉吧。能设置的范围是 1-255,默认值是 OS 指定,我没有查看 BBB 的 Debian 默认值是多少,据说是 1。

有兴趣研究可以参考:

https://en.wikipedia.org/wiki/IP_address

https://en.wikipedia.org/wiki/Subnetwork

http://tools.ietf.org/html/rfc4291#section-2

书的话只需要一本,TCP/IP Illustrated Vol 1 The Protocols,Richard Stevens,ISBN: 9780321336316

要调用它,就需要在app.js那边开GET接口。/query 接到 GET 请求就调用这个 api.js 里面的 query 方法。现在修改 app.js:

var express = require('express');
var app = express();
var api = require('./api/api'); // 引用才能使用 api.js
var svc = new api.udpService(4000,'192.168.7.3'); // 利用 api 创建 udpService 实例app.use(express.static('public'));app.get('/',function(req,res){ res.sendFile('index.html');
});app.get('/query', svc.query); // 调用 query 方法var server = app.listen(4001, function(){ var host = server.address().address; var port = server.address().port; console.log('app listening at http://%s:%s',host,port);
});

下一步,修改 index.html 把图中绿色按钮的点击,用 ajax 请求发到 /query,就完成了。简单点比如就 <a …… οnclick="$.ajax({url:'/query'})"> … 。

最后一步,BBB 插上电源和网线,广播地址改为正确值。web 请求就是这样和 UDP 广播连在一起(/query 的 GET 请求收到后,触发 udpService 的 query 方法)。效果 ok 就来真的了。

整体后台代码

由于空间所限,数据量小,并发少,数据库用 Sqlite 我够了,喜欢其他的请自行修改。首先安装一下 Sqlite3,去到之前建的 lasapp 目录,然后:

npm install sqlite3 –-save

新版的 express 已经没有了内置 body parser,要自己装再自己加入中间件,这样安装:

npm install body-parser

然后可以写代码了,看看我的最终版代码:

/lasapp/app.js

var express = require('express');
var app = express();
var api = require('./api/api');
var bp = require('body-parser');var svc = new api.udpService(4000,'255.255.255.255');app.use(express.static('public'));
app.use(bp.json());
app.get('/',function(req,res){ res.sendFile('index.html');
});app.get('/query', svc.query);app.get('/devices/getAll',api.deviceService().getAll);app.put('/devices', api.deviceService().save);var server = app.listen(4001, function(){ var host = server.address().address; var port = server.address().port; console.log('app listening at http://%s:%s',host,port);
});

与之前代码区别有几个地方:

  • 它引用了 body-parser 并且在 app.use 启用了 json 中间件,目的是对 body 解析 JSON https://www.npmjs.com/package/body-parser
  • udp 广播地址用了 255.255.255.255 全物理网段广播
  • 多了两个接口
    • get /devices/getAll
    • put /devices
  • 两个接口对应调用了 deviceService 里面的两个方法

看看 api.js 里面是怎样的:

/lasapp/api/api.js

exports.dbHelper = function(){var sqlite = require('sqlite3').verbose();var db = new sqlite.Database('lasdb.db');db.serialize(function(){db.run("CREATE TABLE if not exists devices(guid TEXT, dType TEXT,displayName TEXT)");});return {saveOrUpdate: function(device,callback){db.get("SELECT guid FROM devices WHERE guid=?",device.guid,function(err,row){if(err===null && row === undefined) {db.run("INSERT INTO devices VALUES (?,?,?)",device.guid,device.dType,device.displayName);} else if (err===null) {db.run("UPDATE devices SET displayName=? WHERE guid=?",device.displayName,device.guid);} else {console.log(err);}});var getType={};if(callback && getType.toString.call(callback)==='[object Function]'){callback(device);}},getAll: function(callback){var result;db.all("SELECT guid,dType,displayName FROM devices", function(err,rows){if(err!==null){console.log(err);return;}var getType={};if(callback && getType.toString.call(callback)==='[object Function]'){callback(rows);}});},closeDB: function(){db.close();}};
};exports.deviceService = function(){return {getAll: function(req,res){var dbHelper = new exports.dbHelper();dbHelper.getAll(function(r){res.set({'Content-Type':'application/json'});res.send(r);dbHelper.closeDB();});},save: function(req,res){var dbHelper = new exports.dbHelper();dbHelper.saveOrUpdate(req.body,function(r){res.set({'Content-Type':'application/json'});res.send(r);dbHelper.closeDB();});}};
};exports.udpService = function(port,bc_addr){var dgram = require('dgram');var port = port;var bc_addr = bc_addr;var queryTxt = '{"cmd":"0"}';var queryMsg = new Buffer(queryTxt);var client = dgram.createSocket('udp4');client.bind(port, function(){this.setBroadcast(true);this.setMulticastTTL(128);});client.on('message', function(msgRec,remote){var msg = msgRec.toString();if(msg==queryTxt){return;}var cmdObj;try {cmdObj = JSON.parse(msg);} catch(e) {console.log('Improper JSON literial received.');console.log(msg);return;}if(!cmdObj.cmd){console.log('JSON object format error.');return;}console.log('From:'+remote.address+' Port:'+remote.port+' > '+msg);if(cmdObj.cmd==2 && cmdObj.dType){var dbHelper = new exports.dbHelper();dbHelper.saveOrUpdate(cmdObj,function(){dbHelper.closeDB();});} else {console.log('cmd code not recognize or dType missing.');}});return {query : function(req,res){client.send(queryMsg,0,queryMsg.length,port,bc_addr);res.sendStatus(200);}};
};

三大块,一个 dbHelper 做数据层用来和 Sqlite 数据库交互,数据层方法除了closeDB 其他全部有 callback可配置,两个服务分别负责 UDP 处理和 Web Service 的处理。

udpService 除了一些参数验证之外,就是 on(“message”,….) 监听 UDP 包到达,到达后调用 dbHelper 保存或更新值,最后 udpService 实例只开放一个方法,query,用来发出广播 UDP 包。

deviceService 只有 save 和 getAll,两者对应 dbHelper 里面的方法,查询完成后 res.send。

不复杂,然后测试一下:

整体集成测试

用node app.js 启动。

首先用 POSTMAN 对 /query 发 get 请求,另外用工具监听,看看 UDP 是否正常广播。

然后用工具,发三个 UDP ,分别是 guid:0001, 0002 和 0003,模拟 ESP8266 对 cmd:0 命令的响应。

{"cmd":"2","guid":"0001","dType":"powerPlug"}
{"cmd":"2","guid":"0002","dType":"powerPlug"}
{"cmd":"2","guid":"0003","dType":"powerPlug"}

或者再发多一次 guid 0003 看看它有没有重复插入(当然不会)。

然后 POSTMAN 模拟 /devices/getAll 的 GET 请求,看看返回值是否正常。

再试试 PUT,对 /device 发出 PUT 请求,模拟网页对 displayName,智能设备的显示名进行更新,PUT 的 body 为:

{"guid":"0003","dType":"powerPlug","displayName":"主卧插座1"}

记得 Header 加上 Content-Type = application/json

最后再对 /device/getAll 发出 GET 请求看看是否更新正确:

API 初稿就这样完成。改一下页面,让它触发对应的 web service ,或许加个定时自动刷新页面,整个项目初稿就搞定了。代码有太多的改善空间,太混乱半成品不放 GIT 出来了,做好先。

下一篇,智能插座接线,和加上从 UDP 包接收,触发 GPIO 高低电平控制电源开关。整个项目在下一篇就完成了。

重要参考

Node.js http://nodejs.org/
SQLite http://sqlite.org/
node-sqlite3 API https://github.com/mapbox/node-sqlite3/wiki/API
node-sqlite3 流控制、同步异步关闭等 https://github.com/mapbox/node-sqlite3/wiki/Control-Flow
Expressjs http://expressjs.com/
Expressjs 中间件 http://www.expressjs.com.cn/guide/using-middleware.html
Expressjs 其他有用模块列表 https://github.com/expressjs
Expressjs Process Manager http://expressjs.com/en/advanced/pm.html
Project Style Template (仅供参考) https://github.com/jshttp/style-guide/tree/master/template
body-parser https://github.com/expressjs/body-parser
https://www.npmjs.com/package/body-parser
IP / Broadcasting / IPv6

https://en.wikipedia.org/wiki/IP_address
https://en.wikipedia.org/wiki/Subnetwork
http://tools.ietf.org/html/rfc4291#section-2

Postman https://www.getpostman.com/
网络调试助手 http://www.onlinedown.net/soft/47906.htm
Bootstrap 秒速入门(所谓的二十分钟打造站点) http://www.revillweb.com/tutorials/bootstrap-tutorial/
http://www.w3cplus.com/css/twitter-bootstrap-tutorial.html
我曾经买过 Theme 的网站,有个别设计质量相当高 http://themeforest.net/

我在这群里,欢迎加入交流:
开发板玩家群 578649319
硬件创客 (10105555)

转载于:https://www.cnblogs.com/leptonation/p/5181985.html

Beaglebone Black– 智能家居控制系统 LAS - 网页服务器 Node.js 、Web Service、页面 和 TCP 请求转 UDP 发送...相关推荐

  1. 树莓派蓝牙ble gattlib c语言,树莓派构建智能家居控制系统 篇三:花花草草智能监测仪接入树莓派Domoticz...

    树莓派构建智能家居控制系统 篇三:花花草草智能监测仪接入树莓派Domoticz 2017-05-22 19:30:08 35点赞 410收藏 31评论 作为工科男,实在对花花草草没太多感觉.家里阳台养 ...

  2. 基于ZigBee和STM32的智能家居控制系统的设计与实现(五)--终结篇

    基于ZigBee和STM32的智能家居控制系统的设计与实现(五)–终结篇 说明 首先祝贺自己顺利的完成了毕业答辩工作,想起整个过程还是挺让自己感动的.最后还被评为优秀毕业设计,虽然并没有什么luan用 ...

  3. 基于ZigBee和STM32的智能家居控制系统的设计与实现(二)

    基于ZigBee和STM32的智能家居控制系统的设计与实现(二)   上一篇博客中总体介绍智能家居系统的基本实现原理,这篇博客和以后的几篇博客会详细进行相应的介绍.这里首先进行硬件电路的设计. 硬件电 ...

  4. 基于ZigBee和STM32的智能家居控制系统的设计与实现

    基于ZigBee和STM32的智能家居控制系统的设计与实现(一) 时间过的好快,已经到了做毕业设计的时候了,本次毕业设计题目是自己选的,为什么做这个?原因很简单,想把自己所学的大部分知识都应用上,虽然 ...

  5. 基于ZigBee和STM32的智能家居控制系统的设计与实现(三)

    基于ZigBee和STM32的智能家居控制系统的设计与实现(三) 自从前两篇博客介绍了智能家居系统的基本实现机理后,收到了好多朋友的来信,和我讨论了好多的这方面的知识,在此很高兴,虽然自己做的这个所谓 ...

  6. 智能家居控制系统的功能和特点

    说到家居用品,相信大家都比较了解,比如床.沙发.桌子.电视.空调等等.那什么是智能家居控制系统呢?大家可能都比较陌生. 智能家居控制系统是以家庭为平台,主要控制对象为家用电器设备,利用网络通信技术.自 ...

  7. 嵌入式linux智能家居系统,以Arm-Linux为平台的智能家居控制系统的设计详解

    嵌入式系统以其占用资源少.专用性强.功耗低的特点使其广泛应用在移动通信.工业生产.安全监控等领域.针对人们对高效.舒适.安全.便利.环保的居住环境的要求,提出了以Arm-Linux为平台的智能家居控制 ...

  8. java智能家居_基于JAVA的智能家居控制系统的设计(毕业设计).doc

    基于JAVA的智能家居控制系统的设计(毕业设计) 基于JAVA的智能家居系统的设计--信息控制子系统 PAGE 4 目 录 TOC \o "1-3" \h \z \u HYPERL ...

  9. STM32嵌入式实现智能家居控制系统

    本文将讲述如何用STM32嵌入式实现智能家居控制系统,所谓智能就是实时监测自主控制一些因素,如,温度.湿度.光照强度等,当室内光线较暗时自动打开灯,根据光线亮度的不同开灯数量也不同,当室内温度过高时红 ...

  10. 基于ESP32的智能家居控制系统-微信小程序

    一. 课题研究意义.现状及应用分析 1.1课题研究意义及现状 目前,科学技术发展十分迅速,其渗透到各行各业以及生活的方方面面,室内设计和高科技结合便出现了"智能家居".所谓智能家居 ...

最新文章

  1. 基于DeepChem的溶解度预测(图形卷积,神经网络)
  2. oracle 监听程序当前无法识别连接描述符中请求的服务_Go 中的优雅升级
  3. JZOJ 5253. 排列与交换
  4. python什么是交换算法_python算法-015将链表元素两两交换元素(交换值、就地翻转)...
  5. python输出键值列表_Python 键值分组或分区数据
  6. 普大喜奔 | Azure 免费送网站SSL证书啦!
  7. c语言i++和++i程序_使用C ++程序修改链接列表的内容
  8. linux timespec 链接库,Linux内核 timespec_sub()
  9. 【STM32】HAL库 STM32CubeMX教程六----定时器中断
  10. WinForm控件开发总结(五)-----为控件的复杂属性提供类型转换器
  11. Node.js:借助formidable文件上传
  12. 关于用MATLAB求解定积分方程的问题
  13. 【健康管理系统——开题报告 分享(仅供参考呀)】
  14. 关于信度分析的多种方法
  15. python广义矩估计_怎么用软件做广义矩估计GMM的参数估计?
  16. oracle remap schema,oracle 10g DATA PUMP 的REMAP_SCHEMA和REMAP_TABLESPA
  17. 尝试从redis未授权访问到getshell的四种姿势(失败)
  18. 前女友让我撸个植物大战僵尸,我一怒之下把代码开源了...
  19. 【opencv学习笔记】第五篇:访问图像中像素的三种方式、ROI区域图像叠加和图像混合
  20. 阿里云天池携手产学研心血管专家,共话心血管AI发展

热门文章

  1. CTF题库实验吧 py的交易
  2. python实例013--定义一个矩形类
  3. 树莓派cm4安装ax200驱动-wifi6
  4. 流行音乐混音风格 流行音乐混音的压缩技巧
  5. 论文阅读 | Region Proposal by Guided Anchoring
  6. 晶振旁的电阻(并联与串联)和电容的作用
  7. 西游记中孙悟空大闹天宫时期被孙悟空打败的
  8. 不同时区时间换算_时区换算-如何计算时区? 爱问知识人
  9. day10图书编辑删除 字段参数choise(重要)多对多三种创建方式 ajax语法结构
  10. ccf试题1:分蛋糕