文章目录
- 前言
- 一、硬件连接
- MQ-2
- PCF8591
- 二、Onenet平台数据收发程序
- onenetsub.py
- onenetget.py
- 三、程序
- 树莓派开启iic功能
- 完整程序
- OneNet界面展示
前言
用树莓派4b做一个烟雾浓度检测仪,烟雾浓度传感器模块MQ-2收集烟雾浓度数据,把数据上传到onenet平台用网页显示。
所需材料:树莓派;MQ-2烟雾浓度传感器 ;PCF8591 ad转换模块;杜邦线;面包板;
[2021.6.11]
一、硬件连接
参考:
MQ-2 :pm2.5、mq-2 、mq2电压浓度转换
PCF8591:PCF8591、PCFPCF8591使用及Python控制
MQ-2、PCF8591和树莓派之间使用I2C总线通信方式,引脚连接如下:
PCF8591 树莓派
GND---GND
VCC---3.3V
SCL---SCL/GPIO3
SDA---SDA/GPIO2
MQ-2 树莓派
GND---GND
VCC---5V
D0----36引脚
MQ-2 PCF8591
AO----AIN0
MQ-2
MQ-2浓度及电压转换:
烟雾浓度计算公式: ppm = 613.9f * pow(RS/R0, -2.074f)
ppm:为可燃气体的浓度。
VRL:电压输出值。
Rs:器件在不同气体,不同浓度下的电阻值。
R0:器件在洁净空气中的电阻值。
RL:负载电阻阻值。
注:
pow() 方法返回 xy(x 的 y 次方) 的值。
import math
math.pow( x, y )
注2:
#浓度计算参数
CAL_PPM =20 # 校准环境中PPM值
RL = 5 # RL阻值
#R0 = float(6.00) #元件在洁净空气中的阻值
Vrl = 5 * ADC_Value / 255 #5V ad 为8位
RS = (5 - Vrl) / Vrl * RL
R0 = RS / pow(CAL_PPM / 613.9, 1 / -2.074)
ppm = 613.9 * pow(RS / R0, -2.074)
PCF8591
#!/usr/bin/env python3
# -*- Coding: utf-8 -*-
###### pin assign
#PCF8591 --------- Raspberry pi3
# SDA ------------ GPIO2/SDA1
# SCL ------------ GPIO3/SCL1
# VCC ------------- 3.3V
# GND ------------- GND
#
######
import wiringpi
import time
class PCF8591:
def __init__(self, addr):
wiringpi.wiringPiSetup() #setup wiringpi
self.i2c = wiringpi.I2C() #get I2C
self.dev = self.i2c.setup(addr) #setup I2C device
def LED_ON(self):
self.i2c.writeReg8(self.dev, 0x40, 0xFF)
def LED_OFF(self):
self.i2c.writeReg8(self.dev, 0x40, 0x00)
def DAoutput(self,value):
self.i2c.writeReg8(self.dev, 0x40, value)
def analogRead0(self):
self.i2c.writeReg8(self.dev, 0x48,0x40)
self.i2c.readReg8(self.dev,0x48) #read dummy
return self.i2c.readReg8(self.dev,0x48)
def analogRead1(self):
self.i2c.writeReg8(self.dev, 0x48,0x41)
self.i2c.readReg8(self.dev,0x48) #read dummy
return self.i2c.readReg8(self.dev,0x48)
def analogRead2(self):
self.i2c.writeReg8(self.dev, 0x48,0x42)
self.i2c.readReg8(self.dev,0x48) #read dummy
return self.i2c.readReg8(self.dev,0x48)
def analogRead3(self):
self.i2c.writeReg8(self.dev, 0x48,0x43)
self.i2c.readReg8(self.dev,0x48) #read dummy
return self.i2c.readReg8(self.dev,0x48)
def analogRead(self,pin):
self.i2c.writeReg8(self.dev, 0x48,0x40+pin)
self.i2c.readReg8(self.dev,0x48) #read dummy
return self.i2c.readReg8(self.dev,0x48)
if __name__ == "__main__":
pcf8591 = PCF8591(0x48)
while 1:
print("QM-2:"+str(pcf8591.analogRead0())) #read the Variable register
# print("VCC:"+str(pcf8591.analogRead1())) # read the Variable register
# print("END:"+str(pcf8591.analogRead3())) # read the Variable register
二、Onenet平台数据收发程序
参考:onenet_get_put
onenet平台 设置方法见上期:树莓派学习笔记(四)——温湿度检测(本地OLED显示、ONENET云平台显示)
onenetsub.py
将树莓派读取到的传感器数据,通过http协议发送至 平台的对应设备
import urllib.request
import json
import time
from time import sleep
#设备ID
deviceId = "你的设备ID"
APIKey = "你的设备APIKEY"
#上传函数
def http_put_data(data):
url = "http://api.heclouds.com/devices/" + deviceId + '/datapoints'
d = time.strftime('%Y-%m-%dT%H:%M:%S')
values = {"datastreams": [ {"id": "CO2", "datapoints": [{"value": data}]},
{"id": "PM25", "datapoints": [{"value": data}]},
{"id": "PM10", "datapoints": [{"value": data}]},
{"id": "VOC", "datapoints": [{"value": data}]} ]}
jdata = json.dumps(values).encode("utf-8")
request = urllib.request.Request(url, jdata)
request.add_header('api-key', APIKey)
request.get_method = lambda: 'POST'
request = urllib.request.urlopen(request)
return request.read()
if __name__ == '__main__':
R = http_put_data(10)
print(R)
onenetget.py
读取平台对应设备的数据流,可以在另一台设备,如PC、树莓派读取onenet平台对应设备的数据流,不过由于平台限制有3s延时
import requests
import json
#设备ID
deviceId = "你的设备ID"
APIKey = "你的设备APIKEY"
# 基本设置
url = "http://api.heclouds.com/devices/"+deviceId+"/datastreams"
headers = {'api-key': APIKey}
# 获得结果并打印
r = requests.get(url, headers=headers)
t: str = r.text
#print(t)
params = json.loads(t)
#上面这个语句是将我们获得的内容转成数据字典,这样转是因为我们接收到的内容具有数据字典的形式
#转换成数据字典利于我们后面的操作
#print params['error'][]
#print(type(params))
#如果执行上面这条语句我们可以看到返回的结果是dict,也就是我们已经成功转换
x = params['data']
#这个语句是从数据转换后的数据字典中获取我们需要的数据,从结果上看params是一个list
#在data前面的都只是一些描述内容
print('环境参数'+'\t\t\t\t'+'更新时间'+'\t\t\t\t\t'+'数值')
#接下来是获取不同的数据流
for index,values in enumerate(x):
#只需要更新时间,id和值,所以这里对获得的数据字典做一下更改
#print(values)
#这里得到的values也是一个数据字典
#因为在onenet那边对这些数据没有给出来,而且也没有意义,所以我们就不在这里显示,因此现将其删除
# del values['unit']
# del values['unit_symbol']
# del values['create_time']
#print(values.items())
#print(values['update_at'])
#这里不知道为什么直接使用values.get('update_at','')和values.get('current_value','')
#或者用values['update_at']和values['current_value']报:KeyError错误,而且if里面的那条语句会执行
#所以我们通过get方法解决,其中要注意的是,如果没有给定第二个参数,那么默认输出NONE
a= str(values.get('update_at',''))
b= str(values.get('current_value',''))
#因为如果有更新时间就会有相应最新的值,所以这里只用其中一个作为判断条件
if (a != ""):
if (values['id'] == 'm_temp' ): #CPU_temp status
print(str(values['id']) + '\t\t\t' + a + '\t\t\t' + b)
T = b
elif(values['id'] == 'm_hum'):
print(str(values['id']) + '\t\t\t\t' + a + '\t\t\t'+ b)
H = b
elif(values['id'] == 'CPU_temp'):
print(str(values['id']) + '\t\t\t\t' + a + '\t\t\t' + b)
CT = b
else:
if(values['id'] == 'm_temp' or values['id'] == 'm_hum'):
print(values['id']+ '\t\t\t' +'目前还没有收到任何数据')
print(T,H,CT)
三、程序
树莓派开启iic功能
远程登陆树莓派后,输入sudo raspi-config
后,选择5.Interfacing Options
选择P5 I2C
选择 是
完整程序
带ad转换的版本,求出具体烟雾浓度
#! /usr/bin/env python3
import RPi.GPIO as GPIO # 导入库,并进行别名的设置
import time
import wiringpi
import psutil
import numpy as np
#onenet
import urllib
import urllib.request
import json
import datetime
import requests
CHANNEL = 36 # 确定引脚口。按照真实的位置确定
GPIO.setmode(GPIO.BOARD) # 选择引脚系统,这里我们选择了BOARD
GPIO.setup(CHANNEL, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
# 初始化引脚,将36号引脚设置为输入下拉电阻,因为在初始化的时候不确定的的引电平,因此这样设置是用来保证精准,(但是也可以不写“pull_up_down=GPIO.PUD_DOWN”)
###### pin assign
#PCF8591 --------- Raspberry pi3
# SDA ------------ GPIO2/SDA1
# SCL ------------ GPIO3/SCL1
# VCC ------------- 3.3V
# GND ------------- GND
class PCF8591:
def __init__(self, addr):
wiringpi.wiringPiSetup() # setup wiringpi
self.i2c = wiringpi.I2C() # get I2C
self.dev = self.i2c.setup(addr) # setup I2C device
def LED_ON(self):
self.i2c.writeReg8(self.dev, 0x40, 0xFF)
def LED_OFF(self):
self.i2c.writeReg8(self.dev, 0x40, 0x00)
def DAoutput(self, value):
self.i2c.writeReg8(self.dev, 0x40, value)
def analogRead0(self) -> object:
self.i2c.writeReg8(self.dev, 0x48, 0x40)
self.i2c.readReg8(self.dev, 0x48) # read dummy
return self.i2c.readReg8(self.dev, 0x48)
def analogRead1(self):
self.i2c.writeReg8(self.dev, 0x48, 0x41)
self.i2c.readReg8(self.dev, 0x48) # read dummy
return self.i2c.readReg8(self.dev, 0x48)
def analogRead2(self):
self.i2c.writeReg8(self.dev, 0x48, 0x42)
self.i2c.readReg8(self.dev, 0x48) # read dummy
return self.i2c.readReg8(self.dev, 0x48)
def analogRead3(self):
self.i2c.writeReg8(self.dev, 0x48, 0x43)
self.i2c.readReg8(self.dev, 0x48) # read dummy
return self.i2c.readReg8(self.dev, 0x48)
def analogRead(self, pin):
self.i2c.writeReg8(self.dev, 0x48, 0x40 + pin)
self.i2c.readReg8(self.dev, 0x48) # read dummy
return self.i2c.readReg8(self.dev, 0x48)
# if __name__ == "__main__":
# pcf8591 = PCF8591(0x48)
# while 1:
# print("ADC_Value:" + str(pcf8591.analogRead0()))
def getADC_Value():
pcf8591 = PCF8591(0x48)
AD_V=[]
time.sleep(0.3)
for i in range(10):
v = pcf8591.analogRead0()
AD_V.append(v)
time.sleep(0.1)
ADC_Value = np.sum(AD_V)/10 #采样10次,求平均值
# print("ADC_Value:" + str(ADC_Value))
return ADC_Value
def SmokeConcentration(ADC_Value):#烟雾浓度计算
# 浓度计算参数
CAL_PPM = 20 # 校准环境中PPM值
RL = 5 # RL阻值
R0 = 6.00 #元件在洁净空气中的阻值
Vrl = 5 * ADC_Value / 255 #5V ad 为8位
RS = (5 - Vrl) / Vrl * RL
RunTime = psutil.boot_time()
# print("Vrl:"+str(Vrl))
# print("RS:" + str(RS))
if RunTime >=3:
# R0 = RS / pow(CAL_PPM / 613.9, 1 / -2.074)
# print("R0:" + str(R0))
# print("RS/R0:" + str(RS/R0))
ppm = 613.9 * pow(RS / R0, -2.074)
return ppm
def getSmokeAlarm(): #烟雾报警,DO引脚控制
status = GPIO.input(CHANNEL) # 检测36号引脚口的输入高低电平状态 1为正常
#print(status) # 实时打印此时的电平状态
return status
def getPPM():
AD_V=getADC_Value()
concentration = SmokeConcentration(AD_V)
PPM = concentration
Percentage = PPM * pow(10,-6)
print("smoke:"+str(PPM))
return PPM
#上传到onenet
#onenet
api_key = '你的密钥' # 密钥
device_ID = '你的id' # 设备ID
headers = {'api-key': api_key}
url_post = "https://api.heclouds.com/devices/" + device_ID + "/datapoints" # 数据点
url_get = "https://api.heclouds.com/devices/" + device_ID + "/datastreams" # 数据流
def http_post_0():
data = {'datastreams': [
{"id": "status", "datapoints": [{"value": ' 检测到危险气体 ! ! ! '}]}
]} # id是你的数据流名称
jdata = json.dumps(data).encode("utf-8")
r = requests.post(url=url_post, headers=headers, data=jdata)
print("发送成功:", r.text)
def http_post_1():
data = {'datastreams': [
{"id": "status", "datapoints": [{"value": ' 空气状况正常\t无有害气体 '}]}
]} # id是你的数据流名称
jdata = json.dumps(data).encode("utf-8")
r = requests.post(url=url_post, headers=headers, data=jdata)
print("发送成功:", r.text)
def ppm_onenet_put():
# 保留两位小数
Smoke_ppm = round(getPPM(), 2)
Smoke_Alarm = getSmokeAlarm()
CurTime = datetime.datetime.now()
# 这url只需把数字部分换成你在onenet中创建的设备号就行
url = 'http://api.heclouds.com/devices/602743016/datapoints'
values = {
'datastreams': [
{"id": "Smoke_ppm", "datapoints": [{"time": CurTime.isoformat(), "value": Smoke_ppm}]},
{"id": "Smoke_Alarm", "datapoints": [{"time": CurTime.isoformat(), "value": Smoke_Alarm}]}
]}
jdata = json.dumps(values).encode("utf-8") # 对数据进行JSON格式化编码 ##然后
# 打印json内容
print(jdata)
##python2:
request = urllib.request.Request(url, jdata)
request.add_header('api-key', api_key)
request.get_method = lambda: 'POST' # 设置HTTP的访问方式
request = urllib.request.urlopen(request)
return request.read().decode("utf-8")
def mq2_local():
time.sleep(3)
Smoke_ppm = round(getPPM(), 2)
Smoke_Alarm = getSmokeAlarm()
if Smoke_Alarm == True: # 如果为高电平,说明MQ-2正常,并打印“OK”
print(' 正常 ')
else: # 如果为低电平,说明MQ-2检测到有害气体,并打印“dangerous”
print(' 检测到危险气体 ! ! ! ')
print(Smoke_Alarm) # 实时打印此时的电平状态
print("Smoke_ppm=%0.2f" % Smoke_ppm)
def mq2_onenet_put(): #上传 Smoke_ppm status Smoke_Alarm
time.sleep(3)
Smoke_Alarm = getSmokeAlarm()
resp = ppm_onenet_put()
print("OneNET_result: %s" % resp)
if Smoke_Alarm == True:
http_post_1()
else:
http_post_0()
if __name__ == '__main__':
while (1):
mq2_local()
mq2_onenet_put()
time.sleep(1)
GPIO.cleanup() # 清理运行完成后的残余
不带ad转换的程序,没有PCF8591的时候用,输出 是否有 危险气体的开关量
#! /usr/bin/env python3
import RPi.GPIO as GPIO # 导入库,并进行别名的设置
import time
#onenet
import requests
import json
#mq2
CHANNEL = 36 # 确定引脚口。按照真实的位置确定
GPIO.setmode(GPIO.BOARD) # 选择引脚系统,这里我们选择了BOARD
GPIO.setup(CHANNEL, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
# 初始化引脚,将36号引脚设置为输入下拉电阻,因为在初始化的时候不确定的的引电平,因此这样设置是用来保证精准,(但是也可以不写“pull_up_down=GPIO.PUD_DOWN”)
#onenet
api_key = '密钥' # 密钥
device_ID = '设备ID' # 设备ID
headers = {'api-key': api_key}
url_post = "https://api.heclouds.com/devices/" + device_ID + "/datapoints" # 数据点
url_get = "https://api.heclouds.com/devices/" + device_ID + "/datastreams" # 数据流
def http_post_0():
data = {'datastreams': [
{"id": "status", "datapoints": [{"value": ' 检测到危险气体 ! ! ! '}]}
]} # id是你的数据流名称
jdata = json.dumps(data).encode("utf-8")
r = requests.post(url=url_post, headers=headers, data=jdata)
#print("发送成功:", r.text)
def http_post_1():
data = {'datastreams': [
{"id": "status", "datapoints": [{"value": ' 空气状况正常\t无有害气体 '}]}
]} # id是你的数据流名称
jdata = json.dumps(data).encode("utf-8")
r = requests.post(url=url_post, headers=headers, data=jdata)
#print("发送成功:", r.text)
# 带有异常处理的主程序
try:
while True: # 执行一个while死循环
status = GPIO.input(CHANNEL) # 检测36号引脚口的输入高低电平状态
# print(status) # 实时打印此时的电平状态
if status == True: # 如果为高电平,说明MQ-2正常,并打印“OK”
print(' 空气状况正常 ')
http_post_1()
else: # 如果为低电平,说明MQ-2检测到有害气体,并打印“dangerous”
print(' 检测到危险气体 ! ! ! ')
http_post_0()
time.sleep(0.1) # 睡眠0.1秒,以后再执行while循环
except KeyboardInterrupt: # 异常处理,当检测按下键盘的Ctrl+C,就会退出这个>脚本
GPIO.cleanup() # 清理运行完成后的残余
OneNet界面展示