☰
基于ESP32的智能大棚远程监控与自动浇灌系统实战
2026/10/20 23:13:08 网站建设 项目流程

这套基于ESP32的智能大棚项目,在我自己的实验记录里编号是26-041。做它的起因特别简单:我周末才去郊区打理一片小菜地,大棚里种着叶菜和香草,工作日人根本不在。以前用过定时插座浇水,但太阳一晒土干得快、雨天湿度大,固定定时根本不靠谱。心里一直想的是:能不能用一套低成本硬件,让我在手机上随时看到大棚里的土壤湿度、光照强度,能在需要时远程浇水补光,还能通过视频确认设备真的在动,而不是只看数字猜状态。折腾了一个多月,方案从最初的“一块板子全包”改成了“主控+视频分体”结构,稳定性和实用性都提升了一个台阶。这篇把从需求分析、硬件选型、控制逻辑、视频推流、APP设计到踩坑过程完整摊开讲一遍,希望给同样想自己搭一套的朋友省点弯路。

1. 先捋清需求:为什么这套系统选择了ESP32而不是其他方案

1.1 需求清单和约束条件

做项目之前,我习惯先把硬性需求写下来,不然做着做着就跑偏。这套系统的核心需求其实就六条:

  • 实时采集土壤湿度,作为浇水判断依据。
  • 实时采集光照强度,作为补光判断依据。
  • 自动浇灌:湿度低于阈值时启动水泵,到了上限就停。
  • 自动补光:光照不足且白天时段自动开灯。
  • 无线视频监控:能随时肉眼确认棚内状态。
  • 手机APP远程控制:查看实时数据、手动浇水补光、查看视频。

对应的约束条件更现实:硬件成本要压低,整机不含安装不超过一张普通设备的价格;供电尽量从棚里现有的插座取电,但必须隔离可靠;系统要能断网自恢复,不能我在城里却联系不上它;调试要方便,不能每次修改都要焊板子。

把需求摆出来后,方案基本就定型了:主控MCU加上各类传感器、执行器,再加上一个视频模块。关键问题是用什么做主控。

1.2 ESP32与同类方案的对比

我平时手边也有STM32、树莓派Pico W、树莓派等板子,但对比下来ESP32是最合适的,没有之一。

方案处理能力无线能力模拟接口开发难度单片成本
ESP32双核240MHz,带浮点WiFi+蓝牙原生2路12位ADC低,Arduino/IDF均可低
STM32+WiFi模块性能强但外接麻烦需要外加ESP8266/模块优秀,多路高精度ADC中高,要调协议栈中高
树莓派Pico W单核较低WiFi支持一般3路ADC中较低
树莓派(普通版)很强有无模拟输入中,系统复杂高
树莓派Zero 2W较强有无模拟输入中高

ESP32吸引我的点很直接:双核240MHz,跑传感器采集、控制逻辑、网络协议栈完全不费劲;原生WiFi,不用外挂模块;两路ADC虽然性能不算顶级,但土壤湿度这种慢变信号够用;开发环境成熟,Arduino库随便一搜就是一大把,网上出问题也有人遇到过。

对比下来,STM32性能强但自己组网麻烦,树莓派贵且没有模拟输入,还要为Linux的电源管理操心。ESP32正好卡在“够用、便宜、资料多”这个点上。

1.3 总体架构:主控板与视频板各司其职

系统整体分三个部分:

主控板负责传感器采集、自动控制逻辑、执行器驱动,它是大脑中枢,跑着完整的本地自动控制程序,即使断网也能独立工作。视频板独立一片,只做摄像推流,把MJPEG流通过HTTP协议提供出去。手机APP负责远程查看和控制,与主控之间通过MQTT协议通信,与视频板之间用HTTP取流。

数据路径很简单:主控板从土壤湿度传感器和光照传感器读数据,判断是否需要浇水或补光,直接驱动继电器或MOS管。主控板同时把这些状态数据通过MQTT发送到指定Broker,APP收到后展示。APP下发手动控制命令时,也走MQTT Topic回到主控板,主控板收到命令后先做安全检查,再决定是否执行。

这套“主控+视频分体”的结构,是后来改出来的。最初我把摄像头接到主控ESP32上,结果I2C读写传感器经常超时、模拟采样跳动也明显变大,后来把视频独立出去,问题立刻缓解。后面会有专门章节展开说这个细节。

