资讯动态

智能太阳能路灯控制系统:ESP32+MQTT全链路开发实战

发布时间:2026/10/19 3:19:41 来源:尧图企业网站定制
1. 为啥要做这么一套路灯级的智能控制系统先说个背景。这几年我一直在帮朋友折腾各种单片机小项目从温湿度采集到远程开关越做越觉得传感器联网可视化才是物联网项目的灵魂。这次拿到一个挺典型的题目——基于ESP32的智能太阳能路灯系统表面看就是天黑亮灯、天亮灭灯但真往深了做里面塞了光照检测、锂电池电压监测、无线视频监控、手机APP远程控制这么一长串需求几乎把物联网项目的常见模块全占了。我当时第一反应是这不就是一块开发板把感知、供电、传输、控制、展示五层打通了吗做完这一套基本就能摸清智能硬件项目从硬件选型到APP上线的完整链路。这个项目的核心诉求其实很接地气太阳能板给锂电池充电锂电池给路灯供电ESP32负责采集光照强度、监测电池电压和电量再通过WiFi把数据丢到云端或者本地服务器APP端实时看到路灯状态顺手还能远程开关灯、甚至调亮度。如果再挂一个摄像头模块就能顺带实现无线视频监控比如看看路灯周围有没有异常情况。所以它本质上是一个能源管理环境感知远程监控三位一体的综合系统适合拿来练手也非常适合扩展成真正的商用路灯管理方案。适合谁来参考我建议三类人重点关注一是正在做毕业设计或者课程设计的同学这个题目覆盖的知识点广好出成果也好答辩二是想入门ESP32物联网开发、但不想只做点灯级别的初学者三是做智慧城市、智慧园区相关项目的工程师可以把这套东西当作最小可行原型后期换工业级传感器和通信模块就能上量。我自己做下来最大的体会是这项目难点不在单个模块而在系统联调——硬件、电源、网络、软件、APP互相咬合任何一个环节松了整条链路都跑不通。所以下面我会把整套系统的设计思路、关键模块选型、代码逻辑、踩坑记录和APP对接全过程都摊开来讲尽量做到你能照着复现。2. 整套系统到底由哪些模块组成各自干什么活2.1 三层架构感知层、传输层、应用层任何一个物联网项目不管标题多花哨拆到最后都是三层感知层负责采集物理世界的数据传输层负责把数据送出去应用层负责展示和控制。这套太阳能路灯系统也不例外。感知层是眼睛和耳朵光敏传感器或者直接用ESP32的ADC读取光敏电阻的分压负责感知环境光照强度判断白天黑夜锂电池电压检测通过分压电阻网络接到ESP32的ADC引脚实时读取电池电压再根据放电曲线估算剩余电量摄像头模块一般用OV2640负责抓拍视频画面实现无线监控功能。传输层是神经ESP32自带WiFi通过HTTP或者MQTT协议把传感器数据上报到服务器同时接收APP下发的控制指令。这里有个关键点——ESP32的WiFi功耗不低对于太阳能供电的系统来说必须在实时在线和省电之间做权衡后面我会专门讲省电策略。应用层是大脑和操控台APP我这边用的是Android端其实可以用微信小程序、Web端或者现成的物联网平台负责解析ESP32上传的数据展示光照强度、电池电压、电量百分比、路灯开关状态同时下发开关灯、调亮度、定时策略等控制命令。这三层看起来各管各的但实际联调时你会发现任何一个环路的延迟、丢包、误码都会被放大。比如光照传感器数据和电压数据如果分开上报APP端就不好判断当前是夜间且电量偏低这种组合状态所以我在设计协议时把所有数据打包成一条JSON上报让应用层一次拿到完整快照。2.2 电源拓扑太阳能板、锂电池、稳压电路三者的配合太阳能路灯和老式路灯最大的区别就是离网自供电所以电源拓扑是整个硬件设计的命根子。我这边的结构是这样的太阳能板输出经过充电管理模块常用的是TP4056或者CN3065这类单节锂电池充电IC给锂电池充电锂电池再通过稳压模块常用的有AMS1117-3.3、ME6211等低压差LDO输出3.3V给ESP32和相关传感器供电。路灯的LED灯珠如果是大功率的比如10W以上一般不建议直接怼在ESP32的GPIO上而是用MOS管驱动由ESP32输出PWM信号来控制亮度。为什么要用LDO而不是DC-DC降压这里面有个工程取舍。LDO纹波小、电路简单、静态功耗低适合给传感器和WiFi模块这种对电源质量敏感的负载供电而DC-DC效率高但纹波大、外围电路复杂更适合驱动LED灯珠这种功率型负载。所以实际项目里我经常是双路供电一路LDO给逻辑电路一路DC-DC给灯珠。如果你的LED功率不大比如3W以内用一个AO3400 MOS管直接从电池取电、靠PWM斩波调光也完全够用电路上还省事。还有一个容易被忽略的点ADC参考电压和电池电压采样。ESP32的ADC输入范围是0~3.3V而锂电池满电电压是4.2V直接接上去会烧引脚。所以需要分压电阻比如100K100K分压把电池电压缩到一半再进ADC软件里再乘回来。我实测下来ESP32的ADC线性度不算特别好尤其是接近量程两端的时候误差能到3%~5%所以电压检测部分我加了简单的软件校准后面会说到怎么校。2.3 通信协议为什么我选MQTT而不是裸HTTP设备端和APP端通信方案有好几种最简单粗暴的是一台设备开一个HTTP ServerAPP直接GET/POST成熟一点的是ESP32作为MQTT客户端连到BrokerAPP也作为客户端订阅/发布同一主题还有走TCP长连接自定义二进制协议的。我的选择是MQTT理由非常实操第一NAT穿透问题。如果你用HTTP Server方案ESP32在家庭WiFi下是内网IPAPP在外面根本访问不到除非做端口映射或者内网穿透。而MQTT是设备主动连服务器只要ESP32能上网主动出站连接不管它在哪个网络里都能保证双向通信。第二指令和数据的天然解耦。路灯状态数据走一个Topic控制指令走另一个Topic订阅发布模型特别契合多个路灯一个管理端的场景。以后加设备、加管理端都不需要改协议。第三断线重连机制比HTTP好太多。MQTT有Last Will、QoS、Keep Alive这些现成的机制HTTP请求丢了就得自己封装重发逻辑做起来很费劲。我用的Broker是公共测试服务器实际项目你可以在内网搭一个Mosquitto或者用云厂商的MQTT实例。Topic设计上我建议这样solar/device/{device_id}/status设备定时上报状态payload是JSON包含光照lux、电池电压、电量、灯状态、温度等。solar/device/{device_id}/commandAPP下发控制指令payload也是JSON包含cmd字段比如{cmd:toggle}、{cmd:set_brightness,value:128}、{cmd:schedule,on:19:00,off:06:00}。solar/device/{device_id}/alarm设备上报异常比如电压低于阈值、光照传感器无数据等。这样分层设计的好处是以后接多路设备只需在topic里换device_idBroker端做权限控制也方便。这个设计我强烈建议新手直接就按这个思路来别贪图省事把所有消息塞进同一个topic不然后期调试能烦死你。3. 硬件电路搭建ESP32、传感器、电源板怎么焊怎么连3.1 核心元器件清单和选型理由下面是我的整套物料清单全部是市面上容易买到、资料齐全的器件适合复现。别贪便宜买那种没热风枪就焊不了的超小封装新手先从直插和模块开始。器件型号/规格数量说明主控ESP32 DevKitC V4 (ESP32-WROOM-32)1双核240MHz有WiFi和蓝牙够用光敏传感器GY-30 (BH1750数字光强模块)1I2C接口精度高直接读lux锂电池18650容量3000mAh1配18650电池仓方便更换充电模块TP4056 with protection1Micro USB输入充电截止4.2V稳压模块AMS1117-3.313.3V LDO电流800mA够ESP32用LED灯白光LED灯珠3W/1W13W用MOS管驱动1W可以三极管MOS管AO34001N-MOS低导通内阻适合PWM调光摄像头OV2640模块ESP32-CAM也可以1注意引脚复用问题分压电阻100K100K1%精度2电池电压采样其他杜邦线、面包板、PCB端子、二极管若干防反接、接线方便选型时我特意避开了ESP32-CAM一体板虽然那玩意自带摄像头很方便但它的缺点是GPIO和摄像头共用比如GPIO16接SD卡、GPIO4接PSRAM导致没法同时做其他传感器采集和控制。所以我最终用的是ESP32开发板独立OV2640模块的方案虽然接线多了几根但GPIO自由度大很多。如果你做的项目对摄像头要求不高用ESP32-CAM也能省不少事这个看个人取舍。3.2 接线拓扑分清电源线、信号线、控制线接线是整个硬件调试中最容易出问题的地方我见过太多人代码没问题但就是跑不起来最后查出来是某一根线没接对。下面是我整理好的接线表锂电池正极 → TP4056 BAT负极 → TP4056 BAT-如果是带保护板的电池直接接就行TP4056 OUT → AMS1117-3.3 VINOUT- → GNDAMS1117-3.3 输出 → ESP32的3V3和GNDBH1750模块VCC接3V3GND接GNDSCL接GPIO22SDA接GPIO21ESP32的I2C默认引脚电池电压采样电池正极 → 100K → GPIO34电池负极 → 100K → GND构成分压器GPIO34处电压为电池电压的一半LED灯珠如果需要PWM调光LED正极接3V3或5V取决于灯珠工作电压负极接AO3400的D极AO3400的S极接GNDAO3400的G极通过一个100Ω电阻接GPIO17OV2640模块SCCB的SCL接GPIO14、SDA接GPIO13VSYNC接GPIO25、HREF接GPIO26、PCLK接GPIO27、XCLK接GPIO33、D0-D7接GPIO36/37/38/39/34/35/32/具体看你用的库我后面会贴配置特别注意GPIO复用的坑ESP32的GPIO34~39是输入专用引脚不能输出PWMGPIO1、GPIO3是UART0的调试串口不能乱用GPIO12在启动时必须保持低电平否则可能进入Download模式。我一开始接PWM时用了GPIO35死活出不了波形查了半天才发现是输入引脚。所以设计硬件前先拿引脚功能表核对一遍别等板子焊好了再返工。3.3 供电和采样电路的设计计算电源部分我算过一笔账ESP32在WiFi开启、峰值发送时电流能到240mA左右加上传感器和LDO自身的静态功耗整机峰值大概在300mA上下。如果用AMS1117-3.3它的最大输出电流是800mA压差是1.1V800mA也就是说输入电压最低不能低于4.4V而锂电池在3.7V标称电压下直接输出是达不到这个门槛的。所以这里有个关键设计问题锂电池3.7V没办法直接给AMS1117-3.3供出3.3V。我当时怎么解决的两个方案方案A用低压差LDO比如ME6211压差只有200mV输入3.4V就能稳出3.3V适合电池直供逻辑电路的场景。我实测18650从4.2V放到3.5VME6211都能稳定输出3.3V左右。方案B先把电池升压到5V再用AMS1117-3.3降下来。这个方案的坏处是经过两次变换效率低而且升压模块在低负载时也有静态功耗对电池不友好。最终我选了方案A逻辑部分用ME6211稳压灯珠部分直接从电池取电用MOS管控制这样效率最高。ADC采样的话GPIO34读到的是电池电压的一半所以软件里直接用adc_voltage * 2还原真实电压再用分压电阻的误差修正一下实际阻值可以用万用表量测出来多少就是多少。这里有个细节ESP32的ADC输入阻抗不是无穷大所以分压电阻阻值不宜选太大100K100K是我试下来比较稳的组合你要是换成1M1M读数会飘得厉害。4. ESP32端程序设计从传感器读取到数据上报4.1 开发环境配置和工程结构ESP32开发我用的Arduino框架ESP32 Arduino Core原因很简单生态丰富、示例多、调试方便而且对新手特别友好。如果你工作后要上量产可以考虑ESP-IDF原厂框架但做毕设和原型验证Arduino绰绰有余。安装步骤在Arduino IDE里添加开发板管理地址https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json在开发板管理器里搜索esp32安装esp32 by Espressif Systems。选板子ESP32 Dev ModuleFlash Size选4MBPartition Scheme选Huge APP (3MB No OTA)因为摄像头固件体积不小默认分区可能放不下。项目里的库清单WiFi.h连接无线网络。PubSubClient.hMQTT客户端。Wire.hI2C通信。BH1750.h光照强度传感器驱动。Arduino_JSON.h构建和解析JSON消息。esp_camera.h摄像头驱动ESP32自带的核心库。工程结构我习惯拆成几个文件main.ino负责初始化、主循环Config.h放WiFi账号密码、MQTT地址、设备ID等配置PowerManager.cpp负责电池电压采集和电量估算SensorManager.cpp负责光照采集CommandHandler.cpp负责解析APP下发的指令。4.2 核心代码光照采集、电池电压换算、MQTT上报下面这段是传感器读取和电池电压换算的核心代码我直接贴出来并解释关键点#include Wire.h #include BH1750.h #include Arduino_JSON.h BH1750 lightMeter; // 电池电压采样引脚 #define BATTERY_PIN 34 // 分压电阻实际值用万用表量出来的 #define R_TOP 100.0f #define R_BOTTOM 100.0f float readBatteryVoltage() { // 读取ADC原始值范围0~409512位 int raw analogRead(BATTERY_PIN); // ESP32 ADC参考电压约3.3V换算成电压 float adcVoltage (raw / 4095.0f) * 3.3f; // 考虑参考电压误差乘以修正系数实测调整 adcVoltage * 1.08f; // 分压还原实际电压 ADC电压 * (R_TOPR_BOTTOM)/R_BOTTOM return adcVoltage * ((R_TOP R_BOTTOM) / R_BOTTOM); } float readLux() { lightMeter.begin(); return lightMeter.readLightLevel(); } String buildStatusPayload(float voltage, float lux, bool lightOn) { JSONVar doc; doc[device_id] solar_001; doc[voltage] voltage; doc[battery] mapBatteryToPercent(voltage); // 电量百分比 doc[lux] lux; doc[light_on] lightOn ? 1 : 0; doc[timestamp] millis(); return JSON.stringify(doc); }这里的mapBatteryToPercent函数是电量估算的关键。18650锂电池的放电曲线不是线性的4.2V对应100%3.7V对应约50%3.3V基本就该断电了。我用的映射方式是分段线性拟合int mapBatteryToPercent(float voltage) { // 18650电池典型放电曲线近似 if (voltage 4.10f) return map(voltage, 4.10f, 4.20f, 90, 100); else if (voltage 3.90f) return map(voltage, 3.90f, 4.10f, 70, 90); else if (voltage 3.70f) return map(voltage, 3.70f, 3.90f, 50, 70); else if (voltage 3.50f) return map(voltage, 3.50f, 3.70f, 30, 50); else if (voltage 3.30f) return map(voltage, 3.30f, 3.50f, 10, 30); else return 0; }map函数把电压区间映射到百分比区间虽然精度没法跟库仑计相比但对路灯这种知道大概还剩多少电的场景足够用了。如果你想做得更准可以买MAX17048这类电量计芯片通过I2C直接读剩余电量不过成本会高十几块钱看项目预算取舍。主循环里的上报频率我设置了10秒一次这个频率在实时监控和功耗之间算是个折中。如果你做的是纯路灯其实30秒甚至60秒上报一次都可以但带了视频监控功能的话画面延迟会让人更焦虑所以我留了10秒这个档位。4.3 MQTT连接、掉线重连和回调处理MQTT这块是最容易出问题的地方我踩过不少坑。核心逻辑是连接WiFi成功后连接MQTT Broker在loop里调用mqtt.loop()维持心跳断线后自动重连收到订阅主题的消息时执行相应指令。#include WiFi.h #include PubSubClient.h const char* ssid 你的WiFi; const char* password 你的密码; const char* mqttServer broker.emqx.io; const int mqttPort 1883; const char* topicStatus solar/device/solar_001/status; const char* topicCommand solar/device/solar_001/command; WiFiClient espClient; PubSubClient mqtt(espClient); void setup() { // 省略WiFi连接代码... mqtt.setServer(mqttServer, mqttPort); mqtt.setCallback(receiveCommand); connectMQTT(); } void connectMQTT() { while (!mqtt.connected()) { String clientId ESP32Client- String(random(0xffff), HEX); if (mqtt.connect(clientId.c_str())) { Serial.println(MQTT connected); mqtt.subscribe(topicCommand); } else { Serial.print(MQTT failed, rc); Serial.println(mqtt.state()); delay(2000); } } } void receiveCommand(char* topic, byte* payload, unsigned int length) { String message ; for (int i 0; i length; i) { message (char)payload[i]; } Serial.println(Received: message); JSONVar command JSON.parse(message); if (JSON.typeof(command) undefined) { return; } if (command.hasOwnProperty(cmd)) { String cmd (const char*)command[cmd]; if (cmd toggle) { toggleLight(); } else if (cmd set_brightness) { int value (int)command[value]; setBrightness(value); } } } void loop() { if (!mqtt.connected()) { connectMQTT(); } mqtt.loop(); // 每10秒上报一次状态 static unsigned long lastReport 0; if (millis() - lastReport 10000) { float voltage readBatteryVoltage(); float lux readLux(); String payload buildStatusPayload(voltage, lux, lightState); mqtt.publish(topicStatus, payload.c_str()); lastReport millis(); } }这里我特别想说一下随机ClientID的重要性。公共MQTT Broker上如果大家都在用相同前缀的固定ID会互相踢下线。我见过一次很典型的调试事故两台设备用了相同ClientID结果A一上线B就被断开B重连后A又被踢掉两边的串口监视器日志轮流报错折腾了半小时才发现是ClientID撞了。所以连接时加一个随机后缀能避免这种隐身炸弹。4.4 省电策略太阳能供电下如何延长续航既然是太阳能供电省电就是绕不开的话题。ESP32在Deep Sleep模式下电流可以低到10μA左右但代价是没法通过MQTT实时接收消息只能定时唤醒。家用路灯通常不需要毫秒级响应所以我的策略是夜间模式每30秒唤醒一次采集光照和电压、上报状态、检查LED状态然后立即回到Deep Sleep。唤醒时间控制在1秒内占空比约3.3%。白天模式如果光照足够太阳能板在充电此时可以保持轻度Active每60秒上报一次但WiFi可以开启省电模式WiFi.setSleep(true)。LED调光夜深人静时比如23:00后可以把亮度自动降到30%减少功耗。Deep Sleep的唤醒方式我用的定时器唤醒代码很简单esp_sleep_enable_timer_wakeup(30 * 1000000); // 30秒唤醒 esp_deep_sleep_start();需要留意的是Deep Sleep唤醒后所有外设需要重新初始化所以setup()里的传感器初始化逻辑要写得幂等不能因为重复初始化而出错。另外GPIO34在深度睡眠期间的漏电流会经过分压电阻消耗电池虽然很小微安级但如果你要长期运行可以加一个MOS管做采样电路电源开关只在需要采样的时候才接通分压电阻那一路。这个省电策略做完之后纯电池3000mAh在阴天没太阳充电的情况下能撑大约3~5天有太阳正常充放的话基本能做到无限续航。路灯这个东西最怕的就是连续阴雨天电池耗尽所以电池容量选型时一定要按连续无光照天数来算。如果你要商用建议电池容量按3倍日均功耗来配。5. 无线视频监控部分OV2640接入和画面查看5.1 摄像头模块选择的两个方向无线视频监控是这个项目里最有高级感的功能也是最容易翻车的地方。先说选型方向一ESP32-CAM一体板MTD/OV2640自带配合官方的CameraWebServer示例十几分钟就能出一个通过浏览器查看画面的Demo。优点是集成度高、示例多适合快速验证缺点是GPIO几乎全被摄像头占用外接传感器和LED控制引脚不够用。方向二普通ESP32开发板外接OV2640模块。接线多一些但GPIO自由度高能同时兼顾路灯控制和视频监控。我最终选了方向二因为它更贴合一套系统干多件事的真实需求。需要提醒的是OV2640模块的排线非常脆弱插拔几次容易接触不良出现花屏、无数据等故障。如果你是首次做建议直接买焊好引脚的模块或者用杜邦线逐一连接时确认插紧。我一开始图省事买了裸模块结果有根SCCB线虚焊找了很久才发现是接触问题一度以为是库配错了。5.2 摄像头初始化参数配置用Arduino的esp_camera.h库配置逻辑很直接。关键参数如下#include esp_camera.h #define CAM_PIN_PWDN -1 // Power down-1表示不用 #define CAM_PIN_RESET -1 #define CAM_PIN_XCLK 33 #define CAM_PIN_SIOD 13 #define CAM_PIN_SIOC 14 #define CAM_PIN_D7 39 #define CAM_PIN_D6 36 #define CAM_PIN_D5 35 #define CAM_PIN_D4 34 #define CAM_PIN_D3 35 #define CAM_PIN_D2 37 #define CAM_PIN_D1 38 #define CAM_PIN_D0 32 #define CAM_PIN_VSYNC 25 #define CAM_PIN_HREF 26 #define CAM_PIN_PCLK 27 void setupCamera() { camera_config_t config; config.ledc_channel LEDC_CHANNEL_0; config.ledc_timer LEDC_TIMER_0; config.pin_d0 CAM_PIN_D0; config.pin_d1 CAM_PIN_D1; // ... 按上面引脚赋值 config.pin_sscb_sda CAM_PIN_SIOD; config.pin_sscb_scl CAM_PIN_SIOC; config.pin_vsync CAM_PIN_VSYNC; config.pin_href CAM_PIN_HREF; config.pin_pclk CAM_PIN_PCLK; config.xclk_freq_hz 20000000; config.ledc_timer LEDC_TIMER_0; config.pixel_format PIXFORMAT_JPEG; config.frame_size FRAMESIZE_VGA; // 640x480 config.jpeg_quality 12; config.fb_count 1; esp_err_t err esp_camera_init(config); if (err ! ESP_OK) { Serial.printf(Camera init failed with error 0x%x, err); } }这里有个关键设定像素格式一定要设为PIXFORMAT_JPEG因为JPEG格式可以直接通过HTTP流传输浏览器和APP都能直接解码。如果你设成RGB565数据量暴增WiFi带宽很容易跑满。画面尺寸我建议先用VGA640x480调通再考虑SVGA或者XGA因为分辨率越高PCLK频率越高对线材和供电的要求也越苛刻。5.3 视频流传输方案MJPEG over HTTPESP32跑视频流最常用的方式是MJPEG-over-HTTP也就是一帧一帧JPEG图片连续推流。虽然文件很大但好实现、兼容性好。我用的做法是起一个异步WebServer在/stream路径下循环抓帧并发送#include WebServer.h WebServer server(80); void handleJpgStream() { WiFiClient client server.client(); String response HTTP/1.1 200 OK\r\n; response Content-Type: multipart/x-mixed-replace; boundaryframe\r\n\r\n; client.print(response); while (true) { camera_fb_t *fb esp_camera_fb_get(); if (!fb) { continue; } client.print(--frame\r\n); client.print(Content-Type: image/jpeg\r\n\r\n); client.write(fb-buf, fb-len); client.print(\r\n); esp_camera_fb_return(fb); delay(30); } }这个方案的优点是代码量小任何浏览器直接访问http://设备IP/stream就能看到实时画面APP端如果用WebView也能直接嵌入。缺点是比较吃内存一帧VGA JPEG大概是50~100KBESP32空闲内存通常在150KB左右如果摄像头出帧太快而推送不及时内存会溢出导致重启。所以fb_count我设成1而不是2就是为了限帧。另外视频流和MQTT上报同时跑的时候WiFi负载会很高。实测下来视频流占用的吞吐大约1~2Mbps加上MQTT每10秒一包的小流量家用路由器扛得住但如果你用ESP32的AP模式设备自己开热点让手机直连带宽会非常紧张画面掉帧会很严重。所以视频流我建议走STA模式设备连路由器这样路由器做数据中转带宽充裕得多。5.4 监控和路灯控制的联动逻辑既然摄像头都装了就别只做看画面这种被动功能。我加了一个简单的联动逻辑当光照传感器读数低于某个阈值比如10lux且摄像头检测到画面变化可以用简单的帧差法就触发LED高亮并在APP端推送一条夜间检测到运动的通知。这里我没有上AI识别只是做了最基本的像素差检测因为ESP32的算力做不了复杂的模型推理。如果你想要人形检测建议改用ESP32-S3配合Edge Impulse训练轻量模型或者干脆把画面传到服务器端做AI识别。联动逻辑的代码结构大概是这样void checkMotionAndLight() { float lux readLux(); if (lux 10) { // 夜间检查画面变化 int motionScore getFrameDiffScore(); if (motionScore 300) { setBrightness(255); // 全亮 notifyMotionEvent(); } } }帧差法就是把当前帧和上一帧的灰度值做差统计差异像素数量。注意这个方案对光线变化很敏感比如云飘过、树叶晃动都会触发。所以我把阈值调高了一点宁可漏报也不误报——路灯触发是小事但APP通知弹得太频繁用户会直接把通知屏蔽掉那系统就废了。6. APP端设计远程查看、控制、电量显示的完整实现6.1 APP技术选型原生、WebView还是小程序APP是整套系统的门面用户每天看的都是它。市面上的方案有好几种Android原生Java/Kotlin开发功能最全、性能最好但开发周期长还得会Android SDK。WebView包壳用一套HTML5页面套个WebView壳开发快适合原型演示。微信小程序免安装适合现场演示但受微信平台限制部分硬件通信能力受限。跨平台框架Flutter/React Native一套代码双端跑但如果只给学生项目用引入框架的学习成本偏高。我这边用的是Android原生HTTP/MQTT双通道的方案理由很简单原生代码对MQTT的支持最好Eclipse Paho库直接集成不需要额外WebSocket桥接消息实时性有保障。如果你熟悉Web开发用Flutter也可以但前提是你得能吃下消息推送视频流播放按钮控制这三件套的跨平台适配工作。6.2 APP功能模块划分我把APP拆成了四个页面首页Dashboard展示当前光照强度、电池电压、电量百分比路灯开关状态用醒目的大按钮显示。监控页嵌入视频流用WebView直接加载http://设备IP/stream底部有抓拍按钮可以把当前帧保存到手机相册。控制页支持手动开关灯、亮度滑杆、定时策略设置比如晚上18:00自动开灯天亮自动关灯。设置页配置WiFi信息、MQTT服务器地址、设备ID等这个也可以放MQTT配置但最好在设备端完成然后启动时上报。6.3 核心交互MQTT订阅、指令下发、视频流播放MQTT这块用Eclipse Paho Android Client订阅status和alarm主题发布command主题。关键代码片段MqttClient client new MqttClient(brokerUrl, clientId, new MemoryPersistence()); MqttConnectOptions options new MqttConnectOptions(); options.setCleanSession(true); options.setConnectionTimeout(10); client.setCallback(new MqttCallback() { Override public void messageArrived(String topic, MqttMessage message) { String payload new String(message.getPayload()); if (topic.equals(solar/device/solar_001/status)) { updateDashboard(payload); // 解析JSON刷新UI } else if (topic.equals(solar/device/solar_001/alarm)) { showAlarmToast(payload); } } }); client.connect(options); client.subscribe(solar/device/solar_001/status); // 下发控制指令 JSONObject cmd new JSONObject(); cmd.put(cmd, set_brightness); cmd.put(value, 128); client.publish(solar/device/solar_001/command, new MqttMessage(cmd.toString().getBytes()));视频流播放用WebView加载http://设备IP/stream即可注意Android 9及以上默认禁止HTTP明文流量需要在Manifest里配置application android:usesCleartextTraffictrue ...或者在网络安全配置里给IP加白名单。这一步不做你会发现WebView白屏其他地方都正常特别坑。6.4 电量显示如何做得更直观电量百分比在APP里不是简单地显示一个数字我做了颜色分级和进度环电量70%显示绿色40%~70%显示黄色40%显示红色20%会额外弹出一条电池电量低请注意太阳能充电状态的提示。这不算多高深的设计但对用户来说感知非常明显。实际做的时候我建议把电压值和百分比同时显示电压是原始数据百分比是估算值两者对照才不容易让用户产生困惑。比如电池电压3.8V按我的映射函数算出来大约是57%用户看到这个数值才心里有底。7. 联调过程中的各种坑逐个排查与修复记录7.1 ESP32连接WiFi失败路由器信道和静态IP问题这个项目最开始的调试阶段ESP32死活连不上WiFi日志里反复打印Disconnected from AP。排查过程第一步确认路由器信道。ESP32的WiFi兼容性还可以但某些老路由器默认开了13信道而ESP32在某些地区固件里禁用了13信道导致扫描不到。解决办法是把路由器信道改成1、6或11。第二步确认DHCP分配。如果路由器开了AP隔离设备能扫描到SSID但分配不到IP。可以去路由器管理页把AP隔离关掉。第三步如果室外部署用手机热点调试热点名称里别带中文和特殊字符ESP32的WiFi库对UTF-8 SSID支持不好中文名经常连不上。如果你是因为IP经常变导致APP连不上设备建议在路由器里给ESP32固定静态IP或者直接用域名配合DDNS。家庭场景下我一般直接绑定MAC地址固定IP省事。7.2 MQTT正常上报但APP收不到订阅主题不匹配这种问题是最难查的因为设备端、Broker、APP三端看起来都正常。我的排查链路是这样的第一步用MQTTX或者Mosquitto命令行订阅#通配符主题看设备消息到底有没有到Broker。第二步检查APP订阅的主题是不是和设备发布的完全一致包括大小写、斜杠、device_id。我犯过的最蠢错误是设备端Topic里device_id是小写solar_001APP里写成了Solar_001MQTT主题是区分大小写的直接静默丢弃。第三步检查Broker的ACL权限。公共Broker一般没有ACL但如果你自建Mosquitto配置文件里没有allow_anonymous客户端连不上就是拒绝连接状态APP端会一直转圈。我还总结了一个经验开发期把所有设备订阅#只有到了联调后期才收紧到具体主题。这样能最快发现消息是否在传输链路中消失。7.3 电池电压读数漂移ADC参考电压误差和电源噪声ESP32内置ADC的参考电压其实不是精确的3.3V实际会漂移批次不同差别还挺大。我测过几块板子有的参考电压是3.28V有的是3.32V误差一算就到5%左右。解决办法是懒人校准法用万用表测电池真实电压再对比ESP32读出来的电压算出修正系数写死在代码里。比如真实4.05V读到3.978V系数就是1.018。如果你追求更高精度可以把ADC衰减模式配置成ADC_ATTEN_DB_0用外部精密基准电压源校准。电源噪声的问题主要发生在LED灯珠大功率PWM调光时。PWM开关瞬间电流突变会拉低电源电压导致ADC采样值跟着波动。我处理的办法是采样时间避开PWM输出阶段或者在PWM空闲时采样。在ADC引脚和地之间加一个100nF陶瓷电容滤掉高频毛刺。软件做多次采样取中位值而不是直接取一次读数。7.4 视频流卡顿和花屏供电不足和WiFi带宽视频流卡顿的根因十有八九是供电不足。ESP32摄像头模组在全速出帧时电流能飙到几百毫安如果LDO输出能力不够或者线材太细电压跌落会导致摄像头重新初始化表现出来就是画面闪断、花屏。排查办法用示波器或万用表测量ESP32的3.3V引脚观察抓帧瞬间的电压跌落幅度。如果没有示波器可以在抓帧循环里定期打印heap_caps_get_free_size(MALLOC_CAP_SPIRAM)和esp_get_free_heap_size()看看内存是否泄漏或不足。WiFi带宽方面如果路由器带机量大或者离设备太远视频流掉帧率会很高。我的建议是降低分辨率到VGA牺牲画质换流畅度。降低帧率不要追求30fps10fps对于路灯监控完全够用。如果必须高清换ESP32-S3或者外接IPC摄像头模块走RTSP协议不过这属于进阶改造了不在这次范围里。7.5 太阳能板充电效率低MPPT不是必需品但防反接是很多人上来就问要不要上MPPT最大功率点追踪。对这套小系统来说没必要——MPPT是给大功率太阳能系统用的多花几百块钱效果提升微乎其微。真正影响充电效率的反而是这些基础点太阳能板输出电压和充电IC输入电压范围是否匹配。TP4056的输入范围是4.5~8V所以如果用6V太阳能板标称电压是OK的但光照不足时电压会跌到充电IC的截止值以下导致充不进去。阴雨天充电效率极低是正常现象。太阳能板输出线不要太细我见过有人用杜邦线接太阳能板线损能吃掉20%的功率。建议用0.75平方以上硅胶线。TP4056的充电电流引脚PROG接的电阻决定充电电流我一般选1.2K电阻让充电电流约500mA这样在适配器供电和太阳能供电之间有个平衡。还有一个安全细节TP4056本身不带防反接如果你把太阳能板正负极接反IC会直接烧掉。所以一定要在太阳能板输出端串联一个肖特基二极管比如SS34做防反接和防倒灌别省这个几毛钱的元件。8. 现有系统的扩展方向从路灯到智慧杆做完这套基础系统后你会发现它的架构几乎能复用到所有远程终端控制项目里。这里聊几个我实际试验过的扩展方向多灯组网MQTT天然的发布订阅模型非常支持在一个Broker下管理几十台设备每台设备用唯一的device_id区分APP端做一个设备列表页就能管理整个路灯群。我还试过用ESP-NOW做路灯之间的Mesh通信这样即使部分路灯WiFi信号差也可以通过邻近路灯中继数据到Broker。这个方案的亮点是去中心化非常适合路灯沿着道路一字排开的场景。OTA远程升级ESP32支持HTTP OTA可以把固件放在服务器上APP端点击检查更新然后设备自动拉取固件升级。对路灯这种部署在室外的设备来说OTA几乎是刚需——你总不能爬电线杆拿数据线刷固件吧。我在项目里加了简单的版本号检查和升级接口虽然还比较粗糙但已经能解决改个bug不用跑现场的痛点。温湿度监测ESP32的GPIO上还可以挂DHT22或者SHT30监测路灯内部的环境温湿度防止电池在高温环境下过充或者满充时间过长。锂电池最佳工作温度是0~45℃超过这个范围就该降额充电甚至切断充电回路这个逻辑在代码里很好加。云平台对接如果你不想自己管MQTT Broker可以直接对接一些现成的物联网平台把设备数据通过HTTP API推上去然后在平台自带的Dashboards上画图表。这样APP开发量会大幅减少但牺牲的是定制化和数据的私有性。做毕设演示的话用平台自带的组件就足够撑场面了。我个人觉得这个项目真正的价值不在于灯亮不亮而在于它完整地培养了一遍硬件固件网络APP的端到端思维。接下来大部分物联网项目都逃不开这套骨架只是把太阳能板换成电池组、把路灯换成电机、把光照传感器换成温湿度传感器罢了。最后分享一个我自己反复踩的教训联调时一定先把最小闭环跑通——先把光照数据从传感器读到ESP32再走MQTT发到Broker然后用MQTTX订阅确认收到最后才连APP界面。每加一个模块就验证一次不要一口气把代码全写完再调试。这种小步快跑的方式能让问题定位精确到层不然全堆在一起报错都不知道是哪一环的锅。希望这篇内容对做类似项目的朋友有帮助有问题大家评论区交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