树莓派打造的家庭环境监控

最近空气质量又有下降的趋势,想想手头的RPI2B,干脆拿来改造成一个环境监控系统吧,放在客厅随时可以看到,情况不对马上开净化器😊。传感器部分都是以前就买了的,平时拿来偶尔玩一下。主要是三个:一个空气质量检测的激光传感器,一个温湿度的DHT22,一个凑数的带光感的数模转换。屏幕是一块5寸的HDMI破电阻屏,用来搭建本体的是两包白色塑料小积木,所有东西均购自淘宝,包括树莓派物料成本大约500+。说起来这个成本很高了,主要是树莓派本身加一个屏幕,光用来做这个有点浪费。好在我本来也是要放在那边当小服务器用,跑个定时脚本,偶尔看个kernel什么的。

硬件部分

因为传感器都是做好了的,树莓派自己也引出了两排Pin脚,硬件其实就是连几根线而已。只有一个问题需要考虑就是传感器都是上电即工作的,如果一直通电的话,过不了多久估计就烧了。所以我决定用GPIO当作开关来控制sensor的电源,只在需要获得数据时打开。这样一来那两个只需要3.3V的sensor还好,直接用GPIO给电即可,空气质量那个传感器需要5V的就麻烦了。还有LCD屏幕,也是要求5V,并且本身是USB供电,只好把原有的拨动开关拆了,小改电路,使用一个接入的5V电源来解决。
最终的硬件连线图如下:

你看到的是非授权版本!爬虫凶猛,请尊重知识产权!

转载请注明出处:http://conanwhf.github.io/2017/09/16/EnvMonitor/

访问原文「树莓派打造的家庭环境监控」获取最佳阅读体验并参与讨论



图中的空气质量sensor和屏幕的电源都外接了一个场效应管,用来决定5V电源Pin的通断,由一个GPIO接入来控制。空气质量的数据是UART传输,PCF8591的接口是I2C,而DHT11则直接使用GPIO的一个Pin来传输数据(总感觉有点不靠谱的),另外添加了三个LED灯来实时反应空气质量的等级。

软件部分

软件语言使用的是Python3,一个不在乎速度的上上之选。我虽然是那种一边百度一边写python的超级新手,但用过以后就再也不想换回繁琐的C了,更别说在树莓派上有那么多现成的库可以给你用。

整体结构

软件方面只有三个主要的线程,两个负责拿数据,一个负责UI:

程序跑起来后除了UI,会自己开两个线程进行循环,在循环中获取系统、sensor的状态并刷新在UI上。运行的模式有两种:实时和日常。实时模式主要是给需要迅速获得状态的时候使用,例如刚刚打开净化器想看看质量是否在持续改善,或者是网络有问题的时候看看能否ping通google;而日常模式则是无人关注的日常行为。主界面有一个按钮可以切换这两种模式,线程内部会根据切换后的状态判断自己应该睡眠多久再进行下一轮状态更新。
在日常模式中,系统每个循环会获取所有传感器的数据并更新上报至Yeelink,以及根据空气质量调整相对应的LED灯。而在实时模式,每个循环内则会另外获取一些操作系统状态来显示,并刷新ping服务器(Google)的结果。除了日常和实时两个模式中获取的状态,还有一个单独的线程用来刷新开机时间和公网IP。这两个数据对实时的要求不高,更新频率也很低,就直接固定为30分钟一次了。

传感器数据获取

传感器方面,以前都是用过的,底层的库也很成熟,所以代码量很小,主要是封装一下变成类库。要说重新写的部分,就是自己写了个Power的类,免得分散在GPIO的相关代码里面,很难看。以前用的DHT11,这次换成DHT22,懒得再自己写程序了,直接用的官方库。

UI设计

UI部分我用的Python自带的tkinter,虽然是头一次用,但毕竟是Python,随便看看文档就搞定了,API简单得不得了。当然,主要是因为我的UI很简单:显示label,button,监听点击和关闭事件就是所有需求了。而这些对于任何一个语言的UI来说,用法基本都是通用的。唯一有点不同的是它对于控件的排列方式,使用了个叫grid的方法,实际上是将整个界面划分为N*M的小方格,再通过对控件所占用方格的位置、大小、对齐属性,来决定控件的具体位置。
另外一个小问题是在调试时发生的,我发现在SSH的状态下,无法从命令行启动UI,报错的具体内容我忘了,大概意思是找不到具体设备。网上搜了一下有人遇到同样的问题,年代久远,且无人回答。后来看了下API的文档,发现窗口的初始化函数Tk是可以带参数的:

  1. 在有UI的桌面环境下打开终端,运行export
  2. 在输出结果中找到DISPLAY=XXXX的部分,比如说我的是DISPLAY=":0.0"
  3. 在代码中添加参数:tk.Tk(screenName=":0.0")
    问题解决。

数据上传

Yeelink是一个对个人用户免费的物联网服务,你可以将自己的数据上传,并在Web端查看。虽然时常抽风,但既然是免费的我还能抱怨什么呢?感谢天感谢地,感谢Yeelink让我和家人可以随时随地用手机查看家里的环境数据😜。系统每次获取到sensor的数据后会自动上报给Yeelink,让我奇怪的是官方好像只有接口文档而没有给出范例代码,网上的也大都有点过时了,害得我想偷个懒都不成。虽然GitHub上有,但我还是占用一些篇幅放出这部份代码,以造福想用搜索引擎偷懒的小朋友们吧:

#!/usr/bin/python3
import time
import json
import requests