2. 传感器和执行器的实战选型,以及供电电路的保护设计

2.1 土壤湿度传感器:电容式探头与ADC换算

土壤湿度传感器市面上最常见的是两类:电阻式(两针插土里)和电容式(兆帕颜色,镀金镀镍那种插片)。我用的是电容式,不是因为贵,而是电阻式在两针之间加直流电,长期使用电极会电解腐蚀,夏天两个月就开始读数漂移,换探头还要挖土,很麻烦。电容式探头测的是土壤介电常数变化,电极不直接接触土壤直流电流,寿命长很多。

接线很简单:电容式探头输出模拟电压,直接连ESP32的ADC引脚。我用的ADC引脚是GPIO34,属于ADC1通道。为什么不用ADC2?因为ESP32的ADC2和WiFi模块共用部分资源,WiFi开着时ADC2采样会冲突,读出来经常是乱跳的。这是个非常容易踩的坑。

ADC原始值到湿度百分比怎么换算?不能直接看电压表映射,因为不同土壤类型、不同探头甚至不同批次,两端的读数差异很大。我采用的是“两点标定法”:

  • 把探头完全泡在水里,记录高液位时的ADC值作为“湿端”。
  • 把探头放在室内空气中风干24小时以上,记录ADC值作为“干端”。
  • 然后用线性插值把ADC值映射到0%到100%之间,并用constrain限制边界。

参考代码(Arduino框架):

const int sensorPin = 34; int dryValue = 4095; // 干端ADC值,需要自己实测 int wetValue = 1600; // 湿端ADC值,需要自己实测 int readMoisture() { long sum = 0; for (int i = 0; i < 5; i++) { sum += analogRead(sensorPin); delay(10); } int raw = sum / 5; int percent = map(raw, wetValue, dryValue, 100, 0); percent = constrain(percent, 0, 100); return percent; }

这里map和constrain都是Arduino内置函数,很好用。注意两个端点的关系:土壤越湿,ADC读数通常越低,所以湿端映射为100%,干端映射为0%。

实际安装时还有几个细节。探头一定不要插在滴灌带正下方,那个位置浇水后水分会滞留很久,读数一直“虚湿”,会导致真正缺水时错过浇水时机。最好插在根系主要分布层的中部,距离滴灌出水口15厘米左右,才能测到大面积土壤的真实平均湿度。另外,探头埋入深度要一致,我习惯插到探头长度的三分之二,露在外面的部分用白胶带做个标记,方便发现是否有被浇灌冲歪。

2.2 光照传感器:数字勒克斯值与安装位置

光照强度测量我选了数字光照传感器BH1750(模块型号常叫GY-30)。它是I2C接口,直接输出勒克斯(lux)数值,不需要自己算电压,精度对于植物补光场景完全够用。I2C地址是0x23,接ESP32的GPIO21(SDA)和GPIO22(SCL)就可以。

选它还有个原因是传感器表面有光谱响应校正,和人眼感知亮度曲线接近。虽然真正植物生理照明要算光合有效光子通量密度(PPFD),但对业余场景,用勒克斯值作为“光照够不够”的参考足够了。

安装位置才是光照传感器的关键。我第一次把它放在靠近棚顶的位置,结果晴天中午读数是六万多lux,但植株冠层其实只有两万,因为塑料棚膜本身会衰减光照。后来改放到植株冠层上方约30公分处,模拟植物实际接收到的光环境,读数才合理。

传感器感光窗口要保持清洁。大棚里灰尘和凝结水是常态,我用透明胶带贴了一个小弧形护罩,阻挡水滴直接落在感光面上,同时不影响透光。每月去大棚时顺手擦拭一次,保证长期稳定。

还要注意避免直射反光。如果旁边的补光灯板或地面白膜反射到感光窗口,读数会虚高,导致系统误判“光照够了”而关闭补光灯。安装角度我调成了略微朝南倾斜,并且让补光灯的方向与传感器感光面错开。

2.3 水泵和补光灯的驱动,以及“保护比控制更重要”

执行器是我特别想强调的部分:做智能硬件,很多人把精力都放在“控制逻辑”上,忽略了驱动电路的可靠性,结果设备动不动就重启、烧板子。

