IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 基于 WEB 的室内温湿度监测系统(树莓派) -> 正文阅读

[Python知识库]基于 WEB 的室内温湿度监测系统(树莓派)

一、项目介绍

    本实验通过 DHT11 模块测量大气中温湿度,利用树莓派多功能的用途,将温湿度测量结果显示到网页,并包含历史数据、实时折线图表示。

二、 实验介绍

1. 实验原材料

树莓派 Raspberry 4 Model B,DHT11 v1.3,电阻,杜邦线,面包板。

2. 流程图

在这里插入图片描述

三、硬件部分

DHT11 传感器实验相关知识

1.1 DHT11引脚说明
下图为GPIO引脚对照表
GPIO引脚对照表
本次实验使用的DHT11有3个引脚:VCC、DATA、GND。
VCC接 5V PIN 02;DATA接 PIN 08;GND接地 PIN 06。
硬件连接图如下:右上角40引脚的2、6、8所连的三条线为DHT11所连的线。下面的13、14引脚所连的线为温度阈值报警小灯所用(此为附加功能所用)。
在这里插入图片描述
1.2 外设读取步骤
主机和从机之间的通信可通过如下几个步骤完成((外设(如微处理器)读取DHT11的数据的步骤)。
步骤一:
DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时DHT11的DATA数据线由上拉电阻拉高一直保持高电平;此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。
步骤二:
微处理器的I/O设置为输出同时输出低电平,且低电平保持时间不能小于18ms(最大不得超过30ms),然后微处理器的I/O设置为输入状态,由于上拉电阻,微处理器的I/O即DHT11的DATA数据线也随之变高,等待DHT11作出回答信号。发送信号如下图所示: 在这里插入图片描述
步骤三:
DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的 DATA引脚处于输出状态,输出83微秒的低电平作为应答信号,紧接着输出87微秒的高电平通知 外设准备接收数据,微处理器的I/O此时处于输入状态,检测到I/O有低电平(DHT11回应信号) 后,等待87微秒的高电平后的数据接收,发送信号下图所示:
在这里插入图片描述
步骤四:
由DHT11的DATA引脚输出40位数据,微处理器根据I/O电平的变化接收40位数据,位数据 “0”的格式为:54微秒的低电平和23-27微秒的高电平,位数据“1”的格式为:54微秒的低 电平加68-74微秒的高电平。位数据“0”、“1”格式信号如下图所示:
在这里插入图片描述
结束信号:
DHT11的DATA引脚输出40位数据后,继续输出低电平54微秒后转为输入状态,由于上拉电 阻随之变为高电平。但DHT11内部重测环境温湿度数据,并记录数据,等待外部信号的到来。

1.3. DHT11单总线传送数据位定义
DATA 用于微处理器与 DHT11 之间的通讯和同步,采用单总线数据格式,一次传送 40 位数据,高位先出。
数据格式:

8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验位,其中湿度小数部分为 0

示例一:接收到的40位数据为:
0011 0101 / 0000 0000 / 0001 1000 / 0000 0100 / 0101 0001
湿度高8位 / 湿度低8位 / 温度高8位 / 温度低8位 / 校验位
计算:
00110101+00000000+00011000+00000100=01010001
接收数据正确:
湿度:00110101(整数)=35H=53%RH00000000(小数)=00H=0.0%RH=>53%RH+0.0%RH=53.0%RH
温度:00011000(整数)=18H=24℃00000100(小数)=04H=0.4℃=>24℃+0.4℃=24.4℃

实验中dht11.py所读取的温度显示为整数,欲将其变为带有一位小数的输出,只需将return值处理一下,将温度的小数部分the_bytes[3]加上就可以了

return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2]+float(the_bytes[3])/10, the_bytes[0])

四、软件部分

1. 设计流程

在这里插入图片描述

相关参数调用关系

本实验中参数调用主要围绕app.py文件进行,app.py运行后,首先读取dht11读取到的当前温湿度参数,把参数存到数据库中,创建数组调用数据生成折线图。生成的图片通过存到对应路径上。html文件调用路径上存的文件显示到网页上,同时调用style.css文件进行网页美化。

文件夹路径关系:
在这里插入图片描述
主文件app.py位于WebServer文件夹
第三级目录中:
templates用于放html文件;
statics用于存放,css文件和其他的图片(可自选,例如背景图、温度湿度曲线图等便于调用);
history用于放dht11.py文件(因为我的树莓派B4无法使用Adafruit库,所以是使用硬件直接读取温湿度数据的。)

2. 树莓派学习笔记

树莓派相关下载过程此处不再赘述,这里只总结一些我在实验中遇到的问题以及解决的方法。

  1. SSH 远程连接树莓派 使用SSH远程连接后,可以在另外一台PC机上操控树莓派
    在这里插入图片描述
    这个问题的出现的很普遍,因为大家对这个操作平台都还不是很熟悉,这个问题出现的原因是树莓派 SSH 服务没有打开,在树莓派桌面左上角树莓派图标上单击-> Preferences-> Raspberry Pi Configuration-> Interfaces -> SSH Enabled 选中 -> OK,重启PUTTY就可以解决了。
  2. 树莓派远程桌面 在实现远程连接时,要保证两个设备处在同一个网络中。 而且要确认好目前设备开的端口是哪一个,树莓派的IP地址是哪一个,确定好了才可以连接。

