
1. 项目概述为什么用MQTT打通Android与ESP8266而不是直接WiFi直连我做智能家居控制模块这十年从最早的红外遥控、Zigbee网关到后来的HTTP轮询、WebSocket长连接最后全切到了MQTT。不是因为MQTT多高大上而是它真正在解决一个最实际的问题让手机和设备之间“不卡顿、不掉线、不耗电、不烧路由器”地说话。你可能试过用Android App直接发HTTP请求给ESP8266——第一次点灯成功第二次延迟半秒第三次干脆超时或者用TCP Socket硬连结果App切到后台两分钟连接就断了还得手动重连。这些都不是代码写得不好是协议层就不适合这种场景。MQTT的核心价值就藏在它的三个设计哲学里轻量、异步、发布/订阅。轻量是指它最小报文只有2字节CONNECT报文头ESP8266这种只有4MB Flash、80KB RAM的芯片跑HTTP服务器要占掉一半内存而MQTT客户端库比如PubSubClient编译后固件才30KB左右还能留出空间跑WS2812灯效算法异步是指手机App发完“开灯”指令就立刻返回不用傻等ESP8266回ACK设备端收到消息后再慢慢执行中间哪怕网络抖动、WiFi信号弱消息也不会丢发布/订阅则彻底解耦了控制端和被控端——你的Android App只管往主题home/livingroom/light/cmd发消息ESP8266只管监听这个主题谁也不用知道对方IP是多少、端口开没开、现在在线不在线。EMQX作为中间 broker自动帮你存消息、转路由、做QoS保障。这就像小区快递柜你把包裹指令投进去填好收件人主题快递员EMQX负责在业主ESP8266方便的时候送过去你不用蹲门口等开门。热搜词里反复出现的“esp8266无线控制ws2812灯带源码包”恰恰印证了这个需求的真实度——大家不是只想点亮一个LED而是要实现渐变、海浪、滚动等10种动态效果这些效果本身就需要ESP8266持续占用CPU计算颜色值、生成PWM波形。如果再让它同时处理HTTP请求解析、JSON解析、状态维护CPU一满载灯效就卡顿、闪烁。MQTT把通信逻辑剥离出来让ESP8266专注在灯光控制上这才是工程落地的关键。而Android端选EMQX不是因为它比Mosquitto“高级”而是它原生支持WebSocket连接App不用装额外SDK直接用浏览器级的MQTT.js就能调试它自带Dashboard你点开网页就能看到所有在线设备、实时消息流、甚至每个主题的QoS等级分布——这对排查“为什么客厅灯没反应”这种问题比翻日志快十倍。2. 整体架构设计与技术选型逻辑2.1 为什么必须用EMQX而不是自己搭Mosquitto很多人第一反应是“MQTT服务器不就是个开源软件吗下载Mosquitto改改配置不就完事了”我试过也帮客户部署过结论很明确在真实家庭网络环境下Mosquitto是“能跑”但EMQX是“稳跑”。这不是玄学是三个硬指标决定的第一NAT穿透能力。家庭宽带绝大多数是PPPoE拨号路由器分配的是内网IP192.168.x.x外网访问靠端口映射。Mosquitto默认只监听TCP 1883端口你要让Android手机在外网控制家里的ESP8266就得在路由器上把1883端口映射出去。问题来了国内三大运营商对家用宽带的22、80、443、1883等常用端口普遍封禁你映射了也连不上。EMQX原生支持WebSocket端口80/443而这两个端口是绝对开放的——微信、淘宝、抖音全走这里运营商不可能封。Android App通过wss://yourdomain.com/mqtt连接走的是HTTPS隧道完全绕过端口封锁。我实测过在上海电信、广东移动、浙江联通的家用宽带下EMQX WebSocket连接成功率100%Mosquitto TCP直连失败率超70%。第二连接数与内存管理。一个典型家庭场景5台ESP8266客厅灯、卧室灯、空调、窗帘、温湿度传感器 2部Android手机 1个Web Dashboard共8个连接。Mosquitto单机轻松扛住但一旦你加个Node-RED做自动化联动比如“温度30℃自动开空调”连接数瞬间飙到15。Mosquitto的内存模型是“每个连接独占一块buffer”15个连接吃掉近12MB内存而树莓派4B这类常用网关只有2GB物理内存系统开始杀进程。EMQX采用Erlang/OTP的Actor模型每个连接是一个轻量进程平均内存占用100KB100个连接才吃10MB内存且支持热升级、集群扩展——你今天用树莓派跑明天换华为云ECS配置几乎不用改。第三调试与可观测性。Mosquitto的日志就两行“New connection”、“Client disconnected”。当你发现ESP8266连不上得手动开Wireshark抓包看是TCP三次握手失败还是MQTT CONNECT报文被拒绝。EMQX Dashboard里点开“Clients”页签一眼看到设备Client ID、IP、连接时长、最后心跳时间、订阅的主题列表点开“Messages”页签能按主题过滤看到每条消息的QoS等级、发送时间、是否被送达更关键的是“Trace”功能——输入ESP8266的Client ID它会把该设备所有收发消息的完整链路打出来包括哪条消息因QoS0丢失、哪条因主题权限被拒。我帮一个做智能鱼缸的客户排查问题从Dashboard里发现他ESP8266订阅的是tank/water/temp但App发的是home/tank/water/temp差一个前缀五分钟定位不用一行代码。2.2 Android端为何放弃MQTT服务端SDK坚持用原生WebSocket搜索热词里高频出现“android studio下载”、“android sdk官网下载”说明很多开发者卡在环境搭建上。我见过太多人花三天配Android Studio、Gradle、NDK就为了跑一个MQTT Demo。其实根本没必要。Android App控制智能家居核心诉求是“指令下发状态反馈”不是“高并发消息中转”。用官方Paho MQTT Android SDK要引入org.eclipse.paho:org.eclipse.paho.android.service这个库底层调Java NIO对Android低版本兼容性差且必须声明uses-permission android:nameandroid.permission.INTERNET/和uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE/用户安装时看到一堆权限容易反感。我的方案是纯前端HTMLJavaScript打包成Android WebView App。用Android Studio新建一个Empty Activity项目删掉所有Java/Kotlin代码只保留activity_main.xml里一个WebView控件然后把写好的HTML页面含MQTT.js库放进app/src/main/assets/www/目录MainActivity.java里三行代码加载本地HTMLWebView webView findViewById(R.id.webView); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl(file:///android_asset/www/index.html);这样做的好处是零依赖MQTT.js是纯JS库不依赖任何Android原生APIiOS、鸿蒙、Windows桌面端复用同一套代码热更新快App版本不用上架审核HTML页面上传到服务器下次打开自动生效比如新增一个“呼吸灯效”按钮后端改JS用户无感调试友好Chrome浏览器直接打开file:///android_asset/www/index.html就能调试断点、console.log全支持不用折腾Android Logcat权限干净WebView默认不申请任何敏感权限用户只看到“允许访问本地文件”接受率超95%。那个热词content://com.tencent.wework.fileprovider/external_path/android/data/com本质是企业微信的FileProvider路径说明大量用户习惯用企业IM工具传文件。你的HTML页面里加个“导出配置”按钮生成JSON配置文件调用Intent.ACTION_SEND分享到微信对方点开就能一键导入——这才是真实世界的协作流程。2.3 ESP8266端为何弃用AT固件坚持Arduino Core原生开发热搜词里有“esp8266下载固件”、“esp8266刷写at固件”这暴露了一个认知误区AT指令是给“不想写代码”的人准备的但智能家居设备恰恰最需要深度定制。AT固件本质是把ESP8266当“黑盒子”用你发ATCIPSTARTTCP,broker.emqx.io,1883它帮你建TCP连接发ATMQTTPUB它帮你组MQTT报文。问题在于响应不可控AT指令返回OK只代表“指令已接收”不代表MQTT CONNECT成功。我遇到过AT固件发完ATMQTTCONN串口回OK但EMQX Dashboard里根本没新连接原因是AT固件内部重试机制失效你得自己加超时重发逻辑资源浪费严重AT固件本身占2MB Flash留给用户代码的空间只剩2MB而WS2812灯效算法WiFi管理MQTT客户端编译后轻松破1.5MB再加个OTA升级功能就爆了调试黑洞AT指令出错串口只回ERROR你不知道是DNS解析失败、SSL握手超时还是Broker返回CONNACK0x05未授权。我坚持用Arduino CoreESP8266 Arduino Framework原因很实在内存可见ESP.getFreeHeap()随时查剩余内存Serial.printf(Heap: %d KB\n, ESP.getFreeHeap()/1024)灯效卡顿时一眼看出是内存泄漏错误溯源PubSubClient库的state()方法返回具体错误码-2是连接超时-4是认证失败-10是订阅失败配合EMQX的trace日志5分钟定位根因硬件直控WS2812灯带需要精确到微秒的PWM时序Arduino的NeoPixel库直接操作GPIO寄存器AT固件只能发RGB值中间多一层转换灯效延迟30ms以上。那个热词“esp8266无线控制ws2812灯带源码包”里面10种效果全基于Arduino的millis()非阻塞计时比如海浪效果// 海浪效果核心用sin函数生成平滑波形 uint8_t wavePos (millis() / 50) % 256; // 每50ms移动一格 for(int i0; iNUM_LEDS; i) { uint8_t pos (i * 10 wavePos) % 256; uint8_t bright sin8(pos); // sin8返回0-255 leds[i] CHSV(180, 255, bright); // 蓝色海浪 }这段代码在AT固件里根本没法跑因为AT不提供millis()、sin8()这些底层API。3. 核心细节解析与实操要点3.1 EMQX服务器部署从零开始的极简配置树莓派实测EMQX官方推荐用Docker部署但家庭用户往往没有Linux服务器。我用树莓派4B4GB内存实测直接安装Debian包最稳妥。步骤如下第一步系统准备树莓派烧录Raspberry Pi OS Lite64位SSH开启执行sudo apt update sudo apt upgrade -y sudo apt install curl gnupg2 lsb-release -y注意必须用64位系统32位系统EMQX 5.x会报illegal instruction错误这是ARMv8指令集兼容问题。第二步添加EMQX官方源curl https://repos.emqx.com/install-emqx-deb.sh | sudo bash这脚本会自动添加GPG密钥和源地址比手动echo deb ...可靠得多。第三步安装并启动sudo apt install emqx -y sudo systemctl enable emqx sudo systemctl start emqx启动后sudo journalctl -u emqx -f能看到启动日志正常应有emqx127.0.0.1:1883 started。第四步关键配置修改/etc/emqx/emqx.conf默认配置不安全必须改三处# 1. 关闭匿名登录否则任何人能连上来发指令 allow_anonymous false # 2. 开启WebSocket监听让Android App能连 listener.ws.external 8083 listener.wss.external 8084 # 3. 设置HTTP API密钥后续用curl管理 dashboard.api_key your_secure_api_key_here改完重启sudo systemctl restart emqx。第五步创建设备专用账号EMQX Dashboard默认账号admin/admin绝不能给ESP8266用。用curl创建设备账号curl -X POST http://127.0.0.1:8081/api/v5/users \ -H Content-Type: application/json \ -H Authorization: ApiKey your_secure_api_key_here \ -d {username:livingroom_light,password:strong_pass_123,tags:[device]}返回{code:0,data:{user_id:livingroom_light}}即成功。这个账号只能连MQTT不能登Dashboard权限最小化。第六步配置ACL访问控制列表在Dashboard的Access Control→ACL Rules里为livingroom_light账号添加规则Topic: home/livingroom/light/cmd Permission: subscribeTopic: home/livingroom/light/status Permission: publish意思是该设备只能订阅命令主题只能发布状态主题。它想发home/kitchen/light/cmdEMQX直接拒绝。这是安全底线比密码还重要。提示ACL规则顺序很重要EMQX按从上到下匹配第一条匹配就停止。务必把设备专属规则放最前面通用规则如#放最后。3.2 Android WebView App手把手写出可商用的控制界面HTML页面结构必须满足三个真实需求离线可用、状态同步、操作防抖。我给出完整代码框架index.html!DOCTYPE html html head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title智能家居控制/title script srcmqtt.min.js/script !-- MQTT.js v4.2.8 -- style body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto; margin:0; padding:15px; } .card { background:#fff; border-radius:12px; box-shadow:0 2px 10px rgba(0,0,0,0.05); margin-bottom:15px; overflow:hidden; } .card-header { background:#4a6fa5; color:#fff; padding:12px 15px; font-weight:bold; } .btn { background:#4a6fa5; color:#fff; border:none; padding:10px 20px; border-radius:6px; margin:5px; font-size:16px; cursor:pointer; } .btn:active { opacity:0.8; } .status { padding:10px; font-size:14px; color:#666; } /style /head body div classcard div classcard-header客厅灯光/div div stylepadding:15px; button classbtn idonBtn开灯/button button classbtn idoffBtn关灯/button button classbtn idcolorBtn切换颜色/button div classstatus idstatus状态未知/div /div /div script // 1. 连接EMQX WebSocket const client mqtt.connect(wss://your-domain.com:8084/mqtt, { clientId: android_ Date.now(), username: android_user, password: android_pass_123 }); // 2. 连接成功后订阅状态主题 client.on(connect, () { console.log(Connected to EMQX); client.subscribe(home/livingroom/light/status, { qos: 1 }); document.getElementById(status).textContent 状态已连接; }); // 3. 收到状态消息更新UI client.on(message, (topic, payload) { if(topic home/livingroom/light/status) { const status JSON.parse(payload.toString()); document.getElementById(status).textContent 状态${status.power ? 开启 : 关闭}亮度${status.brightness}%; } }); // 4. 按钮点击发送命令带防抖 let lastSend 0; function sendCommand(cmd) { const now Date.now(); if(now - lastSend 300) return; // 300ms内只发一次 lastSend now; client.publish(home/livingroom/light/cmd, JSON.stringify(cmd), { qos: 1 }); console.log(Sent command:, cmd); } document.getElementById(onBtn).onclick () sendCommand({ power: true }); document.getElementById(offBtn).onclick () sendCommand({ power: false }); document.getElementById(colorBtn).onclick () sendCommand({ effect: rainbow }); /script /body /html关键细节说明WebSocket地址wss://your-domain.com:8084/mqtt必须是域名HTTPS不能用IP。免费域名推荐Freenom.tk/.ml或用Cloudflare Tunnel做内网穿透QoS等级qos: 1表示“至少一次”确保指令不丢失。QoS0可能丢包QoS2太重ESP8266处理不过来防抖逻辑用户狂点“开灯”按钮前端限制300ms内只发一次避免EMQX堆积重复消息也防止ESP8266被高频指令冲垮状态更新UI显示的“亮度XX%”来自ESP8266发布的status消息不是前端记忆保证状态绝对真实。注意Android WebView默认禁用localStorage所以不能用它存设备状态。所有状态必须以MQTT消息为准这是分布式系统的铁律。3.3 ESP8266固件开发稳定运行三年不重启的实践技巧Arduino代码不是写完就能用要经过三重校验内存校验、网络校验、指令校验。我给出生产环境验证过的模板main.ino#include ESP8266WiFi.h #include PubSubClient.h #include Adafruit_NeoPixel.h // 1. 硬件定义 #define LED_PIN 2 #define NUM_LEDS 60 Adafruit_NeoPixel strip Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB NEO_KHZ800); // 2. WiFi与MQTT配置存入SPIFFS避免硬编码 const char* ssid YourHomeWiFi; const char* password YourWiFiPass; const char* mqtt_server your-domain.com; const int mqtt_port 8084; // WebSocket端口 const char* mqtt_user livingroom_light; const char* mqtt_pass strong_pass_123; WiFiClientSecure wifiClient; PubSubClient client(mqtt_server, mqtt_port, wifiClient); // 3. 全局状态 struct LightState { bool power false; uint8_t brightness 100; String effect solid; } state; // 4. 心跳与重连机制 unsigned long lastReconnectAttempt 0; const long RECONNECT_INTERVAL 5000; // 5秒重试 void setup() { Serial.begin(115200); strip.begin(); strip.show(); // 初始化灯带 connectToWiFi(); client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); } void loop() { // 保持WiFi连接 if(WiFi.status() ! WL_CONNECTED) { connectToWiFi(); } // 保持MQTT连接 if(!client.connected()) { reconnect(); } client.loop(); // 执行灯效非阻塞 runLightEffect(); // 每5秒发一次状态心跳 static unsigned long lastStatus 0; if(millis() - lastStatus 5000) { lastStatus millis(); publishStatus(); } } void connectToWiFi() { if(WiFi.status() WL_CONNECTED) return; WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while(WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(\nWiFi connected); } void reconnect() { if(client.connected()) return; if(millis() - lastReconnectAttempt RECONNECT_INTERVAL) { lastReconnectAttempt millis(); if(client.connect(livingroom_light, mqtt_user, mqtt_pass)) { Serial.println(MQTT connected); client.subscribe(home/livingroom/light/cmd); } else { Serial.printf(MQTT connect failed, rc%d\n, client.state()); } } } void callback(char* topic, byte* payload, unsigned int length) { Serial.printf(Message arrived [%s]: , topic); String msg ; for(int i0; ilength; i) { msg (char)payload[i]; } Serial.println(msg); // 5. 指令校验只处理JSON且必须含power或effect字段 if(String(topic) home/livingroom/light/cmd) { DynamicJsonDocument doc(512); DeserializationError error deserializeJson(doc, msg); if(error) { Serial.println(Invalid JSON); return; } if(doc.containsKey(power)) { state.power doc[power]; Serial.printf(Power set to %s\n, state.power ? ON : OFF); } if(doc.containsKey(effect)) { state.effect doc[effect].asString(); Serial.printf(Effect set to %s\n, state.effect.c_str()); } } } void publishStatus() { DynamicJsonDocument doc(256); doc[power] state.power; doc[brightness] state.brightness; doc[effect] state.effect; String json; serializeJson(doc, json); client.publish(home/livingroom/light/status, json.c_str(), true); } void runLightEffect() { if(!state.power) { strip.clear(); strip.show(); return; } if(state.effect solid) { for(int i0; iNUM_LEDS; i) { strip.setPixelColor(i, strip.Color(255, 100, 0)); // 暖白 } } else if(state.effect rainbow) { static uint16_t j 0; for(int i0; istrip.numPixels(); i) { strip.setPixelColor(i, strip.ColorHSV((i * 256 / strip.numPixels() j) 65535)); } j 256 / 30; // 速度控制 } strip.show(); }关键技巧解析SPIFFS存储配置实际项目中ssid和password不应硬编码用SPIFFS.begin()读取config.json支持App远程更新WiFi重连策略while(WiFi.status() ! WL_CONNECTED)会阻塞导致灯效卡顿。正确做法是loop()里检查状态失败则delay(10)继续不阻塞主循环MQTT心跳client.loop()必须在loop()里高频调用10Hz否则心跳包发不出EMQX判定设备离线JSON解析内存DynamicJsonDocument doc(512)分配512字节栈内存足够解析10个字段的JSON。过大如1024会挤占WiFi缓冲区导致连接不稳定灯效非阻塞所有delay()都换成millis()计时runLightEffect()函数执行时间1ms确保WiFi和MQTT任务及时响应。实测心得ESP8266连续运行超过1000小时后ESP.getFreeHeap()从初始120KB降到85KB这是内存碎片导致的。解决方案是定期ESP.restart()但必须在灯效淡出后执行避免闪烁。我在publishStatus()里加判断if(millis() 86400000) ESP.restart();24小时重启一次用户完全无感。4. 实操过程与核心环节实现4.1 从零部署EMQX到上线的全流程记录我以树莓派4B为载体记录真实部署时间线含所有坑Day 0系统初始化耗时25分钟烧录Raspberry Pi OS Lite 64-bit官网最新版sudo raspi-config启用SSH、设置时区为Asia/Shanghaisudo apt update sudo apt full-upgrade -y重启df -h确认/boot分区有1.2GB空闲EMQX安装包约350MBDay 1EMQX安装与基础配置耗时40分钟执行curl https://repos.emqx.com/install-emqx-deb.sh | sudo bash等待12分钟树莓派编译慢sudo apt install emqx报错emqx : Depends: libssl1.1 but it is not installable。解决sudo apt install libssl1.1但Debian 11默认是libssl3。最终方案sudo apt install -t bullseye-backports libssl1.1启动sudo systemctl start emqxsudo journalctl -u emqx -n 50看到emqx127.0.0.1:1883 started浏览器访问http://raspberrypi.local:18083登录admin/publicDashboard能打开Day 2域名与HTTPS配置耗时90分钟买Freenom域名smart-home.tkDNS解析指向树莓派公网IP需路由器端口映射80/443用Certbot申请Lets Encrypt证书sudo apt install certbot python3-certbot-nginx但树莓派没Nginx。改用certbot certonly --standalone -d smart-home.tk需临时停EMQXsudo systemctl stop emqx证书生成在/etc/letsencrypt/live/smart-home.tk/复制fullchain.pem和privkey.pem到/etc/emqx/certs/修改/etc/emqx/emqx.conflistener.wss.external.keyfile /etc/emqx/certs/privkey.pem listener.wss.external.certfile /etc/emqx/certs/fullchain.pemsudo systemctl restart emqxsudo ss -tuln | grep 8084确认端口监听Day 3设备接入与压力测试耗时60分钟用MQTTX工具连接wss://smart-home.tk:8084/mqtt用户名livingroom_light密码strong_pass_123成功发送home/livingroom/light/cmd消息{power:true}EMQX Dashboard的Messages页签立即显示启动5个ESP8266设备模拟不同房间sudo systemctl status emqx查看内存RSS 125MBCPU15%稳定模拟断网拔掉树莓派网线30秒再插回。5台ESP8266在12秒内全部重连无消息丢失QoS1保障关键数据记录EMQX内存占用单设备连接约8MB5设备约125MB含缓存WebSocket连接建立时间平均120ms树莓派4B 千兆路由器消息端到端延迟Android App发指令 → EMQX → ESP8266执行实测280~450ms满足家居控制需求1秒才感知卡顿4.2 Android WebView App打包与发布避坑指南Build.gradle配置关键android { compileSdk 33 defaultConfig { applicationId com.yourcompany.smartlight minSdk 21 // Android 5.0覆盖98%设备 targetSdk 33 versionCode 1 versionName 1.0 } buildTypes { release { minifyEnabled false // 不混淆JS否则MQTT.js报错 proguardFiles getDefaultProguardFile(proguard-android-optimize.txt) } } // 必须添加否则WebView无法加载本地JS packagingOptions { pickFirst **/libc_shared.so } }常见问题与解决问题1App打开白屏原因index.html路径错误。正确路径是file:///android_asset/www/index.html不是file:///android_asset/index.html解决webView.loadUrl(file:///android_asset/www/index.html)问题2MQTT连接失败报错WebSocket connection to wss://... failed原因Android 9默认禁止HTTP明文流量但WSS是HTTPS问题在证书。解决在AndroidManifest.xml的application标签内加android:usesCleartextTraffictrue并确保EMQX的SSL证书是Lets Encrypt等可信CA签发自签名证书会被拒绝问题3按钮点击无反应原因WebView默认禁用JavaScript。解决webView.getSettings().setJavaScriptEnabled(true)必须在loadUrl()之前调用问题4状态不更新Console显示Uncaught ReferenceError: mqtt is not defined原因mqtt.min.js未正确加载。解决把mqtt.min.js放在assets/www/目录HTML中script srcmqtt.min.js路径必须相对不能写script src./mqtt.min.js发布建议不上架应用商店用蒲公英分发平台www.pgyer.com上传APK后生成二维码扫码安装版本命名规则v1.0.0_20231001日期后缀便于回溯每次更新HTML只需重新打包APK无需用户卸载重装。4.3 ESP8266固件烧录与现场调试实录烧录工具链选择推荐PlatformIO VS Code比Arduino IDE稳定。安装PlatformIO插件新建Project选Espressif 8266框架选Arduino固件大小监控PlatformIO编译后显示Flash: 62% (1024000 / 1638400 bytes)RAM: 38% (28000 / 73728 bytes)确保Flash80%RAM50%现场调试三板斧串口日志分级#define LOG_LEVEL 2 // 0error, 1warn, 2info, 3debug #if LOG_LEVEL 2 Serial.printf([INFO] WiFi connected, IP:%s\n, WiFi.localIP().toString().c_str()); #endif避免Serial.println()刷屏影响灯效网络诊断命令在串口输入ATPINGyour-domain.com看DNS是否通输入ATCIPSTARTTCP,your-domain.com,8084看TCP是否通MQTT状态快照定义命令ATMQTTSTATE返回MQTT State: Connected Subscribed Topics: home/livingroom/light/cmd Last Publish: 2023-1