水泵我选的是12V直流隔膜泵,额定功率约60W,扬程足够把水从桶里打到棚内高处。补光灯是LED植物灯带,12V供电,功率50W,色温选的全光谱白光,既能补光又能提供一定的视觉照明。这两个执行器都属于大电流、感性或容性负载,绝对不能直接接在ESP32的GPIO上。

驱动方案我用的比较保守但可靠:两个通道分别用光耦隔离后驱动大功率MOS管,由外部12V电源给负载供电。光耦把ESP32侧的地和负载侧的地做了隔离,防止水泵启动瞬间的地线噪声污染传感器的模拟信号。MOS管选用的是低导通电阻的N-MOS,加在低边,负载正极接12V,负极经MOS管到地,通过PWM或简单IO高低电平控制通断。继电器方案也试过,但电磁继电器线圈是明显的感性元件,吸合瞬间反电动势会通过控制引脚干扰主控,必须加续流二极管。相比之下MOS管方案更安静、寿命更长、干扰更小。

电路上还做了几道保护:

  • 每路执行器单独串了一个保险丝,防止短路烧板。
  • MOS管栅极与源极之间并联了一个10kΩ电阻,防止上电瞬间误触发。
  • 用一个外部急停按钮硬切所有执行器电源,不经过MCU。
  • 控制端口上串联100Ω电阻,再并联一个小电容做滤波,提升抗干扰能力。

我的个人建议是:驱动电路宁可多用一个模块,也绝对不要省光耦和保险丝。大棚环境潮湿、灰尘多,一旦MOS管击穿或者水泵堵转,后果不是换一块板子那么简单,还可能烧掉整条线路。

2.4 供电方案与功耗估算

整机供电我用了一个12V 10A的开关电源作为总电源,直接接棚内插座。12V电源一方面给水泵和补光灯供电,另一方面经过降压模块给ESP32和传感器供电。降压模块我选了MP1584这类同步降压模块,输出5V给ESP32主板供电,再由板载稳压转成3.3V给传感器。不建议直接从12V降到3.3V,线性稳压发热太大。

供电设计里最容易被忽略的是“模拟电源”和“数字电源”的分离。ESP32虽然只有一个电源引脚,但我在5V进入主控前加了一级LC滤波(一个10uH电感加两个470uF电解电容),尽量把执行器启停带来的电压纹波挡在外面。水泵启动瞬间电流可以达到额定值的2到3倍,实测12V母线会跌落近2V,如果主控直接从同一个12V取电,板上电压会低于ESP32最低工作电压,直接重启。加了滤波和分开供电后,再没出现过启动重启问题。

功耗方面,水泵60W每天大约运行10分钟,一个月就是5度电左右;补光灯50W阴天每天可能开2小时,一个月约3度电;ESP32和传感器常开功耗大约3W,一个月约2.2度电。整套系统一个月电费大概10元左右,在可接受范围内。

3. 自动浇水与补光的控制逻辑:阈值、迟滞和安全互锁

3.1 单阈值控制的抖动问题:迟滞区间怎么定

自动浇水最直观的思路是:土壤湿度低于某个阈值就启动水泵,到达目标湿度就停止。但实际做的时候,如果启动阈值和停止阈值设成同一个值,会出现一个恼人的现象——系统在边界附近反复启停。

举个例子:湿度40%启动水泵,水泵一开,水往探头方向渗,湿度读数马上升到40.1%,水泵停了;停了以后水分扩散,探头周围湿度又掉回39.9%,水泵又开。一分钟能折腾好几次,水泵电机和MOS管都受不了。

工程上解决这个问题的标准做法叫滞环控制,或者叫迟滞区间。我设置的下限是35%,上限是55%。意思是:只有当湿度低于35%时才启动浇水,并且启动后持续浇水,直到湿度升到55%才停止。处于35%到55%之间时,系统保持当前状态不变,不动作。

这样的好处很直接:水泵每次工作都有足够长的运行时间,水能充分渗透到土壤深处,而不是刚够打湿表面。参考代码:

if (moisture < 35 && !pumpRunning && autoMode) { startPump(); } if (pumpRunning && (moisture >= 55 || pumpRunTime > maxRunTime)) { stopPump(); }

