前言
前陣子入了一個樹莓派盔腔,作為一個盡責(zé)(苦逼)的IT運維狗闸翅,自然想到拿這玩意來做做看看溫濕度的環(huán)境監(jiān)控了贵扰。
想法很簡單,找點傳感器接上樹莓派奠衔,通過 GPIO 讀取到傳感器的數(shù)據(jù)谆刨。然后推送進監(jiān)控系統(tǒng)即刻(比如 Open-Falcon)
傳感器
溫濕度的傳感器種類很多,選了比較常見的3種來測試归斤。
- DHT11
- DHT22
- DS18B20
先對比下參數(shù)
參數(shù) | DHT11 | DHT22 | DS18B20 |
---|---|---|---|
溫度測量范圍 | 0 ~ +50(°C) | -40 ~ +80(°C) | -55 ~ +125(°C) |
溫度誤差 | ±2°C | ±0.5°C | ±0.5°C(-10 ~ +85(°C)內(nèi)) |
濕度范圍 | 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 便宜且給力痊夭,但只有溫度沒有濕度
因為傳感器連接時都需要接一個上拉電阻,所以直接買人家做好的模塊比較方便脏里,電阻給你內(nèi)置接好了她我,直接連線比較無腦
接線
這是樹莓派的 GPIO 圖:
更詳細的例圖:
既然用的是模塊,接線就很簡單了迫横,VCC 接電番舆,GND 接地,DATA 接 GPIO 就好了矾踱,這是示意圖恨狈,實際電阻已經(jīng)內(nèi)置在模塊里了
數(shù)據(jù)讀取
雖說樹莓派本身已經(jīng)集成了 RPi.GPIO,可以很方便的來操作 GPIO 獲取數(shù)據(jù)介返。但是直接通過 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
讀取數(shù)據(jù)超級簡單
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 更加直接圣蝎,樹莓派已經(jīng)自帶了 1-Wire 的驅(qū)動刃宵,只要把他開起來就好了~
先更新下內(nèi)核
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
默認(rèn)用的是 4 號口牲证,如果你沒有接在 4 號口上的話,要人工指定关面,例如我這里寫的 19 號口坦袍。
這里的參數(shù)詳細可以看 /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
就是當(dāng)前的溫度捂齐,也就是 31.187
讀取這個東西當(dāng)然是相當(dāng)容易的事情了蛮放,然而它還是有輪子的~
pip install w1thermsensor
有輪子又何必自己動手叻,讀取數(shù)據(jù)之~
from w1thermsensor import W1ThermSensor
sensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "031561d43aff")
temperatur = sensor.get_temperature()
print temperature
納入監(jiān)控系統(tǒng)
傳感器能夠工作之后奠宜,我就要把他納入到我們的監(jiān)控系統(tǒng)里去了包颁。繪圖,告警這就是監(jiān)控系統(tǒng)的工作了压真,我們需要做的是把數(shù)據(jù)給它娩嚼。
監(jiān)控系統(tǒng)獲取數(shù)據(jù)通常可以分為 PULL(拉)和 PUSH(推)兩種模式滴肿。實際上就是看誰更主動一些岳悟,
- PULL 模式里,我們把數(shù)據(jù)以接口方式暴露出來泼差,由監(jiān)控系統(tǒng)來主動拉走
- PUSH 模式里贵少,監(jiān)控系統(tǒng)提供數(shù)據(jù)的推送接口,我們主動的對數(shù)據(jù)進行封裝拴驮,推送給監(jiān)控系統(tǒng)
PULL 模式
PULL 的模式會比較通用一些春瞬。無論是用哪一個監(jiān)控系統(tǒng),反正我數(shù)據(jù)就在這里套啤,拿走自己處理就是宽气。如果這個東西要做成個通用產(chǎn)品的話,那大抵是要做成 PULL 的模式來主動暴露接口的潜沦。
我們用 flask 簡單的封裝個 http 的接口萄涯,先裝一下 flask
pip install flask
因為讀取傳感器的數(shù)據(jù)還是要花點時間的,我們肯定不希望每次請求接口數(shù)據(jù)的時候都去讀一次傳感器唆鸡。所以先弄個腳本定期的把傳感器的數(shù)據(jù)讀出來涝影,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 里争占,這個腳本每分鐘運行一次燃逻,這樣我們的數(shù)據(jù)延遲也就是 1 分鐘而已,完全可以接受臂痕。
現(xiàn)在通過 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 的模式需要根據(jù)我們所使用的監(jiān)控系統(tǒng)伯襟,來封裝數(shù)據(jù)格式進行主動的推送。對于特定的監(jiān)控系統(tǒng)而言握童,這種模式更為簡單一些姆怪。以 Open-Falcon 為例,我寫了 3 個腳本對應(yīng)不同的傳感器模塊,主動把數(shù)據(jù)推送給 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: 你大概已經(jīng)發(fā)現(xiàn)了稽揭,這里主動 Push 的地址是本地的 127.0.0.1~俺附,也就是說這里的 Open-Falcon 其實也是裝在樹莓派上的~~這事下回再說
看下三個傳感器的數(shù)據(jù)繪圖
dht11 的誤差確實可能要大一些,也沒有到無法接受的程度溪掀,便宜嘛
生產(chǎn)環(huán)境
目前為止這個還只是個玩具事镣,要進入生產(chǎn)環(huán)境真的拿來用的話,還需要解決一些問題
- PoE 供電
可以通過 PoE 分離器來搞定膨桥,淘寶上 20~30 塊錢一個 - console tty
安裝地方可能沒有 dhcp蛮浑,你得靜態(tài)給樹莓派配地址≈幌總不能出門都帶個屏幕和 hdmi 線吧。得讓他支持 console艺沼,出去串口一接完事册舞。可以用 PL2303 這樣的 USB 轉(zhuǎn) TTL 芯片障般,4-5塊錢一個 - 外殼
拖著一堆杜邦線在外面肯定是太丑了调鲸,殼子得把線藏一藏,弄整潔一點挽荡。這得費點功夫 - 走線
如果考慮監(jiān)控機柜溫度的話藐石。在多機柜的機房里,肯定是要 1 臺樹莓派拖多個傳感器掛在機柜里面定拟。走線或許可以考慮直接用網(wǎng)線拉走于微,焊在針腳上(或者直接絕緣膠布一纏~)
這些問題,留到下回做個原型機出來再說吧~