1. 项目概述为什么选择ESP32做“会思考”的空气监测器最近工作室的空气质量总让我觉得有点“不对劲”不是那种明显的异味而是长时间待着会有点闷开窗通风又怕外面的PM2.5飘进来。市面上的空气检测仪要么功能单一要么数据“傻傻的”只会显示一个数字告诉你“优”或“差”至于为什么差、接下来会怎么变化、该开窗还是该开净化器它一概不知。这让我萌生了自己动手做一个“聪明”一点的空气质量监测器的想法。我的核心需求是它不仅要能测还要能“想”能根据多维度数据给出贴合我生活习惯的智能建议。在硬件选型上我几乎没怎么犹豫就锁定了FireBeetle ESP32。原因很直接第一它集成了Wi-Fi和蓝牙天生就是为物联网而生的数据上传到云端或者手机App查看是刚需第二ESP32的双核处理器和相对充裕的内存让它有足够的“脑力”去运行一些轻量级的机器学习模型实现本地化的智能推理这正是“人工智能辅助”的关键第三围绕ESP32的生态太成熟了无论是用Arduino框架快速原型开发还是用ESP-IDF进行深度定制都有海量的库和社区支持各种传感器驱动一应俱全。至于人工智能部分我打算从简单的开始比如用线性回归或决策树算法让设备学会根据温湿度、历史PM2.5数据来预测短期内的空气质量趋势或者判断当前环境是否适合开窗。这个项目适合所有对物联网、嵌入式开发和机器学习交叉领域感兴趣的爱好者。无论你是想学习如何将传感器数据接入网络还是好奇如何在资源受限的单片机上跑AI模型这个从数据采集到智能决策的完整流程都会是一次非常棒的实战体验。接下来我就把从硬件组装、数据采集、云端搭建到智能算法嵌入的整个过程毫无保留地拆解给你看。2. 硬件选型与核心电路设计解析一个可靠的空气质量监测器硬件是地基。我的设计思路是核心控制传感器阵列友好交互。下面这张表格是我最终确定的硬件清单和选型理由模块名称具体型号/关键参数选型理由与注意事项主控板DFRobot FireBeetle ESP32 (主控核心)集成Wi-Fi/蓝牙低功耗设计GPIO丰富兼容Arduino与IDF双开发环境。注意需确认是ESP32-WROOM模组确保无线性能。PM2.5/PM10传感器攀藤PMS5003或PMS7003激光散射原理精度高数据稳定串口输出有成熟Arduino库。注意需单独5V供电串口通信引脚勿接错。温湿度传感器DHT22 (AM2302)性价比高数字信号输出精度足以满足环境监测需求温度±0.5℃湿度±2%。注意单总线协议读取间隔建议大于2秒。甲醛/TVOC传感器炜盛科技ZE08-CH2O或SGP30ZE08为电化学原理针对甲醛SGP30为金属氧化物测总VOC和等效CO2。本项目选用SGP30因其I2C接口更省线且能反映更广泛的有机污染物。二氧化碳传感器MH-Z19B非分散红外原理精度高寿命长串口输出。注意需要预热3分钟左右读数才稳定。交互与显示0.96寸OLED (SSD1306, I2C)低功耗无需背光可实时显示关键数据。I2C接口仅需2根线。供电与拓展18650锂电池及充放电管理模块FireBeetle板载充放电管理方便移动部署。确保电池容量在2000mAh以上以保证续航。实操心得传感器供电的坑PM2.5传感器和CO2传感器通常需要5V供电而ESP32的引脚和DHT22等是3.3V电平。千万不要直接用ESP32的5V输出如果有的話同时给多个传感器供电瞬间电流可能导致板子重启。我的方案是使用一个小型DC-DC降压模块直接从锂电池取电稳定输出5V给这些“电老虎”传感器单独供电与主控板的3.3V系统完全隔离稳定性大增。电路连接是整个项目的物理骨架连接错误轻则数据不准重则烧毁传感器。下面是我的接线图详解以FireBeetle ESP32的引脚为例电源总线将18650电池接入FireBeetle的电池接口。从FireBeetle的VCC引脚这是经过板载稳压后的3.3V引出给DHT22、OLED、SGP30供电。另外用独立的5V降压模块给PMS5003和MH-Z19B供电。I2C总线这是最简洁的总线。将OLED的SDA、SCL分别接ESP32的D21(SDA)、D22(SCL)。SGP30同样接入这组I2C引脚。注意I2C设备都需要上拉电阻通常OLED和SGP30模块板载已集成如果通信不稳定可以在SDA和SCL上各加一个4.7kΩ电阻上拉到3.3V。串口总线ESP32有多个硬件串口。我定义Serial2(RX2: D16,TX2: D17) 用于连接PMS5003。PMS5003的TX接ESP32的D16(RX2)RX接D17(TX2)。Serial1(RX1: D9,TX1: D10) 用于连接MH-Z19B。MH-Z19B的TX接ESP32的D9(RX1)RX接D10(TX1)。注意务必交叉连接即传感器的TX接MCU的RX。同时PMS5003的SET引脚可以悬空或接高电平进入主动上传模式。单总线DHT22的数据线接D4引脚同时接一个4.7kΩ上拉电阻到3.3V。完成连接后建议先不要组装外壳上电后用简单的测试程序分别读取每个传感器数据确保每一个都工作正常后再进行整合。这是避免后期排查问题时“拆机”痛苦的关键一步。3. 固件开发数据采集、上传与本地显示硬件准备就绪后我们进入“赋予灵魂”的环节——编写运行在ESP32上的固件。我的代码结构分为几个核心模块传感器驱动初始化、数据定时采集与滤波、本地OLED显示、以及通过Wi-Fi上传数据到云端。这里我选择Arduino IDE进行开发因为它库管理方便对新手友好。3.1 开发环境搭建与库管理首先确保你的Arduino IDE已安装ESP32开发板支持。可以在“文件-首选项”的附加开发板管理器网址中添加https://espressif.github.io/arduino-esp32/package_esp32_index.json。然后在开发板管理器中搜索安装“ESP32”。选择开发板为“DFRobot Firebeetle ESP32”。接下来通过库管理器项目-加载库-管理库安装以下必需的库Adafruit_SGP30用于SGP30 TVOC/eCO2传感器。DHT sensor library用于DHT22温湿度传感器。Adafruit_SSD1306和Adafruit_GFX用于OLED显示。PMSLibrary一个优秀的攀藤PM2.5传感器库。ArduinoJson处理JSON数据格式为上传云端做准备。WiFiManager强烈推荐它允许设备在启动时进入AP模式让你用手机配置Wi-Fi的SSID和密码无需将密码硬编码在代码里大大提升了部署灵活性。3.2 核心数据采集与滤波代码解析数据采集的稳定性直接决定后续AI模型的质量。我的策略是定时采集软件滤波。以下是一些关键代码片段和逻辑#include PMS.h #include DHT.h #include Adafruit_SGP30.h #include MHZ19.h #include WiFiManager.h // 定义引脚与对象 #define DHTPIN 4 #define DHTTYPE DHT22 PMS pms(Serial2); DHT dht(DHTPIN, DHTTYPE); Adafruit_SGP30 sgp; MHZ19 mhz19(Serial1); // 数据结构体用于存储滤波后的数据 struct SensorData { float temperature; float humidity; uint16_t pm25; uint16_t pm10; uint16_t tvoc; uint16_t eco2; uint16_t co2; } avgData; // 滑动平均滤波数组 const int FILTER_SIZE 5; float tempBuffer[FILTER_SIZE]; int bufferIndex 0; float slidingAverage(float newValue, float buffer[]) { buffer[bufferIndex] newValue; bufferIndex (bufferIndex 1) % FILTER_SIZE; float sum 0; for (int i 0; i FILTER_SIZE; i) { sum buffer[i]; } return sum / FILTER_SIZE; } void readSensors() { // 读取DHT22 float h dht.readHumidity(); float t dht.readTemperature(); if (!isnan(h) !isnan(t)) { avgData.humidity slidingAverage(h, humidityBuffer); avgData.temperature slidingAverage(t, tempBuffer); } // 读取PMS5003 if (pms.read(data)) { avgData.pm25 slidingAverage(data.PM_AE_UG_2_5, pm25Buffer); avgData.pm10 slidingAverage(data.PM_AE_UG_10_0, pm10Buffer); } // 读取SGP30注意它需要温湿度进行补偿 if (sgp.IAQmeasure()) { avgData.tvoc sgp.TVOC; avgData.eco2 sgp.eCO2; } // 读取MH-Z19B avgData.co2 mhz19.getCO2(); }注意事项传感器读数时机与补偿SGP30传感器有一个关键特性它的TVOC和eCO2读数算法需要当前的绝对湿度作为输入进行补偿否则长期运行会有基线漂移。我们需要在代码中计算绝对湿度并设置给SGP30。公式和代码片段如下// 根据温湿度计算绝对湿度克/立方米 float getAbsoluteHumidity(float temperature, float humidity) { // 计算饱和水汽压 float saturationVaporPressure 6.1078 * pow(10, (7.5 * temperature) / (temperature 237.3)); // 计算实际水汽压 float vaporPressure humidity / 100.0 * saturationVaporPressure; // 计算绝对湿度 return (216.7 * vaporPressure) / (temperature 273.15); } // 在读取SGP30前调用 sgp.setHumidity(getAbsoluteHumidity(avgData.temperature, avgData.humidity));3.3 数据上传与云端交互设计数据上传我选择了MQTT协议因为它轻量、适合物联网设备并且与主流的云平台如阿里云、腾讯云、私有部署的EMQX等无缝集成。我这里以连接一个公共的MQTT Broker例如broker.emqx.io为例进行说明。#include PubSubClient.h #include ArduinoJson.h WiFiClient espClient; PubSubClient mqttClient(espClient); const char* mqttServer broker.emqx.io; const int mqttPort 1883; const char* topic firebeetle/air_quality; void reconnectMQTT() { while (!mqttClient.connected()) { String clientId ESP32Client- String(random(0xffff), HEX); if (mqttClient.connect(clientId.c_str())) { Serial.println(MQTT connected!); } else { delay(5000); } } } void publishSensorData() { StaticJsonDocument256 doc; doc[temp] avgData.temperature; doc[humi] avgData.humidity; doc[pm25] avgData.pm25; doc[pm10] avgData.pm10; doc[tvoc] avgData.tvoc; doc[eco2] avgData.eco2; doc[co2] avgData.co2; char jsonBuffer[256]; serializeJson(doc, jsonBuffer); mqttClient.publish(topic, jsonBuffer); }在主循环loop()中定期例如每30秒调用publishSensorData()即可。同时OLED显示模块我会让它循环显示最重要的几项数据PM2.5、CO2、温度和湿度刷新频率可以快一些比如每2秒切换一项。4. “人工智能辅助”的核心本地轻量级AI模型实现这是本项目区别于普通监测器的精髓所在。所谓“人工智能辅助”我将其定义为设备能基于实时和历史数据进行简单的本地推理给出超出阈值判断的、更具洞察力的建议。考虑到ESP32的资源双核240MHz约520KB SRAM复杂的深度学习模型不现实但传统的机器学习算法如决策树、线性回归或简单的神经网络如TinyML是完全可行的。我设计了两个智能功能空气质量趋势预测基于过去一段时间如1小时的PM2.5、温湿度数据预测未来15-30分钟的PM2.5变化趋势上升、下降、平稳。智能通风建议综合室内外假设我们通过云端获取了室外数据的PM2.5、CO2、温湿度判断当前是否适合开窗通风。4.1 模型训练与转换我们首先在PC上完成模型的训练。以趋势预测为例我们可以将其简化为一个三分类问题趋势向上2平稳1向下0。使用Python的scikit-learn库可以快速训练一个决策树模型。# 示例Python训练代码在PC上运行 import pandas as pd from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split import joblib # 假设我们有包含历史传感器数据和对应趋势标签的数据集 # 特征过去N个时间点的PM2.5, 温度, 湿度 # 标签未来时刻的趋势 (0,1,2) data pd.read_csv(sensor_history.csv) X data[[pm25_1, temp_1, humi_1, pm25_2, temp_2, ...]] # 特征工程 y data[trend_label] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2) model DecisionTreeClassifier(max_depth5) # 控制树深度防止过拟合和模型过大 model.fit(X_train, y_train) print(Accuracy:, model.score(X_test, y_test)) # 保存模型 joblib.dump(model, air_trend_model.pkl)训练好模型后需要将其转换为能在ESP32上运行的格式。这里推荐使用TensorFlow Lite for Microcontrollers。我们可以先将scikit-learn的模型转换为TensorFlow格式可能需要使用sklearn-onnx等工具先转ONNX再转TensorFlow或者更直接地因为决策树本身逻辑不复杂我们可以手动“翻译”它。4.2 在ESP32上部署与运行推理对于决策树我们可以直接将其推理逻辑用C的if-else语句实现。这虽然“笨”但极其高效且不依赖任何额外的库。假设我们训练出的决策树规则如下简化示例如果 (当前PM2.5 35) 且 (过去10分钟PM2.5变化率 0.1): 趋势 2 (上升) 否则如果 (当前CO2 1000) 或 (当前TVOC 500): 趋势 1 (平稳但污染水平高) 否则: 趋势 0 (下降或良好)我们在ESP32固件中实现这个推理函数int predictAirTrend(float currentPM25, float pm25ChangeRate, float currentCO2, float currentTVOC) { // 规则1PM2.5本身较高且仍在快速上升 if (currentPM25 35.0 pm25ChangeRate 0.1) { return 2; // 趋势向上预警 } // 规则2虽然PM2.5不高但室内CO2或有机污染物浓度已超标 else if (currentCO2 1000.0 || currentTVOC 500) { return 1; // 趋势平稳但需注意通风 } // 规则3各项指标均良好 else { return 0; // 趋势向下或保持良好 } } // 在主循环中调用 int trend predictAirTrend(avgData.pm25, calculatePM25ChangeRate(), avgData.co2, avgData.tvoc); // 根据trend值在OLED上显示不同的图标或文字提示如“↑风险”、“⚠注意”、“↓良好”对于更复杂的模型如小型的神经网络则需要集成TensorFlow Lite Micro库。这需要在Arduino IDE中安装相关的库并将转换后的.tflite模型文件作为数组嵌入到代码中。这个过程稍复杂但能实现更强大的识别能力例如识别出“烹饪污染”、“灰尘污染”等模式。实操心得本地AI的边界在ESP32上跑AI一定要明确边界。它的算力主要用于“推理”而非“训练”。模型必须足够小通常小于100KB输入特征要精心设计降维、选择关键特征。我的经验是对于环境预测这类任务基于规则的决策树或随机森林模型经过精炼后其C实现版本往往比一个等效的微型神经网络更快、更省内存且效果足够好。先尝试用规则实现如果效果不理想再考虑TinyML。5. 系统集成、功耗优化与外壳设计当各个模块传感器采集、数据显示、数据上传、本地AI的代码都调试通过后我们需要将它们整合到一个稳定、高效的主循环中并考虑实际部署的功耗和外观问题。5.1 固件主循环与状态机设计一个鲁棒的主循环不能是简单的delay那会阻塞程序。我采用非阻塞定时和简单状态机的设计让各个任务有条不紊地执行。unsigned long previousSensorMillis 0; const long sensorInterval 30000; // 传感器读取和上传间隔30秒 unsigned long previousDisplayMillis 0; const long displayInterval 2000; // 显示刷新间隔2秒 unsigned long previousAIMillis 0; const long aiInterval 60000; // AI推理间隔60秒 void loop() { unsigned long currentMillis millis(); // 任务1定时读取传感器并上传 if (currentMillis - previousSensorMillis sensorInterval) { previousSensorMillis currentMillis; readSensors(); // 包含滤波 publishSensorData(); } // 任务2定时刷新OLED显示 if (currentMillis - previousDisplayMillis displayInterval) { previousDisplayMillis currentMillis; updateDisplay(); // 循环显示PM2.5, CO2, Temp, Humi, AI建议 } // 任务3定时运行AI推理 if (currentMillis - previousAIMillis aiInterval) { previousAIMillis currentMillis; int trend predictAirTrend(...); String advice generateVentilationAdvice(...); // 将建议存储到全局变量供显示和上传使用 } // 任务4保持MQTT连接 if (!mqttClient.connected()) { reconnectMQTT(); } mqttClient.loop(); // 维持MQTT心跳处理传入消息 // 任务5处理可能的串口数据如MH-Z19B的自动校准指令 // ... }5.2 低功耗优化策略如果你想用电池供电并长期部署功耗是关键。FireBeetle ESP32本身支持深度睡眠但我们的传感器大多不支持且需要持续监测。因此我的优化策略是“局部深度睡眠外部分时供电”ESP32本身在loop()的末尾如果没有任务可以调用delay(10)或使用esp_sleep_enable_timer_wakeup()进入轻度睡眠但我们的任务周期最短2秒轻度睡眠省电效果有限。更激进的做法是如果数据上传间隔很长比如5分钟一次可以在完成一次“采集-上传-显示”循环后让ESP32进入深度睡眠用定时器唤醒。但这需要所有外设都能容忍断电重启且Wi-Fi重连会耗电。传感器分时供电对于耗电大的传感器如PMS5003约100mA和MH-Z19B约80mA可以通过一个MOSFET开关电路由ESP32的GPIO控制其电源通断。仅在需要读数前几百毫秒打开电源读数后立即关闭。这能大幅降低平均电流。降低工作频率将ESP32的CPU频率从240MHz降到80MHz对传感器读取和简单AI推理影响不大但能显著降低功耗。关闭不用的功能如果不用蓝牙在setup()里用btStop()关闭蓝牙射频。实测下来在每30秒采集上传一次、OLED常亮的情况下整个系统的平均工作电流约120mA。使用一节2000mAh的18650电池可以连续工作约16小时。如果采用深度睡眠方案将采集间隔延长到5分钟理论续航可达数天甚至一周。5.3 外壳设计与实际部署一个好的外壳既能保护电路也能提升产品感。我使用3D打印来制作外壳。设计要点风道设计为PM2.5传感器设计独立的进风风道避免电路板发热影响读数。进气口可以加一层简单的防尘海绵。屏幕开孔精确对应OLED屏幕并考虑一定的漫射板或遮光设计让显示更清晰。传感器开孔为DHT22、SGP30等需要接触空气的传感器开孔。电源开关与充电口预留位置方便操作。安装方式设计壁挂孔或底座可以放在桌面或挂在墙上。部署时选择一个能代表室内平均空气状况的位置避免放在墙角、通风口、空调出风口或阳光直射处。离地面1-1.5米高度为宜。6. 项目进阶从监测到智能联动当这个监测器稳定运行积累了足够的数据后它的价值可以进一步放大实现真正的智能家居联动。进阶方向一云端数据可视化与历史分析我们可以将MQTT数据接入更强大的云端平台如Home Assistant、Node-RED或阿里云物联网平台。在这些平台上可以轻松地绘制出所有参数的历史曲线图设置更复杂的自动化规则。例如在Node-RED中可以创建一个仪表盘实时展示所有数据并设置规则“如果室内PM2.5连续5分钟高于室外且差值大于20则自动打开空气净化器通过智能插座”。进阶方向二多设备组网与数据融合在客厅、卧室、书房各部署一个监测点利用ESP32的Wi-Fi组成一个分布式监测网络。数据可以汇总到一个中心节点比如用另一个ESP32做网关或者直接上报到同一个云平台。通过对比不同房间的数据可以更精准地判断污染源位置和空气流动情况。进阶方向三模型持续优化与个性化将设备端采集的数据同步到云端后我们可以在云端用更强大的算力甚至你的个人电脑定期重新训练AI模型。你可以根据自己家庭的实际情况为“舒适”、“闷”、“需要通风”等主观感受打标签将这些标签与当时的传感器数据结合训练出更符合你个人体感的个性化预测模型。然后将训练好的新模型参数比如更新后的决策树规则阈值通过OTA空中升级的方式下发到ESP32设备中完成模型的迭代更新。这才是“人工智能辅助”的完整闭环感知-学习-优化-再感知。这个项目从想法到实现最深的体会是物联网和AI的结合并不是要做出多么高大上的东西而是用技术去解决身边那些细小却真实存在的痛点。当你看到自己制作的设备不仅准确地显示出PM2.5的数值还能在你准备做饭时提前提示“空气质量可能下降建议开启油烟机”那种成就感远超单纯购买一个成品。过程中遇到的每一个传感器通信问题、每一个数据跳变的排查、每一次AI模型效果的调优都是实实在在的经验积累。希望我的这份详细拆解能帮你少走些弯路更快地做出属于你自己的、会“思考”的空气质量管家。
基于ESP32与轻量级AI的智能空气质量监测器全栈开发指南
1. 项目概述为什么选择ESP32做“会思考”的空气监测器最近工作室的空气质量总让我觉得有点“不对劲”不是那种明显的异味而是长时间待着会有点闷开窗通风又怕外面的PM2.5飘进来。市面上的空气检测仪要么功能单一要么数据“傻傻的”只会显示一个数字告诉你“优”或“差”至于为什么差、接下来会怎么变化、该开窗还是该开净化器它一概不知。这让我萌生了自己动手做一个“聪明”一点的空气质量监测器的想法。我的核心需求是它不仅要能测还要能“想”能根据多维度数据给出贴合我生活习惯的智能建议。在硬件选型上我几乎没怎么犹豫就锁定了FireBeetle ESP32。原因很直接第一它集成了Wi-Fi和蓝牙天生就是为物联网而生的数据上传到云端或者手机App查看是刚需第二ESP32的双核处理器和相对充裕的内存让它有足够的“脑力”去运行一些轻量级的机器学习模型实现本地化的智能推理这正是“人工智能辅助”的关键第三围绕ESP32的生态太成熟了无论是用Arduino框架快速原型开发还是用ESP-IDF进行深度定制都有海量的库和社区支持各种传感器驱动一应俱全。至于人工智能部分我打算从简单的开始比如用线性回归或决策树算法让设备学会根据温湿度、历史PM2.5数据来预测短期内的空气质量趋势或者判断当前环境是否适合开窗。这个项目适合所有对物联网、嵌入式开发和机器学习交叉领域感兴趣的爱好者。无论你是想学习如何将传感器数据接入网络还是好奇如何在资源受限的单片机上跑AI模型这个从数据采集到智能决策的完整流程都会是一次非常棒的实战体验。接下来我就把从硬件组装、数据采集、云端搭建到智能算法嵌入的整个过程毫无保留地拆解给你看。2. 硬件选型与核心电路设计解析一个可靠的空气质量监测器硬件是地基。我的设计思路是核心控制传感器阵列友好交互。下面这张表格是我最终确定的硬件清单和选型理由模块名称具体型号/关键参数选型理由与注意事项主控板DFRobot FireBeetle ESP32 (主控核心)集成Wi-Fi/蓝牙低功耗设计GPIO丰富兼容Arduino与IDF双开发环境。注意需确认是ESP32-WROOM模组确保无线性能。PM2.5/PM10传感器攀藤PMS5003或PMS7003激光散射原理精度高数据稳定串口输出有成熟Arduino库。注意需单独5V供电串口通信引脚勿接错。温湿度传感器DHT22 (AM2302)性价比高数字信号输出精度足以满足环境监测需求温度±0.5℃湿度±2%。注意单总线协议读取间隔建议大于2秒。甲醛/TVOC传感器炜盛科技ZE08-CH2O或SGP30ZE08为电化学原理针对甲醛SGP30为金属氧化物测总VOC和等效CO2。本项目选用SGP30因其I2C接口更省线且能反映更广泛的有机污染物。二氧化碳传感器MH-Z19B非分散红外原理精度高寿命长串口输出。注意需要预热3分钟左右读数才稳定。交互与显示0.96寸OLED (SSD1306, I2C)低功耗无需背光可实时显示关键数据。I2C接口仅需2根线。供电与拓展18650锂电池及充放电管理模块FireBeetle板载充放电管理方便移动部署。确保电池容量在2000mAh以上以保证续航。实操心得传感器供电的坑PM2.5传感器和CO2传感器通常需要5V供电而ESP32的引脚和DHT22等是3.3V电平。千万不要直接用ESP32的5V输出如果有的話同时给多个传感器供电瞬间电流可能导致板子重启。我的方案是使用一个小型DC-DC降压模块直接从锂电池取电稳定输出5V给这些“电老虎”传感器单独供电与主控板的3.3V系统完全隔离稳定性大增。电路连接是整个项目的物理骨架连接错误轻则数据不准重则烧毁传感器。下面是我的接线图详解以FireBeetle ESP32的引脚为例电源总线将18650电池接入FireBeetle的电池接口。从FireBeetle的VCC引脚这是经过板载稳压后的3.3V引出给DHT22、OLED、SGP30供电。另外用独立的5V降压模块给PMS5003和MH-Z19B供电。I2C总线这是最简洁的总线。将OLED的SDA、SCL分别接ESP32的D21(SDA)、D22(SCL)。SGP30同样接入这组I2C引脚。注意I2C设备都需要上拉电阻通常OLED和SGP30模块板载已集成如果通信不稳定可以在SDA和SCL上各加一个4.7kΩ电阻上拉到3.3V。串口总线ESP32有多个硬件串口。我定义Serial2(RX2: D16,TX2: D17) 用于连接PMS5003。PMS5003的TX接ESP32的D16(RX2)RX接D17(TX2)。Serial1(RX1: D9,TX1: D10) 用于连接MH-Z19B。MH-Z19B的TX接ESP32的D9(RX1)RX接D10(TX1)。注意务必交叉连接即传感器的TX接MCU的RX。同时PMS5003的SET引脚可以悬空或接高电平进入主动上传模式。单总线DHT22的数据线接D4引脚同时接一个4.7kΩ上拉电阻到3.3V。完成连接后建议先不要组装外壳上电后用简单的测试程序分别读取每个传感器数据确保每一个都工作正常后再进行整合。这是避免后期排查问题时“拆机”痛苦的关键一步。3. 固件开发数据采集、上传与本地显示硬件准备就绪后我们进入“赋予灵魂”的环节——编写运行在ESP32上的固件。我的代码结构分为几个核心模块传感器驱动初始化、数据定时采集与滤波、本地OLED显示、以及通过Wi-Fi上传数据到云端。这里我选择Arduino IDE进行开发因为它库管理方便对新手友好。3.1 开发环境搭建与库管理首先确保你的Arduino IDE已安装ESP32开发板支持。可以在“文件-首选项”的附加开发板管理器网址中添加https://espressif.github.io/arduino-esp32/package_esp32_index.json。然后在开发板管理器中搜索安装“ESP32”。选择开发板为“DFRobot Firebeetle ESP32”。接下来通过库管理器项目-加载库-管理库安装以下必需的库Adafruit_SGP30用于SGP30 TVOC/eCO2传感器。DHT sensor library用于DHT22温湿度传感器。Adafruit_SSD1306和Adafruit_GFX用于OLED显示。PMSLibrary一个优秀的攀藤PM2.5传感器库。ArduinoJson处理JSON数据格式为上传云端做准备。WiFiManager强烈推荐它允许设备在启动时进入AP模式让你用手机配置Wi-Fi的SSID和密码无需将密码硬编码在代码里大大提升了部署灵活性。3.2 核心数据采集与滤波代码解析数据采集的稳定性直接决定后续AI模型的质量。我的策略是定时采集软件滤波。以下是一些关键代码片段和逻辑#include PMS.h #include DHT.h #include Adafruit_SGP30.h #include MHZ19.h #include WiFiManager.h // 定义引脚与对象 #define DHTPIN 4 #define DHTTYPE DHT22 PMS pms(Serial2); DHT dht(DHTPIN, DHTTYPE); Adafruit_SGP30 sgp; MHZ19 mhz19(Serial1); // 数据结构体用于存储滤波后的数据 struct SensorData { float temperature; float humidity; uint16_t pm25; uint16_t pm10; uint16_t tvoc; uint16_t eco2; uint16_t co2; } avgData; // 滑动平均滤波数组 const int FILTER_SIZE 5; float tempBuffer[FILTER_SIZE]; int bufferIndex 0; float slidingAverage(float newValue, float buffer[]) { buffer[bufferIndex] newValue; bufferIndex (bufferIndex 1) % FILTER_SIZE; float sum 0; for (int i 0; i FILTER_SIZE; i) { sum buffer[i]; } return sum / FILTER_SIZE; } void readSensors() { // 读取DHT22 float h dht.readHumidity(); float t dht.readTemperature(); if (!isnan(h) !isnan(t)) { avgData.humidity slidingAverage(h, humidityBuffer); avgData.temperature slidingAverage(t, tempBuffer); } // 读取PMS5003 if (pms.read(data)) { avgData.pm25 slidingAverage(data.PM_AE_UG_2_5, pm25Buffer); avgData.pm10 slidingAverage(data.PM_AE_UG_10_0, pm10Buffer); } // 读取SGP30注意它需要温湿度进行补偿 if (sgp.IAQmeasure()) { avgData.tvoc sgp.TVOC; avgData.eco2 sgp.eCO2; } // 读取MH-Z19B avgData.co2 mhz19.getCO2(); }注意事项传感器读数时机与补偿SGP30传感器有一个关键特性它的TVOC和eCO2读数算法需要当前的绝对湿度作为输入进行补偿否则长期运行会有基线漂移。我们需要在代码中计算绝对湿度并设置给SGP30。公式和代码片段如下// 根据温湿度计算绝对湿度克/立方米 float getAbsoluteHumidity(float temperature, float humidity) { // 计算饱和水汽压 float saturationVaporPressure 6.1078 * pow(10, (7.5 * temperature) / (temperature 237.3)); // 计算实际水汽压 float vaporPressure humidity / 100.0 * saturationVaporPressure; // 计算绝对湿度 return (216.7 * vaporPressure) / (temperature 273.15); } // 在读取SGP30前调用 sgp.setHumidity(getAbsoluteHumidity(avgData.temperature, avgData.humidity));3.3 数据上传与云端交互设计数据上传我选择了MQTT协议因为它轻量、适合物联网设备并且与主流的云平台如阿里云、腾讯云、私有部署的EMQX等无缝集成。我这里以连接一个公共的MQTT Broker例如broker.emqx.io为例进行说明。#include PubSubClient.h #include ArduinoJson.h WiFiClient espClient; PubSubClient mqttClient(espClient); const char* mqttServer broker.emqx.io; const int mqttPort 1883; const char* topic firebeetle/air_quality; void reconnectMQTT() { while (!mqttClient.connected()) { String clientId ESP32Client- String(random(0xffff), HEX); if (mqttClient.connect(clientId.c_str())) { Serial.println(MQTT connected!); } else { delay(5000); } } } void publishSensorData() { StaticJsonDocument256 doc; doc[temp] avgData.temperature; doc[humi] avgData.humidity; doc[pm25] avgData.pm25; doc[pm10] avgData.pm10; doc[tvoc] avgData.tvoc; doc[eco2] avgData.eco2; doc[co2] avgData.co2; char jsonBuffer[256]; serializeJson(doc, jsonBuffer); mqttClient.publish(topic, jsonBuffer); }在主循环loop()中定期例如每30秒调用publishSensorData()即可。同时OLED显示模块我会让它循环显示最重要的几项数据PM2.5、CO2、温度和湿度刷新频率可以快一些比如每2秒切换一项。4. “人工智能辅助”的核心本地轻量级AI模型实现这是本项目区别于普通监测器的精髓所在。所谓“人工智能辅助”我将其定义为设备能基于实时和历史数据进行简单的本地推理给出超出阈值判断的、更具洞察力的建议。考虑到ESP32的资源双核240MHz约520KB SRAM复杂的深度学习模型不现实但传统的机器学习算法如决策树、线性回归或简单的神经网络如TinyML是完全可行的。我设计了两个智能功能空气质量趋势预测基于过去一段时间如1小时的PM2.5、温湿度数据预测未来15-30分钟的PM2.5变化趋势上升、下降、平稳。智能通风建议综合室内外假设我们通过云端获取了室外数据的PM2.5、CO2、温湿度判断当前是否适合开窗通风。4.1 模型训练与转换我们首先在PC上完成模型的训练。以趋势预测为例我们可以将其简化为一个三分类问题趋势向上2平稳1向下0。使用Python的scikit-learn库可以快速训练一个决策树模型。# 示例Python训练代码在PC上运行 import pandas as pd from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split import joblib # 假设我们有包含历史传感器数据和对应趋势标签的数据集 # 特征过去N个时间点的PM2.5, 温度, 湿度 # 标签未来时刻的趋势 (0,1,2) data pd.read_csv(sensor_history.csv) X data[[pm25_1, temp_1, humi_1, pm25_2, temp_2, ...]] # 特征工程 y data[trend_label] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2) model DecisionTreeClassifier(max_depth5) # 控制树深度防止过拟合和模型过大 model.fit(X_train, y_train) print(Accuracy:, model.score(X_test, y_test)) # 保存模型 joblib.dump(model, air_trend_model.pkl)训练好模型后需要将其转换为能在ESP32上运行的格式。这里推荐使用TensorFlow Lite for Microcontrollers。我们可以先将scikit-learn的模型转换为TensorFlow格式可能需要使用sklearn-onnx等工具先转ONNX再转TensorFlow或者更直接地因为决策树本身逻辑不复杂我们可以手动“翻译”它。4.2 在ESP32上部署与运行推理对于决策树我们可以直接将其推理逻辑用C的if-else语句实现。这虽然“笨”但极其高效且不依赖任何额外的库。假设我们训练出的决策树规则如下简化示例如果 (当前PM2.5 35) 且 (过去10分钟PM2.5变化率 0.1): 趋势 2 (上升) 否则如果 (当前CO2 1000) 或 (当前TVOC 500): 趋势 1 (平稳但污染水平高) 否则: 趋势 0 (下降或良好)我们在ESP32固件中实现这个推理函数int predictAirTrend(float currentPM25, float pm25ChangeRate, float currentCO2, float currentTVOC) { // 规则1PM2.5本身较高且仍在快速上升 if (currentPM25 35.0 pm25ChangeRate 0.1) { return 2; // 趋势向上预警 } // 规则2虽然PM2.5不高但室内CO2或有机污染物浓度已超标 else if (currentCO2 1000.0 || currentTVOC 500) { return 1; // 趋势平稳但需注意通风 } // 规则3各项指标均良好 else { return 0; // 趋势向下或保持良好 } } // 在主循环中调用 int trend predictAirTrend(avgData.pm25, calculatePM25ChangeRate(), avgData.co2, avgData.tvoc); // 根据trend值在OLED上显示不同的图标或文字提示如“↑风险”、“⚠注意”、“↓良好”对于更复杂的模型如小型的神经网络则需要集成TensorFlow Lite Micro库。这需要在Arduino IDE中安装相关的库并将转换后的.tflite模型文件作为数组嵌入到代码中。这个过程稍复杂但能实现更强大的识别能力例如识别出“烹饪污染”、“灰尘污染”等模式。实操心得本地AI的边界在ESP32上跑AI一定要明确边界。它的算力主要用于“推理”而非“训练”。模型必须足够小通常小于100KB输入特征要精心设计降维、选择关键特征。我的经验是对于环境预测这类任务基于规则的决策树或随机森林模型经过精炼后其C实现版本往往比一个等效的微型神经网络更快、更省内存且效果足够好。先尝试用规则实现如果效果不理想再考虑TinyML。5. 系统集成、功耗优化与外壳设计当各个模块传感器采集、数据显示、数据上传、本地AI的代码都调试通过后我们需要将它们整合到一个稳定、高效的主循环中并考虑实际部署的功耗和外观问题。5.1 固件主循环与状态机设计一个鲁棒的主循环不能是简单的delay那会阻塞程序。我采用非阻塞定时和简单状态机的设计让各个任务有条不紊地执行。unsigned long previousSensorMillis 0; const long sensorInterval 30000; // 传感器读取和上传间隔30秒 unsigned long previousDisplayMillis 0; const long displayInterval 2000; // 显示刷新间隔2秒 unsigned long previousAIMillis 0; const long aiInterval 60000; // AI推理间隔60秒 void loop() { unsigned long currentMillis millis(); // 任务1定时读取传感器并上传 if (currentMillis - previousSensorMillis sensorInterval) { previousSensorMillis currentMillis; readSensors(); // 包含滤波 publishSensorData(); } // 任务2定时刷新OLED显示 if (currentMillis - previousDisplayMillis displayInterval) { previousDisplayMillis currentMillis; updateDisplay(); // 循环显示PM2.5, CO2, Temp, Humi, AI建议 } // 任务3定时运行AI推理 if (currentMillis - previousAIMillis aiInterval) { previousAIMillis currentMillis; int trend predictAirTrend(...); String advice generateVentilationAdvice(...); // 将建议存储到全局变量供显示和上传使用 } // 任务4保持MQTT连接 if (!mqttClient.connected()) { reconnectMQTT(); } mqttClient.loop(); // 维持MQTT心跳处理传入消息 // 任务5处理可能的串口数据如MH-Z19B的自动校准指令 // ... }5.2 低功耗优化策略如果你想用电池供电并长期部署功耗是关键。FireBeetle ESP32本身支持深度睡眠但我们的传感器大多不支持且需要持续监测。因此我的优化策略是“局部深度睡眠外部分时供电”ESP32本身在loop()的末尾如果没有任务可以调用delay(10)或使用esp_sleep_enable_timer_wakeup()进入轻度睡眠但我们的任务周期最短2秒轻度睡眠省电效果有限。更激进的做法是如果数据上传间隔很长比如5分钟一次可以在完成一次“采集-上传-显示”循环后让ESP32进入深度睡眠用定时器唤醒。但这需要所有外设都能容忍断电重启且Wi-Fi重连会耗电。传感器分时供电对于耗电大的传感器如PMS5003约100mA和MH-Z19B约80mA可以通过一个MOSFET开关电路由ESP32的GPIO控制其电源通断。仅在需要读数前几百毫秒打开电源读数后立即关闭。这能大幅降低平均电流。降低工作频率将ESP32的CPU频率从240MHz降到80MHz对传感器读取和简单AI推理影响不大但能显著降低功耗。关闭不用的功能如果不用蓝牙在setup()里用btStop()关闭蓝牙射频。实测下来在每30秒采集上传一次、OLED常亮的情况下整个系统的平均工作电流约120mA。使用一节2000mAh的18650电池可以连续工作约16小时。如果采用深度睡眠方案将采集间隔延长到5分钟理论续航可达数天甚至一周。5.3 外壳设计与实际部署一个好的外壳既能保护电路也能提升产品感。我使用3D打印来制作外壳。设计要点风道设计为PM2.5传感器设计独立的进风风道避免电路板发热影响读数。进气口可以加一层简单的防尘海绵。屏幕开孔精确对应OLED屏幕并考虑一定的漫射板或遮光设计让显示更清晰。传感器开孔为DHT22、SGP30等需要接触空气的传感器开孔。电源开关与充电口预留位置方便操作。安装方式设计壁挂孔或底座可以放在桌面或挂在墙上。部署时选择一个能代表室内平均空气状况的位置避免放在墙角、通风口、空调出风口或阳光直射处。离地面1-1.5米高度为宜。6. 项目进阶从监测到智能联动当这个监测器稳定运行积累了足够的数据后它的价值可以进一步放大实现真正的智能家居联动。进阶方向一云端数据可视化与历史分析我们可以将MQTT数据接入更强大的云端平台如Home Assistant、Node-RED或阿里云物联网平台。在这些平台上可以轻松地绘制出所有参数的历史曲线图设置更复杂的自动化规则。例如在Node-RED中可以创建一个仪表盘实时展示所有数据并设置规则“如果室内PM2.5连续5分钟高于室外且差值大于20则自动打开空气净化器通过智能插座”。进阶方向二多设备组网与数据融合在客厅、卧室、书房各部署一个监测点利用ESP32的Wi-Fi组成一个分布式监测网络。数据可以汇总到一个中心节点比如用另一个ESP32做网关或者直接上报到同一个云平台。通过对比不同房间的数据可以更精准地判断污染源位置和空气流动情况。进阶方向三模型持续优化与个性化将设备端采集的数据同步到云端后我们可以在云端用更强大的算力甚至你的个人电脑定期重新训练AI模型。你可以根据自己家庭的实际情况为“舒适”、“闷”、“需要通风”等主观感受打标签将这些标签与当时的传感器数据结合训练出更符合你个人体感的个性化预测模型。然后将训练好的新模型参数比如更新后的决策树规则阈值通过OTA空中升级的方式下发到ESP32设备中完成模型的迭代更新。这才是“人工智能辅助”的完整闭环感知-学习-优化-再感知。这个项目从想法到实现最深的体会是物联网和AI的结合并不是要做出多么高大上的东西而是用技术去解决身边那些细小却真实存在的痛点。当你看到自己制作的设备不仅准确地显示出PM2.5的数值还能在你准备做饭时提前提示“空气质量可能下降建议开启油烟机”那种成就感远超单纯购买一个成品。过程中遇到的每一个传感器通信问题、每一个数据跳变的排查、每一次AI模型效果的调优都是实实在在的经验积累。希望我的这份详细拆解能帮你少走些弯路更快地做出属于你自己的、会“思考”的空气质量管家。