主要功能
在终端对环境温湿度进行采集,通过WIFI接入网络
能够显示实时参数变化(表格和曲线),具备一定的历史数据回溯功能
后期可加入一些联动控制或报警功能
项目目的
没有什么特别的实用性,就是随便玩一玩,建立基础的物联网概念
开展思路
采集端每两秒采集一次数据并传输,数据传输可采用post或websocket,后续考虑直接采用websocket
服务器端实时接受采集的数据并进行显示,每十分钟对数据进行一次存储,作为历史数据(后面这个还没搞。。。)
服务器搭建选择
网络连接可以采用虚拟服务器进行内网映射
本来想用之前的那个红米1s作为本地服务器的,刷了N次机,官方的非官方的刷机包都试过了,稳定版root不了,用开发版每次root之后wifi连接都出现了问题,不知道是什么原因,已经基本放弃了,以后再说吧,还是先老老实实用电脑试试吧
写在前面:
在玩ESP8266的时候,想在ESP8266和DHT11的基础上搞个wifi数据采集模块,实现传感器数据的采集和实时显示。
一开始的时候想通过http轮询get或post方式进行传感器数据的传输,想了一下过程有点繁琐,就在网上找了一下发现websocket是一种比较好的方式。
HTTP请求头部比较长,浪费带宽,而websocket就比较好了,既可以双向传输,也比较节省带宽,目前直播网站中普遍采用websocket方式传输弹幕信息,一些聊天类应用也有所采用。websocket协议的标识符是ws,像https一样如果加密的话是wxs。
在此后的过程中踩了很多坑,包括了各个方面
研究通讯模式时先后发现了两个websocket应用,即socket.io和Nodejs Websocket,他们都是基于Node.js的
Socket.IO
官网:https://socket.io/docs/
Socket.IO是一个库,它支持浏览器和服务器之间的实时、双向和基于事件的通信。它包括:
Node.js服务器:Source | API
浏览器的Javascript客户端库(也可以从Node.js运行):Source | API
具备以下特性:
1.可靠性
2.自动重连
3.断开检测
4.二进制传输
5.多路复用
Socket.IO不是WebSocket实现。尽管Socket.IO确实在可能的情况下使用WebSocket作为传输,但它在每个数据包中添加了一些元数据:需要消息确认时的数据包类型、命名空间和数据包id。这就是为什么WebSocket客户端无法成功连接到Socket.IO服务器,而Socket.IO客户端也无法连接到WebSocket服务器。
上面是Socket.IO的官方简介,尤其是最后一段,一开始的时候我没有注意到,在对示例进行调试的过程中发现,每个连接的端口是不固定的,而且发送数据的格式也如上面所述,都是封装好的,需要浏览器的支持。
这也就造成了我后面没法用这个和8266进行通讯了。
Nodejs Websocket
websocket服务器和客户端的nodejs模块
可以提供一些基础的websocket功能,进而实现传感器数据的传输
所以这里我们就采用这个了
服务器端:
开启websocket服务,接收来自客户端和8266的信息
var http = require("http")
var ws = require("nodejs-websocket")
var fs = require("fs")
var moment = require('moment')
moment.locale('zh-cn')
http.createServer(function (req, res) {
fs.createReadStream("index.html").pipe(res)
}).listen(8080)
var server = ws.createServer(function (connection) {
connection.nickname = null
connection.on("text", function (str) {
if (connection.nickname === null) {
connection.nickname = str
broadcast(str+" entered")
} else
broadcast("["+connection.nickname+"] "+str)
})
connection.on("close", function () {
broadcast(connection.nickname+" left")
})
connection.on('error',(e)=>{
console.log('====>an error occured: '+e.stack)
})
})
server.listen(8081)
function broadcast(str) {
server.connections.forEach(function (connection) {
connection.sendText(str+" time-"+moment().format('YYYY-MM-DD HH:mm:ss'))
})
}
客户端:
接收服务器传递的websocket数据,使用echarts进行动态数据的显示,echarts更新由温度信息的接收触发
温度在线采集显示
var connection
window.addEventListener("load", function () {
var nickname = prompt("Choose a nickname")
if (nickname) {
connection = new WebSocket("ws://"+window.location.hostname+":8081")
connection.onopen = function () {
console.log("Connection opened")
connection.send(nickname)
document.getElementById("form").onsubmit = function (event) {
var msg = document.getElementById("msg")
if (msg.value)
connection.send(msg.value)
msg.value = ""
event.preventDefault()
}
}
connection.onclose = function () {
console.log("Connection closed")
}
connection.onerror = function () {
console.error("Connection error")
}
connection.onmessage = function (event) {
var div = document.createElement("div")
div.textContent = event.data
var board = document.getElementById("main")
var object = board.appendChild(div)
tempinfo = event.data
if (tempinfo.includes("DHT Temperature:"))
{redraw(tempinfo)}
}
}
})
//***************************************************************************************************
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('chart'));
function checkTime(i)
{
if (i<10)
{i="0" + i}
return i
}
function getnewData(tempinfo) {
now = tempinfo.split("time-")[1];
value = tempinfo.split("DHT Temperature:")[1].split(";Humidity")[0];
return {
name: now,
value: [
now,
Math.round(value)
]
};
}
var data = [];
var tempinfo_init = "[abc] DHT Temperature:24;Humidity:35 time-2020-03-23 21:44:57";
for (var i = 0; i < 90; i++) {
data.push(getnewData(tempinfo_init));
}
option = {
title: {
text: '实时温度显示'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'line' // 默认为直线,可选为:'line' | 'shadow'
},
formatter: function(params) {
//console.log(params)
return params[0].name + '
' +
params[0].seriesName + ' : ' + params[0].value[1] + '
℃ '
}
},
xAxis: {
type: 'time',
splitNumber: 8,
splitLine: {
show: false
}
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
splitLine: {
show: false
}
},
series: [{
name: '实时温度',
type: 'line',
smooth: true,
showSymbol: false,
hoverAnimation: false,
data: data
}]
};
function redraw(i)
{
if (data.length<100)
{
s = getnewData(i)
data.push(s);
}
else
{
data.shift();
s = getnewData(i)
data.push(s);
}
myChart.setOption(option);
}
//***************************************************************************************************
ESP8266程序
首先连接wifi,定时采集DHT11的温度信息,将信息以一定的格式发送至websocket服务端,服务端将这些信息发送至客户端,并在客户端进行解析和数据显示
设置自动重连功能,断开后一段时间就再次连接服务端
程序中设置温度采集开启和关闭功能,通过发送“”和“”进行设置
init.lua程序:
if not tmr.create():alarm(1000, tmr.ALARM_SINGLE, function()
print("Init start")
wifi.setmode(wifi.STATION)
station_cfg={}
station_cfg.ssid="user"
station_cfg.pwd="abc456456"
station_cfg.save=false
wifi.sta.config(station_cfg)
wifi.sta.connect(function(connected_cb)
print(connected_cb.SSID)
dofile("wstest.lua")
end)
end)
then
print("whoopsie")
end
wstest.lua程序
onclose = false
ws = nil
ws = websocket.createClient()
ws:close()
onclose = true
temp_flag = false
tempsendtmr = tmr.create()
tempsendtmr:register(3000, tmr.ALARM_AUTO, function()
tempinfo = get_tempinfo()
ws:send(tempinfo)
end)
tempsendtmr:stop()
ws:on("connection", function(ws)
print('got ws connection')
ws:send('abc')
end)
ws:on("receive", function(_, msg, opcode)
print('got ws message:', msg, opcode) -- opcode is 1 for text message, 2 for binary
if string.find(msg, "temp send open", 1) then
print("temp_true")
temp_flag = true
tempsendtmr:start()
end
if string.find(msg, "temp send close", 1) then
print("temp_true")
temp_flag = true
tempsendtmr:stop()
end
end)
--***********************close event***************************
ws:on("close", function(_, status)
onclose = false
print('ws connection closed', status)
if not tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
print('wstest reconnected....')
ws:connect('ws://192.168.0.144:8081')
end)
then
print("whoopsie")
end
end)
--*************************************************************
--***********************init connection***************************
if not tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
print("if init connection")
if onclose then
print('init connection....')
ws:connect('ws://192.168.0.144:8081')
end
end)
then
print("whoopsie")
end
--*************************************************************
function get_tempinfo()
pin = 4
status, temp, humi, temp_dec, humi_dec = dht.read(pin)
if status == dht.OK then
-- Integer firmware using this example
text_data=string.format("DHT Temperature:%d;Humidity:%d\r\n",
temp,
humi
)
--print(text_data)
elseif status == dht.ERROR_CHECKSUM then
print( "DHT Checksum error." )
elseif status == dht.ERROR_TIMEOUT then
print( "DHT timed out." )
end
return text_data
end
完成之后的效果如图
后续考虑
1.在数据显示上,目前只显示了温度,没有湿度,同时对坐标轴等进一步优化
2.考虑加入数据库对采集到的信息进行存储,后续也可以对历史数据进行展示
3.对于通信方式,可采用更为专业的MQTT协议,这个也是在后续了解的过程中才知道的,所以这里面就先用websocket了
开展上述工作中,学习到了很多新的知识,对回调函数和事件驱动有了更进一部的认识