这里maxRunTime我设成了15分钟。为什么?因为如果湿度传感器故障或者水管堵塞没出水,湿度永远升不到55%,水泵会一直空转烧毁。加了超时保护,即使水分没上升,最多转15分钟就强制停止,然后进入异常报警状态,等APP端收到提示后人工处理。

迟滞区间的具体数值不是固定不变的,要根据实际土壤调整。保水性好的黏土可以把上限调低一些(比如50%就可以停),透水性太强的沙土需要把上限调高(比如60%),这样浇水次数不会太频繁。我是观察了两三天的浇水周期才定下的35%到55%。

3.2 补光不是“亮了就开”:时间窗口和滑动平均

补光逻辑比很多人想象中复杂一点,不只是“光线暗就开灯”那么机械。大棚里最典型的情况是阴天:一整天光照都在2000lux左右徘徊,此时确实该补光。但如果是晴天,云朵飘过时瞬间会从12000lux降到1500lux,如果逻辑只看瞬时值,灯就会云来就开、云走就关,一天开关几十次,LED电源寿命急剧缩短。

对策就是加时间窗口和滑动平均。时间窗口很容易理解,我设定只在7:00到19:00之间允许自动补光。晚上棚外完全没有自然光,但植物也需要休息,夜间补光不仅浪费电,还可能干扰植物生理节律。

滑动平均就是不要把当前读到的瞬时光照直接拿来做判断,而是取过去2分钟内每隔10秒采样一次的平均值。云飘走之后,平均值会缓慢上升,不会瞬间触发开关。

补光启动阈值我定的是3000lux,停止阈值10000lux,同样带有滞环。为什么是3000?植物叶菜在阴天时如果光照强低于3000lux,光合作用明显受抑,徒长明显。而停止阈值设得比启动阈值高很多,是为了避免阴天出太阳、太阳又瞬间被云遮住时灯频繁开关。

参考逻辑:

void updateLightControl(long luxAvg) { if (autoMode && hour >= 7 && hour <= 19) { if (luxAvg < 3000 && !lightOn) { turnOnLight(); } if (luxAvg > 10000 && lightOn) { turnOffLight(); } } else { if (lightOn) turnOffLight(); } }

补光灯的选择也有讲究。LED植物灯照射距离不能太近,光强过高时叶片容易灼伤。我安装时让灯面距离植株冠层至少40厘米,并且灯板角度稍微偏向走道,而不是正对叶片。

3.3 手动优先、自动兜底的模式切换与断电恢复

这套系统最让我放心的一点,是手动模式永远优先于自动模式,但自动模式永远作为兜底存在。

具体实现是这样的:系统有两个布尔标志,autoMode和manualOverride。初始状态autoMode为true,所有控制走自动逻辑。当用户在APP上手动启动了一次浇水,autoMode会被临时置为false,系统进入“手动生效中”状态。这个状态持续到手动命令执行完毕(比如浇水120秒结束),或者手动超时时间到,系统自动恢复autoMode=true。

为什么这么设计?因为我不可能24小时盯着APP,偶尔上班路上看了一眼湿度,手动开了一次浇水,浇完以后还是希望系统重新接管。如果手动模式一直按住不放,过两天人不在,自动浇水就彻底停了,棚里的菜就要遭殃。

断电恢复也需要特别处理。系统上电后,默认把所有执行器置于关闭状态,并且不允许立即执行自动控制,必须等至少10秒让传感器稳定。更重要的是,断电前如果正在浇水,重新上电后绝不能自动恢复浇水,否则可能发生“断电一小时,恢复后继续浇”的危险情况。我的做法是把上次的浇水剩余时间存到ESP32的Flash里,恢复时直接清零。

还有一层安全互锁:当系统检测到传感器异常(比如ADC读值长期不变、I2C无响应、光照传感器读数为0或远超量程),会主动把自动模式调整为保守模式,此时不自动浇水、不自动补光,只在APP上推送异常告警。宁可让菜渴两个小时,也不能让异常状态导致设备失控。

4. 无线视频监控这块:ESP32的算力边界与推流取舍

4.1 为什么视频要单独用一片ESP32-CAM

最初我天真地以为,一片带摄像头的ESP32-CAM可以把传感控制、视频推流一起干了。实际试下来,问题很明显。ESP32的CPU同时要跑WiFi协议栈、MQTT客户端、传感器I2C读写、控制逻辑,再叠加视频采集压缩推流,CPU占用经常跑到90%以上。传感器采集任务开始频繁被打断,I2C读BH1750时偶发超时,模拟湿度采样也出现明显噪声。

