Чтение DHT22 из flask убийств gpio - PullRequest
1 голос
/ 12 июля 2020

Я столкнулся с препятствием, и мне нужна помощь ...

У меня есть raspberry pi 4 с датчиком температуры / влажности DHT22 и flask для отображения данных на простой веб-странице (данные также записывается в базу данных sqlite3, но это не имеет отношения к проблеме). Считывание датчика запускается каждые 15 минут после задания cron.

Все работает отлично (у меня есть месяцы успешного сбора данных), пока я недавно не попытался добавить кнопку html на веб-страницу, чтобы разрешить пользователь, чтобы заставить датчик читать сейчас, а не ждать следующего интервала. Он работает в первый раз, но все последующие попытки чтения датчика теперь терпят неудачу со следующей ошибкой (обратите внимание, что мой провод данных dht22 находится на контакте 4 gpio):

Unable to set line 4 to input

На основе моих исследований (ссылки ниже), это похоже на проблему с библиотекой, используемой для взаимодействия с выводами gpio libgpiod_pulsein , поскольку, когда я нахожу и убиваю этот процесс с терминала, все работает нормально до следующего раза, когда я попытаюсь запустить датчик чтение с кнопки.

https://github.com/adafruit/Adafruit_CircuitPython_DHT/issues/27

RPI dht22 с flask: невозможно установить строку 4 для ввода - истекло время ожидания сообщения PulseIn

Я уже пробовал два часто цитируемых исправления: (1) установить Debug = False в flask, (2) попробовать другой вывод GPIO безуспешно или без заметных изменений в поведении. Единственное исправление, кажется, убивает процесс проблематики c, но очевидно, что это не долгосрочное решение. Некоторые из этих других страниц, похоже, подразумевают, что libgpiod_pulsein не завершается правильно, но для этого не было предложено никакого решения, и я недостаточно сообразителен, чтобы пытаться найти решение самостоятельно.

My вопросы:

  1. Подхожу ли я к «кнопке чтения» принципиально неправильно? Является ли метод html POST плохим способом вызова сценария python, и если да, то что мне делать вместо этого? Я стараюсь избегать добавления новых языков в этот проект (ajax и c.) Только для 1 или 2 функций, но если это единственный способ, я открыт для этого ...

  2. Есть ли здесь ошибка, которая приводит к плохому поведению libgpiod_pulsein, которое можно исправить?

Я добавил код ниже, который кажется актуальным, полный код доступно по адресу https://github.com/nathansibon/raspberry_pi_plant_datalogger

Flask Сервер

from flask import Flask, render_template
from config import *
import csv
import collect_data
from forms import *
from time import sleep

app = Flask(__name__)
app.config['SECRET_KEY'] = 'sparklingcider' #TODO change to random number

@app.route('/', methods=['GET', 'POST'])
def index():

    f_name = name.replace('_', '')
    f_location = location.replace('_', '')

    button = collect_data_button()

    data = {
        'name': f_name,
        'location': f_location
    }

    # Get the current status variables from CSV
    reader = csv.reader(open('webpage_sensor_data.csv'))
    for row in reader:
        if row[0] == '':
            pass
        else:
            data[row[0]] = row[1]

    # Get the daily of the variables from CSV
    reader = csv.reader(open('webpage_daily_data.csv'))
    for row in reader:
        if row[0] == '':
            pass
        else:
            data[row[0]] = row[1]

    if button.is_submitted():

        print('submit detected')
        collect_data.do()
        sleep(2)

        return render_template('index.html', **data, button=button)

    return render_template('index.html', **data, button=button)

# this method sets the chart images to expire after 5 mins so the browser will fetch new images instead of using the cache
@app.after_request
def add_header(response):
    response.cache_control.max_age = 300
    response.cache_control.public = True
    return response

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Главная HTML Страница

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Current Data {{name}}</title>
    <!-- this next part disables browser caching, otherwise the charts won't update! -->
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
<style>
    th, td {padding: 5px;}
</style>
</head>

<body>

