
一、背景ESP32 智能家居项目涉及多个传感器的数据采集和云端通信代码迭代非常频繁。每次更新固件都要用 USB 线连接开发板拔插、选端口、烧录实在太麻烦了。于是决定把 OTAOver-The-Air空中升级功能加上实现无线固件更新。按理说 Arduino 的 OTA 功能很成熟几行代码就能搞定。但实际上我在这条路上踩了好几个坑花了两天才彻底打通。这篇文章记录整个排查过程希望帮你省点时间。二、环境说明项目配置开发板TinkerNode NB-IoT核心库版本1.0.8基于 ESP32 2.0.xIDEArduino 1.8.19OTA 库ArduinoOTA核心库自带电脑系统Windows 11三、具体问题使用IDE自带的示例程序进行验证#include WiFi.h #include ESPmDNS.h #include WiFiUdp.h #include ArduinoOTA.h const char* ssid ..........; const char* password ..........; void setup() { Serial.begin(115200); Serial.println(Booting); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() ! WL_CONNECTED) { Serial.println(Connection Failed! Rebooting...); delay(5000); ESP.restart(); } ArduinoOTA .onStart([]() { String type; if (ArduinoOTA.getCommand() U_FLASH) type sketch; else // U_SPIFFS type filesystem; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println(Start updating type); }) .onEnd([]() { Serial.println(\nEnd); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf(Progress: %u%%\r, (progress / (total / 100))); }) .onError([](ota_error_t error) { Serial.printf(Error[%u]: , error); if (error OTA_AUTH_ERROR) Serial.println(Auth Failed); else if (error OTA_BEGIN_ERROR) Serial.println(Begin Failed); else if (error OTA_CONNECT_ERROR) Serial.println(Connect Failed); else if (error OTA_RECEIVE_ERROR) Serial.println(Receive Failed); else if (error OTA_END_ERROR) Serial.println(End Failed); }); ArduinoOTA.begin(); Serial.println(Ready); Serial.print(IP address: ); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); }第一步USB烧录OTA示例代码一切正常。第二步拔掉USBArduino IDE切换为网络端口xxx.xxx.x.xxx。第三步修改代码比如加个版本号打印点击上传。预期结果上传成功实际结果就卡在这里反复试了十几次同一行报错。能看到设备能认证通过但就是传不上去。四、问题排查把常规方向都排查了一遍代码没问题用的官方示例网络没问题能 ping 通能发现设备防火墙关了端口没被占用都没问题开始抓包用 Wireshark 监听 OTA 通信过程红色方框为电脑端绿色方框为开发板端。从截图可以清楚看到UDP发现阶段正常ESP32成功广播了自己的存在但进入TCP握手阶段后电脑没有回复ESP32发来的SYN包。通俗点说ESP32主动发起连接请求三次握手但电脑那边像没听见一样完全不搭理。更诡异的是我多次抓包后发现TCP传输时ESP32用的是随机端口而不是固定端口。这意味着无法判断电脑端是否在对应端口上正常监听排查一度陷入僵局。上ESP32 OTA官方文档查资料时发现官方推荐用的工具是espota.py而Arduino IDE默认调用的OTA上传工具是TinkerNode核心库自带的espota.exe。这两个工具名字相似但前者是Python脚本后者是独立可执行文件。尝试在命令行手动调用espota.py上传固件.bin文件——一次成功。再去Wireshark里看这次UDP发现和TCP握手都正常了。看来问题就出在espota.exe这个工具上。五、密码认证失败第一次调试时没设密码直接用了默认无密码模式。项目正式用肯定要加密码在代码里添加ArduinoOTA.setPassword(123456);然后重新上传结果密码明明是对的但就是验证不过去一直显示密码错误。WiFi连接、IP地址也都检查过没问题。同样的环境不加密码就通加了密码就失败——问题只能出在密码认证这个环节。查看ArduinoOTA源码ArduinoOTA.cpp发现setPassword()底层把明文密码转成了MD5哈希值存储——也就是说开发板端存的是密码的MD5而不是密码本身。而espota.py默认走的是新安全协议与旧版核心库的MD5协议不兼容。两边对不上自然验证失败。ArduinoOTAClass ArduinoOTAClass::setPassword(const char * password) { if (!_initialized !_password.length() password) { MD5Builder passmd5; passmd5.begin(); passmd5.add(password); passmd5.calculate(); _password passmd5.toString(); // 存的是 MD5 哈希值 } return *this; }解决方案查了下espota.py的参数说明发现有一个-m参数可以强制走MD5协议加了-m参数后密码认证通过上传成功。六、一劳永逸每次都敲命令行太麻烦直接修改 Arduino IDE 的配置文件。找到platform.txtC:\Users\你的用户名\...\Arduino15\packages\TinkerNode_NB-IoT\hardware\esp32\1.0.8\platform.txt不同安装路径可能不同按实际情况来修改upload.network_pattern这一行原本是tools.esptool_py.upload.network_pattern{network_cmd} -i {serial.port} -p {network.port} --auth{network.password} -f {build.path}/{build.project_name}.bin替换为tools.esptool_py.upload.network_patternC:\Users\86136\AppData\Local\Programs\Python\Python312\python.exe {runtime.platform.path}/tools/espota.py -i {serial.port} -p {network.port} --auth{network.password} -f {build.path}/{build.project_name}.bin -m保存后重启 Arduino IDE点击上传直接走网络端口一键无线升级。七、总结踩坑清单问题原因解决OTA 上传无响应espota.exeTCP 服务端启动失败换用espota.py密码认证失败新工具默认用新协议核心库用 MD5加-m强制 MD5每次都敲命令行麻烦IDE 配置未修改修改platform.txt相关资源ArduinoOTA 源码ESP32 OTA 官方文档espota.py 工具说明