RaspberryPi C/C++ 温湿度传感器数据采集
树莓派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