<h1>Data from {{name}} located at {{location}}</h1>
<h1>Current:</h1>
<h2>Last read at {{time}}</h2>
<form action="" method="post"> {{ button.submit() }} </form>
<table>
    <tr>
        <th></th>
        <th style='text-align:center'>Drybulb</th>
        <th style='text-align:center'>Relative Humidity</th>
        <th style='text-align:center'>RH for 1 kPa VPD</th>
        <th style='text-align:center'>Vapor Pressure Deficit</th>
        <th style='text-align:center'>Light</th>
    </tr>
    <tr>
        <td>Indoor</td>
        <td style='text-align:center'>{{indoor_drybulb}} °C</td>
        <td style='text-align:center'>{{indoor_rh}}%</td>
        <td style='text-align:center'>{{req_rh}}%</td>
        <td style='text-align:center'>{{indoor_vpd}} kPa</td>
        <td style='text-align:center'>{{indoor_lux}} lux</td>
    </tr>
    <tr>
        <td>Outdoor</td>
        <td style='text-align:center'>{{outdoor_drybulb}} °C</td>
        <td style='text-align:center'>{{outdoor_rh}}%</td>
        <td></td>
        <td style='text-align:center'>{{outdoor_vpd}} kPa</td>
        <td></td>
    </tr>
</table>

<h1>Yesterday's Indoor Conditions:</h1>
<table>
    <tr>
        <th></th>
        <th style='text-align:center'>Mean</th>
        <th style='text-align:center'>High</th>
        <th style='text-align:center'>High Time</th>
        <th style='text-align:center'>Low</th>
        <th style='text-align:center'>Low Time</th>
    </tr>
    <tr>
        <td>Drybulb</td>
        <td style='text-align:center'>{{yesterday_drybulb_mean}} °C</td>
        <td style='text-align:center'>{{yesterday_drybulb_max}} °C</td>
        <td style='text-align:center'>{{yesterday_drybulb_max_time}}</td>
        <td style='text-align:center'>{{yesterday_drybulb_min}} °C</td>
        <td style='text-align:center'>{{yesterday_drybulb_min_time}}</td>

    </tr>
    <tr>
        <td>Relative Humidity</td>
        <td style='text-align:center'>{{yesterday_rh_mean}}%</td>
        <td style='text-align:center'>{{yesterday_rh_max}}%</td>
        <td style='text-align:center'>{{yesterday_rh_max_time}}</td>
        <td style='text-align:center'>{{yesterday_rh_min}}%</td>
        <td style='text-align:center'>{{yesterday_rh_min_time}}</td>
    </tr>
    <tr>
        <td>Vapor Pressure Deficit</td>
        <td style='text-align:center'>{{yesterday_vpd_mean}} kPa</td>
        <td style='text-align:center'>{{yesterday_vpd_max}} kPa</td>
        <td style='text-align:center'>{{yesterday_vpd_max_time}}</td>
        <td style='text-align:center'>{{yesterday_vpd_min}} kPa</td>
        <td style='text-align:center'>{{yesterday_vpd_min_time}}</td>
    </tr>
    <tr>
        <td>Light</td>
        <td style='text-align:center'>{{yesterday_lux_mean}} lux</td>
        <td style='text-align:center'>{{yesterday_lux_max}} lux</td>
        <td style='text-align:center'>{{yesterday_lux_max_time}}</td>
        <td></td>
        <td></td>
    </tr>
</table>

<h1>Last Week</h1>
<table>
    <tr>
        <img src="/static/last_week_drybulb.png" alt="drybulb graph" width="80%" height="auto">
    </tr>
    <tr>
        <img src="/static/last_week_rh.png" alt="drybulb graph" width="80%" height="auto">
    </tr>
    <tr>
        <img src="/static/last_week_vpd.png" alt="drybulb graph" width="80%" height="auto">
    </tr>
    <tr>
        <img src="/static/last_week_lux.png" alt="lux graph" width="80%" height="auto">
    </tr>
</table>

</body>
</html>

Flaskforms для кнопки

from flask_wtf import FlaskForm
import wtforms as wt
from wtforms.fields import html5 as wt5

class collect_data_button(FlaskForm):

    submit = wt.SubmitField('Read Now')

Сценарий сбора данных (это то, что cron запускает каждые 15 минут)

from function_library import *
import logging, time, os, datetime
from config import *


