基于ESP32智能空气站实现
Published in:2024-10-27 |
Words: 4.1k | Reading time: 20min | reading:

基于ESP32智能空气站实现

简介

采用D1 mini模块,颗粒物传感器,二氧化碳传感器,光线传感器,温湿度传感器,屏幕等通过串口语言进行开发,可检测空气中主要有害物质–pM2.5,PM10的仪器,并实现实时监测功能

实现功能

1
2
3
4
5
6
7
8
1.二氧化碳监测
2.环境光检测
3.可吸入颗粒物检测
4.温湿度检测
5.天气预报
6.时钟功能
7.显示监测数据动态切换
8.便携、低功耗

使用硬件

硬件中通过ESP8266与PMS5003可吸入物颗粒物传感器、BME280温湿度传感器、BH1750光线传感器、UsartGPU26B串口屏、Sense air s8二氧化碳传感器使用杜邦线连接,用电源线进行供电

原理图

如图, 分为以下模块:BH1750(光线传感器)、BME280(温湿度传感器)、Switch(开关)、PMS5003(颗粒物传感器)、UsartGPU26B(串口屏)、Sense air s8(二氧化碳传感器)、D1MINI(主控板)。

img

技术

  • ardunio

  • python

  • esp32等硬件

  • mqtt

  • requests

  • GPUMarker

数据指标

1
2
3
4
5
6
功能	               指标
颗粒物浓度检测 0~1000pm
室内温湿度检测 温度量程:-40度~85度 湿度量程:0~100RH
二氧化碳浓度检测 量程:0~10000ppm(正常的二氧化碳量程为0~2000ppm)
时钟功能 北京时间
自动调节光线亮度功能 1-65535lx(光线亮度量程)

功能实现

总体结构如图

img

天气数据获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# python
# -*- coding:utf-8 -*-
# @FileName :server.py
# @Time :2021/3/24 21:26
# @Author :czq

from urllib.parse import quote
import urllib.request
import re
import json
import paho.mqtt.publish as publish
import time
import requests
from io import BytesIO
import gzip

'''
parameter
'''
location = '117.22,39.0'
key = '8d7a3e69f9644bb5bf9ed5da19209e67'
client_id = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
host = '121.43.238.135'
port = 18083
user = 'admin'
password = 'public'


weather_url = "https://devapi.heweather.net/v7/weather/now?location="+location+"&key="+key
weather_response = urllib.request.urlopen(weather_url)
weather_responsebyte = weather_response.read()
buff = BytesIO(weather_responsebyte)
f = gzip.GzipFile(fileobj=buff)
weather_result = f.read().decode('utf-8')
data2 = json.loads(weather_result)
real = data2["now"]["temp"]
hum = data2["now"]["humidity"]
tempfl = data2["now"]["feelsLike"]
code = data2["now"]["icon"]
if code == '100' or code == '150':
code = 1
elif code == '104' or code == '154':
code = 2
elif code == '101' or code == '103' or code == '102' or code == '153':
code = 3
elif code == '300' or code == '301' or code == '350' or code == '351':
code = 4
elif code == '302' or code == '303':
code = 5
elif code == '304':
code = 6
elif code == '305' or code == '309':
code = 7
elif code == '306' or code == '307' or code == '308' or code == '310' or code == '311' or code == '312' or code == '313' or code == '314' or code == '315' or code == '316' or code == '317' or code == '318' or code == '399':
code = 8
elif code == '404' or code == '405' or code == '406' or code == '456':
code = 9
elif code == '400' or code == '408' or code == '407' or code == '457':
code = 10
elif code == '401' or code == '402' or code == '403' or code == '499' or code == '410' or code == '409':
code = 11
elif code == '503' or code == '504' or code == '507' or code == '508':
code = 12
elif code == '500' or code == '509' or code == '510' or code == '514' or code == '515' or code == '501':
code = 13
elif code == '511' or code == '512' or code == '513':
code = 14
elif code == '900':
code = 16
elif code == '901':
code = 17
elif code == '999':
code = 18

print(real)
print(hum)
print(tempfl)
print(code)

