利用dht11与Raspberry Pi构建温湿度监控平台

这次学校短学期的课题是利用dht11与树莓派做一个温湿度监控平台,要求能实现实时显示,查询历史,定时浇灌,报警等功能。

硬件

dht11是一款有已校准数字信号输出的温湿度传感器。 其精度湿度+-5%RH, 温度+-2°C,量程湿度20-90%RH, 温度0~50°C。

首先我们要从dht11上读出温湿度,dht11分别有3个引脚,分别为VCC、GND和DOUT,DOUT为数据输出的引脚。

树莓派引脚图

树莓派引脚图

我们将DOUT接在物理引脚7脚上,代码如下

因为我们选了BCM模式,所以代码中的引脚编号是4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# dht11.py
import time
import RPi.GPIO as GPIO


def calTemp():
channel = 4
data = []
j = 0

GPIO.setmode(GPIO.BCM)

time.sleep(1)

GPIO.setup(channel, GPIO.OUT)
GPIO.output(channel, GPIO.LOW)
time.sleep(0.02)
GPIO.output(channel, GPIO.HIGH)
GPIO.setup(channel, GPIO.IN)

while GPIO.input(channel) == GPIO.LOW:
continue
while GPIO.input(channel) == GPIO.HIGH:
continue

while j < 40:
k = 0
while GPIO.input(channel) == GPIO.LOW:
continue
while GPIO.input(channel) == GPIO.HIGH:
k += 1
if k > 100:
break
if k < 8:
data.append(0)
else:
data.append(1)

j += 1

humidity_bit = data[0:8]
humidity_point_bit = data[8:16]
temperature_bit = data[16:24]
temperature_point_bit = data[24:32]
check_bit = data[32:40]

humidity = 0
humidity_point = 0
temperature = 0
temperature_point = 0
check = 0

for i in range(8):
humidity += humidity_bit[i] * 2 ** (7 - i)
humidity_point += humidity_point_bit[i] * 2 ** (7 - i)
temperature += temperature_bit[i] * 2 ** (7 - i)
temperature_point += temperature_point_bit[i] * 2 ** (7 - i)
check += check_bit[i] * 2 ** (7 - i)

tmp = humidity + humidity_point + temperature + temperature_point

if check == tmp:
# print "temperature :", temperature, "*C, humidity :", humidity, "%"
return {'datetime':int(time.time()),'temperature' : temperature,'humidity' :humidity,}
else:
# print "wrong"
# print "temperature :", temperature, "*C, humidity :", humidity, "% check :", check, ", tmp :", tmp
return {'datetime':int(time.time()),'temperature' : temperature,'humidity' :humidity,'error':1}

GPIO.cleanup()

软件

好了,温度读取完成了,该思考怎么把数据反馈到网页上了,老师建议我们用Flask来做,并给了我们一份材料。材料中给的范例是在拿到温湿度数据后,利用render_template,把数据传进网页,如下。

1
2
3
4
5
6
7
8
@app.route("/")
def hello():
templateData=dht11();
templateData={
'temperature' : 23,
'humidity' :30,
}
return render_template("index.html", **templateData)

这时我想,既然都用到Flask了,为何不能做一下前后端分离呢?

后端

上网查了一下利用Flask架设后端服务器的相关知识,写了一个获取温湿度数据的接口。

1
2
3
4
@app.route("/getData", methods=['GET'])
def home():
response=dht11.calTemp()
return jsonify(response)

历史查询功能,乍一想可能需要上个数据库,但无奈技艺不精,选择了将数据保存在json文件中去进行反复的读写。

同时新增了一个历史查询的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route("/getData", methods=['GET'])
def home():
list=[]
response=dht11.calTemp()
with open("./data.json",'r+') as f:
if os.path.getsize('./data.json')==0:
list.append(response)
json.dump(list,f)
else:
list=json.load(f)
list.append(response)
f.seek(0)
f.truncate()
json.dump(list,f)
return jsonify(response)

@app.route("/getHistory", methods=['GET'])
def history():
with open("./data.json",'r+') as f:
res=json.load(f)
return jsonify(res)

还有一个定时浇灌的功能,我们用led灯去模拟浇灌系统,由前端传一个时间戳和浇灌时间,然后进入一个死循环,去判断设定时间和当前时间是否相等,再进行浇灌。

这里涉及到一个问题,进入死循环后,其他操作无法进行,整个程序会卡死在这一步直到到达预定的时间,一开始以为可以用多线程来解决,就去看了一下thread,后来操作了一下发现还是无解。之后经老师指点,建议我使用多进程试一试,便用了multiprocessing实现多进程,可以在进入死循环的同时,其余功能也正常运行。

