环境监测站:多传感器采集 + MQTT 上报 + 超阈值告警
- 做出一台能同时采集温湿度、光照、空气质量,联网上报并在超阈值时本地告警的环境监测站
- 把「单总线读 DHT」「I2C 读 BH1750」「ADC 读气体模拟量」三种不同接口的传感器混接到同一块板子上,避开 S3 选脚雷区
- 把 esp-mqtt 客户端接进来,按结构化主题把多路读数发布出去,理解「一台设备多个测点」的主题该怎么分
- 给设备加「本地阈值判断 + 告警」这层自主逻辑,体会为什么监测设备不能只当哑巴上报
| 器材 | 数量 | 参考 |
|---|---|---|
| ESP32-S3 开发板 | 1 | —约 25-45 元(以商城实际为准) |
| DHT22 温湿度传感器(或 DHT11,精度低些但更便宜) | 1 | —约 5-15 元(以商城实际为准) |
| BH1750 光照强度模块(I2C) | 1 | —约 3-6 元(以商城实际为准) |
| MQ 系列气体传感器(如 MQ-135 空气质量,模拟输出) | 1 | —约 5-12 元(以商城实际为准) |
| 有源蜂鸣器(告警用,可选换成 LED) | 1 | —约 1-3 元(以商城实际为准) |
| 面包板 + 杜邦线 | 1 套 | —约 8-15 元(以商城实际为准) |
价格随渠道波动,以购买页实时为准。
想象客厅角落挂着一块巴掌大的小板子。它不停地量着屋里的温度、湿度、光线明暗,还嗅着空气里有没有异味;每隔十几秒,把这几个数悄悄发到你手机能看到的地方;一旦哪项越了线——夜里空气质量骤降、或湿度高得能长霉——它当场"嘀嘀嘀"叫起来。你不用盯屏幕,它自己盯着环境,出事了才吵你。这就是这一篇要带你做出来的东西:一台能长期挂着、多路采集、联网上报、还会自己报警的环境监测站。
它比台灯那个项目进了一大步。台灯是"一个输入驱动一个输出",这台监测站要同时管三路进来的传感器、一路出去的网络、还有一层自己的判断逻辑——这正是真实监测类产品的骨架。你之前在图鉴里单独认过 DHT11/DHT22,在 L3 MQTT 那节 单独学过怎么发消息,但把三个不同接口的传感器和 MQTT 拼到一块板子上、还要让它们互不打架,是这一篇才干的活。
这一篇不重复讲 DHT 的时序怎么解、MQTT 的 QoS 是什么——那些图鉴和 guide 讲透了,这里默认你读过。我们只干一件事:把它们组织成一台监测站,讲清"多路传感器 + 联网 + 告警"拼在一起时那些单看一个知识点看不到的坑。
第一步:想清楚这台设备要做什么,再定选型
动手前先把"成品行为"钉死。我们的监测站就四件事:
- 采集:周期性读温度、湿度、光照、空气质量四个量(三个传感器,DHT 一次出温湿度两个)。
- 上报:每个周期把四个读数通过 MQTT 发到服务器,你在手机或电脑上能看到。
- 告警:任一读数越过你设的阈值(如空气质量差、湿度过高),本地蜂鸣器立刻响,不等网络。
- 联动(进阶):把异常数据喂给 AI 判断"是不是真异常"、加块屏本地显示、做成挂墙成品。
行为定了,选型每一步就都有了理由。
为什么这三个传感器凑一台
挑这三个不是随便凑的,是故意让你碰到三种不同的接口——这台监测站的核心训练价值就在这:
| 传感器 | 测什么 | 接口 | 你要练的 |
|---|---|---|---|
| DHT22 / DHT11 | 温度 + 湿度 | 单总线(一根数据线自定义时序) | 时序读取、一次拿两个量 |
| BH1750 | 光照强度(lux) | I2C(两根线,标准协议) | I2C 主机读寄存器 |
| MQ-135 | 空气质量(相对浓度) | 模拟输出(一路电压) | ADC 采样、原始值当趋势看 |
真实的多传感器设备就是这样:接口五花八门,你得让单总线、I2C、ADC 在一块板子上和平共处。 这比"三个都是 I2C"难,但练到的正是真本事。
MQ 系列只当"相对趋势"用,别当精确 PPM。 MQ-135 输出的是一路随气体浓度变化的电压,要标定成准确的 CO₂/氨气浓度需要专门的校准环境和公式,家用条件下标不准。我们只把它的原始 ADC 值当"空气变差了没有"的趋势指标——值明显升高就是变差,够触发告警了。想测准确 CO₂ 浓度得上 MH-Z19 这类 NDIR 传感器,那是另一条路。别拿 MQ 的读数去写"当前 CO₂ 为 800ppm"这种唬人不实的话。
DHT22 还是 DHT11?
手头钱够就上 DHT22。 两者接线、代码几乎一样,差在精度和量程:DHT11 温度 ±2℃、湿度 ±5%,够看个大概;DHT22 温度 ±0.5℃、湿度 ±2%,还能测负温。监测站是要长期看趋势的,DHT22 的稳定和精度值那几块钱差价。图鉴里 DHT11 那页 把两者的时序和坑讲全了,代码里我用能通吃两者的读法。
用蜂鸣器还是 LED 告警?
有源蜂鸣器:给电就自己响,代码里当普通开关拉高即可,最省事,挂墙上人不看也能听见——监测站首选。手边没有就用一颗 LED代替(告警时点亮),逻辑完全一样,只是从"听得见"变成"看得见"。下面代码用蜂鸣器写,换 LED 一个字不用改(都是拉高一个 GPIO)。
第二步:接线——三种接口混接,避开 S3 选脚雷区
这是这个项目最容易翻车的一步:四路信号线要同时接上,还不能踩 ESP32-S3 的雷区。先记牢雷区,再分配脚。
ESP32-S3 选脚雷区,接线前逐条排除:
- GPIO0 / 3 / 45 / 46:strapping 脚,上电电平决定启动模式,挂外设可能刷不进、起不来;
- GPIO26-37:绝大多数模组内部连着 SPI flash / PSRAM,动了直接死机;
- GPIO19 / 20:默认 USB D-/D+,占用它俩会断掉 USB 串口,日志都看不到;
- GPIO22 / 23 / 24 / 25:ESP32-S3 上根本不存在这几个号(GPIO 号从 21 直接跳到 26),写了编译不报错、运行行为诡异。 另外:ADC 采样要用 ADC1(GPIO1-10),别用 ADC2——S3 上 ADC2 和 WiFi 共用硬件,一联网 ADC2 就读不了,而这台设备恰恰要联网。
避开雷区,本项目这样分配(全在安全区):
DHT22(单总线):
DATA ── GPIO4 (VCC→3V3, GND→GND;模块多自带上拉,裸传感器需在 DATA↔VCC 间接 4.7kΩ 上拉)
BH1750(I2C):
SDA ── GPIO8
SCL ── GPIO9 (VCC→3V3, GND→GND;模块板载上拉,无需外接)
ADDR ── GND (ADDR 接地 = 地址 0x23;接 3V3 则为 0x5C)
MQ-135(模拟输出):
AO ── GPIO1 (ADC1 通道,务必用 ADC1!VCC→5V, GND→GND)
蜂鸣器(告警输出):
正极 ── GPIO5 (有源蜂鸣器;负极→GND)
- DHT22 的 DATA:GPIO4,单总线只需一根信号线。买的是模块版(带小 PCB)通常已焊上拉电阻;买的是裸传感器,得在 DATA 和 VCC 之间自己接一颗 4.7kΩ 上拉,否则读出来全是错。
- BH1750:SDA→GPIO8、SCL→GPIO9,这对脚我们初始化 I2C 时指定,不是固定的,避开雷区随手挑一对即可。ADDR 脚接地确定成 0x23 地址,代码里按这个地址读。
- MQ-135 的 AO:接 GPIO1(属 ADC1)。这是全篇最硬的一条规则——MQ 的模拟量必须走 ADC1,因为设备要连 WiFi,而 S3 的 ADC2 一开 WiFi 就瘫。注意 MQ 模块多要 5V 供电才够加热,但它的 AO 输出电压可能超过 3.3V,接前确认你的模块 AO 已分压到 3.3V 以内,否则要在 AO 到 GPIO 之间加分压电阻,别把 GPIO 灌超压。
- 蜂鸣器:GPIO5,告警时拉高。
四路信号 GPIO4 / 8 / 9 / 1 / 5 全在安全区,互不冲突。
第三步:分步把代码写出来
我们不一次甩一大段,分三步长出来,每步单独烧进去能看到效果——出问题时你才知道坏在哪一步。
步 1:先把三路传感器读出来(还不联网)
第一步只验证一件事:三个传感器都接对了、都能读出合理的数。DHT 的时序解析见 DHT11 图鉴,I2C 主机怎么读寄存器见 L3 之前的外设基础 里的 I2C 章节,这里我们把它们各封成一个"读一次"的函数,主循环里轮流调。
为了篇幅聚焦"组织",DHT 的位解析用 ESP-IDF 社区常用组件(如 esp-idf-lib 的 dht 或 DHT22 组件)的接口示意;实际工程里你会 idf.py add-dependency 把它拉进来,这里只关心怎么用它的返回值:
#include "driver/i2c_master.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "envsta";
// ---- 三路传感器读到的当前值,主循环刷新,上报/告警任务共享 ----
typedef struct {
float temp; // 温度 ℃
float humi; // 湿度 %
float lux; // 光照 lux
int air_raw; // 空气质量原始 ADC 值(越大越差,仅当趋势)
bool valid; // 本轮读数是否全部有效
} env_reading_t;
// ---------- BH1750(I2C)----------
#define I2C_PORT I2C_NUM_0
#define I2C_SDA GPIO_NUM_8
#define I2C_SCL GPIO_NUM_9
#define BH1750_ADDR 0x23 // ADDR 接地
#define BH1750_CONT_H 0x10 // 连续高分辨率测量模式
static i2c_master_dev_handle_t bh1750;
static void bh1750_init(void)
{
i2c_master_bus_config_t bus = {
.i2c_port = I2C_PORT,
.sda_io_num = I2C_SDA,
.scl_io_num = I2C_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus_h;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus, &bus_h));
i2c_device_config_t dev = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = BH1750_ADDR,
.scl_speed_hz = 100000,
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_h, &dev, &bh1750));
uint8_t cmd = BH1750_CONT_H; // 启动连续测量
ESP_ERROR_CHECK(i2c_master_transmit(bh1750, &cmd, 1, 100));
vTaskDelay(pdMS_TO_TICKS(180)); // 首次转换需 ~180ms
}
static float bh1750_read_lux(void)
{
uint8_t raw[2] = {0};
if (i2c_master_receive(bh1750, raw, 2, 100) != ESP_OK) return -1.0f;
uint16_t level = (raw[0] << 8) | raw[1];
return level / 1.2f; // 数据手册换算系数
}
// ---------- MQ-135(ADC1)----------
#define MQ_ADC_CH ADC_CHANNEL_0 // GPIO1 = ADC1 通道 0
static adc_oneshot_unit_handle_t adc1;
static void mq_init(void)
{
adc_oneshot_unit_init_cfg_t unit = { .unit_id = ADC_UNIT_1 }; // 必须 ADC1
ESP_ERROR_CHECK(adc_oneshot_new_unit(&unit, &adc1));
adc_oneshot_chan_cfg_t ch = {
.atten = ADC_ATTEN_DB_12, // 量程约 0-3.1V,够覆盖分压后信号
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1, MQ_ADC_CH, &ch));
}
static int mq_read_raw(void)
{
int v = 0;
adc_oneshot_read(adc1, MQ_ADC_CH, &v);
return v; // 原始值,越大空气越差
}
// ---------- DHT22(单总线,用现成组件)----------
// 假设已 add-dependency 了 dht 组件,dht_read_float_data 是其读接口
extern int dht_read_float_data(int type, int gpio, float *humi, float *temp);
#define DHT_TYPE_DHT22 1
#define DHT_GPIO GPIO_NUM_4
static bool dht_read(env_reading_t *r)
{
float h = 0, t = 0;
if (dht_read_float_data(DHT_TYPE_DHT22, DHT_GPIO, &h, &t) == 0) {
r->humi = h; r->temp = t; return true;
}
return false; // 读失败(时序抖动很常见,靠重试)
}
// ---------- 采集一轮 ----------
static env_reading_t read_all(void)
{
env_reading_t r = {0};
r.valid = dht_read(&r); // DHT 最易失败,它决定本轮 valid
r.lux = bh1750_read_lux();
r.air_raw = mq_read_raw();
return r;
}
void app_main(void)
{
bh1750_init();
mq_init();
while (1) {
env_reading_t r = read_all();
if (r.valid) {
ESP_LOGI(TAG, "温 %.1f℃ 湿 %.1f%% 光 %.0flux 气 %d",
r.temp, r.humi, r.lux, r.air_raw);
} else {
ESP_LOGW(TAG, "DHT 本轮读失败,跳过");
}
vTaskDelay(pdMS_TO_TICKS(3000)); // 3 秒一轮(DHT 至少间隔 2 秒)
}
}
烧进去 idf.py build flash monitor。你应该看到:串口每 3 秒打一行,温度是屋里合理值(20 多度)、湿度合理(40%-70%)、光照白天几百到上千 lux、气体是个几百到几千的原始数。看到四个数都在谱,说明三路传感器接线和读取全对——这一步的意义就是把"多接口混接"这个最容易错的地方先隔离验证掉,别急着联网,网叠上来出问题你就分不清是传感器错还是网络错。
DHT 隔一会儿报一次读失败很正常,它的单总线时序对时间敏感,偶尔被别的任务打断就崩一次。所以我用
valid标记本轮好坏,失败就跳过这一轮、不上报脏数据——监测设备宁可漏报一次,也别报个错数出去误导判断。
步 2:联网,把读数 MQTT 上报
第一步的数在串口里自己看没意义,监测站得让远处的你看到。这一步把 L3 连 WiFi 和 L3 MQTT 接进来,把四个读数发布出去。
关键设计是主题(topic)怎么分。一台设备四个测点,别全塞一个主题里,按"设备/测点"分层,将来加设备、订阅单项都方便:
#include "mqtt_client.h"
// WiFi 连接沿用 l3-wifi 的 wifi_init_sta(),此处略,假设联网已就绪
#define MQTT_URI "mqtt://你的broker地址:1883"
#define DEV_ID "envsta-01" // 这台设备的唯一 id
static esp_mqtt_client_handle_t mqtt;
static void mqtt_start(void)
{
esp_mqtt_client_config_t cfg = { .broker.address.uri = MQTT_URI };
mqtt = esp_mqtt_client_init(&cfg);
esp_mqtt_client_start(mqtt);
}
// 把一轮读数按分层主题发布出去
static void publish_reading(const env_reading_t *r)
{
char topic[64], payload[32];
// qifudev/envsta-01/temp → "24.5"
snprintf(topic, sizeof topic, "qifudev/%s/temp", DEV_ID);
snprintf(payload, sizeof payload, "%.1f", r->temp);
esp_mqtt_client_publish(mqtt, topic, payload, 0, 1, 0); // QoS 1
snprintf(topic, sizeof topic, "qifudev/%s/humi", DEV_ID);
snprintf(payload, sizeof payload, "%.1f", r->humi);
esp_mqtt_client_publish(mqtt, topic, payload, 0, 1, 0);
snprintf(topic, sizeof topic, "qifudev/%s/lux", DEV_ID);
snprintf(payload, sizeof payload, "%.0f", r->lux);
esp_mqtt_client_publish(mqtt, topic, payload, 0, 1, 0);
snprintf(topic, sizeof topic, "qifudev/%s/air", DEV_ID);
snprintf(payload, sizeof payload, "%d", r->air_raw);
esp_mqtt_client_publish(mqtt, topic, payload, 0, 1, 0);
}
主循环里把上报接上(app_main 里在采集后加一句):
// ... wifi_init_sta(); 联网就绪后 ...
mqtt_start();
while (1) {
env_reading_t r = read_all();
if (r.valid) {
ESP_LOGI(TAG, "温 %.1f 湿 %.1f 光 %.0f 气 %d",
r.temp, r.humi, r.lux, r.air_raw);
publish_reading(&r); // ← 上报
}
vTaskDelay(pdMS_TO_TICKS(3000));
}
你应该看到:在电脑上用 MQTT 客户端(如 MQTTX)订阅 qifudev/envsta-01/#(# 是通配符,收这台设备所有测点),每 3 秒收到四条消息,主题分别是 temp/humi/lux/air,值和串口一致。收到了,说明"采集 → 联网 → 上报"整条链路通了。
为什么用 QoS 1 而不是 0? QoS 0 发出去不管有没有到,网络一抖就丢;QoS 1 保证至少送达一次。监测数据偶尔重复不要紧(你看的是趋势),但丢了就断了一段曲线,所以选 1。QoS 的三档区别 MQTT 那节 讲透了,这里按"数据不能随便丢"的场景选。发布的这套"设备/测点"分层主题结构,也正是接 L3 Dashboard 面板 时最好订阅、最好画图的形状——一个测点一条曲线,天生对得上。
步 3:本地阈值判断 + 告警
监测站的灵魂在这一步。前两步做完,它还只是个"哑巴上报器"——把数发出去就完事,出了问题得靠你盯着面板发现。真正的监测设备要自己会判断:任一项越线,本地立刻响,不等网络、不靠人盯。
逻辑很简单:给每个量设一对阈值,每轮采集后逐一对比,只要有一项超标就拉响蜂鸣器;全部回到正常范围才关。
#include "driver/gpio.h"
#define BUZZER_GPIO GPIO_NUM_5
// 阈值:按你的场景调。这里给一组家用参考
#define TEMP_MAX 35.0f // 温度超 35℃ 告警
#define HUMI_MAX 80.0f // 湿度超 80% 有霉变风险
#define LUX_MIN 0.0f // 光照一般不做告警,留着示意
#define AIR_MAX 2500 // 空气原始值超此阈 = 明显变差(需你实测标定)
static void buzzer_init(void)
{
gpio_config_t cfg = {
.pin_bit_mask = 1ULL << BUZZER_GPIO,
.mode = GPIO_MODE_OUTPUT,
};
gpio_config(&cfg);
gpio_set_level(BUZZER_GPIO, 0); // 默认不响
}
// 返回是否有任一项越线,并把越线项也上报出去(方便远端知道为啥响)
static bool check_alarm(const env_reading_t *r)
{
bool alarm = false;
char topic[64];
snprintf(topic, sizeof topic, "qifudev/%s/alarm", DEV_ID);
if (r->temp > TEMP_MAX) { ESP_LOGW(TAG, "温度超标 %.1f", r->temp); alarm = true;
esp_mqtt_client_publish(mqtt, topic, "temp_high", 0, 1, 0); }
if (r->humi > HUMI_MAX) { ESP_LOGW(TAG, "湿度超标 %.1f", r->humi); alarm = true;
esp_mqtt_client_publish(mqtt, topic, "humi_high", 0, 1, 0); }
if (r->air_raw > AIR_MAX) { ESP_LOGW(TAG, "空气变差 %d", r->air_raw); alarm = true;
esp_mqtt_client_publish(mqtt, topic, "air_bad", 0, 1, 0); }
gpio_set_level(BUZZER_GPIO, alarm ? 1 : 0); // 有事就响,没事就静
return alarm;
}
主循环收尾:采集 → 上报 → 判断告警。
buzzer_init();
mqtt_start();
while (1) {
env_reading_t r = read_all();
if (r.valid) {
publish_reading(&r);
check_alarm(&r); // ← 本地判断 + 告警
}
vTaskDelay(pdMS_TO_TICKS(3000));
}
你应该看到:正常时安静上报;你拿手捂住 MQ 传感器一会儿(或往湿度传感器哈气把湿度冲高),对应项越线,蜂鸣器立刻响、串口打出"XX 超标"、远端还收到一条 alarm 主题消息说明哪项出的事;松开、恢复正常后自动停响。
告警为什么要本地判断,而不是让服务器判完再命令设备响? 因为网络会断。监测站最该发威的时刻——深夜、无人、网络恰好抽风——恰恰可能连不上服务器。把"越线就响"这层最基础的自主逻辑放在设备本地,断网也能报警,这是监测类设备和普通上报器的分水岭。 服务器可以做更聪明的判断(趋势、多设备关联),但"底线告警"必须在设备本地兜住。
到这里核心功能全齐了。回头看,你没写多少全新的东西:DHT 读法是图鉴的、MQTT 发布是 l3-mqtt 的、I2C/ADC 是外设基础的——你干的是把三种接口的采集、一路网络上报、一层本地告警组织成一条闭环。这个"组织",就是 project 比 guide、比图鉴多出来的那层功夫。
第四步:调试——对不上就查这张表
分步烧的好处是范围已经缩小。真出岔子,照这张表查:
| 现象 | 最可能的原因 | 怎么办 |
|---|---|---|
| DHT 一直读失败 / 全 0 | 裸传感器缺 4.7kΩ 上拉;采样间隔 <2 秒;被别的任务打断时序 | 加上拉;把周期放到 ≥2 秒;先单独跑步 1 只读 DHT 确认 |
| BH1750 读出来恒为 0 或 -1 | I2C 地址错;SDA/SCL 接反;没等 180ms 首次转换 | 确认 ADDR 接地对应 0x23;对调 SDA/SCL 试;init 里留够延时 |
| 空气值一联网就变成 0 / 乱跳 | 踩了 ADC2!S3 上 ADC2 与 WiFi 冲突 | 必须用 ADC1(GPIO1-10),把 AO 挪到 GPIO1-10 任一 |
| 空气值一直顶到最大 4095 | AO 输出电压超 3.3V 灌爆 ADC;或 MQ 未接 5V 预热不足 | AO 到 GPIO 间加分压电阻降到 3.3V 内;确认 5V 供电、预热几分钟 |
| MQTT 连不上 | broker 地址/端口错;WiFi 没连上;防火墙挡了 1883 | 先确认 WiFi 拿到 IP;用电脑同网测 broker 通不通;查端口 |
| 订阅收不到消息 | 主题写错;client 还没连上就 publish | 订阅用 qifudev/envsta-01/# 通配;确认 MQTT 已连上再发 |
| 蜂鸣器不响 / 一直响 | 用了无源蜂鸣器(需 PWM 驱动);阈值设太低/太高 | 确认是有源蜂鸣器(给高电平就响);按实测调阈值 |
| 板子刷不进 / 重启 | 信号脚踩了 strapping(0/3/45/46)或 flash(26-37) | 对照第二步雷区表,把信号脚挪到安全区 |
| 联网后整机卡顿 / 看门狗重启 | 采集和网络挤在一个循环里,DHT 阻塞时把 MQTT 心跳饿死 | 进阶:把采集和上报拆成两个 FreeRTOS 任务(见下一节思路) |
一次只接一个传感器、只改一处。三个传感器一起上、数不对,你根本分不清是哪路的锅。先单独跑通 DHT,再加 BH1750,再加 MQ,最后叠 MQTT——加一样、验一样,是多传感器项目省时间的铁律。
第五步:从"能跑的 demo"做成"像样的产品"
到这它已经是台能用的监测站了。但"能用"和"像个产品"之间还差几步——这几步正好通向后面的阶梯,先给你指条路。
让 AI 帮你判断"是不是真异常"
现在的告警是死阈值:超过 2500 就响。但空气值受温度、开窗、做饭影响会正常波动,死阈值要么误报、要么漏报。更聪明的做法是把连续几轮的多路读数打包,交给模型判断"这个组合是不是真的异常"——比如"湿度高 + 温度高 + 空气差"同时出现才是要紧的霉变前兆,单项高一下可能只是刚洗完澡。这套"把传感器数据喂给 AI 做异常判断"正是 L4 传感器 + AI 那节 的主线,把它接上,你的监测站就从"会看表盘"进化成"会看情况"。
加一块本地屏
现在读数只在串口和远端看,挂墙上时你路过想瞄一眼当前值还得掏手机。加一块 OLED 或小 LCD,把四个数直接显在上面,本地一眼可见。屏显和联网互不干扰——屏是本地输出、MQTT 是远端上报,各走各的。
上报做得更省电、更稳
3 秒一报太密,长期跑费流量也费电。真产品会变频上报:正常时几分钟报一次,某项接近阈值就自动加密到几秒一次盯紧。再把采集和上报拆成两个任务(采集任务专心读传感器,上报任务专心管网络和重连),DHT 卡顿时也不会把 MQTT 心跳饿死——这套"多任务 + 变频"的骨架,是从 demo 迈向可靠产品的关键一步。
做成真正挂墙的成品
面包板挂不上墙。想变成能长期挂着的东西,要走产品化那条路:稳定供电(别再吊 USB 线)、电路挪到洞洞板或画一块小 PCB、传感器开孔透气的外壳、固件量产烧录。这些是 L4 之后阶梯 往上的活儿,等你多做几个项目、手感稳了再来啃——这台监测站正是最适合做成第一件"实用型实体产品"的对象,因为它有真实的日常用途。
小结 · 你做出了什么、下一步去哪
- 你做出了一台多路采集(温湿度 + 光照 + 空气)、MQTT 联网上报、超阈值本地告警的环境监测站,从选型、三接口混接避雷、分步写码到调试,走完了一件闭环成品的全流程。
- 你第一次把单总线读 DHT(DHT 图鉴)、I2C 读 BH1750、ADC 读气体三种不同接口的传感器,和esp-mqtt 分层主题上报(l3-mqtt)、本地阈值告警组织成一条闭环——这个"让不同接口和平共处、再串成一件事"的功夫,就是 project 比图鉴、比 guide 多出来的那一层。
- 你学到了几个从 demo 到成品的关键思维:脏数据宁可漏报不报错(valid 标记)、上报主题按"设备/测点"分层、告警底线必须落在设备本地(断网也要响)、多传感器"加一样验一样"的调试铁律。
下一步:想让它从"看表盘"进化成"看情况",把多路读数喂给模型做异常判断,那是 L4 传感器 + AI 的活儿;想把上报的数据画成好看的实时曲线面板,去 L3 Dashboard。回看全部实战项目见项目总览,想系统补 AI 全栈这一块顺着 L4 阶梯 往下走。这台监测站是你所有硬件项目里最有真实用途的一个——做完它,你手里第一次有了一件"能天天替你盯着环境"的东西。
本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。