3. 代码程序报错记录

在运行这几个py文件时,遇到的几个主要的报错以及出现原因、处理方法如下:

  1. 报错:SyntaxError: invalid syntax
    这句话的意思是Python代码语法有问题。Python对语法的要求非常严格,有时候代码是网上直接找来复制粘贴的,就有可能导致明明看上去没错,但就一直报语法错误,可以尝试清空格式后重新输入一遍。
    另外需要注意的是Python2和Python3的语法有区别,比如Python2的print后面不用加括号,而Python3中就会报错。
  2. 报错:ModuleNotFoundError: No module named 'Adafruit_DHT’
    “import Adafruit_DHT ”语句错误,提示找不到Adafruit_DHT模块,但是在terminal里输入“import Adafruit_DHT”不报错,并且已经下载了相关库。可能是python版本的问题,先明确你运行程序时使用的是哪个Python版本。
    使用的是Python2使用的语句是:sudo pip install Adafruit_Python_DHT; sudo python setup.py install,
    但使用的是Python3使用的语句是:sudo pip3 install Adafruit_Python_DHT; sudo python3 setup.py install
    注意一定要用pip3
  3. 报错:ImportError: cannot import name ‘Beaglebone_Black_Driver’ from 'Adafruit_DHT’
    通过报错信息,发现是库中的common.py里引用get_platform有问题,再去platform_detect.py里查看发现树莓派的版本定义里没有4B,由于我们使用的是4B树莓派,所以导致返回了None
    解决:出现这个原因是AdaFruit不再更新这个旧的Adafruit_DHT库,所以里面没有更新树莓派4B对应的cpu。有两种解决方法,使用这个传感器的新库“Adafruit_CircuitPython_DHT”,或者在你原来的Adadruit_DHT库中自己手动更新,具体操作如下:
    (1)找到Adafruit_DHT文件夹, “/usr/local/lib/python3.7/dist-packages/Adafruit_DHT/”,
    (2)打开文件夹里的platform_detect.py文件,可以看到把下面这两行代码添加到最后。
    在这里插入图片描述
    其中BCM2711是树莓派4B的CPU,它是四核Cortex-A72 64位。其它的BCMxxxx是其它树莓派版本的CPU。
  4. Adafruit库无法使用
    在根据讲义下载DHT库Adafruit库时,下载到的库中缺少Raspberry_Pi_2driver.py文件,导致执行DHT11_test测试文件时就没能读出数据,经过原因排查和多次下载,我们推断猜测可能是因为树莓派的问题,因为我们使用的是树莓派B4,可能是版本更新了的问题。
    对此我们的解决方案是:不使用Adafruit读取温湿度数据,而是用我们之前用过的DHT11.py文件进行读取,这个文件是通过GPIO端口直接读取数据的,并具有CRC纠检错功能,足够实验的功能实现。
    实际操作:WebServer文件夹下添加新文件夹:library,将dht11.py文件放在其中作调用功能,并在app.py文件加调用语言from library import dht11,将接下来的变量名重置。

4. 代码

基础功能的代码:使用dht11.py读取温度后,将温湿度显示到网页上。

(1)数据读取部分dht11.py

用于通过传感器读取数据的文件

import time
import RPi


class DHT11Result:
    'DHT11 sensor result returned by DHT11.read() method'

    ERR_NO_ERROR = 0
    ERR_MISSING_DATA = 1
    ERR_CRC = 2

    error_code = ERR_NO_ERROR
    temperature = -1
    humidity = -1

    def __init__(self, error_code, temperature, humidity):
        self.error_code = error_code
        self.temperature = temperature
        self.humidity = humidity

    def is_valid(self):
        return self.error_code == DHT11Result.ERR_NO_ERROR


