资讯动态

咸鱼ESP32+MQTT实战:低成本搭建物联网远程控制

发布时间:2026/10/13 21:20:39 来源:尧图企业网站定制
“咸鱼ESP32实例—MQTT”这个标题我一开始看到就想笑——太贴切了。谁没在闲鱼上淘过几块“原厂烧录测试板”或者“拆机模块”我手里这块ESP32 DevKit就是某天刷闲鱼花十几块收来的板子边缘还带点撬痕但上电一点毛病没有。用最便宜的成本玩转物联网通信这就是“咸鱼精神”的精髓。这片博文我就拿这块“战损成色”的板子从头到尾走一遍MQTT的实战流程。从选板子、搭环境、讲明白MQTT到底是个什么逻辑到真正写代码、接线路、远程点灯再到把设备接入公网Broker实现手机远程控制。全程都是我一个人实际操作过的路径踩过的坑、得出的教训都写在里面。不管是刚摸ESP32的新手还是想搞明白MQTT具体怎么落地的小伙伴这篇都能给你省下不少试错时间。1. 为什么ESP32是“咸鱼玩家”的性价比天花板先聊点实在的搞技术归搞技术钱包不能受伤。市面上能跑MQTT的联网方案不少树莓派、各种Linux开发板、甚至手机都能实现但从成本、学习门槛、实际效果三个维度来讲ESP32几乎是最平衡的选择。在闲鱼上搜“ESP32”你会发现价格普遍在十几二十块浮动。这还不是最夸张的最离谱的是很多卖家把“ESP32-S2”、“ESP32-C3”也卖到这个价位这些芯片论性能和功能可一点不弱。我见过有人用二十块出头的价格收到一块带外壳和天线的“航海家”开发板那做工、那稳定性原价起码翻三倍。这种捡漏的快感是直接买全新开发板完全体会不到的。最关键的是这块板子的Wi-Fi蓝牙双模能力在物联网场景下几乎是标配。MQTT协议走的是TCP/IP栈ESP32自带的Wi-Fi功能天然就是为这个准备的。你在电脑上开个软件模拟也行但那种方式永远也体验不到“设备物理地在客厅里你躺在卧室用手机控制它”的真实感。另外ESP32这玩意儿功耗还低。我实测过在深度睡眠模式下电流能压到10微安左右配合电池供电做个温湿度传感器挂墙角半年不用换电池。这对于咸鱼垃圾佬来说简直是完美的硬件底座。而且它的GPIO口足够丰富I2C、SPI、UART、ADC、PWM齐全意味着你能外接的传感器和执行器几乎没有上限想搞点什么乱七八糟的自动化创意都能承载得了。所以我的结论很直接如果你想低成本地把一个设备接入物联网ESP32是这个价位上唯一值得认真玩的方案。剩下的问题就是怎么把MQTT这个通信协议在它上面跑起来。2. MQTT协议核心概念别被术语吓住其实就是“微信群聊”一开始接触MQTT大家容易被一堆术语砸晕Broker、Topic、Client、QoS、Retain……听起来很高级但实际上它的模型特别简单我用一个“微信群聊”的比喻给你讲明白。2.1 Broker就是群主Client就是群成员你的ESP32你的手机甚至你的电脑上跑的小程序全部都是“群成员”也就是Client。而MQTT Broker就是那个“群主”——它自己不生产消息不消费消息只负责把消息在成员之间传来传去。打个比方你和朋友都在一个微信群里你在群里说了一句“今晚吃火锅”其他人只要在看群就都收到了。MQTT里也一样一个Client往Broker上发了一条消息Broker就把这条消息转发给所有订了这个主题的其他Client。没有群主Broker群成员之间就没法沟通。所以整个架构就是设备们不直接互相连接而是全部连到一个中央服务器Broker上通过它来中转消息。这带来一个巨大的好处设备A不需要知道设备B的IP地址在哪里只需要知道往哪个“群”里发消息就行。这就是MQTT比设备直连HTTP通信更灵活的核心原因。2.2 Topic和Payload群聊里的“话题标签”和“消息内容”在群里面我们不能随便乱发消息得分话题。MQTT里这个话题就是Topic格式长这样/home/bedroom/led/status。这个Topic本质就是一个字符串有层级结构用斜杠分隔。订阅方可以对这个Topic进行通配符匹配比如我订阅home//led/status这个加号就表示“任意一层”。如果家里有 bedroom、livingroom、study三个房间我一口气就能收到所有房间LED的状态。这个通配符机制非常强大是MQTT节省流量的重要手段。Topic后面跟着的就是消息本身叫Payload。Payload可以是任何格式最常见的是纯文本、JSON、二进制数据。比如我们控制一个灯最简单的协议就是往/home/bedroom/led/set这个Topic发送一个ON的字符串如果做传感器上报就往/home/bedroom/temperature发一个 JSON{temp: 26.5, hum: 58}。2.3 QoS等级这消息重要到什么程度微信群聊有个问题如果群成员手机没联网消息基本就错过了。MQTT为了解决“错过的消息”设计了服务质量等级QoS。QoS 0发出去就完事了最多发一次不管对方收到没有。适合实时性传感器数据丢了拉倒下一条补上。QoS 1保证至少到达一次Broker会回一个ACK没收到ACK就重发。这样保证了送达但可能重复。QoS 2保证只到达一次用四次握手协议确保不重不漏。代价是开销大、速度慢。一般情况下我们用QoS 1就足够了。我刚入门的时候上来就全部用QoS 2结果发现又慢又占资源。后来才明白大部分场景QoS 1就够了。像控制灯这种操作就算消息重复发两次执行结果都是“开灯”重复不重复没区别真正该用QoS 2的是那种交易类、幂等性要求高的场景比如支付通知但普通智能家居基本碰不到。2.4 Retain标志群主帮你“记住”最后一条消息微信群里如果有人最后说了一句“明天放假”你晚上才进群就看不见了。Retain就是解决这个问题发布者发送消息时可以带上Retain标志Broker收到后会把这条消息存下来新的Client订阅这个Topic时Broker会立刻把这条保存的最后一条消息推给新订阅者。这个功能太适合智能家居了。我用它做设备状态同步ESP32上电后订阅状态Topic因为设了Retain能立刻知道上次设备断电时灯是开着还是关着从而保持状态一致性。否则每次设备重启灯就会回到默认的关闭状态但用户家里开关的物理状态可能却是“开”这就对不上了。2.5 Broker选型公网用云服务局域网用开源私有搞懂了这些概念就可以选Broker了。如果想让设备接入公网实现手机在外也能控制那你需要一个有公网地址的Broker。常见的有云服务商提供的Broker如各种物联网平台的公共实例。不过从省钱和可控的角度讲我更推荐自己搞定一台家里的旧笔记本、一个路由器就能跑一个开源Broker。最常用的私有Broker是Mosquitto这一轻量级软件直接装在树莓派或老电脑上就行。它本身就是开源的配置不算复杂跑起来稳如老狗。我自己家里就用一台N年前的老台式机装着Mosquitto再配合路由器的端口转发就能从外部访问到家里的设备。具体怎么做后面的章节我会详细演示。3. 开发环境与板子选型靠“白嫖”和“捡漏”搭建最小成本开发台3.1 挑一块能用的板子别买到古董型号闲鱼上搜“ESP32”会蹦出一堆型号最容易翻车的是买到“第一代ESP32”或者叫“ESP32 DevKit”。这代芯片支持Wi-Fi和蓝牙性能也够用但有一些驱动的坑烧录时偶尔要手动拉低GPIO0。我还是建议优先买ESP32-S2或者ESP32-S3特别是S3不仅原生支持USB烧录还带OTA升级和加密玩法更多。另一个坑是买到“ESP8266”冒充的。这俩名称看着像兄弟但芯片性能差着级别ESP8266没有蓝牙这对于很多场景是硬伤。购买时看清楚型号丝印ESP32芯片上肯定印着“ESP32”字样或者是“ESP32-D0WD”别只看板子印刷的字。我的捡漏经验是先搜“ESP32开发板”然后筛选“个人卖家”找那些“买了吃灰”“项目废案”之类的标题价格基本能打对折。这种板子多半只是被插过一次排针功能完全正常。收到后第一件事就是用万用表量一下3V3和GND有没有短路别直接上电这是硬件玩家的基本素养。3.2 Arduino IDE安装与ESP32库配置我始终推荐用Arduino IDE来入门ESP32理由三个一是生态成熟网上教程一搜一大把二是串口监视器用着舒服调试效率高三是代码逻辑简单写起来不像ESP-IDF那样又长又绕。安装的时候有个关键步骤打开“开发板管理器”在“附加开发板管理器网址”里填上乐鑫官方的json地址然后搜索ESP32安装核心库。核心库很大下载可能需要一会儿最好开个代理或者换个空闲时间段下载否则容易失败。装完库把开发板选成你的板子对应型号。我手里这块是“ESP32 Dev Module”选这个准没错。下一步就是点一下“工具”菜单里的“端口”确认你的板子被识别成COM口了。如果你收到的板子装了CP2102芯片电脑里一般会显示“Silicon Labs CP210x USB to UART Bridge”如果是CH340芯片就显示“CH340”字样。3.3 烧录前的“三秒检查法”每次给ESP32烧录前我都会用三秒检查法确认状态按一下板子上的RST复位键串口监视器里如果能看到一堆乱码或者“boot”开头的内容说明板子活着Bootloader没坏。如果完全没反应检查BOOT引脚是否悬空。烧录时如果显示“Failed to connect to ESP32”八成是USB线和驱动的问题。ESP32的USB口不是所有线都有数据传输功能我身边跑了五年开发都没人提过这个坑——充电线虽然看起来一样但里面可能根本没有数据线芯。换一根手机数据线就好经验之谈。4. 从零跑通一个MQTT远程开关灯项目纯干货步骤接下来是本篇的重头戏让ESP32连接你电脑上的MQTT Broker最终实现“手机/电脑发送指令板子上的LED灯响应开关”。我用的成本极低一块闲鱼收的ESP32一个面包板一个发光二极管LED一个220欧姆电阻几根杜邦线总成本不超过25块。4.1 硬件接线三根线就够把LED灯珠的长引脚正极接到ESP32的GPIO2口短引脚串一个220欧姆电阻后接到GND。就这么简单。选GPIO2是因为这块板子上GPIO2连接着一个板载蓝色的LED把它当外接灯的信号指示灯非常方便。很多教程里还习惯用GPIO2是因为ESP32的默认Boot模式约束了部分引脚GPIO2是个安全选择。换其他普通GPIO口同理只改代码里的引脚号就行。4.2 搭建本地Mosquitto BrokerWindows版实战记录为了方便调试先在电脑上把Mosquitto装好。下载Windows安装包后一路Next装完打开命令行工具进入Mosquitto的安装目录。启动它就一条命令mosquitto -v-v参数代表verbose模式会把所有客户端连接、订阅、发布的信息都打印出来调试时超级有用。装好之后打开另一个命令行窗口先订阅所有消息mosquitto_sub -t #这行命令的#是多级通配符代表订阅所有Topic。运行后别关这个窗口。再开一个窗口测试发布一条消息mosquitto_pub -t test/hello -m 咸鱼大法好回到订阅窗口你会看到这条消息“咸鱼大法好”立刻显示的。Broker就算是通了。4.3 完整代码解释连接、订阅、回调函数三件套现在写ESP32的代码。在Arduino IDE里新建一个工程粘贴下面这段代码。这是整个项目的主干每个函数我都写了说明#include WiFi.h #include PubSubClient.h // 修改成你自己的WiFi账号密码 const char* ssid 你的WiFi名; const char* password 你的WiFi密码; // Broker地址和端口 const char* mqtt_server 192.168.1.100; // 改成你电脑的局域网IP const int mqtt_port 1883; WiFiClient espClient; PubSubClient client(espClient); // 按钮引脚和LED引脚 const int ledPin 2; void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); setup_wifi(); client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); // 设置消息回调函数 // 重试连接服务器 if (client.connect(ESP32_Desk_Light)) { Serial.println(connected to MQTT Broker); client.subscribe(home/desk/led/set); // 订阅控制指令主题 } } void setup_wifi() { delay(10); Serial.println(Connecting to WiFi...); WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(WiFi connected); } // 回调函数当订阅的主题收到消息时会自动执行这个函数 void callback(char* topic, byte* message, unsigned int length) { Serial.print(Message arrived on topic: ); Serial.print(topic); String messageTemp; for (int i 0; i length; i) { messageTemp (char)message[i]; } Serial.println(messageTemp); // 如果收到 ON开灯收到 OFF关灯 if (String(topic) home/desk/led/set) { if (messageTemp ON) { digitalWrite(ledPin, HIGH); Serial.println(LED ON); } else if (messageTemp OFF) { digitalWrite(ledPin, LOW); Serial.println(LED OFF); } } } void loop() { // 如果断开了连接就重连 if (!client.connected()) { reconnect(); } client.loop(); // 让MQTT客户端保持活跃状态 } void reconnect() { while (!client.connected()) { Serial.print(Attempting MQTT connection...); // 尝试连接这个“ESP32_Desk_Light”是客户端的唯一ID if (client.connect(ESP32_Desk_Light)) { Serial.println(connected); client.subscribe(home/desk/led/set); } else { Serial.print(failed, rc); Serial.print(client.state()); Serial.println( try again in 5 seconds); delay(5000); } } }代码里几个注意点MQTT客户端的ID“ESP32_Desk_Light”必须唯一如果两个设备用同一个ID后连接的会顶掉先连接的这在生产环境是个严重问题。另外client.loop()必须在loop()里频繁调用否则收不到订阅消息。最后是WiFi重连机制这套代码在短时间断网时会自动尝试重连WiFi和MQTT但长期断网后WiFi重连逻辑需要稍作优化否则会卡死在WiFi.begin()里面。这个坑稍后细说。4.4 消息发送测试从命令行到手机App代码烧录完成后打开串口监视器波特率设为115200。如果一切正常你会看到板子成功打印出“WiFi connected”和“connected to MQTT Broker”。这个时候回到刚才订阅了#的Mosquitto命令行窗口执行mosquitto_pub -t home/desk/led/set -m ON一瞬间串口监视器显示“Message arrived”物理世界里的LED“啪”地亮了。再把消息换成OFF灯就灭了。从这一步开始你可以用任何MQTT客户端工具来发指令。手机上装的MQTT调试软件连上同一个WiFi填上Broker的IP发一条消息就能控制灯体验和命令行完全一样。很多人嘲讽智能家居是“伪需求”但当你躺在床上手里举着手机控制床头灯的那一刻那种“科技果然能让生活更好”的感觉还是特别真实。5. 把设备接入公网手机在户外也能控制家里设备局域网里玩得再花也就是自娱自乐。真正的物联网场景必须让手机在户外、在公司、在任何一个有网的地方都能控制网关里那块ESP32。方法有两种各有取舍。5.1 方案一路由器端口转发零成本但有风险这是最直接的方案。在路由器管理后台里设置端口转发Port Forwarding把公网的1883端口转发到家里运行Mosquitto那台电脑的内网IP上。这样外部设备就能通过路由器公网IP端口访问到家里的MQTT Broker了。但这方案有个致命问题1883端口走的是明文TCP数据包全网裸奔。MQTT里如果涉及敏感信息明文传输就是大筛子。更别说暴露公网端口的扫描攻击到处都是。所以我强烈不推荐直接暴露1883端口。如果非得用也要开启Mosquitto的TLS加密和账户认证也就是用8883端口做TLS通信。步骤麻烦而且路由器公网IP随时可能变今天能用明天可能就断了。所以这方案只适合临时调试不适合长期部署。5.2 方案二云服务器自建Broker推荐稳定可控我现在的部署方式是买了一台最低配置的云服务器装了个Ubuntu系统在上面安装Mosquitto Broker配好用户名密码认证然后52.xx.xx.xx:1883这个地址全局可用。装Mosquitto就三条命令sudo apt update sudo apt install mosquitto mosquitto-clients -y sudo systemctl enable mosquitto启用认证是必须的修改配置文件/etc/mosquitto/mosquitto.conflistener 1883 allow_anonymous false password_file /etc/mosquitto/passwd然后创建账号密码sudo mosquitto_passwd -c /etc/mosquitto/passwd 你的用户名重启服务配置生效。从此ESP32连接Broker时需要带上用户名密码了。代码里加上两句client.connect(ESP32_Desk_Light_2, mqtt_user, mqtt_pass);这就是最标准的做法。云服务器最低配置一年也花不了多少钱而且它不占用家里带宽24小时在线稳定可靠。我用了大半年重启次数屈指可数比家里路由器稳定多了。5.3 公网局域网双环境代码自适应换到公网Broker后我突然意识到一个需求局域网调试的时候不想依赖外网公网的时候又希望无缝切换。于是写了一套自适应逻辑ESP32上电后优先尝试连接内网Broker如果连不上再尝试连云端Broker并做一次主题重订阅。这题看似简单实际实现时需要注意MQTT的Broker切换是高频连接操作失败重连的退避时间要合理否则容易把Broker的并发连接数占满。我最终的策略是优先固定连接某个Broker当连接失败且失败次数超过3次时自动切换到备用Broker再定期切回首选。这部分代码我放在下面给你当个参考const char* mqtt_server_home 192.168.1.100; const char* mqtt_server_cloud your-cloud-ip; int broker_fail_cnt 0; bool use_cloud_broker false; String active_broker; void loop() { if (!client.connected()) { if (broker_fail_cnt 3) { use_cloud_broker !use_cloud_broker; // 切换Broker broker_fail_cnt 0; } reconnect(); } client.loop(); } void reconnect() { while (!client.connected()) { const char* target_server use_cloud_broker ? mqtt_server_cloud : mqtt_server_home; active_broker use_cloud_broker ? cloud : home; if (client.connect(ESP32_Desk_Light_3, mqtt_user, mqtt_pass)) { client.subscribe(home/desk/led/set); } else { broker_fail_cnt; delay(5000); } } }这逻辑撑起了我家里一个长期跑的传感器节点它在客厅角落默默上报温度局域网内直连家里的Broker偶尔家里网络抽风它自动切到云Broker服务一直没断过。这体验很“咸鱼”——不追求高大上的方案但求一个实用稳定。6. 跑通之后的那些坑实测中的翻车记录与修复每个项目不踩几个坑就说不过去。这个MQTT项目我也不是一次跑通的经历的这几个问题个个都有代表性。6.1 第一坑内存泄漏导致反复重启一开始我的代码里在回调函数中用String拼接消息每个消息处理后手工释放看起来没毛病。但跑了半天后发现板子每隔十几分钟就自动重启一次。查了半天才反应过来是PubSubClient库的消息接收缓冲区默认128字节长消息一覆盖缓冲区就崩了。解决办法很简单要么在库的头文件里搜索MQTT_MAX_PACKET_SIZE这个宏把它改成512或1024要么干脆别用String改用char数组手动解析。我现在是改到512对付传感器上报和简单指令绰绰有余。这类问题最阴险的地方在于——它不会立刻崩而是跑到一定状态才崩非常考验耐性。6.2 第二坑MQTT连接闪断QoS把Broker拖垮还有一次测试中我20个ESP32设备同时连Broker每隔5秒上报一次数据结果Broker的CPU占用一直100%。后来发现是我所有设备都用了QoS 1每个消息都要等ACK再发送一旦网络抖动重传、重传、再重传直接把Broker给压垮了。把传感器项改成QoS 0后CPU占用立刻降到10%出头。这教训很深刻高频率低价值数据就选QoS 0别什么都QoS 1代价也许是省下成倍的资源占用。设备数量一多这种“过度保障”的代价会被放大到非常恐怖的程度。6.3 第三坑WiFi断网后“假重连”这个坑前面提过具体场景是家里路由器半夜自动重启ESP32的WiFi连接断开但芯片里WiFi状态机卡住了一直认为自己在连接中。MQTT客户端自然也就断了而且没有触发重连逻辑。第二天早上我才发现灯怎么点都不亮了。修复方法是给loop()加一个WiFi状态检测unsigned long lastWiFiCheck 0; const unsigned long wifiCheckInterval 30000; // 30秒检查一次 void loop() { if (millis() - lastWiFiCheck wifiCheckInterval) { if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi lost, reconnecting...); WiFi.disconnect(true); WiFi.begin(ssid, password); } lastWiFiCheck millis(); } if (WiFi.status() WL_CONNECTED) { if (!client.connected()) { reconnect(); } client.loop(); } }核心思路是定期主动检查WiFi状态而不是被动等事件通知。这招对于所有长期运行的ESP32设备都适用。设备在线率从原来的99.4%提升到了99.9%那0.5%的差距就是时间长了才看得出价值。7. 向“咸鱼智能家居”进阶从单灯到全屋状态同步单灯控制只是热身MQTT真正香的地方在于天然的“主题分级”结构能让全屋设备井然有序。我的后续扩展方案供你参考。7.1 主题分层的实践把家里所有设备按“房间-设备-属性”的规则命名主题就能用通配符轻松构建总控逻辑home/bedroom/led/status home/bedroom/temperature home/livingroom/led/status home/livingroom/temperature home/gateway/online我使用MQTT Explorer这个工具订阅根主题home/#整个家里的设备状态就像一棵树一样展示在界面上哪里在线、温度多少、哪个灯亮着一目了然。这比用一堆手机App分开控制各种设备舒服得多——一整套逻辑都集中在一个树形视图里。7.2 物理按钮、传感器和状态回传单方向的“发指令控制”只是第一步真正好用的智能家居系统需要感知环境和反馈状态。我后来又给板子加了一个物理按键按下时发送“开灯”指令灯亮的同时ESP32把最新的状态发布到home/bedroom/led/status。手机端订阅这个状态主题就能实时显示当前灯是开是关。这就实现了一个双向闭环指令下发 - 设备执行 - 状态回传 - 控制端同步展示。你看着手机App上的开关状态永远不会和实物打架。7.3 报警器和定时器的复杂度再上一个台阶当MQTT用熟之后我开始在这个架构上叠加强大逻辑门口放一个人体红外传感器检测到有人移动就通过MQTT发消息给音箱模块——不是播放预设好的音频文件而是让音箱的语音合成模块直接播报“欢迎回家”。这个体验做完当天我兴奋了好一阵这种“自己搭出来的智能家居”比任何买来的现成方案都来得爽。还能利用MQTT的Retain消息做“状态保持”断电再上电灯自动恢复到断电前状态用WoL技术远程唤醒电脑再由电脑端脚本控制全屋设备……这些都是顺水推舟的事。8. 写给“咸鱼本鱼”的几条经验项目到这里主线已经全跑通了。最后分享几条我折腾MQTT项目所得的实在经验开干之前先拿纸画好主题树主题命名就是你的“数据库Schema”。一旦设备多了、消息杂了改主题名等于重构整个系统。一开始就规划好区域/设备/属性/操作的格式能省下后面无数麻烦。保留现场日志三天以上我跑了一个星期后就发现底层的MQTT消息记录是排查设备离线原因的最大功臣。Mosquitto可以配置日志轮转保留最近几天的完整日志出问题时直接翻日志找时间点比瞎猜效率高百倍。给设备加“心跳”每30秒往一个device/heartbeat主题上报一次在线心跳。一旦某设备超过两分钟没心跳用脚本发报警。这比单纯依赖MQTT的自动断线机制更早发现问题。别碰那些太花哨的协议有些设备支持CoAP、支持WebSocket甚至能直接上HTTP轮询。但除非你的项目有极其特殊的约束否则统一用MQTT作为主通信协议。生态完整、社区资料多、排错容易这套核心优势短期内难有对手。不要舍不得手里那一块吃灰的ESP32很多人的开发板买来就吃灰了原因多半是“不知道怎么玩”。其实一条MQTT命令发过去LED灯亮起来的那一刹那你心里那个“我也能搞物联网”的火苗就彻底点着了。一块十几块的板子就能让你跨进物联网的大门这笔买卖怎么看都划算。最后说一句公网的MQTT Broker一定要开密码认证。我开着匿名访问期间Broker日志里涌进来一大堆乱七八糟的设备来自全世界各地3分钟一次扫描端口、用未知Client ID订阅数据。别让你的设备裸奔在公网上这是所有“咸鱼”玩家的底线。

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

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

免费获取报价 →
↑