fweather_url = "https://devapi.heweather.net/v7/weather/3d?location="+location+"&key="+key
fweather_response = urllib.request.urlopen(fweather_url)
fweather_responsebyte = fweather_response.read()
bufff = BytesIO(fweather_responsebyte)
ff = gzip.GzipFile(fileobj=bufff)
fweather_result = ff.read().decode('utf-8')
data3 = json.loads(fweather_result)
high = data3["daily"][0]["tempMax"]
low = data3["daily"][0]["tempMin"]
fcode = data3["daily"][0]["iconDay"]
thigh = data3["daily"][1]["tempMax"]
tlow = data3["daily"][1]["tempMin"]
tcode = data3["daily"][1]["iconDay"]
if fcode == '100' or fcode == '150':
fcode = 19
elif fcode == '104' or fcode == '154':
fcode = 20
elif fcode == '101' or fcode == '103' or fcode == '102' or fcode == '153':
fcode = 21
elif fcode == '300' or fcode == '301' or fcode == '350' or fcode == '351':
fcode = 22
elif fcode == '302' or fcode == '303':
fcode = 23
elif fcode == '304':
fcode = 24
elif fcode == '305' or fcode == '309':
fcode = 25
elif fcode == '306' or fcode == '307' or fcode == '308' or fcode == '310' or fcode == '311' or fcode == '312' or fcode == '313' or fcode == '314' or fcode == '315' or fcode == '316' or fcode == '317' or fcode == '318' or fcode == '399':
fcode = 26
elif fcode == '404' or fcode == '405' or fcode == '406' or fcode == '456':
fcode = 27
elif fcode == '400' or fcode == '408' or fcode == '407' or fcode == '457':
fcode = 28
elif fcode == '401' or fcode == '402' or fcode == '403' or fcode == '499' or fcode == '410' or fcode == '409':
fcode = 29
elif fcode == '503' or fcode == '504' or fcode == '507' or fcode == '508':
fcode = 30
elif fcode == '500' or fcode == '509' or fcode == '510' or fcode == '514' or fcode == '515' or fcode == '501':
fcode = 31
elif fcode == '511' or fcode == '512' or fcode == '513':
fcode = 32
elif fcode == '900':
fcode = 33
elif fcode == '901':
fcode = 34
elif fcode == '999':
fcode = 35

if tcode == '100' or tcode == '150':
tcode = 19
elif tcode == '104' or tcode == '154':
tcode = 20
elif tcode == '101' or tcode == '103' or tcode == '102' or tcode == '153':
tcode = 21
elif tcode == '300' or tcode == '301' or tcode == '350' or tcode == '351':
tcode = 22
elif tcode == '302' or tcode == '303':
tcode = 23
elif tcode == '304':
tcode = 24
elif tcode == '305' or tcode == '309':
tcode = 25
elif tcode == '306' or tcode == '307' or tcode == '308' or tcode == '310' or tcode == '311' or tcode == '312' or tcode == '313' or tcode == '314' or tcode == '315' or tcode == '316' or tcode == '317' or tcode == '318' or tcode == '399':
tcode = 26
elif tcode == '404' or tcode == '405' or tcode == '406' or tcode == '456':
tcode = 27
elif tcode == '400' or tcode == '408' or tcode == '407' or tcode == '457':
tcode = 28
elif tcode == '401' or tcode == '402' or tcode == '403' or tcode == '499' or tcode == '410' or tcode == '409':
tcode = 29
elif tcode == '503' or tcode == '504' or tcode == '507' or tcode == '508':
tcode = 30
elif tcode == '500' or tcode == '509' or tcode == '510' or tcode == '514' or tcode == '515' or tcode == '501':
tcode = 31
elif tcode == '511' or tcode == '512' or tcode == '513':
tcode = 32
elif tcode == '900':
tcode = 33
elif tcode == '901':
tcode = 34
elif tcode == '999':
tcode = 35

print(high)
print(low)
print(fcode)
print(thigh)
print(tlow)
print(tcode)

aqiindex_url = "http://219.233.250.38:8087/AQI/PatrolHandler.do?provider=SEMCShare.ChildWeb&method=RegionData&groupID=213&tdsourcetag=s_pctim_aiomsg"
aqiindex_response = requests.get(aqiindex_url)
aqiindex_result = aqiindex_response.text
result = json.loads(aqiindex_result)

PM25 = str(result[1]['pm25Value'])
PM10 = str(result[1]['pm10Value'])
aqi = str(result[1]['primaryPollutantAQI'])
o3 = str(result[1]['o3Value'])
primary = str(result[1]['primaryPollutantType'])
print(PM25)
print(PM10)
print(aqi)
print(o3)

