使用ESP8266构建一个简单的温湿度在线监测装置_esp8266温湿度_gitdive的博客-程序员宅基地

主要功能

在终端对环境温湿度进行采集,通过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更新由温度信息的接收触发

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>温度在线采集显示</title>
<script src="https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js"></script>
</head>


<body>
<form id="form">
Message: <input size="50" id="msg"> <input type="submit" value="Submit">
</form>
<div id="chart" style="width: 600px;height:400px;"></div>

<div id="main" style="width: 600px;height:400px;overflow:auto;></div>


<script type="text/javascript">
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 + '<br/>' +
                params[0].seriesName + ' : ' + params[0].value[1] + ' <span>&#8451<SUP> </SUP></span>'
        }
    },
    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);
}


//***************************************************************************************************
</script>

</body>
</html>

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了
开展上述工作中,学习到了很多新的知识,对回调函数和事件驱动有了更进一部的认识

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gitdive/article/details/117636825

智能推荐

vfork-程序员宅基地

相关函数:wait, execve头文件:#include 定义函数:pid_t vfork(void);函数说明:vfork()会产生一个新的子进程, 其子进程会复制父进程的数据与堆栈空间, 并继承父进程的用户代码,组代码, 环境变量、已打开的文件代码、工作目录和资源限制等。Linux 使用copy-on-write(COW)技术, 只有当其中一进程试图修改欲

仿微信6.0的界面按钮切换产生渐变效果-程序员宅基地

最近看了一个视频讲的是自定义个view模仿微信的四个菜单按钮切换时的颜色产生渐变的效果,最后实现出来感觉好不错所以我就做了总结希望对你们有用。先看效果吧,不知道用什么软件来录制gif图你们就凑合看吧,大概的效果打开微信自己左右滑动看看。默认的样式:滑动中的样式:切换后的效果:基本的效果和radiobutton实现的一样。。。。在滑动的过程中设

java.lang.String cannot be cast to java.lang.Integer_mybatis java.lang.string cannot be cast to java.la-程序员宅基地

项目场景:Controller层 使用Map<String, object> params 接参,使用swaager进行传参,参数类型String例如:项目场景:示例:通过蓝牙芯片(HC-05)与手机 APP 通信,每隔 5s 传输一批传感器数据(不是很大)问题描述:提示:这里描述项目中遇到的问题:结果报错在mybatisplus的条件构造器里,出现类型转换异常,java.lang.String cannot be cast to java.lang.Integerprivate _mybatis java.lang.string cannot be cast to java.lang.integer

GB28181协议支持的H264的PS封装实现-程序员宅基地

1、写在前面:最开始接触H264的PS封装的时候,参考的是:关于对H264码流的PS的封装的相关代码实现 , 确实是很有帮助,但完全参照这个实现,发现问题也很多,主要还是对MPEG213818的封装协议理解不深产生,所以我们在参考代码实现时,还是需要对原理做深入细致的分析,特别是封装涉及到bit级别的配置,一个bit配错了,可能就播放不了,所以记录下,做个备份。2、封装需要基本了解的概念:

运维面试问题-1_面试运维的算法-程序员宅基地

1、为什么我们要使用tomcat,类似的软件有哪些?因为Apache仅支持静态网站,不能解析Java、Jsp,它们服务端口也不同Apache端口80 tomcat端口8080类似的软件有Weblogic (收费)Jboss(免费)Resin、Jetty2、tomcat优化内存优化:JAVA_OPTS='-Xms=256m -Xmx=1024m -Xmn=512m'并发优..._面试运维的算法

clone()方法使用-程序员宅基地

clone()函数使用在使用clone()方法克隆元素时:如果单纯的只是为了克隆元素,那么里面不需要传参数。如果需要连同元素身上的事件一起克隆,那么就给他传一个true的参数...

随便推点

IRP结构体之Flag成员_irpno-程序员宅基地

IRP_NOCACHE //表示I/O请求从存储的媒介而不是高速缓存中读取数据IRP_PAGING_IO //表示此时执行内存页的I/O操作IRP_MOUNT_COMPLETION //卷挂载操作完成IRP_SYNCHRONOUS_API //该操作是一个同步分页I/O操作。IRP_ASSOCIATED_IRP_irpno

环信大学:基于韧性服务的航司退改签智能化变革_韧性中的服务韧性-程序员宅基地

服务请求和服务资源不匹配,是永恒的话题。每一次恶劣天气都可能成为“山呼海啸”式的严峻考验,退改签服务已经成为航司服务部门的无解难题!这些年,航司在柜台服务、电话服务的基础上,利用更多的技术手段,扩展更多的服务渠道和服务资源(官网、机场自助设备、手机APP、微信公众号、微信小程序、H5小页面等),但在“山呼海啸”的冲击面前,所有服务资源都“杯水车薪”(图1)。当AI技术出现后,很多人都认为迎来了终极方案,但通过这几年的实践,发现现实和理想之间还有很长的路,行业还需摸着石头过河。图1:近十年三大运输_韧性中的服务韧性

汇编语言程序设计第一章小结-程序员宅基地

第一章节介绍了汇编语言程序设计的基础知识,首先介绍了机器语言和汇编语言。机器语言的核心是机器指令集,而这些机器指令是一列二进制数字。汇编语言的核心是汇编指令,汇编指令又分为3类:有对应的机器码的汇编指令、没有对应的机器码,由编译器执行,计算机不执行的伪指令和同样没有对应机器码,由汇编器识别的其他符号。这两种语言构成了编写程序的工作过程:程序员编写汇编指令,通过可以将汇编指令转换为机器..._汇编语言程序设计课程小结

struts2关于A web application created a ThreadLocal with key of type 异常解决办法-程序员宅基地

今天开始学习了struts2, 于是下了最新的版本struts2.2.3.1,在使用的过程中总是报错:A web application created a ThreadLocal with key of type , 尽管出现了这个错误,但是并不妨碍程序正常运行, 虽然程序虽然

ETL的考虑-程序员宅基地

做数据仓库系统,ETL是关键的一环。说大了,ETL是数据整合解决方案,说小了,就是导数据的工具。回忆一下工作这么些年来,处理数据迁移、转换的工作倒还真的不少。但是那些工作基本上是一次性工作或者很小数据量,使用access、D...

经典排序——快排及三种优化-程序员宅基地

经典排序——快排及三种优化快排时间复杂度为O(nlogn)~O(n^2),不稳定排序 废话不多说,直接上代码:public class Main{ public static void main(String[] args){ //产生随机数作为测试数据,[0,100)的10个随机数 int[] arr = generateData(0,100,1...