还有更麻烦的内存问题。ESP32虽然有520KB SRAM,但MJPEG编码需要逐帧JPEG压缩,每帧320x240的RGB565缓冲大约150KB,加上其它任务开销,很容易触发内存不足。系统运行几小时后,堆内存碎片化,摄像头直接罢工,只能重启。

所以我把视频独立到另一片ESP32-CAM上,主控板只跑传感和控制。两者通过同一个WiFi网络通信,视频板独立提供HTTP MJPEG流,主控板完全不去碰视频数据。好处立竿见影:主控板传感器读数恢复稳定,视频板即使崩溃也不会影响到任何控制功能。

成本代价是一片ESP32-CAM模块,几十块钱,换来的是整体稳定性的大幅提升。这个取舍我觉得非常值。

4.2 分辨率、帧率和推流格式的选择

视频参数不是说越高越好,而是要在“能看清”和“扛得住”之间找平衡。我实测了几组参数:

分辨率帧率CPU占用实测卡顿适用场景
640x48015fps高,偶尔掉帧明显不适合长时间推流
640x48010fps中高偶尔需要看细节时临时用
320x24010fps中流畅日常监控推荐
160x1205fps低流畅纯看状态指示灯足够

最终我选择了320x240,10fps。在这个参数下,ESP32-CAM能稳定运行数小时不重启,画面能看清植株整体状态、水泵是否出水,但对细节不奢求。大棚监控不是看高清大片,关键是要能确认“作物状态”和“设备执行状态”。

推流格式选MJPEG而不是视频流编码(如H.264)。MJPEG的思路很简单:摄像头每帧压缩成一张JPEG图片,按顺序通过HTTP的multipart/x-mixed-replace机制持续发送。优点是不需要复杂的视频解码库,手机端加载一个URL就能显示实时画面;缺点是码率比较高。320x240分辨率下实测码率大约300到500kbps,局域网内完全不是问题。

ESP32-CAM本身自带OV2640摄像头,Arduino环境里有现成的CameraWebServer示例,我在其基础上删掉了人脸识别等不用的功能,只保留MJPEG推流部分,内存占用减少了不少。

4.3 夜间监控和存储轻量化处理

大棚夜间黑灯瞎火,普通摄像头视野一片黑,视频监控等于摆设。我加了一组红外LED灯板,波长850nm,用12V单独供电,ESP32-CAM的镜头前加了一片红外截止滤镜的替代方案。严格来说OV2640本身自带IR cut滤镜,红外LED的照明效果可能打折扣。实际测试下来,在红外灯板的照射下,夜间能看到植株轮廓和设备周围状态的灰度图像,足够确认“棚里没有异常”。如果一定要彩色夜视,就得换带IRCUT自动切换的摄像头,价格更高,我这套系统里觉得没必要。

存储方面我也试过长时间录像,结论是不推荐。ESP32-CAM虽然有MicroSD卡槽,但JPG写入慢、卡片寿命消耗快,而且28GB级的连续录制对一张几十块的TF卡是巨大负担。我的方案是只做实时推流,不做本地录像。如果哪天需要证据存档,在APP端截一张图就好。实际上更多时候我用摄像头的是白天确认状态,夜间只有异常告警时才会看一眼视频。

5. APP端的通信协议、数据结构和界面交互设计

5.1 通信选型和Topic设计

设备端和手机APP之间的通信,我用MQTT而不是HTTP轮询。原因很简单:MQTT是长连接,服务器能主动把状态变化推给APP,实时性好;HTTP轮询需要APP每隔几秒发一次请求,费电费流量,而且大棚和APP之间网络延迟不稳定时,状态总会有几秒的滞后感。

我搭了一个轻量级MQTT Broker,跑在一台云服务器上。设备端通过WiFi连接公网Broker,APP也连同一个Broker。为了让局域网场景更快,我在代码里加了自动判断:如果APP和设备在同一个局域网,就直连设备的IP,否则走云Broker。实测局域网控制延迟在几十毫秒,公网延迟受网络影响,通常1秒以内。

Topic设计成三层结构,方便不同设备订阅:

