『瀚思彼岸』» 智能家居技术论坛

 找回密码
 立即注册
查看: 62764|回复: 81

[修仙教程] 打造属于自己的空气检测器(实现本地、home-assistant和homeki...

  [复制链接]

26

主题

94

帖子

1943

积分

金牌会员

Rank: 6Rank: 6

积分
1943
金钱
1814
HASS币
60

教程狂人论坛风云人物突出贡献

发表于 2017-5-23 11:27:02 | 显示全部楼层 |阅读模式
本帖最后由 FrankLv 于 2017-5-23 22:07 编辑

最近几年,雾霾收到大众的关注,特别是在北京这样的城市,雾霾天也习以为常了,气象部分也公布一些检测数据,但是对于数据的准确度,本人是持怀疑态度的,所以才有了下面的折腾。
目前空气监测设备某宝上面已经有很多了,但是准确度不敢保证,因为使用的传感器质量有好坏,另一方面,为了后续能够与家中的净化系统联动,而目前市面上的监测设备多数无法联网,所以就萌生自己DIY一个空气监测装置。监测及查看方式如下所示:
所使用到的设备有:esp8266开发板(带WIFI功能)、PMS5003ST G5T(攀藤)、senseAir红外CO2二氧化碳传感器 S8 0053、DS-HCHO甲醛传感器(攀藤)以及液晶显示屏(1.3寸 I2C OLED)。
由于家中监测甲醛只是偶尔会用到,所以我就将甲醛传感器单独做成一个监测设备,颗粒物+温湿度+CO2做成一个监测设备,这样颗粒物的那个设备可以长期在线,甲醛的那个设备随用随开。
先来介绍下各个设备的作用吧。esp8266开发板是承担读取各个传感器的数据以及远传数据到我的服务器,开发语言可以使用lua或者arduino,本人使用的是lua语言;PMS5003ST G5T是颗粒物及温湿度传感器,可以监测空气中的PM1、PM10、PM2.5以及温度和湿度,其读取方式为主动读取(即直接读数);S8二氧化碳传感器可以监测空气中的二氧化碳,其读数为被动读取(需要写入数据指令过后再能读取数据);DS-HCHO甲醛传感器可以监测空气中的甲醛浓度,其读数也是被动读取的;液晶显示屏可以实时本地显示各个设备的监测值。
开发使用的软件:notepad++、ESP8266Flasher、ESPlorer,以及modemcu固件
notepad++负责代码的编写,下载地址:https://notepad-plus-plus.org/
ESP8266Flasher负责将官方固件刷入esp8266,下载地址:https://github.com/nodemcu/nodemcu-flasher
ESPlorer负责将编写的代码上传到esp8266,下载地址:http://esp8266.ru/esplorer/#download
固件采用nodemcu的官方固件,此固件需要进行定制,只需输入邮箱和需要的扩展模块即可(扩展模块不能全部选择,不然无法完成制作),地址:https://nodemcu-build.com/
我使用到的模块:cjson, crypto, dht, enduser_setup, file, gpio, http, i2c, mdns, mqtt, net, node, ow, pwm, rc, sntp, spi, struct, tmr, u8g, uart, websocket, wifi, tls。模块的选择可以根据自己所需的函数来确定。
1.自动运行脚本编写
以上准备完毕后就开始程序的编写了,代码1是PMS5003ST G5T(攀藤)和senseAir红外CO2二氧化碳传感器 S8 0053组合的脚本,您编写的时候可以根据自己的需求进行修改
function initOLED(sda, scl) --Set up the u8glib lib
sla = 0x3c
i2c.setup(0, sda, scl, i2c.SLOW)
disp = u8g.sh1106_128x64_i2c(sla)
disp:setFont(u8g.font_6x10)
disp:setFontRefHeightExtendedText()
disp:setDefaultForegroundColor()
disp:setFontPosTop()
end --初始化OLED
function initSCREEN()
SDA = 5  --D5
SCL = 6  --D6
initOLED(SDA, SCL) --需要换成自己的串口
disp:firstPage()
repeat
disp:drawFrame(15,15,100,25)
disp:drawStr(25,25,"PM2.5 Detector")
disp:drawStr(30,50,"Frank's Home")
until disp:nextPage() == false
end --初始化屏幕
function dispOLED(d1, d2, d3, d4, d5) --dispOLED(pm25, co2, aqi1, temp, hum)
disp:firstPage()
repeat
disp:drawFrame(25,0,80,12)
disp:drawStr(30, 2, "Frank's Home")
disp:drawStr(0, 15, "PM2.5")
disp:drawStr(0, 23, "ug/m3")
disp:drawStr(98, 15, "CO2")
disp:drawStr(98, 23, "ppm")
disp:drawStr(60, 15, "AQI")
if (d1 == nil) then
disp:drawStr(0, 37, "")
else
disp:drawStr(0, 37, d1)
end
if (d2 == nil) then
disp:drawStr(98, 37, "")
else
disp:drawStr(98, 37, d2)
end
if (d3 == nil) then
disp:drawStr(60, 35, "")
else
disp:drawStr(60, 35, d3)
end
if (d4 == nil) then
disp:drawStr(0, 52, "Temp ".."".."C")
else
disp:drawStr(0, 52, "Temp "..d4.."C")
end
if (d5 == nil) then
disp:drawStr(66, 52, "Humi ".."".."%")
else
disp:drawStr(66, 52, "Humi "..d5.."%")
end
until disp:nextPage() == false
end --OLED显示
function initWIFI()
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID", "password")
wifi.sta.connect()
tmr.alarm(1, 500, 1,
function()
j =j + 1
if wifi.sta.getip()== nil then
disp:firstPage()
repeat
disp:drawFrame(15,5,100,25)
disp:drawStr(25,15,"PM2.5 Detector")
disp:drawStr(30,35,"Frank's Home")
disp:drawStr(33,50,"IP Getting")
until disp:nextPage() == false
else
j = 0
tmr.stop(1)
disp:firstPage()
repeat
disp:drawFrame(15,5,100,25)
disp:drawStr(25,15,"PM2.5 Detector")
disp:drawStr(30,35,"Frank's Home")
disp:drawStr(20,50,"IP:"..wifi.sta.getip())
until disp:nextPage() == false
end
if wifi.sta.getip()== nil and j >= 20 then
j = 0
tmr.stop(1)
disp:firstPage()
repeat
disp:drawFrame(15,5,100,25)
disp:drawStr(25,15,"PM2.5 Detector")
disp:drawStr(30,35,"Frank's Home")
disp:drawStr(25,50,"IP unavailable")
until disp:nextPage() == false
end
end) -- function
end -- initWIFI
function sendData(t1, t2, t3, t4, t5, t6, t7) --sendData(aqi1,pm25,pm10,pm01,temp,hum,co2)
m = mqtt.Client()
m:connect("服务器地址", 端口, 0, 0,
function(conn)
print("Connected to MQTT")
if t1 ~= nil then
m:publish("/aqi1", t1, 0, 0,
function(client)
end)
end
if t2 ~= nil then
m:publish("/pm25", t2, 0, 0,
function(client)
end)
end
if t3 ~= nil then
m:publish("/pm10", t3, 0, 0,
function(client)
end)
end
if t4 ~= nil then
m:publish("/pm01", t4, 0, 0,
function(client)
end)
end
if t5 ~= nil then
m:publish("/temp", t5, 0, 0,
function(client)
end)
end
if t6 ~= nil then
m:publish("/hum", t6, 0, 0,
function(client)
end)
end
if t7 ~= nil then
m:publish("/co2", t7, 0, 0,
function(client)
end)
end
end,
function(client, reason)
print("Connection failed, reason: " .. reason)
end)
end -- 连接到MQTT服务器
function parse(data)
local bs = {}
for i = 1, #data do
bs = string.byte(data, i)
end
if (bs[1] ~= 0x42) or (bs[2] ~= 0x4d) then
return nil
end
local d = {}
d['pm1_0-CF1-ST'] = bs[5] * 256 + bs[6]
d['pm2_5-CF1-ST'] = bs[7] * 256 + bs[8]
d['pm10-CF1-ST']  = bs[9] * 256 + bs[10]
d['pm1_0-AT']     = bs[11] * 256 + bs[12]
d['pm2_5-AT']     = bs[13] * 256 + bs[14]
d['pm10-AT']      = bs[15] * 256 + bs[16]
d['0_3um-count']  = bs[17] * 256 + bs[18]
d['0_5um-count']  = bs[19] * 256 + bs[20]
d['1_0um-count']  = bs[21] * 256 + bs[22]
d['2_5um-count']  = bs[23] * 256 + bs[24]
d['temperature']  = bs[25] * 256 + bs[26]
d['humidity']     = bs[27] * 256 + bs[28]
return d
end -- parse
function aqipm25(t)
if (t <= 12) then return t * 50 / 12
elseif (t <= 35) then return 50 + (t - 12) * 50 / 23
elseif (t <= 55) then return 100 + (t - 35) * 5 / 2
elseif (t <= 150) then return 150 + (t - 55) * 2
elseif (t <= 350) then return 50 + t
else return 400 + (t - 350) * 2 / 3
end
end --aqipm25
function aqipm10(t)
if (t <= 55) then return t * 50 / 55
elseif (t <= 355) then return 50 + (t - 55) / 2
elseif (t <= 425) then return 200 + (t - 355) * 10 / 7
elseif (t <= 505) then return 300 + (t - 425) * 10 / 8
else return t - 105
end
end --aqipm10
function aqi(t25,t10)
if (t25 > t10) then return math.ceil(t10)
else return math.ceil(t25)
end
end --aqi
function getaqi()
uart.alt(0)
data = nil
uart.setup(0, 9600, 8, 0, 1, 0)
uart.on("data", 32,function(data)
pms5 = parse(data)
if pms5 ~= nil then
pm01 = pms5['pm1_0-AT']
pm25 = pms5['pm2_5-AT']
pm10 = pms5['pm10-AT']
temp = pms5['temperature'] / 10
hum = pms5['humidity'] / 10
aqi25 = aqipm25(pm25)
aqi10 = aqipm10(pm10)
aqi1  = aqi(aqi25,aqi10)
end
getco2()
end,0)
end --getaqi
function getco2()
uart.alt(1)
data = nil
uart.setup(0, 9600, 8, 0, 1, 0)
tmr.alarm(3, 10000, 1, function()
data = nil
uart.write(0,0xFE,0x04,0x00,0x03,0x00,0x01,0xD5,0xC5)
uart.on("data", 7, function(data)
co = data
if (string.len(co) == 7) and ((string.byte(co,1) == 0xFE) or (string.byte(co,2) == 0x04)) then
co2 = string.byte(co,4)*256 + string.byte(co,5)
k = k + 1
tmr.stop(3)
dispOLED(pm25, co2, aqi1, temp, hum)
if wifi.sta.getip()~= nil and k >= 2 then
k = 0
sendData(aqi1,pm25,pm10,pm01,temp,hum,co2)
end
getaqi()
end
end,0)
end)
end --getco2
j = 0
k = 0
pms5 = nil
aqi1 = nil
pm25 = nil
pm10 = nil
pm01 = nil
temp = nil
hum = nil
co = nil
co2 = nil
disp = nil
initSCREEN()
initWIFI()
getaqi()

代码2是单个甲醛传感器的代码

function initOLED(sda, scl) --Set up the u8glib lib
sla = 0x3c
i2c.setup(0, sda, scl, i2c.SLOW)
disp = u8g.sh1106_128x64_i2c(sla)
disp:setFont(u8g.font_6x10)
disp:setFontRefHeightExtendedText()
disp:setDefaultForegroundColor()
disp:setFontPosTop()
end --初始化OLED
function initSCREEN()
SDA = 5  --D5
SCL = 6  --D6
initOLED(SDA, SCL) --需要换成自己的串口
disp:firstPage()
repeat
disp:drawFrame(15,15,100,25)
disp:drawStr(28,25,"HCHO Detector")
disp:drawStr(30,50,"Frank's Home")
until disp:nextPage() == false
end --初始化屏幕
function dispOLED(d1) --dispOLED(HCHO)
disp:firstPage()
repeat
disp:drawFrame(15,15,100,20)
disp:drawStr(30,20,"Frank's Home")
disp:drawStr(10, 50, "HCHO")
disp:drawStr(90, 50, "mg/m3")
if (d1 == nil) then
disp:drawStr(50, 50, "")
else
disp:drawStr(50, 50, d1)
end
until disp:nextPage() == false
end --OLED显示
function initWIFI()
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID", "password")
wifi.sta.connect()
tmr.alarm(1, 500, 1,
function()
j =j + 1
if wifi.sta.getip()== nil then
disp:firstPage()
repeat
disp:drawFrame(15,5,100,25)
disp:drawStr(25,15,"HCHO Detector")
disp:drawStr(30,35,"Frank's Home")
disp:drawStr(33,50,"IP Getting")
until disp:nextPage() == false
else
j = 0
tmr.stop(1)
disp:firstPage()
repeat
disp:drawFrame(15,5,100,25)
disp:drawStr(25,15,"HCHO Detector")
disp:drawStr(30,35,"Frank's Home")
disp:drawStr(20,50,"IP:"..wifi.sta.getip())
until disp:nextPage() == false
end
if wifi.sta.getip()== nil and j >= 20 then
j = 0
tmr.stop(1)
disp:firstPage()
repeat
disp:drawFrame(15,5,100,25)
disp:drawStr(25,15,"HCHO Detector")
disp:drawStr(30,35,"Frank's Home")
disp:drawStr(25,50,"IP unavailable")
until disp:nextPage() == false
end
end) -- function
end -- initWIFI
function sendData(t1) --sendData(HCHO)
m = mqtt.Client()
m:connect("服务器地址", 端口, 0, 0,
function(conn)
print("Connected to MQTT")
if t1 ~= nil then
m:publish("/HCHO", t1, 0, 0,
function(client)
end)
end
end,
function(client, reason)
print("Connection failed, reason: " .. reason)
end)
end -- 连接到MQTT服务器
function gethcho()
uart.alt(0)
data = nil
uart.setup(0, 9600, 8, 0, 1, 0)
tmr.alarm(2, 5000, 1, function()
data = nil
uart.write(0,0x42,0x4d,0x01,0x00,0x00,0x00,0x90)
uart.on("data", 10, function(data)
hh = data
if (string.len(hh) == 10) and (string.byte(hh,1)==0x42) and (string.byte(hh,2)==0x4d)then
HCHO = (string.byte(hh,7)*256+string.byte(hh,8))/100
k = k + 1
dispOLED(HCHO)
if wifi.sta.getip()~= nil and k >= 4 then
k = 0
sendData(HCHO)
end
end
end, 0)
end)
end --gethcho
j = 0
k = 0
hh = nil
HCHO = nil
disp = nil
initSCREEN()
initWIFI()
gethcho()

以上代码需要根据自己的配置修改保存为init.lua,使用ESPlorer上传到你的esp8266即可自动运行(此代码是上传数据到MQTT服务器上面,上传到其他平台需要自行修改)。
2.固件及脚本的刷入
接下来就是刷写固件到esp8266中,ESP8266Flasher是比较傻瓜的输入方式,当驱动安装完成后直接识别COM口,这个就不需要你管了,刷入固件需要选择如下方式即可(切记第二个齿轮后面是0x3FC000),过后回到Operation界面点击flash即可:
刷入固件完成后就可以刷入自己写的脚本了,将脚本命名为init.lua,通过ESPlorer连接esp8266来上传脚本,具体步骤可见下图:
3.传感器的接线与外壳DIY
上传完后,接下来就是各种传感器和设备的连接,具体的连接方式可以间下图所示,由于网上有相关的资料我就直接引用了(自己DIY的灵感也是取自这位大神,自己在代码上面做了优化和修改),出处为:http://post.smzdm.com/p/539326/
具体实物连接见下图:
在后面就是DIY外壳了,外壳使用废旧的卡片!
4. HomeAssistant中的配置
最后在说说HASS中的配置,由于使用的MQTT服务的发布和订阅功能,如果需要实时显示监测数据就需要对HASS进行配置,订阅的主题和esp8266发布的主题一致。本人使用的主题是:
/aqi1代表AQI空气指数
/pm25代表PM2.5
/pm10代表PM10
/pm01代表PM1
/temp代表温度
/hum代表湿度
/co2代表二氧化碳
/HCHO代表甲醛

首先需要测试下MQTT通道是否能用,将以下代码放入configuration.yaml里面
mqtt:
broker: MQTT 地址
port: MQTT端口

过后在将如下代码放入配置子文件中,如果不想放在子文件中,可以直接写入configuration.yaml中,需要去除platform前面的“-”。
- platform: mqtt
state_topic: "/aqi1"
name: "AQI"
unit_of_measurement: "AQI"
- platform: mqtt
state_topic: "/pm25"
name: "PM2.5"
unit_of_measurement: "ug/m3"
- platform: mqtt
state_topic: "/pm10"
name: "PM10"
unit_of_measurement: "ug/m3"
- platform: mqtt
state_topic: "/pm01"
name: "PM1"
unit_of_measurement: "ug/m3"
- platform: mqtt
state_topic: "/temp"
name: "Temperature"
unit_of_measurement: "°C"
- platform: mqtt
state_topic: "/hum"
name: "Humidity"
unit_of_measurement: "%"
- platform: mqtt
state_topic: "/co2"
name: "CO2"
unit_of_measurement: "ppm"
- platform: mqtt
state_topic: "/HCHO"
name: "HCHO"
unit_of_measurement: "mg/m3"

格式可参照如下:
在进行分组就可以将在顶端显示的图标移至栏中显示:
最后在HASS中的显示效果如下:
过后再将其和Homekit联动,配置方法在我的另一个帖子中有涉及,所以就不在这边累述了

最后附上:esp_init_data_default,这个也可以自行google下载也可以。


群里面有人折腾lua,我就将我的lua文件分享下: init.lua (8.33 KB, 下载次数: 72)


另外,忘了说显示屏接法了,我的代码是接法是SDA接D5, SCL接D6。



好了就写到这里吧!有问题可以站内PM我,希望能帮助大家打造自己的空气检测器。

评分

参与人数 1金钱 +1 收起 理由
jyz_0501 + 1 谢谢分享!

查看全部评分

回复

使用道具 举报

14

主题

184

帖子

1701

积分

金牌会员

Rank: 6Rank: 6

积分
1701
金钱
1517
HASS币
0
发表于 2017-5-23 11:35:40 | 显示全部楼层
顶一个,是沙发不
回复

使用道具 举报

12

主题

397

帖子

2289

积分

金牌会员

Rank: 6Rank: 6

积分
2289
金钱
1892
HASS币
10
发表于 2017-5-23 18:28:03 | 显示全部楼层
支持  支持
回复

使用道具 举报

3

主题

219

帖子

932

积分

高级会员

Rank: 4

积分
932
金钱
713
HASS币
0
发表于 2017-5-23 18:56:49 来自手机 | 显示全部楼层
感谢分享
回复

使用道具 举报

30

主题

997

帖子

4153

积分

论坛元老

Rank: 8Rank: 8

积分
4153
金钱
3151
HASS币
0

活跃会员

发表于 2017-5-23 21:34:40 | 显示全部楼层
谢谢分享!
回复

使用道具 举报

0

主题

70

帖子

1025

积分

金牌会员

Rank: 6Rank: 6

积分
1025
金钱
955
HASS币
0
发表于 2017-5-23 22:12:44 | 显示全部楼层
感谢分享
回复

使用道具 举报

5

主题

253

帖子

1946

积分

金牌会员

Rank: 6Rank: 6

积分
1946
金钱
1693
HASS币
0
发表于 2017-5-23 23:49:12 | 显示全部楼层

谢谢分享!!!
回复

使用道具 举报

24

主题

608

帖子

3488

积分

元老级技术达人

积分
3488
金钱
2875
HASS币
0

卓越贡献

发表于 2017-5-24 22:13:28 | 显示全部楼层
真心不错!!首先我得去马家店买东西了。。
回复

使用道具 举报

1

主题

281

帖子

1224

积分

金牌会员

Rank: 6Rank: 6

积分
1224
金钱
942
HASS币
0
发表于 2017-5-25 06:45:13 来自手机 | 显示全部楼层
自己做太强
回复

使用道具 举报

0

主题

19

帖子

126

积分

注册会员

Rank: 2

积分
126
金钱
107
HASS币
0
发表于 2017-5-25 12:30:41 | 显示全部楼层
谢谢分享!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian

GMT+8, 2024-11-26 10:49 , Processed in 0.317036 second(s), 36 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表