前言
前阵子入了一个树莓派,作为一个尽责(苦逼)的IT运维狗,自然想到拿这玩意来做做看看温湿度的环境监控了。
想法很简单,找点传感器接上树莓派,通过 GPIO 读取到传感器的数据。然后推送进监控系统即刻(比如 Open-Falcon)
传感器
温湿度的传感器种类很多,选了比较常见的3种来测试。
- DHT11
- DHT22
- DS18B20
先对比下参数
参数 | DHT11 | DHT22 | DS18B20 |
---|---|---|---|
温度测量范围 | 0 ~ +50(°C) | -40 ~ +80(°C) | -55 ~ +125(°C) |
温度误差 | ±2°C | ±0.5°C | ±0.5°C(-10 ~ +85(°C)内) |
湿度范围 | 20 ~ 95(%RH) | 0 ~ 100(%RH) | 无 |
湿度误差 | ±5%RH | ±2%RH | 无 |
工作电压 | 3.3 ~ 5(V) | 3.3 ~ 5(V) | 3.0 ~ 5.5(V) |
模块参考价格(淘宝) | 5¥ | 20¥ | 6¥ |
所以基本上就是:
DHT11 最渣但是最便宜
DHT22 比较给力但是贵
DS18B20 便宜且给力,但只有温度没有湿度
因为传感器连接时都需要接一个上拉电阻,所以直接买人家做好的模块比较方便,电阻给你内置接好了,直接连线比较无脑
接线
这是树莓派的 GPIO 图:
更详细的例图:
既然用的是模块,接线就很简单了,VCC 接电,GND 接地,DATA 接 GPIO 就好了,这是示意图,实际电阻已经内置在模块里了
数据读取
虽说树莓派本身已经集成了 RPi.GPIO,可以很方便的来操作 GPIO 获取数据。但是直接通过 GPIO 读取还是太麻烦了,好在轮子总是会有的~
DHT 系列
轮子
https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code
有轮子了这事情就非常好办,首先把轮子弄下来~
sudo apt-get update
sudo apt-get install build-essential python-dev
git clone https://github.com/adafruit/Adafruit_Python_DHT
cd Adafruit_Python_DHT
sudo python setup.py install
读取数据超级简单
import Adafruit_DHT
sensor1 = Adafruit_DHT.DHT11
humidity1, temperature1 = Adafruit_DHT.read_retry(sensor1, 26)#26 是 GPIO 的引脚编号
print humidity1,temperature1
sensor2 = Adafruit_DHT.DHT22
humidity2, temperature2 = Adafruit_DHT.read_retry(sensor2, 13)#13 是 GPIO 的引脚编号
print humidity2,temperature2
DS18B20
DS18B20 更加直接,树莓派已经自带了 1-Wire 的驱动,只要把他开起来就好了~
先更新下内核
sudo apt-get update
sudo apt-get upgrade
检查一下 1-Wire 模块是否开启
root@raspberrypi:/etc# lsmod | grep w1
w1_therm 6401 0
w1_gpio 4818 0
wire 32619 2 w1_gpio,w1_therm
如果没有,开启 1-Wire 模块
sudo modprobe w1_gpio
sudo modprobe w1_therm
修改/boot/config.txt
配置文件,增加 dtoverlay=w1-gpio,gpiopin=19,pullup=on
默认用的是 4 号口,如果你没有接在 4 号口上的话,要人工指定,例如我这里写的 19 号口。
这里的参数详细可以看 /boot/overlays/README
,里面有详细说明
Name: w1-gpio
Info: Configures the w1-gpio Onewire interface module.
Use this overlay if you *don't* need a GPIO to drive an external pullup.
Load: dtoverlay=w1-gpio,<param>=<val>
Params: gpiopin GPIO for I/O (default "4")
pullup Non-zero, "on", or "y" to enable the parasitic
power (2-wire, power-on-data) feature
Name: w1-gpio-pullup
Info: Configures the w1-gpio Onewire interface module.
Use this overlay if you *do* need a GPIO to drive an external pullup.
Load: dtoverlay=w1-gpio-pullup,<param>=<val>
Params: gpiopin GPIO for I/O (default "4")
pullup Non-zero, "on", or "y" to enable the parasitic
power (2-wire, power-on-data) feature
extpullup GPIO for external pullup (default "5")
配好以后重启,然后就可以看到我们的传感器了
root@raspberrypi:/etc# ls /sys/bus/w1/devices/
28-0516a718e1ff w1_bus_master1
查看传感器的温度
root@raspberrypi:/etc# cat /sys/bus/w1/devices/28-0516a718e1ff//w1_slave
f3 01 4b 46 7f ff 0c 10 17 : crc=17 YES
f3 01 4b 46 7f ff 0c 10 17 t=31187
31187/1000
就是当前的温度,也就是 31.187
读取这个东西当然是相当容易的事情了,然而它还是有轮子的~
pip install w1thermsensor
有轮子又何必自己动手叻,读取数据之~
from w1thermsensor import W1ThermSensor
sensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "031561d43aff")
temperatur = sensor.get_temperature()
print temperature
纳入监控系统
传感器能够工作之后,我就要把他纳入到我们的监控系统里去了。绘图,告警这就是监控系统的工作了,我们需要做的是把数据给它。
监控系统获取数据通常可以分为 PULL(拉)和 PUSH(推)两种模式。实际上就是看谁更主动一些,
- PULL 模式里,我们把数据以接口方式暴露出来,由监控系统来主动拉走
- PUSH 模式里,监控系统提供数据的推送接口,我们主动的对数据进行封装,推送给监控系统
PULL 模式
PULL 的模式会比较通用一些。无论是用哪一个监控系统,反正我数据就在这里,拿走自己处理就是。如果这个东西要做成个通用产品的话,那大抵是要做成 PULL 的模式来主动暴露接口的。
我们用 flask 简单的封装个 http 的接口,先装一下 flask
pip install flask
因为读取传感器的数据还是要花点时间的,我们肯定不希望每次请求接口数据的时候都去读一次传感器。所以先弄个脚本定期的把传感器的数据读出来,json 格式存在本地就好了。
#!/usr/bin/python
import Adafruit_DHT
import json
import copy
from w1thermsensor import W1ThermSensor
sensor1 = Adafruit_DHT.DHT11
humidity_dht11, temperature_dht11 = Adafruit_DHT.read_retry(sensor1, 26)
sensor2 = Adafruit_DHT.DHT22
humidity_dht22, temperature_dht22 = Adafruit_DHT.read_retry(sensor2, 13)
sensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "0516a718e1ff")
temperature_ds18b20 = sensor.get_temperature()
env = []
if humidity_dht11 is not None and temperature_dht11 is not None:
data = {"metric":"humidity","tag":"module=dht11","value":humidity_dht11}
env.append(copy.copy(data))
data = {"metric":"temperature","tag":"module=dht11","value":temperature_dht11}
env.append(copy.copy(data))
if humidity_dht22 is not None and temperature_dht22 is not None:
data = {"metric":"humidity","tag":"module=dht22","value":humidity_dht22}
env.append(copy.copy(data))
data = {"metric":"temperature","tag":"module=dht22","value":temperature_dht22}
env.append(copy.copy(data))
if temperature_ds18b20 is not None:
data = {"metric":"temperature","tag":"module=ds18b20","value":temperature_ds18b20}
env.append(copy.copy(data))
if len(env) > 0:
with open("/opt/falcon-scripts/env.json", 'w') as f:
f.write(json.dumps(env))
放入 crontab 里,这个脚本每分钟运行一次,这样我们的数据延迟也就是 1 分钟而已,完全可以接受。
现在通过 flask 来封装一个简单的 http 接口
#!/usr/bin/python
# -*- coding: utf-8 -*-
from flask import Flask,jsonify
import json
app = Flask(__name__)
@app.route('/env', methods=['GET'])
def env():
with open('/opt/falcon-scripts/env.json', 'r') as f:
env_json = f.read()
env_data = json.loads(env_json)
return jsonify(env=env_data)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)
跑起来
root@raspberrypi:/opt/flask# python env.py
* Running on http://0.0.0.0:80/
* Restarting with reloader
测试一下
PS C:\Users\qfeng> bash
44 packages can be updated.
29 updates are security updates.
qfeng@QFENG-PC:/mnt/c/Users/qfeng$
qfeng@QFENG-PC:/mnt/c/Users/qfeng$
qfeng@QFENG-PC:/mnt/c/Users/qfeng$
qfeng@QFENG-PC:/mnt/c/Users/qfeng$ curl http://192.168.2.221/env
{
"env": [
{
"metric": "humidity",
"tag": "module=dht11",
"value": 68.0
},
{
"metric": "temperature",
"tag": "module=dht11",
"value": 32.0
},
{
"metric": "humidity",
"tag": "module=dht22",
"value": 70.9000015258789
},
{
"metric": "temperature",
"tag": "module=dht22",
"value": 30.399999618530273
},
{
"metric": "temperature",
"tag": "module=ds18b20",
"value": 31.125
}
]
}qfeng@QFENG-PC:/mnt/c/Users/qfeng$
看起来不错
PUSH 模式
PUSH 的模式需要根据我们所使用的监控系统,来封装数据格式进行主动的推送。对于特定的监控系统而言,这种模式更为简单一些。以 Open-Falcon 为例,我写了 3 个脚本对应不同的传感器模块,主动把数据推送给 Open-Falcon
- dht11
import Adafruit_DHT
import time
import json
import requests
import copy
if __name__ == '__main__':
sensor = Adafruit_DHT.DHT11
humidity, temperature = Adafruit_DHT.read_retry(sensor, 26)
ts = int(time.time())
push_url = "http://127.0.0.1:1988/v1/push"
payload = []
if humidity is not None:
humidity_data = {"endpoint":"home","metric":"room.humidity","timestamp":ts,"step":60,"value":humidity,"counterType":"GAUGE","tags":"module=dht11"}
payload.append(copy.copy(humidity_data))
if temperature is not None:
temperature_data = {"endpoint":"home","metric":"room.temperature","timestamp":ts,"step":60,"value":temperature,"counterType":"GAUGE","tags":"module=dht11"}
payload.append(copy.copy(temperature_data))
r = requests.post(push_url, data=json.dumps(payload))
- dht22
#!/usr/bin/python
import Adafruit_DHT
import time
import json
import requests
import copy
if __name__ == '__main__':
sensor = Adafruit_DHT.DHT22
humidity, temperature = Adafruit_DHT.read_retry(sensor, 13)
ts = int(time.time())
push_url = "http://127.0.0.1:1988/v1/push"
payload = []
if humidity is not None:
humidity_data = {"endpoint":"home","metric":"room.humidity","timestamp":ts,"step":60,"value":humidity,"counterType":"GAUGE","tags":"module=dht22"}
payload.append(copy.copy(humidity_data))
if temperature is not None:
temperature_data = {"endpoint":"home","metric":"room.temperature","timestamp":ts,"step":60,"value":temperature,"counterType":"GAUGE","tags":"module=dht22"}
payload.append(copy.copy(temperature_data))
r = requests.post(push_url, data=json.dumps(payload))
- ds18b20
#!/usr/bin/python
import time
import json
import requests
import copy
from w1thermsensor import W1ThermSensor
if __name__ == '__main__':
sensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "0516a718e1ff")
temperature = sensor.get_temperature()
ts = int(time.time())
push_url = "http://127.0.0.1:1988/v1/push"
payload = []
if temperature is not None:
temperature_data = {"endpoint":"home","metric":"room.temperature","timestamp":ts,"step":60,"value":temperature,"counterType":"GAUGE","tags":"module=ds18b20"}
payload.append(copy.copy(temperature_data))
r = requests.post(push_url, data=json.dumps(payload))
PS: 你大概已经发现了,这里主动 Push 的地址是本地的 127.0.0.1~,也就是说这里的 Open-Falcon 其实也是装在树莓派上的~~这事下回再说
看下三个传感器的数据绘图
dht11 的误差确实可能要大一些,也没有到无法接受的程度,便宜嘛
生产环境
目前为止这个还只是个玩具,要进入生产环境真的拿来用的话,还需要解决一些问题
- PoE 供电
可以通过 PoE 分离器来搞定,淘宝上 20~30 块钱一个 - console tty
安装地方可能没有 dhcp,你得静态给树莓派配地址。总不能出门都带个屏幕和 hdmi 线吧。得让他支持 console,出去串口一接完事。可以用 PL2303 这样的 USB 转 TTL 芯片,4-5块钱一个 - 外壳
拖着一堆杜邦线在外面肯定是太丑了,壳子得把线藏一藏,弄整洁一点。这得费点功夫 - 走线
如果考虑监控机柜温度的话。在多机柜的机房里,肯定是要 1 台树莓派拖多个传感器挂在机柜里面。走线或许可以考虑直接用网线拉走,焊在针脚上(或者直接绝缘胶布一缠~)
这些问题,留到下回做个原型机出来再说吧~