# This is where the main code is kept for this script.
# We enclose it in a function so we can wrap it in a general purpose error-handler as shown below
# This makes writing log files much simpler and will work whether it's run from the shell or a cron job
def do():

    # retrieve data from OpenWeatherMap API, then calculate additional metrics from retrieved data and append list
    outdoor = get_outdoor_weather()
    outdoor = calc_outdoor_weather(outdoor)

    # retrieve data from sensor(s), then calculate additional metrics from retrieved data and append list.
    indoor = get_indoor_all()

    # Full path of database: /home/pi/share/env_datalogger/pi_X_data.db
    update_db(name + '_data.db', indoor, outdoor)

    update_web_vars_sensor(indoor, outdoor)
    update_web_charts(name + '_data.db')


# Set all paths to the current directory so cron job will not crash, but code will still run if you move files later...
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

logging.basicConfig(filename='logs/collect_data.log', level=logging.INFO)
logging.info('started @ ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))

# this 'if' is necessary to allow other scripts (such as buttons on the flask server) to trigger the system to read sensors, but prevent this from running on module import
if __name__ == "__main__":
    # general purpose error handling to log file. helpful when executing from cron since you don't see errors
    try:
        do()
    except Exception as e:
        logging.exception('Error in main')
        logging.info(e)

    logging.info('completed @ ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + '\n')

    exit()

Импорт для function_library (содержит функции ниже)

import math, numpy, sqlite3, time, logging, pyowm
import pandas as pd
import numpy as np
from config import *
from datetime import datetime
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

# These packages will raise an error if not running this code on a Pi
# Adding this error handling will allow coding and debugging on PC
try:
    import board, adafruit_dht
except Exception as e:
    logging.info('not connected to pi, problematic libraries not imported (1 of 2)')
    logging.info(e)
    pass

try:
    import busio, adafruit_veml7700
except Exception as e:
    logging.info('not connected to pi, problematic libraries not imported (2 of 2)')
    logging.info(e)
    pass

Функция чтения внутреннего датчика (внутри function_library.py)

def get_indoor_all():

    output = [
        datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        pi_serial,
        location
    ]

    if sensors.get('temp_humid') != 'none':
        # output is [0] datetime [1] serial [2] location [3] drybulb [4] rh
        for i in get_indoor_weather():
            output.append(i)

        # output is now [0] datetime [1] serial [2] location [3] drybulb [4] rh [5] wet bulb [6] dew point, [7] vapor pressure deficit [8] rh for vpd [9] white_light [10] lux
        output = calc_indoor_weather(output)
    else:
        logging.info('No temp/humidity sensor listed, adding placeholders')
        for i in range(6):
            output.append(0)

    if sensors.get('light') != 'none':
        # output is [0] datetime [1] serial [2] location [3] drybulb [4] rh [5] wet bulb [6] dew point, [7] vapor pressure deficit [8] rh for vpd [9] white_light [10] lux
        for i in get_indoor_light():
            output.append(i)
    else:
        logging.info('No light sensor listed, adding placeholders')
        for i in range(2):
            output.append(0)

    # TODO add soil moisture sensor

    return output

Считывание датчика DHT22 (вызывается get_indoor_all)

def get_indoor_weather():

    # Initial the dht device, with data pin connected to:
    print('starting dht22')
    dhtDevice = adafruit_dht.DHT22(board.D4)

    # Unfortunately, this sensor doesn't always read correctly so error handling and noise reduction is included
    max = 5
    err_count = 0
    drybulb = []
    humid = []
    output = []

    while len(drybulb) < max and err_count < max :
        logging.info('read: '+str(len(drybulb))+', err: '+str(err_count))
        # The DHT22 is reportedly slow at reading, this wait hopefully prevents a bad read (i.e. wildly different than it should be) on fail
        time.sleep(2)

        try:
            drybulb.append(dhtDevice.temperature)
            humid.append(round(dhtDevice.humidity / 100, 2))  # convert RH from "50%" to "0.50" to match outdoor weather format and work in calculations
            logging.info('success')
        except Exception as ee:
            #logging.info('Error reading Temp-Humid Sensor, retrying...')
            logging.info(ee)
            err_count += 1

    output.append(std_filter(drybulb, 1, 2))
    output.append(std_filter(humid, 1, 2))

    return output
...