尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP32接入ONENET:传感器数据上报与四路远程控制实战

ESP32接入ONENET:传感器数据上报与四路远程控制实战 简介这套资源是一份基于STM32与ESP8266的物联网环境监控与四路下发控制系统完整工程面向嵌入式开发者和物联网爱好者解决传感器数据采集、OneNET云平台上传及远程控制执行设备的实际需求。系统功能涵盖温湿度、土壤湿度、光照强度与MQ2气体浓度检测并联动控制水泵、风扇、蜂鸣器等外设支持云平台实时查看与操作。压缩包为RAR格式约5.07MB包含229个文件主要由C语言头文件.h、源文件.c、编译生成的十六进制文件.hex以及Keil工程配置.uvprojx、.uvoptx组成既有完整源码也包含可直接烧录的固件便于学习与部署。目前已有484人学习下载值得参考。通过该工程可掌握多传感器采集、ESP8266 AT指令接入OneNET、阈值判断与PWM控制等关键技巧也可依据注释修改报警阈值适合课程设计、毕业设计或实际项目二次开发。1. 一套传感器上云加四路控制的系统到底在解决什么在很多实际场景里比如温室大棚、智能水培、机房温湿度监控都需要同时读温度、湿度、土壤湿度、光照强度还要留意烟雾或可燃气体浓度MQ2。数据上传到ONENET只是第一步真正麻烦的是反向控制当发现土壤太干要开水泵温度太高要开风扇气体浓度超标要响蜂鸣器这些动作不能只在设备端本地判断还得让人在手机或网页上点一下就能远程干预。这个标题里的“四路下发控制”指的就是云平台到设备端的方向通过MQTT指令驱动4个GPIO输出口分别控制水泵、风扇、蜂鸣器等执行器。适合正做智能农业/物联网课设或者想用成品模块快速搭一套可远程控制原型的人。我建议直接用ESP32省去外接WiFi模块一根USB线就能搞定编译和烧录。2. 传感器选型与数据采集从DHT11到MQ2的接线、供电和采样代码2.1 四类传感器怎么选怎么接到ESP32先列一张选型表照着接基本不会错传感器作用输出信号建议接法DHT11/DHT22温湿度单总线数字任意GPIODHT22精度更高土壤湿度探头土壤含水率模拟电压0~3.3V接ADC引脚配合LM393模块可用数字输出光敏电阻模块光照强度模拟电压接ADC读数越小环境越暗MQ2烟雾传感器烟雾/可燃气体模拟AO 数字DOAO接ADCDO接GPIO阈值可调ESP32的ADC引脚标有ADC1和ADC2两类接口但ADC2在WiFi开启时不可用所以优先选用ADC1通道比如GPIO32、34、35。DHT系列需要库支持在PlatformIO或Arduino的库管理器里搜“DHT sensor library”安装即可。土壤湿度传感器的两根金属针之间的电阻会随含水量变化模块自带比较器输出数字信号但模拟量才能反映连续变化所以读数用AO。光照强度最省钱的是光敏电阻加一个10kΩ电阻分压如果想要精确的lux值可以直接用BH1750走I2C总线。MQ2上电后加热丝会把内部电阻加热到工作温度刚上电那几分钟模拟输出电压会漂移属于正常现象需要在软件里做预热等待或忽略前几秒的读数。2.2 用于本地验证的最小读取程序Arduino/PlatformIO先写一个不连WiFi的示例把四类传感器的值打印到串口确认接线无误再进入下一步。#include Arduino.h #include DHT.h #define DHT_PIN 4 #define DHT_TYPE DHT11 #define SOIL_PIN 34 // ADC1 #define LIGHT_PIN 35 // ADC1 #define MQ2_PIN 32 // ADC1 #define MQ2_DO_PIN 5 // 数字输出可不接 DHT dht(DHT_PIN, DHT_TYPE); void setup() { Serial.begin(115200); dht.begin(); pinMode(MQ2_DO_PIN, INPUT); analogReadResolution(12); // 0-4095 } void loop() { float h dht.readHumidity(); float t dht.readTemperature(); if (isnan(h) || isnan(t)) { Serial.println(DHT read error); return; } int soil analogRead(SOIL_PIN); int light analogRead(LIGHT_PIN); int mq2 analogRead(MQ2_PIN); int mq2_do digitalRead(MQ2_DO_PIN); Serial.println(Temp: String(t) Hum: String(h)); Serial.println(Soil: String(soil) Light: String(light)); Serial.println(MQ2_AO: String(mq2) MQ2_DO: String(mq2_do)); delay(2000); }analogReadResolution(12)把ESP32的ADC分辨率设为12位0~4095比Arduino Uno的10位0~1023精细得多。DHT读取失败时isnan返回真这时直接退出本次循环不然t和h会是无效值。土壤湿度和光照强度直接读原始值后续自行标定比如把传感器放在干燥空气和水中记录两个极值再映射成百分比。MQ2的DO脚可以在模块上旋转电位器调整报警阈值读数字量能快速测试相关逻辑但上报到平台建议用AO因为你不知道现场的真实浓度阈值到底是多少。2.2.1 关于供电的提醒DHT11和光敏模块是低功耗用ESP32的3.3V给它俩供电没问题土壤湿度模块和MQ2建议用5V供电。MQ2的加热丝电流接近几百毫安如果和ESP32板载稳压器挤在一起板子容易过热。我一般用一个USB 5V输入单独给传感器排供电或者用一块TP4056锂电池模块。所有模块的GND必须和ESP32共地否则ADC读数会乱飘采样值完全没有参考意义。2.3 常见坑量程、预热、插入深度的一致性土壤湿度传感器的两根金属针如果插入深度不一样读数会非常不稳定。我习惯在探头上用马克笔做一个刻度每次都插到同一深度。光敏电阻的安装要避开阳光直射否则数据变化范围特别小。MQ2除了预热还要注意它对外壳密封有一定要求不能完全封闭在盒子里否则气体进不去。这些细节在你后期调阈值时会直接影响体验。对于土壤湿度的标定我一般记录两个值干燥土壤中的AO值设为dry和水中的AO值设为wet然后用公式(dry - raw) / (dry - wet) * 100换算成百分比。光照强度如果不是必须lux就用原始值做相对变化曲线避免无效换算。MQ2的模拟输出和气体浓度是非线性关系这里不做精确ppm只要设定一个报警阈值就够用。3. ONENET接入与数据上传APIKey、MQTT参数和JSON格式3.1 在ONENET控制台生成APIKey拿到MQTT连接参数进入ONENET云平台注册登录。控制台路径会随改版变化但核心操作一致创建产品协议类型选MQTT数据格式选JSON。添加设备得到设备ID和设备鉴权信息token。设备管理-设备详情页里找到“MQTT连接信息”通常会有Broker地址、端口、ClientID、Username、Password。同时生成一条APIKey旧版本叫APIKey新版本叫token作用都是鉴权。参数从哪里获得作用MQTT Broker设备详情页接入服务器地址形如mqtts.heclouds.com端口同上1883明文或8883TLSClientID产品ID与设备ID组合标识唯一设备Username产品ID或APIKey以平台为准认证身份PasswordAPIKey/鉴权token签名密钥提示不同版本的ONENET平台对Username和Password用法不同Studio版使用“三元组认证”ProductKey、DeviceName、DeviceSecret进行签名。正式接入前先在控制台的“设备调试”功能里复制官方示例代码确认协议字段再动代码能省掉很多周折。3.2 用PlatformIO搭建ESP32工程把MQTT连接跑起来项目根目录下的platformio.ini示例[env:esp32dev] platform espressif32 board esp32dev framework arduino lib_deps bblanchon/ArduinoJson^6.19.4 knolleary/PubSubClient^2.8PubSubClient处理MQTT收发ArduinoJson组装和解析JSON。PlatformIO会自动下载依赖不需要手动拷贝库文件夹。主程序关键部分#include WiFi.h #include PubSubClient.h const char* ssid 你的WiFi; const char* wifi_pwd 你的WiFi密码; const char* mqtt_host 你的平台Broker地址; int mqtt_port 1883; const char* mqtt_clientId 你的ClientID; const char* mqtt_username 你的Username; const char* mqtt_password 你的Password; WiFiClient wf; PubSubClient mqtt(wf); void callback(char* topic, byte* payload, unsigned int len) { // 第四章节处理下行控制 } void setup() { Serial.begin(115200); WiFi.begin(ssid, wifi_pwd); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } mqtt.setServer(mqtt_host, mqtt_port); mqtt.setCallback(callback); if (!mqtt.connect(mqtt_clientId, mqtt_username, mqtt_password)) { Serial.println(MQTT connect failed); Serial.println(mqtt.state()); } } void loop() { mqtt.loop(); // 后续在这里周期上报 }mqtt.setCallback注册下行消息回调函数任何订阅主题上收到的消息都会进这个函数。mqtt.connect的四个参数分别是ClientID、Username、Password务必替换成控制台获取的值。mqtt.state()返回连接失败的错误码-2表示网络未连接-5表示用户名密码不对-4表示协议版本不一致。3.3 上报温湿度、土壤湿度、光照强度、MQ2数据ONENET经典多协议接入的数据上传主题是$dp负载为下面的JSON结构{ datastreams: [ { id: temperature, datapoints: [ { value: 25.5 } ] }, { id: humidity, datapoints: [ { value: 60.2 } ] }, { id: soil_moisture, datapoints: [ { value: 78.0 } ] }, { id: light, datapoints: [ { value: 1200 } ] }, { id: mq2, datapoints: [ { value: 350 } ] } ] }在ESP32中用ArduinoJson组装String buildPayload(float temp, float hum, int soil, int light, int mq2Value) { StaticJsonDocument256 doc; JsonArray streams doc.createNestedArray(datastreams); auto addPoint [](const char* id, float val) { JsonObject s streams.createNestedObject(); s[id] id; JsonArray pts s.createNestedArray(datapoints); pts.createNestedObject()[value] val; }; addPoint(temperature, temp); addPoint(humidity, hum); addPoint(soil_moisture, soil); addPoint(light, light); addPoint(mq2, mq2Value); String payload; serializeJson(doc, payload); return payload; }在loop里每5秒发布一次unsigned long lastPub 0; void loop() { mqtt.loop(); if (millis() - lastPub 5000) { float h dht.readHumidity(); float t dht.readTemperature(); int soil analogRead(SOIL_PIN); int light analogRead(LIGHT_PIN); int mq2 analogRead(MQ2_PIN); String payload buildPayload(t, h, soil, light, mq2); bool ok mqtt.publish($dp, payload.c_str()); if (ok) { Serial.println(published: payload); } else { Serial.println(publish failed, state String(mqtt.state())); } lastPub millis(); } }$dp是经典版本的上传主题。如果你用的是新版Studio需要把主题替换成控制台提供的属性上报topic通常以thing/property/post结尾JSON结构也要换成params包一层。平台控制台的“设备调试”里有官方模板直接参照模板写最稳妥。4. 四路下发控制从命令主题到GPIO驱动水泵、风扇、蜂鸣器4.1 四路控制到底控制什么电气接法与驱动能力先给一张输出电路建议物理输出执行器驱动方式建议GPIOOUT1水泵12V继电器模块ESP32 GPIO给高电平触发25OUT2风扇5V继电器模块或MOS管26OUT3蜂鸣器5V有源三极管或直接GPIO27OUT4备用如加热器/补光等继电器14提示ESP32的GPIO输出电压是3.3V不能直接驱动12V水泵。继电器模块有低电平触发和高电平触发两种接线前看模块背面丝印代码里的HIGH/LOW也要跟着换。蜂鸣器如果是无源蜂鸣器需要PWM方波驱动不能用简单digitalWrite。4.2 ONENET上的命令怎么下发到设备ONENET的MQTT下行控制采用“订阅/发布”模式设备端先订阅一个命令主题平台控制台或API向这个主题发布一条命令数据设备收到后解析执行。这个主题和JSON格式在你的系统里由你自定义但要保证设备端解析和平台发送端一致。我在setup里添加订阅const char* cmd_topic dev_cmd/out; // 自定义命令主题 mqtt.subscribe(cmd_topic);平台下发时发送下面这种JSON{ch:1,act:ON}也可以一次控制四路{relay:[{ch:1,act:ON},{ch:2,act:OFF},{ch:3,act:OFF},{ch:4,act:ON}]}我习惯用单路命令逐个测试方便调试页面上也能清楚定位是哪一路出现问题。4.3 在回调函数里解析命令并驱动GPIO在之前留下的callback函数里解析JSONint outputPinForCh(int ch) { switch(ch) { case 1: return 25; // 水泵 case 2: return 26; // 风扇 case 3: return 27; // 蜂鸣器 case 4: return 14; // 备用 default: return -1; } } void callback(char* topic, byte* payload, unsigned int len) { String msg; for (unsigned int i 0; i len; i) { msg (char)payload[i]; } Serial.printf(got [%s] %s\n, topic, msg.c_str()); StaticJsonDocument128 doc; if (deserializeJson(doc, msg)) { Serial.println(JSON parse error); return; } int ch doc[ch] | -1; const char* act doc[act] | ; if (ch 1 || ch 4) { Serial.println(invalid ch); return; } int gpio outputPinForCh(ch); int level (strcmp(act, ON) 0) ? HIGH : LOW; digitalWrite(gpio, level); Serial.printf(ch%d set %d\n, ch, level); }deserializeJson失败说明平台发来了无法解析的字符串这时候不能盲目开关继电器。ch用| -1做默认值防止解析到缺少该字段的JSON时直接出错。继电器模块如果是低电平触发这里高低电平需要反过来。我一般先不接执行器用万用表量GPIO电平变化来验证逻辑。4.4 必做的互锁和手动自动切换水泵和蜂鸣器同时动作可能造成误操作比如MQ2报警时应该允许风扇打开排风而不是关风扇土壤湿度低时水泵要开但蜂鸣器最好不响。我在代码里加一个简单互锁蜂鸣器处于ON状态时水泵不允许打开bool alarmOn false; void setRelay(int ch, bool on) { if (ch 1 on alarmOn) { Serial.println(blocked: alarm is running, pump off); on false; } if (ch 3) alarmOn on; int gpio outputPinForCh(ch); digitalWrite(gpio, on ? HIGH : LOW); }另外setup里要把所有输出引脚初始化成确定电平避免上电瞬间继电器乱跳int outputPins[] {25, 26, 27, 14}; for (int i 0; i 4; i) { pinMode(outputPins[i], OUTPUT); digitalWrite(outputPins[i], LOW); }新版ONENET平台的下行控制可能不走自定义主题而是通过“设备服务”调用你需要把服务定义里的参数映射到setRelay原理一致JSON结构以平台示例为准。5. 联调技巧用MQTTX验证命令过滤传感器抖动并自检四路输出5.1 先用MQTTX模拟设备验证平台确实能下发在没有接ESP32之前我用MQTTX先建立一个和ESP32相同的MQTT连接订阅同样的命令主题然后在ONENET控制台执行“下发命令”观察MQTTX是否收到指令。这样把问题一分为二收不到说明平台配置错了收到了但ESP32没动作那就是设备端的问题。具体步骤MQTTX新建连接填入从控制台拷贝的MQTT参数完成连接。订阅你在ESP32代码里用的dev_cmd/out。到ONENET的设备调试工具发送一条{ch:1,act:ON}回到MQTTX看是否出现。如果能出现再烧录ESP32打开串口监视器看回调函数打印出来的got [dev_cmd/out] {ch:1,act:ON}。5.2 传感器数据抖动和MQ2预热的处理土壤湿度和光敏电阻的ADC原始值经常有小幅波动如果不做处理ONENET平台画出来的折线图会像毛刺。我常用递推平均滤波#define FILTER_N 5 int adcFilter(int pin) { long sum 0; for (int i 0; i FILTER_N; i) { sum analogRead(pin); delay(10); } return sum / FILTER_N; }这样每次读取耗时50ms对于秒级上报来说完全够用。MQ2上电前5分钟数据不可信我在代码里记录启动时间只有满5分钟后才把MQ2值写进上报JSON。具体实现就是unsigned long bootTime millis();然后在loop里判断millis() - bootTime 300000满足条件才将mq2变量赋值到buildPayload的参数。5.3 四路输出的开机自检写一个简单的selfTestRelay在setup里延时2秒后逐个把所有继电器吸合3秒再断开确认哪一路坏了能当场听出来void selfTestRelay() { for (int i 0; i 4; i) { digitalWrite(outputPins[i], HIGH); delay(2000); digitalWrite(outputPins[i], LOW); delay(500); } }自检时务必把执行器的电源关掉只留ESP32和继电器模块的电观察继电器吸合指示灯即可。等自检通过再接通水泵、风扇电源避免短路。我把自检函数、滤波函数和MQTT回调都放在同一个.ino文件顶部每次烧录完直接看串口输出能快速判断新增命令是否被正确解析。本文还有配套的精品资源点击获取
返回列表