跳转到内容

传感器数据读取逻辑

传感器数据读取逻辑

本节介绍如何将温湿度传感器和光照传感器的数据读取整合为一个完整的、健壮的采集逻辑。学习完成后,您将能够:

  • 设计非阻塞的定时数据采集程序
  • 实现传感器错误重试和状态管理
  • 构建标准化的 JSON 数据格式
  • 为 MQTT 发布做好准备

在开始本节之前,请确保:

  • 已完成 DHT22 传感器集成
  • 已完成 BH1750 传感器集成
  • 理解非阻塞编程模式
  • 了解 JSON 数据格式

核心设计原则

  1. 非阻塞运行:主循环不能使用 delay() 阻塞,确保 MQTT 连接和命令响应及时
  2. 错误容错:单次读取失败不应影响系统整体运行
  3. 数据标准化:统一的数据格式便于后续处理和存储
  4. 可配置性:采样频率、单位、校准参数可灵活调整
┌──────────────────┐
│ 系统初始化 │
│ - WiFi 连接 │
│ - MQTT 连接 │
│ - 传感器初始化 │
└────────┬─────────┘
┌──────────────────┐
│ 主循环 │ ←────┐
│ - 检查 MQTT │ │
│ - 处理回调 │ │
└────────┬─────────┘ │
↓ │
┌──────────────────┐ │
│ 定时采集触发? │ 否 ──┘
│ 3 秒到? │
└────────┬─────────┘
↓ 是
┌──────────────────┐
│ 采集传感器数据 │
│ - DHT22 读取 │
│ - BH1750 读取 │
└────────┬─────────┘
┌──────────────────┐
│ 数据验证 │
│ - NaN 检查 │
│ - 范围检查 │
└────────┬─────────┘
┌──────────────────┐
│ 构建 JSON │
│ - 序列化 │
└────────┬─────────┘
┌──────────────────┐
│ MQTT 发布 │
│ - 发布到 Topic │
└──────────────────┘
#ifndef CONFIG_H
#define CONFIG_H
// WiFi 配置
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
// MQTT 配置
#define MQTT_BROKER "192.168.1.100"
#define MQTT_PORT 1883
#define MQTT_USER ""
#define MQTT_PASSWORD ""
#define MQTT_TOPIC "factory/zone1/environment"
#define MQTT_TOPIC_TEMP "factory/zone1/temperature"
#define MQTT_TOPIC_HUM "factory/zone1/humidity"
#define MQTT_TOPIC_LIGHT "factory/zone1/light"
#define MQTT_CLIENT_ID "esp32_weather_001"
// 传感器引脚配置
#define DHTPIN 4
#define DHTTYPE DHT22
// 采集配置
#define SAMPLE_INTERVAL_MS 3000 // 采样间隔 3 秒
#define SAMPLE_AVG_COUNT 3 // 移动平均窗口
#define SENSOR_RETRY_COUNT 3 // 失败重试次数
// 传感器阈值
#define TEMP_MAX 60.0 // 温度上限 °C
#define TEMP_MIN -20.0 // 温度下限 °C
#define HUM_MAX 100.0 // 湿度上限 %
#define HUM_MIN 0.0 // 湿度下限 %
#define LUX_MAX 65535.0 // 光照上限
#define LUX_MIN 0.0 // 光照下限
// 设备信息
#define DEVICE_ID "ESP32_001"
#define DEVICE_LOCATION "Factory Zone 1"
#endif
#include <Arduino.h>
#include <Wire.h>
#include <BH1750.h>
#include <DHT.h>
#include <ArduinoJson.h> // 需要安装 ArduinoJson 库
#include "config.h"
// 传感器对象
DHT dht(DHTPIN, DHTTYPE);
BH1750 lightMeter;
// 传感器数据结构体
struct SensorData {
float temperature;
float humidity;
float lux;
bool valid;
};
// 移动平均缓冲区
struct {
float temperatures[SAMPLE_AVG_COUNT];
float humidities[SAMPLE_AVG_COUNT];
float luxes[SAMPLE_AVG_COUNT];
int index;
int count;
} avgBuffer;
// 系统状态
struct {
unsigned long lastSampleTime;
int errorCount;
bool wifiConnected;
bool mqttConnected;
bool sensorsInitialized;
} sysStatus;
// 函数声明
SensorData readAllSensors();
bool validateReading(SensorData &data);
void addToAverage(SensorData &data);
SensorData getAveragedData();
void publishData(SensorData &data);
void initSensors();
void checkSystemHealth();
void setup() {
Serial.begin(115200);
Serial.println("\n=== Factory Environment Monitor v1.0 ===");
Serial.print("Device: ");
Serial.println(DEVICE_ID);
Serial.print("Location: ");
Serial.println(DEVICE_LOCATION);
// 初始化 I2C 总线
Wire.begin();
// 初始化传感器
initSensors();
// 初始化平均缓冲区
memset(&avgBuffer, 0, sizeof(avgBuffer));
// 初始化系统状态
sysStatus.lastSampleTime = 0;
sysStatus.errorCount = 0;
Serial.println("System ready, starting data collection...\n");
}
void loop() {
unsigned long currentMillis = millis();
// 定时采集触发
if (currentMillis - sysStatus.lastSampleTime >= SAMPLE_INTERVAL_MS) {
sysStatus.lastSampleTime = currentMillis;
// 采集传感器数据
SensorData data = readAllSensors();
// 数据验证
if (validateReading(data)) {
// 添加到平均缓冲区
addToAverage(data);
// 获取移动平均结果
SensorData avgData = getAveragedData();
// 发布数据
publishData(avgData);
// 输出到串口
printData(avgData);
// 系统健康检查
checkSystemHealth();
} else {
sysStatus.errorCount++;
Serial.println("Warning: Invalid sensor reading");
}
}
// 此处预留 MQTT 连接维护和回调处理
// (将在后续章节实现)
}
void initSensors() {
// 初始化 DHT22
dht.begin();
delay(100);
// 初始化 BH1750
if (lightMeter.begin()) {
Serial.println("BH1750: OK");
} else {
Serial.println("BH1750: FAILED - Check I2C connection");
}
sysStatus.sensorsInitialized = true;
}
SensorData readAllSensors() {
SensorData data = {0, 0, 0, false};
// 读取 DHT22(带重试)
for (int i = 0; i < SENSOR_RETRY_COUNT; i++) {
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
if (!isnan(data.temperature) && !isnan(data.humidity)) {
break;
}
delay(50); // 重试间隔
}
// 读取 BH1750
data.lux = lightMeter.readLightLevel();
return data;
}
bool validateReading(SensorData &data) {
// 检查 DHT22 读数是否有效
if (isnan(data.temperature) || isnan(data.humidity)) {
return false;
}
// 检查 BH1750 读数是否有效
if (data.lux < 0) {
return false;
}
// 检查数值是否在合理范围内
if (data.temperature < TEMP_MIN || data.temperature > TEMP_MAX) {
Serial.print("Temperature out of range: ");
Serial.println(data.temperature);
return false;
}
if (data.humidity < HUM_MIN || data.humidity > HUM_MAX) {
Serial.print("Humidity out of range: ");
Serial.println(data.humidity);
return false;
}
if (data.lux < LUX_MIN || data.lux > LUX_MAX) {
Serial.print("Lux out of range: ");
Serial.println(data.lux);
return false;
}
data.valid = true;
return true;
}
void addToAverage(SensorData &data) {
avgBuffer.temperatures[avgBuffer.index] = data.temperature;
avgBuffer.humidities[avgBuffer.index] = data.humidity;
avgBuffer.luxes[avgBuffer.index] = data.lux;
avgBuffer.index = (avgBuffer.index + 1) % SAMPLE_AVG_COUNT;
if (avgBuffer.count < SAMPLE_AVG_COUNT) {
avgBuffer.count++;
}
}
SensorData getAveragedData() {
SensorData avg = {0, 0, 0, false};
if (avgBuffer.count == 0) return avg;
float sumTemp = 0, sumHum = 0, sumLux = 0;
for (int i = 0; i < avgBuffer.count; i++) {
sumTemp += avgBuffer.temperatures[i];
sumHum += avgBuffer.humidities[i];
sumLux += avgBuffer.luxes[i];
}
avg.temperature = sumTemp / avgBuffer.count;
avg.humidity = sumHum / avgBuffer.count;
avg.lux = sumLux / avgBuffer.count;
avg.valid = true;
return avg;
}
void printData(SensorData &data) {
Serial.println("┌─────────────────────────────────────┐");
Serial.println("│ Sensor Data Snapshot │");
Serial.println("├─────────────────────────────────────┤");
Serial.print("│ Temperature: ");
Serial.print(data.temperature, 1);
Serial.println(" °C │");
Serial.print("│ Humidity: ");
Serial.print(data.humidity, 1);
Serial.println(" % │");
Serial.print("│ Light: ");
Serial.print(data.lux, 0);
Serial.println(" lux │");
Serial.print("│ Samples in avg: ");
Serial.print(avgBuffer.count);
Serial.println("/" + String(SAMPLE_AVG_COUNT) + "");
Serial.println("└─────────────────────────────────────┘");
}
void checkSystemHealth() {
static unsigned long lastHealthCheck = 0;
if (millis() - lastHealthCheck > 60000) { // 每分钟检查一次
lastHealthCheck = millis();
Serial.print("[Health] Runtime: ");
Serial.print(millis() / 60000);
Serial.print(" min, Errors: ");
Serial.println(sysStatus.errorCount);
// 如果连续错误过多,重启系统
if (sysStatus.errorCount > 50) {
Serial.println("CRITICAL: Too many errors, restarting...");
ESP.restart();
}
}
}
{
"device_id": "ESP32_001",
"location": "Factory Zone 1",
"timestamp": 1697123456,
"temperature": {
"value": 26.5,
"unit": "celsius"
},
"humidity": {
"value": 62.3,
"unit": "percent"
},
"lux": {
"value": 450,
"unit": "lux"
},
"quality": {
"signal_rssi": -65,
"samples": 3
}
}