class DHT11:
    'DHT11 sensor reader class for Raspberry'

    __pin = 0

    def __init__(self, pin):
        self.__pin = pin

    def read(self):
        RPi.GPIO.setup(self.__pin, RPi.GPIO.OUT)

        # send initial high
        self.__send_and_sleep(RPi.GPIO.HIGH, 0.05)

        # pull down to low
        self.__send_and_sleep(RPi.GPIO.LOW, 0.02)

        # change to input using pull up
        RPi.GPIO.setup(self.__pin, RPi.GPIO.IN, RPi.GPIO.PUD_UP)

        # collect data into an array
        data = self.__collect_input()

        # parse lengths of all data pull up periods
        pull_up_lengths = self.__parse_data_pull_up_lengths(data)

        # if bit count mismatch, return error (4 byte data + 1 byte checksum)
        if len(pull_up_lengths) != 40:
            return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)

        # calculate bits from lengths of the pull up periods
        bits = self.__calculate_bits(pull_up_lengths)

        # we have the bits, calculate bytes
        the_bytes = self.__bits_to_bytes(bits)

        # calculate checksum and check
        checksum = self.__calculate_checksum(the_bytes)
        if the_bytes[4] != checksum:
            return DHT11Result(DHT11Result.ERR_CRC, 0, 0)

        # ok, we have valid data, return it
        return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2]+float(the_bytes[3])/10, the_bytes[0]+float(the_bytes[1])/10)

    def __send_and_sleep(self, output, sleep):
        RPi.GPIO.output(self.__pin, output)
        time.sleep(sleep)

    def __collect_input(self):
        # collect the data while unchanged found
        unchanged_count = 0

        # this is used to determine where is the end of the data
        max_unchanged_count = 100

        last = -1
        data = []
        while True:
            current = RPi.GPIO.input(self.__pin)
            data.append(current)
            if last != current:
                unchanged_count = 0
                last = current
            else:
                unchanged_count += 1
                if unchanged_count > max_unchanged_count:
                    break

        return data

    def __parse_data_pull_up_lengths(self, data):
        STATE_INIT_PULL_DOWN = 1
        STATE_INIT_PULL_UP = 2
        STATE_DATA_FIRST_PULL_DOWN = 3
        STATE_DATA_PULL_UP = 4
        STATE_DATA_PULL_DOWN = 5

        state = STATE_INIT_PULL_DOWN

        lengths = [] # will contain the lengths of data pull up periods
        current_length = 0 # will contain the length of the previous period

        for i in range(len(data)):

            current = data[i]
            current_length += 1

            if state == STATE_INIT_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # ok, we got the initial pull down
                    state = STATE_INIT_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_INIT_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # ok, we got the initial pull up
                    state = STATE_DATA_FIRST_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_FIRST_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # we have the initial pull down, the next will be the data pull up
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # data pulled up, the length of this pull up will determine whether it is 0 or 1
                    current_length = 0
                    state = STATE_DATA_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # pulled down, we store the length of the previous pull up period
                    lengths.append(current_length)
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue

        return lengths

    def __calculate_bits(self, pull_up_lengths):
        # find shortest and longest period
        shortest_pull_up = 1000
        longest_pull_up = 0

        for i in range(0, len(pull_up_lengths)):
            length = pull_up_lengths[i]
            if length < shortest_pull_up:
                shortest_pull_up = length
            if length > longest_pull_up:
                longest_pull_up = length

        # use the halfway to determine whether the period it is long or short
        halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
        bits = []

        for i in range(0, len(pull_up_lengths)):
            bit = False
            if pull_up_lengths[i] > halfway:
                bit = True
            bits.append(bit)

        return bits

    def __bits_to_bytes(self, bits):
        the_bytes = []
        byte = 0

        for i in range(0, len(bits)):
            byte = byte << 1
            if (bits[i]):
                byte = byte | 1
            else:
                byte = byte | 0
            if ((i + 1) % 8 == 0):
                the_bytes.append(byte)
                byte = 0

        return the_bytes

    def __calculate_checksum(self, the_bytes):
        return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255

(2)主文件app.py

  1. 这里要注意地址需要修改成自己主机的地址,查看树莓派地址的方式:在终端输入ifconfig然后回车。
  2. from-import是用来引用文件中的文件内容的。 title中的内容"Sensor Data"和html中的代码</>Sensor Data</>是对应的,如果想要修改,需要两边同时修改。
  3. time.sleep(1)的用处是让程序间隔一秒读取数据。这里的timesleep是python自带的一个函数,在编写程序时候,我们有时需要将程序短暂的停顿一下,这个时候就需要用到time包下面的sleep。
  4. 有的时候运行报错可能是因为端口的问题,尝试把port=50改成80/5080/8080之类的就好了。
from flask import Flask ,render_template
from library import dht11
import RPi.GPIO as GPIO


app = Flask(__name__)
# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

#read data using pin 14
instance = dht11.DHT11(pin=14)
    
    
@app.route("/")
def hello():
    result = instance.read()
    time.sleep(1)
    temperature = result.temperature
    humidity = result.humidity
    templateData={
        'title': "Sensor Data",
        'temp': temperature,
        'hum': humidity,
    }       
    return render_template("index.html", **templateData)
    
if __name__ == "__main__":
     app.run(host="192.168.1.xxx", port=50, debug=True)


(3)index.html

<!DOCTYPE html>
   <head>
      <html>
   <head>
   <title>{{title}}</title>

   <link rel="stylesheet" href='../static/style.css'/>
   </head>
   <body>
       <h1>Sensor Data</h1>
       <h2>TEMPERATURE ==> {{ temp }} oC</h2>
       <h3>HUMIDITY (Rel.) ==> {{ hum }} %</h3>
        
        </body>
       </html>

(4)style.css:用于美化html背景和字体颜色

body{
 	background: blue;
 	color: yellow;
	 padding:1%
}
.button {
 	font: bold 15px Arial;
 	text-decoration: none;
 	background-color: #EEEEEE;
 	color: #333333;
 	padding: 2px 6px 2px 6px;
 	border-top: 1px solid #CCCCCC;
 	border-right: 1px solid #333333;
 	border-bottom: 1px solid #333333;
 	border-left: 1px solid #CCCCCC;
}
  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:36:25  更:2021-07-25 11:37:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 0:18:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码