RaspberryPi C/C++ 温湿度传感器数据采集

Posted on Aug 4, 2024
树莓派5B使用C/C++采集DHT11传感器温湿度并使用MySQL存储, 使用python-flask展示温湿度曲线.

开发环境

硬件: RaspberryPi5, DHT11
系统: Debian 12
软件: gcc-12.2 python3.11 wiringpi mysql5.7

完整项目代码

树莓派传感器数据采集:
https://github.com/rb-plan/amadoi.git

Python绘制温度曲线图:
https://github.com/rb-plan/blackboard.git

准备开发环境

apt install gcc g++ wiringpi

MySQL: apt install mariadb-server 或编译安装: Mysql-5.7 Compile

GPIO接线

dht11有效针脚有3个 从左到右依次 + out - , 接线:

“+” : pin2 (5v)
“out”: pin6 (0v)
“-” : pin7 (gpio)

 +-----+-----+---------+------+---+---Pi 5---+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 | ALT3 | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 | ALT3 | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 0 |  -   | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 0 |  -   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 1 | OUT  | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   -  | 0 | 15 || 16 | 0 |  -   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 1 | OUT  | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI | ALT0 | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO | ALT0 | 0 | 21 || 22 | 1 | OUT  | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT  | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | OUT  | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   -  | 0 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   -  | 0 | 31 || 32 | 0 |  -   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   -  | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   -  | 0 | 35 || 36 | 0 |  -   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   -  | 0 | 37 || 38 | 0 |  -   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 |  -   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 5---+---+------+---------+-----+-----+

RaspberryPi Pinout: https://github.com/pinout-xyz/Pinout.xyz.git

读取传感器数据

// sensor.h
#ifndef SENSOR_H
#define SENSOR_H

class Sensor {
public:
    virtual void readData() = 0; // Pure virtual function
    virtual int getTemperature() const = 0;
    virtual int getHumidity() const = 0;
    virtual int getStatus() const =0;
    virtual ~Sensor() {}
};

#endif // SENSOR_H
// dht11.h
#ifndef DHT11_H
#define DHT11_H

#include "sensor.h"
#include <wiringPi.h>
#include <array>
#include <iostream>
#include <iomanip>

class DHT11 : public Sensor {
public:
    DHT11(int pin);
    void readData() override;
    int getTemperature() const override;
    int getHumidity() const override;
    int getStatus() const override;

private:
    static const int MAXTIMINGS = 85;
    int pin;
    std::array<uint8_t, 5> data;

    void resetData();
    int temperature;
    int humidity;
    int status;
};

#endif // DHT11_H
// dht11.cc
#include "dht11.h"

DHT11::DHT11(int pin) : pin(pin), temperature(0), humidity(0) {
    data.fill(0);
}

void DHT11::resetData() {
    data.fill(0);
}

void DHT11::readData() {
    uint8_t laststate = HIGH;
    uint8_t counter = 0;
    uint8_t j = 0;

    resetData();

    // pull pin down for 18 milliseconds
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    delay(18);
    // then pull it up for 40 microseconds
    digitalWrite(pin, HIGH);
    delayMicroseconds(40);
    // prepare to read the pin
    pinMode(pin, INPUT);

    // detect change and read data
    for (int i = 0; i < MAXTIMINGS; i++) {
        counter = 0;
        while (digitalRead(pin) == laststate) {
            counter++;
            delayMicroseconds(1);
            if (counter == 255) {
                break;
            }
        }
        laststate = digitalRead(pin);

        if (counter == 255) {
            break;
        }

        // ignore first 3 transitions
        if ((i >= 4) && (i % 2 == 0)) {
            // shove each bit into the storage bytes
            data[j / 8] <<= 1;
            if (counter > 16) {
                data[j / 8] |= 1;
            }
            j++;
        }
    }

    // check we read 40 bits (8bit x 5 ) + verify checksum in the last byte
    // print it out if data is good
    if ((j >= 40) && (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))) {
        humidity = data[0];
        temperature = data[2];
        status = 0; // Data is good
    } else {
        status = 2; // Data not good
        // std::cout << "Data not good, skip" << std::endl;
    }
}

int DHT11::getTemperature() const {
    return temperature;
}

int DHT11::getHumidity() const {
    return humidity;
}

int DHT11::getStatus() const {
    return status;
}

创建数据库

databases.sql

CREATE DATABASE IF NOT EXISTS amadoi CHARACTER SET utf8mb4;

USE amadoi;

CREATE TABLE t_constant (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    param VARCHAR(255) NOT NULL,
    value VARCHAR(255),
    summary VARCHAR(255),
    PRIMARY KEY (id)
);

INSERT INTO t_constant (param, value, summary) 
VALUES ('dht11_rate', '5000', 'ms');

CREATE TABLE IF NOT EXISTS t_sensors (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    temp TINYINT NOT NULL,         -- temperature in Celsius
    hum TINYINT NOT NULL,          -- relative humidity in percentage
    status TINYINT NOT NULL,       -- data status, 0 for normal 2 for error
    type TINYINT NOT NULL,         -- sensor type, 1 for DHT11, 2 for DHT22
    ctime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- creation time
    utime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- update time
    PRIMARY KEY (id)
);

写入数据库

// mysql_connector.h
#ifndef MYSQL_CONNECTOR_H
#define MYSQL_CONNECTOR_H

#include <mysql.h>
#include <iostream>
#include <string>