#yeelink api配置
apiUrl='http://api.yeelink.net/v1.1/device/%s/sensor/%s/datapoints'
apiKey='123456' #请填入专属的api key
apiHeaders={'U-ApiKey':apiKey,'content-type': 'application/json'}
deviceID = 1232312
sensorID = {'pm25':123, 'pm10': 456,'temperature':789, 'humidity':111}

#上传sensor数据到yeelink
def upload_to_yeelink(name, value):
    url= apiUrl % (deviceID,sensorID[name])
    strftime=time.strftime("%Y-%m-%dT%H:%M:%S")
    #print(url, strftime)
    data={"timestamp":strftime , "value": value}
    try:
        res=requests.post(url,headers=apiHeaders,json=data, timeout=3.0)
        if res.status_code!=200:
            print("status_code:",res.status_code)
        else:
            pass
    except:
        print("report to yeelink fail")

if __name__ == "__main__":
    upload_to_yeelink('pm25', 100)

从哪里获取deviceID、sensorID我就不多说了,注册和新建的时候自然都知道了。值得一提的是其中的timeout参数(3.0那个),是因为我发现即使网络没问题,有时候也会无限卡死在等response的地方,不知道是不是服务器会抽风。
使用这段代码需要先装requests库,原因?自然是为了偷懒。不然原生的http api处理什么json, error,timeout要多写好几行呢!

其他

软件方面的其他内容还有三个小地方值得注意:

  1. 桌面图标。要想完全脱离键盘用UI来操作,一个桌面的打开快捷方式必不可少。原来我以为直接放一个main的软链接就行了,结果人家直接不显示,原来需要固定格式的桌面图标文件才行。我以chrome的图标为基准修改了一个,还能自己定义ico,挺好玩。
  2. 循环睡眠的代码。之前我想当然地在日常模式中直接写了sleep(600),结果发现这个bug太蠢了:在UI上切换入实时模式时根本无法实时响应变化,非要等10分钟后睡醒了才知道。后来就改成了无论那种模式都是每次睡一秒,醒来看看状态是否变为实时模式,以决定是否要继续睡。
  3. 循环时间。在循环中,每个cycle的时间并不真的是1秒或者10分钟,那个只是睡眠的时间;整个cycle还包括了获取数据的时间、上报数据的时间、以及其他开销。

碰到的问题

改造系统的过程中碰到了不少问题,记得起来的写一下。

电压问题

我的树莓派上除了接这些sensor和屏幕,还有一个蓝牙、一个USB WIFI、一个摄像头,可想而知耗电量是相当大的了。如果只提供5V1A的供电,系统则会在屏幕右上角显示一个黄色的闪电符号,提醒你电量不足。但即使是电量不足,程序也都是能运行的,只是……会影响sensor的读数。
刚开始放到电视柜上的时候,发现空气质量的读数突然变得巨大,完全不正常,而拿去书房debug又降下来很多(依然不正常),百思不得其解,直到把屏幕拿掉读数完全正常,才发现是给电的问题。最后我用了两个5V2A的USB供电(一个专供显示屏),才算解决了这个问题。

DHT22的官方库

说起来虽然DHT系列的sensor本身质量不怎么样,公司还不错,能提供python的库直接用,而且同时支持DHT22和DHT11。DHT11之前我是自己写的,拿一个GPIO Pin来发命令、收数据,该说这想法很👍呢还是很😫呢?其实用C还好,用python就总觉的太不……专业。DHT11的数据特别是湿度实在是太不准了,差了十万八千里,有个Pin脚好像也有点问题,于是打算换个DHT22试试。结果嘛,虽然比原来准了一点但依然有25%-40%的误差🙄,并且这个官方给的库坑得我不轻。
首先是无响应问题。在硬件都正常的情况下,API很偶尔会无响应,估计是因为timming的问题miss掉了某个状态之类的。其实timeout是有的,但是那时间长得令人发指,直接可以当作死循环了。这个问题我也懒得去搞什么花样来解决了,死等就死等吧,别真死就行。
然后是返回值的问题。返回值一般是float的,比如35.4这类,坑的是如果它是35.0,就会直接给你个int的35,害得我用.1f输出就死了,需要自己强制转换下类型;这还不够奇葩,奇葩的是如果有什么问题拿不到数据,它直接给你个NULL,害得我在转换类型时又死了😳……心累!

散热问题

除了电压,散热也是会影响sensor工作的。而最直接的影响,就是温度检测了。机器跑了两天后,我突然发现上报的温度太高,比普通温度计测量的结果高差不多4度。而同样的,当我拿去书房debug,一切又恢复了正常😓……这次我没那么傻了,估计是散热问题,之前做的积木壳,只留出一面开口用来插数据线和通风,确实散热太差,CPU长期保持在59℃也是挺吓人的。于是我将外壳加高了两层,让里面的各种线和元器件不要那么拥挤,又在侧面加了个小风扇,果然解决问题,CPU降到了45℃,温度测量也正常了。
值得一提的是这个风扇是当初买rpi亚克力壳子送的,接上之后持续发出我无法忍受的高频噪音,我这才想起来为什么当初被我扔到角落里了😌。想来想去,采取了折中的手段:将原本需要5V的风扇电源接到3.3V上,风扇转速降低,但是一点都不响了。CPU这次大概在48℃左右还可以接受,温度的sensor也读数正常。

最终效果

整个项目中,组装其实是最大的挑战:用那个小积木拼东西手太疼了啊!拼了我整整一天,手指头都脱皮了,才搞定整个造型。
这是没有盖上屏幕的样子:

以及最终成品:

项目源码已上传至GitHub:Rpi-envMonitor,欢迎Star!