之后这里还有一个小问题,传回来的时间戳和python自己获取当前时间的时间戳貌似很难相等,猜测应该是程序本身运行也需要时间,无法做到如此精确,便更改了一下设计思路,前端传回设定时间和当前时间的时间差,然后进入程序后先sleep,再运行。

具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
@app.route("/lightLed",methods=['GET'])
def light():
sleepTime=request.args.get("sleepTime")
lightTime=request.args.get("lightTime")
p=multiprocessing.Process(target=led.lightLED,args=(sleepTime,lightTime,))
p.start()
response={
'light':'true',
'sleeptime':sleepTime,
'lighttime':lightTime,
}
return jsonify(response)
1
2
3
4
5
6
7
8
9
10
11
12
13
# led.py
import time
import RPi.GPIO as GPIO

def lightLED(sleepTime,lightTime):
GPIO.setmode(GPIO.BCM)
time.sleep(float(sleepTime)
GPIO.setup(27, GPIO.OUT)
GPIO.output(27, GPIO.HIGH)
time.sleep(float(lightTime))
GPIO.output(27, GPIO.LOW)
time.sleep(1)
GPIO.cleanup()

整个后端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from flask import Flask, render_template, jsonify, request
from flask_cors import CORS
import datetime
import dht11
import led
import json
import time
import os
import multiprocessing
app = Flask(__name__)
CORS(app, resources={r"/getData": {"origins": "*"}})
CORS(app, resources={r"/lightLed": {"origins": "*"}})
CORS(app, resources={r"/getHistory": {"origins": "*"}})

@app.route("/getData", methods=['GET'])
def home():
list=[]
response=dht11.calTemp()
with open("./data.json",'r+') as f:
if os.path.getsize('./data.json')==0:
list.append(response)
json.dump(list,f)
else:
list=json.load(f)
list.append(response)
f.seek(0)
f.truncate()
json.dump(list,f)
return jsonify(response)

@app.route("/getHistory", methods=['GET'])
def history():
with open("./data.json",'r+') as f:
res=json.load(f)
return jsonify(res)

@app.route("/lightLed",methods=['GET'])
def light():
sleepTime=request.args.get("sleepTime")
lightTime=request.args.get("lightTime")
p=multiprocessing.Process(target=led.lightLED,args=(sleepTime,lightTime,))
p.start()
# led.lightLED(lightTime)
response={
'light':'true',
'sleeptime':sleepTime,
'lighttime':lightTime,
}
return jsonify(response)

if __name__ == '__main__':
app.run(host='0.0.0.0',port=5000,threaded=True)

后端和前端运行在同一个路由器下,所以运行在0.0.0.0地址,这样前端可以直接用树莓派所在的ip访问到。

前端

前端用了自己比较擅长的vue来写,界面如下



向后端请求数据的相关操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
methods: {
async getData() {
let res = await this.$http.get('http://172.20.10.4:5000/getData');
this.obj=res.body;
// this.obj.data=this.obj.data*1000;
this.tableData.unshift(res.body);
this.tempOptions.series[0].data[0].value = res.body.temperature * 2.7;
this.tempOptions.series[0].data[0].name = res.body.temperature + '°C';
this.tempOptions.series[0].data[1].value = (100 - res.body.temperature) * 2.7;
this.humiOptions.series[0].data[0].value = res.body.humidity * 2.7;
this.humiOptions.series[0].data[0].name = res.body.humidity + '%';
this.humiOptions.series[0].data[1].value = (100 - res.body.humidity) * 2.7;
},
}
created() {
this.timer = setInterval(this.getData, 5000);
},
beforeDestroy() {
clearInterval(this.timer);
},

定时浇灌的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async lightLed() {
let now = new Date().getTime()
let setTime = this.setTime.getTime();
let sleepTime = (setTime-now)/1000;
let lightTime = this.lightTime;
await this.$http.get(`http://172.20.10.4:5000/lightLed?sleepTime=${sleepTime}&lightTime=${lightTime}`).then(res => {
this.$message({
message: '成功发送浇灌信息',
type: 'success'
});
this.lightTime = '';
},
err => {
this.$message.error('发送浇灌信息失败,请再次尝试');
});
},

温湿度监控报警

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
watch: {
'obj': function(obj){
if(obj.temperature>37){
this.$notify({
title: '警告',
message: '温度过高!',
type: 'warning'
});
};
if(obj.humidity>70){
this.$notify({
title: '警告',
message: '湿度过高!',
type: 'warning'
});
}
}
},

GitHub地址:dht11-flask-vue