← 返回实战项目

环境监测站:多传感器采集 + MQTT 上报 + 超阈值告警

最后更新 2026-07-01
⏱ 约 16 分钟 🟢 软件/低风险
你将学到
  • 做出一台能同时采集温湿度、光照、空气质量,联网上报并在超阈值时本地告警的环境监测站
  • 把「单总线读 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 连 WiFiL3 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 联网上报、超阈值本地告警的环境监测站,从选型、三接口混接避雷、分步写码到调试,走完了一件闭环成品的全流程。
  • 你第一次把单总线读 DHTDHT 图鉴)、I2C 读 BH1750ADC 读气体三种不同接口的传感器,和esp-mqtt 分层主题上报l3-mqtt)、本地阈值告警组织成一条闭环——这个"让不同接口和平共处、再串成一件事"的功夫,就是 project 比图鉴、比 guide 多出来的那一层。
  • 你学到了几个从 demo 到成品的关键思维:脏数据宁可漏报不报错(valid 标记)、上报主题按"设备/测点"分层、告警底线必须落在设备本地(断网也要响)、多传感器"加一样验一样"的调试铁律。

下一步:想让它从"看表盘"进化成"看情况",把多路读数喂给模型做异常判断,那是 L4 传感器 + AI 的活儿;想把上报的数据画成好看的实时曲线面板,去 L3 Dashboard。回看全部实战项目见项目总览,想系统补 AI 全栈这一块顺着 L4 阶梯 往下走。这台监测站是你所有硬件项目里最有真实用途的一个——做完它,你手里第一次有了一件"能天天替你盯着环境"的东西。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明