对于 MQTT 传输,可以使用更紧凑的格式以减少带宽:

{
"d": "ESP32_001",
"ts": 1697123456,
"t": 26.5,
"h": 62.3,
"l": 450
}

售前提示:在售前演示中使用完整 JSON 格式更易理解;在生产部署中可根据带宽需求采用紧凑格式。

Terminal window
# 打开串口监视器后的预期输出

预期输出

=== Factory Environment Monitor v1.0 ===
Device: ESP32_001
Location: Factory Zone 1
BH1750: OK
System ready, starting data collection...
┌─────────────────────────────────────┐
│ Sensor Data Snapshot │
├─────────────────────────────────────┤
│ Temperature: 26.5 °C │
│ Humidity: 62.3 % │
│ Light: 450 lux │
│ Samples in avg: 1/3 │
└─────────────────────────────────────┘

验证检查清单

  • 传感器能稳定读取数据,无 NaN 输出
  • 温度值在合理范围内,无跳变
  • 光照值随环境变化及时响应
  • 系统运行 10 分钟无重启或异常
  • 平均滤波能够平滑数据波动

症状:相邻采样之间温度跳变 > 2°C

解决方案

  1. 增加移动平均窗口大小(从 3 增加到 10)
  2. 添加中值滤波去除异常值
  3. 确认传感器远离热源或风口

症状:系统数小时后停止输出数据

解决方案

  1. 检查是否内存泄漏(减少 String 使用)
  2. 添加看门狗定时器自动恢复
  3. 定期重启系统(每天一次)
  • 推荐: 使用结构体组织传感器数据,便于扩展
  • 推荐: 实现数据验证层,过滤明显错误的数据
  • 推荐: 使用移动平均平滑数据,减少噪声影响
  • 避免: 在采集代码中使用动态内存分配(new/malloc)
  • 避免: 忽视初始化失败的处理(应有明确的错误指示)
  • 避免: 采样频率与 MQTT 发布频率不一致导致数据堆积

本节要点总结:

  1. 采集架构:非阻塞定时采集 + 错误重试 + 移动平均滤波
  2. 数据验证:检查 NaN、范围检查、有效性标记
  3. JSON 格式化:标准 JSON 包含设备信息、时间戳、所有传感器值
  4. 系统健康:错误计数监控、自动重启保护
  5. 可配置性:所有参数通过 config.h 集中管理