publish.single("casatift/weather/shanghai/tempfl", tempfl, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/real", real, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/hum", hum, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/code", code, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/high", high, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/low", low, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/fcode", fcode, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/tcode", tcode, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/thigh", thigh, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/tlow", tlow, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/pm25", PM25, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/pm10", PM10, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/aqi", aqi, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
publish.single("casatift/weather/shanghai/o3", o3, qos=1, hostname=host, port=port, client_id=client_id,
auth={'username': user, 'password': password})
if primary == 'PM2.5':
primary = 1
elif primary == 'PM10':
primary = 2
elif primary == 'O3':
primary = 3
elif primary == 'CO':
primary = 4
elif primary == 'SO2':
primary = 5
elif primary == 'NO2':
primary = 6
publish.single("casatift/weather/shanghai/primary", primary, qos=1, hostname=host, port=port,
client_id=client_id, auth={'username': user, 'password': password})

mqtt 数据上传与传感器数据处理

  • BME280传感器实物 温湿度传感器

激光照射在空气中,遇到颗粒物后由于光的折射使激光形成散射光,再通过散射光收集装置,将空气中反射回来的散射光收集起来,持续一段时间之后,传感器将会把单位体积内连续收集的不同的散射光进行整理,计算与分析,其优点在于体积小,工作无噪音,测量精度高。

img

pm10 pm2.5 hum(温度,湿度)数据获取与发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
on System#Boot do
GPIO,0,0
timerSet,1,60
SerialSend SPG(1);
endon

On Rules#Timer=1 do
GPIO,0,1
timerSet,2,10
endon

On PMS#pm10 do
Publish domoticzc/in,'{"idx":152,"nvalue":0,"svalue":"[PMS#pm2.5]"}' “这一段是mqtt发送pm2.5数值,请自行更改topic和payload,并将此句说明删除”
Publish domoticzc/in,'{"idx":151,"nvalue":0,"svalue":"[PMS#pm10]"}'“这一段是mqtt发送pm10数值,请自行更改topic和payload,并将此句说明删除”
Publish domoticzc/in,'{"idx":313,"nvalue":0,"svalue":"[PMS#temp];[PMS#hum];0"}'“这一段是mqtt发送温湿度,请自行更改topic和payload,并将此句说明删除”
SerialSend DS16(214,104,'indoor: [PMS#temp]/[PMS#hum]% ',15,0);
Delay 20
SerialSend DS16(214,120,'pm2.5: [PMS#pm2.5]ug/m3 ',18,0);
Delay 20
if [PMS#pm10] > 50
timerSet,2,30
endif
endon

On Rules#Timer=2 do
if [PMS#pm10] > 35
GPIO,0,0
timerSet,1,60
else
GPIO,0,0
timerSet,1,120
endif
endon
  • PMS5003S系列传感器实物图

img

  • co2 二氧化碳传感器 Sense air s8

氧化碳传感器的工作原理是非色散红外:测量被红外传感器吸收的红外光,来判断被测二氧化碳的浓度,再通过数字模拟,仪器仪表就能够计算出被测气体的浓度。
img

  • 光线传感器 BH1750

img

temp,co2(温度 二氧化碳)数据获取与转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
on MQTTCODE#PCODE do
If %systime% > 07:00:00 and %systime% < 21:00:00
SerialSend SEBL(100);
Delay 20
else
SerialSend SEBL(5);
Endif
SerialSend BPIC(1,0,0,[MQTTNOW#CODE]);
Delay 20
SerialSend DS64(210,45,'[MQTTNOW#NOW]/[MQTTNOW#TEMPFL] ',15,0);
Delay 20
SerialSend DS16(214,104,'indoor: [PMS#temp]/[PMS#hum]%',15,0);
Delay 20
SerialSend DS16(214,120,'pm2.5: [PMS#pm2.5]ug/m3 ',18,0);
Delay 20
SerialSend DS16(214,136,'co2: [CO2#ppm] ppm ',16,0);
Delay 20
if [MQTTCODE#PCODE] = 1
SerialSend DS16(214,153,'aqi: [MQTTAQI#AQI](PM2.5) ',15,0);
endif
if [MQTTCODE#PCODE] = 2
SerialSend DS16(214,153,'aqi: [MQTTAQI#AQI](PM10) ',15,0);
endif
if [MQTTCODE#PCODE] = 3
SerialSend DS16(214,153,'aqi: [MQTTAQI#AQI](O3) ',15,0);
endif
if [MQTTCODE#PCODE] = 4
SerialSend DS16(214,153,'aqi: [MQTTAQI#AQI](CO) ',15,0);
endif
if [MQTTCODE#PCODE] = 5
SerialSend DS16(214,153,'aqi: [MQTTAQI#AQI](SO2) ',15,0);
endif
if [MQTTCODE#PCODE] = 6
SerialSend DS16(214,153,'aqi: [MQTTAQI#AQI](NO2) ',15,0);
endif
Delay 20
SerialSend BPIC(1,340,52,35);
Delay 20
SerialSend DS24(20,204,'%syshour_0%:%sysmin_0% ',15,0);;
Delay 20
SerialSend BPIC(1,207,200,[MQTTCODE#FCODE]);
Delay 20
SerialSend DS24(239,204,'[MQTTTEMP#HIGH]/[MQTTTEMP#LOW] ',15,0);
Delay 20
SerialSend BPIC(1,300,200,[MQTTCODE#TCODE]);
Delay 20
SerialSend DS24(332,204,'[MQTTTEMP#THIGH]/[MQTTTEMP#TLOW] ',15,0);
Delay 20
endon

on CO2#ppm do
Publish domoticzc/in,'{"idx":25,"nvalue":[CO2#ppm]}'“这一段是mqtt发送CO2数值,请自行更改topic和payload,并将此句说明删除”
SerialSend DS16(214,136,'co2: [CO2#ppm] ppm',16,0);
Delay 20
endon

on Clock#Time=All,**:** do
SerialSend DS24(20,204,'%syshour_0%:%sysmin_0% ',15,0);;
endon

  • 显示屏UsartGPU26B

img

硬件连接

如图

img

连接前数据线改装

我们需要改装一个一转六的杜邦线来供使用,由于各个传感器之间所需要的工作电压也不尽相同,包含5v和3.3的工作电压,因而还需要制作两根一转4的杜邦线以供使用,而光线传感器和温湿度传感器,考虑到它们是I2C的接口,可以用并联的方式连接,便还需要2根1转2的杜邦线,其余需要准备若干单根的杜邦线去单独连接开关,二氧化碳传感器,屏幕

img

屏幕程序写入

串口屏幕的VCC接口接USB -TTL的5V,两者的GND接GND,形成回路,工具的RX接收端口接屏幕的TX发送端口,工具的发送TX端口接屏幕的RX接受端口

在控制面板中找到电脑端口,选择端口为USB-TTL的端口,鼠标右键点击,将波特率设置为115200

下载GpuMaker,打开首页,使用串口命令将我们所需要开机屏幕欢迎界面信息,时间显示信息,温湿度显示信息,PM2.5,PM10,以及二氧化碳界面显示信息导入。

  • 举例

img

线路连接

以DI mini为连接起点,用改装的一转六杜邦线连接所有其他设备的GN,我们需拿出一根一转四的杜邦线,杜邦线的其中一头连接D1 mini的5v,另外几头分别连接颗粒物传感器,二氧化碳传感器及串口屏幕的VCC端,保证有足够的电压输入给我们的设备

使用一根一转四的杜邦线连接D1 mini的3.3v端,其余线口分别去连接开关,光线传感器,温湿度传感器等额定电压为3.3v的传感器

img

使用两根一转二的杜邦线使温湿度传感器的SDA与光线传感器的SDA相连,SCL与SCL相连,其中SDA表示数据传送线,SCL表示数据数据控制线,

D1 mini的D6端,需要用单线去连接颗粒物传感器的TXD控制线,二氧化碳传感器的UART OX/OT 端接MCU的D3、D4端.开关的output接DI mini的D5端

img

D1 mini(esp32) 测试 页面配置

将D1 mini与电脑连接,去GitHub下载ESPEASY固件,点开文件夹运行FlashESP.exe,在设备管理器中选择D1 mini端口号,选择test_ESP8266_4096.bin的即可

移植之后翻看板子背面,轻按下reset使MCU重启,然后最好通过电脑搜索WIFI,之后就能搜索到一个名为ESP-Easy_0的WIFI。WIFI连接密码为configure,连入后会在页面看到此时你电脑所监测到的所有WiFi网络名称

找到你自己的WiFi网络,点击连接,网站会提醒你需要等待20秒,之后会告知IP的地址,浏览器中输入这个IP地址,等待数秒之后就能进入ESPEASY的配置页面,出现IP地址即表示配置成功

img

硬件配置

web 检测物配置

  • 打开D1 mini,进入ESPEASY的网络配置界面,找到tools界面,配置各项功能:

其中rules的勾选是为了给按键设置切换规则及延迟时间,勾选NTP可以自动达成网络时间同步协议

img

  • 检测值

以二氧化碳传感器为例,在Device处选择好设备型号,Name处填写好设备名称,点击Enabled添加后,一定注意端口的设置与设备连接时对应,由于与D1 mini模块连接时二氧化碳连接5v电压,为保证设备正常运行,需要连接上拉电阻引脚。因此此处 GPIO处选择D3,D4,Sensor处选择二氧化碳Carbon Dioxide,检测刷新时间定为60秒,而后将输出单位设置为PPM。

img

命令配置

当所有设备添加设置完毕后,点击Rules选项,如图所示,设置按钮响应,其中delay指令代表延迟,on Button#Switch=1 do 这一行表示,当开关的状态为开时,执行do之后的 if逻辑命令,其中命令SPG表示显示第几界面,具体情况看数据显示,CIR(156,120,119,[Dummy#cc])命令表示在屏幕中坐标为(156,120)的位置用#cc的颜色画一个半径为180的圆形。BOXF命令可以在屏幕上呈现一个方框,而DS64(98,65,‘[PMS#pm10]’,15)命令表示在(98,65)坐标处用15号颜色显示颗粒物传感器中PM10的数据。

img

效果

img

最终效果

如图

img

img

img

img

See

Prev:
基于AX516 FPGA 开发板的音频录制与回放
Next:
基于ESP32-CAM的智能猫眼实现