class MySQLConnector {
public:
    MySQLConnector(const std::string& host, const std::string& user, const std::string& password, const std::string& db);
    ~MySQLConnector();
    bool insertData(int temp, int hum, int status, int type);
    int getConstant(const std::string& param, int defaultValue);

private:
    MYSQL* conn;
};

#endif // MYSQL_CONNECTOR_H
// mysql_connector.cc
#include "mysql_connector.h"
#include <mysql.h>
#include <iostream>

MySQLConnector::MySQLConnector(const std::string& host, const std::string& user, const std::string& password, const std::string& db) {
    conn = mysql_init(nullptr);
    if (conn == nullptr) {
        std::cerr << "mysql_init() failed" << std::endl;
        exit(1);
    }

    if (mysql_real_connect(conn, host.c_str(), user.c_str(), password.c_str(), db.c_str(), 0, nullptr, 0) == nullptr) {
        std::cerr << "mysql_real_connect() failed" << std::endl;
        mysql_close(conn);
        exit(1);
    }
}

MySQLConnector::~MySQLConnector() {
    mysql_close(conn);
}

bool MySQLConnector::insertData(int temp, int hum, int status, int type) {
    std::string query = "INSERT INTO t_sensors (temp, hum, status, type, ctime, utime) VALUES (" +
                        std::to_string(temp) + ", " +
                        std::to_string(hum) + ", " +
                        std::to_string(status) + ", " +
                        std::to_string(type) + ", NOW(), NOW())";

    if (mysql_query(conn, query.c_str())) {
        std::cerr << "INSERT failed: " << mysql_error(conn) << std::endl;
        return false;
    }
    return true;
}

int MySQLConnector::getConstant(const std::string& param, int defaultValue) {
    std::string query = "SELECT value FROM t_constant WHERE param = '" + param + "'";

    if (mysql_query(conn, query.c_str())) {
        std::cerr << "SELECT failed: " << mysql_error(conn) << std::endl;
        return defaultValue;
    }

    MYSQL_RES* result = mysql_store_result(conn);
    if (result == nullptr) {
        std::cerr << "mysql_store_result() failed: " << mysql_error(conn) << std::endl;
        return defaultValue;
    }

    MYSQL_ROW row = mysql_fetch_row(result);
    if (row && row[0]) {
        int value = std::stoi(row[0]);
        mysql_free_result(result);
        return value;
    }

    mysql_free_result(result);
    return defaultValue;
}

使用Python绘制温度曲线图

# data_plotter.py
import pandas as pd
import matplotlib.pyplot as plt
import mysql.connector
from matplotlib.dates import DateFormatter, AutoDateLocator
import io

def fetch_data():
    # Connect to MySQL database
    conn = mysql.connector.connect(
        host='10.24.0.1',
        user='usr1',
        password='debian',
        database='amadoi'
    )
    cursor = conn.cursor(dictionary=True)

    # Calculate the date 7 days ago
    one_week_ago = pd.Timestamp.now() - pd.Timedelta(days=7)
    one_week_ago_str = one_week_ago.strftime('%Y-%m-%d %H:%M:%S')

    query = """
    SELECT ctime, temp, hum 
    FROM t_sensors 
    WHERE ctime >= %s 
    ORDER BY ctime DESC
    """
    cursor.execute(query, (one_week_ago_str,))
    data = cursor.fetchall()
    cursor.close()
    conn.close()

    # Convert to DataFrame for easier manipulation
    df = pd.DataFrame(data)
    df['ctime'] = pd.to_datetime(df['ctime'])
    df.set_index('ctime', inplace=True)

    # Resample the data to every some minutes
    df_resampled = df.resample('30T').mean()

    # Drop rows with NaN values that might result from resampling
    df_resampled = df_resampled.dropna()

    return df_resampled

def plot_data():
    df_resampled = fetch_data()
    timestamps = df_resampled.index
    temperatures = df_resampled['temp']
    humidities = df_resampled['hum']

    fig, ax1 = plt.subplots(figsize=(12, 6))

    ax1.set_xlabel('Time', fontsize=14)
    ax1.set_ylabel('Temperature (°C)', color='tab:red', fontsize=14)
    ax1.plot(timestamps, temperatures, color='tab:red', label='Temperature', linestyle='-', marker='o')
    ax1.tick_params(axis='y', labelcolor='tab:red')
    ax1.set_ylim(temperatures.min() - 5, temperatures.max() + 5)  # Dynamic range for temperature
    ax1.legend(loc='upper left')

    ax2 = ax1.twinx()
    ax2.set_ylabel('Humidity (%)', color='tab:blue', fontsize=14)
    ax2.plot(timestamps, humidities, color='tab:blue', label='Humidity', linestyle='--', marker='x')
    ax2.tick_params(axis='y', labelcolor='tab:blue')
    ax2.set_ylim(humidities.min() - 5, humidities.max() + 5)  # Dynamic range for humidity
    ax2.legend(loc='upper right')

    # Set x-axis major locator and formatter
    locator = AutoDateLocator(minticks=7, maxticks=7)
    formatter = DateFormatter('%Y-%m-%d %H:%M')
    ax1.xaxis.set_major_locator(locator)
    ax1.xaxis.set_major_formatter(formatter)

    fig.tight_layout()
    plt.xticks(rotation=45)
    plt.grid(True)

    # Save the plot to a BytesIO object
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    plt.close()

    return buf.getvalue()

参考

WiringPi 库的示例代码: https://github.com/nkundu/wiringpi-examples.git