TopicQoS发布者说明
device/{deviceId}/sensor0主控板上传传感器数据和设备状态
device/{deviceId}/cmd1APP下发控制命令
device/{deviceId}/ack1主控板命令执行回执
device/{deviceId}/event1主控板告警事件(如传感器异常)

QoS设成0用于高频传感器数据,丢了也无所谓;命令和回执必须用QoS 1,确保至少送达一次,避免命令丢失导致设备状态未知。

5.2 上下行数据包格式

数据结构我选了JSON,灵活、可读性强,后续要加字段也不至于破坏旧版本。上行传感器数据包长这样:

{ "type": "sensor", "mid": "1", "moisture": 42, "lux": 8500, "temp": 26, "hum": 65, "pump": 0, "light": 1, "vol": 12.1, "rsig": -58, "ts": "2025-05-04 12:00:00" }

其中temp和hum是从主控板上另一个温湿度传感器读的,顺便监控大棚环境;vol是电源电压,用来远程预警供电异常;rsig是WiFi信号强度(RSSI)。

下行命令包设计时我特别加了一个seq字段,防止命令重复执行。APP每发一条控制命令,seq加一,主控板收到后对比本机记录的上一条seq,如果相同就判定为重复消息并丢弃。这样即使MQTT服务端因为弱网重发消息,设备也只会实际执行一次。

示例:

{ "type": "cmd", "seq": 12, "cmd": "water", "duration": 120 }

主控板执行完命令后,发送ACK回执:

{ "type": "ack", "seq": 12, "status": "ok", "detail": "pump_started" }

APP收到ACK后才把按钮状态从“执行中”恢复为正常。如果超过10秒没收到ACK,APP会提示“设备无响应”,而不是让用户以为命令发出去了其实设备没动。

5.3 Flutter端的关键实现与跨平台打包

APP端我用Flutter写,因为一套代码同时出Android和iOS版本,后续维护省事。核心依赖就两个:mqtt_client用来做MQTT通信,image网络流用来拉MJPEG视频。

MQTT连接代码大致这样:

final client = MqttClient('你的Broker地址', 'app_${DateTime.now().millisecondsSinceEpoch}'); client.onConnected = () { client.subscribe('device/26-041/sensor', MqttQos.atMostOnce); client.subscribe('device/26-041/ack', MqttQos.atLeastOnce); client.subscribe('device/26-041/event', MqttQos.atLeastOnce); }; client.onMessage = (topic, message) { final payload = utf8.decode(message.payload); // 解析JSON并更新UI状态 }; await client.connect();

视频显示更简单,Flutter的Image.network直接支持MJPEG流:

Image.network( 'http://视频板IP:81/stream', fit: BoxFit.contain, )

这里有个Android平台的大坑:Android 9开始默认禁止应用使用明文HTTP流量,直接打开局域网HTTP视频流会报错。需要在AndroidManifest.xml的application标签里加上:

android:usesCleartextTraffic="true"

或者更规范的做法是配置networkSecurityConfig,只允许特定IP段走明文HTTP。我用的是后者,把局域网的网段加进白名单,公网视频地址就走HTTPS。

界面设计分成三块:

首页就是核心监控页,最上面是实时数据卡片(土壤湿度、光照、温度、湿度),中间是视频画面,下面是两个大控制按钮:浇水、补光。每个控制按钮点击后先弹确认对话框,并且需要长按确认,避免误触。

第二个Tab是历史曲线页,用本地SQLite存储每天的传感器采样数据,每隔一个小时从设备端拉一次当天数据做补全,图表用简单的Canvas手绘折线图。

第三个Tab是设置页,包含自动模式开关、湿度上下限参数调整、补光时段设置。这些设置项通过MQTT下发到设备端并存入ESP32的Preferences,断电不会丢失。

5.4 公网访问的安全边界

让设备直连云服务器的MQTT Broker后,控制命令就可以跨网络到达,视频流则要格外小心。视频板本身没有任何鉴权机制,默认任何能访问它IP的人都能看到画面。我这个大棚视频并不涉及隐私,但设备控制是大事。我的做法是:

  • 视频板只在局域网内暴露,不直接映射到公网。
  • 公网需要看视频时,通过云服务器中转,具体是云服务器上跑一个简单的静默代理,只允许带有效Token的HTTP请求去转发到视频板。
  • Token由APP登录时从Server端获取,定期更换。
  • 主控板和APP之间的所有命令都走MQTT,即使MQTT的凭证泄漏,设备端也限制了同一时间只允许一个合法客户端连接,其他客户端强制踢出。

先说结论:如果把公网端口直接暴露出去,没有任何保护,用户就是把自己家的电气设备控制权拱手让人。宁可不做公网视频,也不能让陌生人进入控制通道。

6. 实跑过程中踩过的坑:从ADC漂移到继电器反电动势

6.1 ADC读数漂移与WiFi射频干扰

第一次跑通系统是在我办公桌上,传感器读数还挺稳定。结果拿到大棚里一测,土壤湿度数值疯狂跳动,范围能达到±15%。排查到最后,罪魁祸首是WiFi射频干扰和电源噪声叠加。

ESP32的ADC有个特性:内部参考电压有一定温漂,同时模拟输入引脚对射频干扰比较敏感,尤其是设备离WiFi天线比较近时。我的主控板正好把天线放在了ADC引脚旁边,等于让模拟信号直接暴露在2.4GHz辐射场下。

解决了几步:

  • 把ADC引脚改用ADC1通道(GPIO34),彻底避开ADC2和WiFi共享的问题。
  • 采样时连续读5次取平均,并且每次采样之间加10ms延时,相当于一个低通滤波器。
  • 在ADC引脚和地之间并联了一个0.1uF电容,进一步滤除高频噪声。
  • 调整板布局,让WiFi天线远离模拟输入区域,至少隔开3厘米以上。

经过这几层操作,读数从±15%的抖动降低到±2%以内,完全在可接受范围内。如果还有人想追求更高精度,可以直接换I2C接口的数字土壤传感器,彻底跳过模拟量这条链路,但成本和功耗也会相应增加。

6.2 水泵继电器吸合瞬间的重启问题

这个坑我印象特别深。当时的驱动方案是继电器,水泵一启动,ESP32就会瞬间复位,表现为串口打印重启日志。后来才明白,这是继电器线圈感性和水泵电机感性负载叠加造成的反电动势脉冲,直接灌回了控制电路。

解决路径分了三步:

第一步,继电器线圈两端并联续流二极管。方向必须正确:二极管阴极接继电器线圈正极端,阳极接负极端。这样才能在线圈断电时导通泄放,反接等于短路,瞬间就可能烧毁继电器驱动管。

第二步,把负载电源和控制电源彻底分开。水泵的12V电源和ESP32的5V逻辑电源从同一个开关电源输入,但每个电源支路都加了独立的LC滤波,共用接地点只有一个点。这样负载电流突变时,地线电压波动就不会传导到逻辑地。

第三步,控制系统软件上也做了配合。水泵启动前先给继电器控制脚一个低电平持续1秒,让电源稳定后再切换;停止水泵时也先延时300ms再操作其他任务。这套软硬件组合后,主控板再没出现过启动重启。

6.3 大棚环境下的WiFi稳定性与自动恢复

大棚看着就几根塑料膜和钢管骨架,但对2.4GHz信号来说,它是实实在在的电磁屏障。金属骨架会反射信号,塑料膜会吸收一部分,加上棚内湿度常年较高,信号衰减比我预想严重很多。主控板如果放在角落,WiFi RSSI能掉到-70dBm以下,MQTT频繁重连,系统变成半瘫痪状态。

最后我把WiFi天线从控制盒里引出来,连接到棚中央高处的一个支架上,用户看不见什么高深的,就是一根普通的2.4G棒状天线,用延长线接上。信号一下就变到-45dBm,稳定了。

软件上还加了几层保证:

  • ESP32每隔30秒检测一次MQTT连接状态,如果断开就尝试重连,尝试5次失败则重启WiFi模块。
  • 设置一个任务看门狗,主循环如果卡死超过10秒,系统自动重启。
  • 每天凌晨3点定时重启主控板一次,清理可能的内存碎片。

这套重启策略不是什么高大上的技术,但它在长达数月的连续运行中非常管用,保证了我即使离开大棚一个月,回来系统也在正常工作。

6.4 视频子板长时间运行的卡死对策

ESP32-CAM跑MJPEG流,长时间运行后偶尔会卡死,输出不再有画面,TCP连接也断开。这个现象不是偶发,运行几天就会出现一次,必须自动恢复。

我的对策是“外力干预”和“内部自愈”结合。

内部自愈:在ESP32-CAM的代码里启用看门狗,主循环如果超过5秒没有喂狗就自动重启。同时把摄像头缓冲区大小设置到合理范围,避免连续采集时内存分配失败。

外力干预:视频板有一个EN(复位)引脚,我把它接到主控板的GPIO上。主控板每24小时(我设在早上6点)主动把视频板EN拉低500ms再拉高,强制冷启动。这个操作对用户完全无感,但明显降低了视频板“带病运行”的概率。

实测下来,加了看门狗和定时重启后,视频板连续运行三个多星期没有再卡死过。我现在的经验是:对ESP32这种资源有限芯片,不要指望一个进程长期不死,而是要用“快速重启、快速恢复”的思路去管理。

7. 联调效果与长期运行数据:稳定运行三周后的复盘

7.1 联调时间线和各模块测试结果

整套系统的联调花了大约5天。记录一下时间线,给准备照抄的朋友做个参考:

第一天:主控板跑通传感器采集和继电器控制。主要工作是调ADC滤波参数、标定湿度探头、修正BH1750的采样频率。测试结果是湿度读数稳定在±3%以内,光照读数平滑,继电器通断正常。

第二天:接入MQTT和APP通信。在本地电脑上用MQTT测试工具收发Topic,调试了下行命令和ACK回执逻辑。中间发现过一个问题:设备掉线重连后,刚才收到但未执行的命令会被执行两次,后来加了seq去重逻辑解决。

第三天:摄像头推流调试。切换了几组分辨率和帧率,最终锁定320x240、10fps。晚上用红外灯板测试夜视效果,确认能看清植株轮廓。

第四天:整体供电布局和模拟环境测试。把设备放到密闭箱里,用水桶模拟浇水,用拉黑布模拟阴天,验证自动逻辑是否按预期触发。

第五天:现场安装调试。把传感器埋好、摄像头固定好、天线架起来,看了一天数据,确认正常后离开,进入长期运行阶段。

7.2 两周内的真实运行数据

运行两周后,我把设备端记录的数据导出,给几个关键点做个复盘。

湿度控制方面,系统一共自动触发了7次浇水,每次从35%左右开始,持续12到15分钟后湿度升到55%停止,间隔基本在2天左右,符合叶菜的需水节奏。

补光控制方面,晴天中午光照在3万到5万lux,系统没有误开灯;阴天下雨的两天,光照持续低于3000lux,补光灯在9点左右自动开启,下午4点后自动关闭,连续工作了约6小时,没有出现频繁跳变。

供电方面,12V母线电压一直稳定在11.8到12.2V之间,主控板电压纹波系数也正常。

我在APP上查看视频时,实测画面延迟约0.5秒,局域网内肉眼可接受。视频板运行三周只出现过一次主动重启,主控板一次都没重启过。

从产量上看,这次棚里的叶菜长势比之前用定时插座时期明显更均匀,没有出现过浇水浇多根腐或补光不足徒长的问题。虽然这是主观判断,但设备数据能对应上。

7.3 再做一个版本的话,我会改哪些地方

三周运行下来,整体满意,但心里也积了一些改进点,如果开下一版我肯定会改。

第一,加一片外部高精度ADC芯片,用来接4路土壤湿度探头。现在只有一路探头,测的是“一个点的湿度”,如果土壤不均匀,容易误判。多个探头取平均值,才能真正反映整个根区的水分状况。第二,主控板换成带PSRAM的ESP32-S3,算力余量更足,后续要加边缘计算(比如图像判断病虫害)也不至于卡死。第三,增加本地离线控制面板,用一块小LCD实时显示关键数据,这样去大棚现场时不需要掏手机也能看到状态。第四,供电部分预留太阳能充电接口,虽然电费不高,但野外无电场景会更灵活。

最后分享一个我自己的习惯。这套系统运行到现在,最大的体会不是功能做得多全,而是“稳”比“多”重要。我特意每天早上6点安排视频板重启,就是为了在它卡死之前先自我了断。如果你也要跟着搭,建议先只跑控制主逻辑跑一天,确认传感器读数平稳、执行器不误动,再挂视频和APP,不要一上来就全堆。不然出了问题,很难分清乱在传感器、网络还是摄像头。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询