Osheep

时光不回头,当下最重要。

树莓派上的温湿度环境监控

前言

前阵子入了一个树莓派,作为一个尽责(苦逼)的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 图:

《树莓派上的温湿度环境监控》

image.png

更详细的例图:

《树莓派上的温湿度环境监控》

image.png

既然用的是模块,接线就很简单了,VCC 接电,GND 接地,DATA 接 GPIO 就好了,这是示意图,实际电阻已经内置在模块里了

《树莓派上的温湿度环境监控》

image.png

数据读取

虽说树莓派本身已经集成了 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 其实也是装在树莓派上的~~这事下回再说

看下三个传感器的数据绘图

《树莓派上的温湿度环境监控》

image.png

dht11 的误差确实可能要大一些,也没有到无法接受的程度,便宜嘛

生产环境

目前为止这个还只是个玩具,要进入生产环境真的拿来用的话,还需要解决一些问题

  1. PoE 供电
    可以通过 PoE 分离器来搞定,淘宝上 20~30 块钱一个
  2. console tty
    安装地方可能没有 dhcp,你得静态给树莓派配地址。总不能出门都带个屏幕和 hdmi 线吧。得让他支持 console,出去串口一接完事。可以用 PL2303 这样的 USB 转 TTL 芯片,4-5块钱一个
  3. 外壳
    拖着一堆杜邦线在外面肯定是太丑了,壳子得把线藏一藏,弄整洁一点。这得费点功夫
  4. 走线
    如果考虑监控机柜温度的话。在多机柜的机房里,肯定是要 1 台树莓派拖多个传感器挂在机柜里面。走线或许可以考虑直接用网线拉走,焊在针脚上(或者直接绝缘胶布一缠~)

这些问题,留到下回做个原型机出来再说吧~

以上

点赞