IoT 系列第 1 篇。在《09_无线通信》里我们解决了"数据怎么飞出去",这一篇往上抬一层,看整张地图。
适用人群:玩过 STM32 或 ESP32、能读传感器、能连 Wi-Fi,但一被问"你这套东西架构是怎样的"就答不上来的同学。不需要服务器经验,Docker 命令我会从 0 给。
读完你能得到:
- 一张能默出来的端-边-云三层架构图,面试画在白板上不心虚;
- “为什么中间非要多一个网关”——四个能折算成钱或可靠性的理由;
- 常见软硬件的对号入座(STM32/ESP32/树莓派/EMQX/云平台各站哪一层);
- 一次完整数据流的六步走,知道你的字节过了几道手;
- 协议选型视角:MQTT / CoAP / HTTP / LwM2M 不是四选一,是各管一段;
- 半小时在本机跑起来的最小端边云(ESP32 + EMQX Docker + Node-RED)。
没看过无线通信那几篇也能懂的核心:传感器只负责"把物理量变成数字",数字要变成"别人能用的信息",中间还隔着链路、协议、网关、平台四层。本文讲的就是这四层怎么分工。
一、我曾经以为"IoT = ESP32 连 Wi-Fi 发个 HTTP"
大二那年我做了个"教室温湿度监测":ESP32 读 SHT30,每 10 秒POST一个 JSON 到我自己写的 Flask 接口,网页能看曲线。当时我觉得物联网不过如此。后来两个"看起来差不多"的需求把我打醒了。
第一个是学校试验田的土壤墒情。田里没 Wi-Fi、没市电,要太阳能 + 电池活一学期。我原来的方案直接作废:一台设备开一个 HTTP 长连,光 TCP 握手的开销都比一包数据大。而且 20 个节点各自直连我的云服务器,20 条 TCP 连接 24 小时挂着——云是我自己的,钱包也是我自己的。
第二个是智慧路灯。要求是"天黑自动亮、人走过加亮、断网了也要亮"。我第一反应是"云端写个规则呗",老师问我一句:校园网断了,你的路灯是不是就全瞎了?我答不上来。这类"断网也要能工作"的需求,本质上是要求判断逻辑不在云上,而在路灯旁边。
这两个需求把我逼出了"设备直连云"的单一思维。真实的 IoT 系统里,设备和云之间几乎总是夹着一层东西,它叫边(Edge)。加上它之后,原本无解的问题突然有解了:
- 田里 20 个电池节点不用各自连云,用 BLE 或 LoRa 甩给田埂上那个有电有网的网关统一上行——省电、省流量、省连接数;
- 路灯的联动规则写在本地网关里,断网只是"上不了报",灯照样亮;
- 上行数据先在网关滤波、聚合、只传变化量,流量费直接降一个数量级。
我没有量产经验,这些是我在课设和啃文档过程中整理的。但"端-边-云"不是我发明的,它是行业通用语言——你去面试,白板上画出来的那张图应该跟本文图 1 同一个骨架。
二、前置概念表:先把这几个词对齐
| 术语 | 白话解释 | 一句话记住它 |
|---|---|---|
| 端(Device / Node) | 真正接触物理世界的板子:采集数据或执行动作 | 大多数端节点压根不会直接上网,也上不起网 |
| 边(Edge) | 端和云之间的"中转 + 处理站"。有电、有网、算力比端强 | 它不是"转发器",是会思考的中转站 |
| 云(Cloud) | 远端服务器集群:存储、分析、下发、对接业务 | 长处是算力和全局视角,短处是离设备太远 |
| Broker | MQTT 的消息中转服务器,按主题转发 | 设备之间从不直连,都在跟 Broker 说话 |
| 网关(Gateway) | 协议转换器:把 BLE/Modbus/Zigbee 翻译成 MQTT/HTTP | 网关是边最常见的物理形态;"边"还包括网关里跑的逻辑 |
| 上行 / 下行 | 设备→云叫上行(遥测),云→设备叫下行(命令) | 上行求"稳",下行求"快" |
| 主题(Topic) | MQTT 里消息的地址,agri/gw01/telemetry,用/分层 | 主题设计好不好,决定以后加设备痛不痛苦 |
⚠️ 最容易搞混的一对:"边"是逻辑层,"网关"是具体设备。一个树莓派上跑 Mosquitto + Node-RED + 联动脚本,它既是网关(硬件),也是边(逻辑)。阿里云的"边缘计算实例"则是把边做成软件塞进别的机器——层还在,只是不再是一台看得见的盒子。
三、端-边-云三层全景图
[图 1] IoT 系统"端-边-云"三层全景(面试白板上画的那张) ┌──────────────────────────── 云 CLOUD ────────────────────────────┐ │ ┌─────────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ 物联网平台 │ │ 规则引擎 │ │ 业务应用 │ │ │ │ EMQX / 阿里云IoT │──>│ 阈值告警 │──>│ 大屏 / App / MES │ │ │ │ 华为云 / OneNET │ │ 数据清洗 │ │ 时序数据库 │ │ │ └─────────────────┘ └──────────────┘ └──────────────────┘ │ └────────┼─────────────────────────────────────────────────────────┘ │ MQTT over TLS:1883 / 8883 │ ↑ 这一段是广域网,会断、会慢、按流量收费 ═════════╪══════════════════════════════════════════════════════════ │ 园区 / 现场局域网:通常免费、快,但地理范围有限 ┌──────────────────────────── 边 EDGE ────────────────────────────┐ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ 边缘网关(树莓派 / ESP32-S3 / 工控机 / 工业网关) │ │ │ │ ① 协议转换 BLE / Modbus / Zigbee / LoRa ⇄ MQTT │ │ │ │ ② 本地 Broker(可选)Mosquitto / EMQX,断网也能内部通信 │ │ │ │ ③ 边缘计算 滑动滤波、越限判断、聚合上报、单位换算 │ │ │ │ ④ 断网续传 本地队列 + SQLite / 文件缓存,联网后补传 │ │ │ │ ⑤ 本地联动 "温度>30 就开风扇",不依赖云 │ │ │ └───────────────────────────────────────────────────────────┘ │ └──────▲──────────────▲─────────────────────▲─────────────────────┘ │ │ │ BLE / Zigbee RS-485 / Modbus LoRa / 私有射频 ┌──────┴───────┐ ┌────┴───────┐ ┌──────┴────────┐ │ 温湿度节点 │ │ 电表 / 水表 │ │ 土壤墒情节点 │ │ STM32+传感器 │ │ 工业仪表 │ │ 电池 + 太阳能 │ └──────────────┘ └────────────┘ └───────────────┘ 端 DEVICES(数量多、资源少、大多不通 IP)3.1 三层各自该干什么、不该干什么
判断一个功能放哪层,我的土办法是问三个问题:要不要全局信息?能不能容忍延迟?断网时还要不要工作?
| 层 | 该它干的 | 不该它干的 | 典型软硬件 |
|---|---|---|---|
| 端 | 采样、执行、本地安全联锁、低功耗休眠(Stop / Deep Sleep)、基础自检 | 复杂规则判断、长连接、TLS 握手、大数据缓存 | STM32(F1/F4/L4/U5 按功耗算力选)、ESP32-C3/S3(自带 Wi-Fi/BLE)、nRF52(纯 BLE) |
| 边 | 协议转换、聚合/滤波、本地联动、断网续传、本地告警、固件分发 | 跨站点全局分析、长期历史存储 | 树莓派/香橙派(Linux + Docker)、ESP32-S3(轻量网关)、工业网关(RS-485/DI/DO)、跑 Mosquitto / EMQX / Node-RED |
| 云 | 海量存储、跨设备关联分析、用户权限、OTA 版本管理、对接业务 | 毫秒级实时控制、单点高频联动 | 阿里云 IoT / 华为云 / OneNET(托管);自建:EMQX / Mosquitto + 时序库(TDengine/InfluxDB)+ 规则引擎 |
⚠️平台差异:同样叫"边",ESP32-S3 和树莓派不是一个量级。ESP32-S3 几百 KB RAM、无 MMU、跑 FreeRTOS,让它做"本地 Broker + 一周缓存"是想多了;树莓派是瓦级功耗、GB 级内存,能干的多得多但费电、贵、要管系统。选硬件前先算清楚"要缓存多少"“要不要跑容器”,别上来就上树莓派,也别硬扛。
💡云端选型:托管平台的好处是"接入、鉴权、影子、OTA 都给你做好了",坏处是被绑死、按消息数收费、数据过别人手。自建全在自己手里、本地就能练手,代价是鉴权/高可用/监控自己扛。我的建议是先自建把 MQTT 搞明白,再学平台的封装——顺序反了,平台的"物模型"“影子”"Topic 类"你一个都看不懂。
四、为什么非要多一个"边":四个理由,逐个能算账
如果你以后只记得一句话,我希望是这句:边不是为了"多一层显得专业",它解决的都是能折算成钱或可靠性的问题。
4.1 省钱:带宽和云成本
假设一个大棚有50 个节点,每 30 秒上报一次 100 字节:
方案 A:50 个节点各自直连云(每个都开 TCP + TLS) 每包实际开销 ≈ 100 B 载荷 + 40 B TCP/IP 头 + ~30 B MQTT 头 + TLS 记录头 ≈ 200 B 往上 每天:50 × 2880 × 200 B ≈ 28.8 MB/天 ≈ 864 MB/月;服务器维持 50 条 TLS 长连接 方案 B:节点走 BLE/LoRa 给网关,网关每 5 分钟聚合发一包(约 2 KB) 每天:288 × 2 KB ≈ 0.58 MB/天 ≈ 17 MB/月;服务器只维持 1 条连接 → 流量降到约 1/50,连接数从 50 降到 1数字是估算的,但数量级是对的:聚合 + 批量上传,是边缘层最直白的省钱方式。
4.2 省电:把最费电的活从电池节点身上拿走
《低功耗》那几篇的公式:平均电流 = Σ(各状态电流 × 停留时间) ÷ 周期。一次 MQTT 上报要 3~5 秒的高电流(含建连、TLS 握手),而一次 BLE 广播只要几十毫秒。把射频发射时长砍掉一两个数量级,续航直接翻倍。所以我那个"发 HTTP"的 ESP32 教室节点用市电毫无问题,一换电池架构就得改:电池端只做短距离低功耗通信,联网的重活交给有市电的网关。
4.3 断网续传:最容易被忽视,也最容易在验收时翻车
我第一次做户外项目时写的是:采样 → 连 Wi-Fi → 发 HTTP → 失败就丢弃。只要那 5 分钟网络抖一下,数据就永久没了。验收时对方问我"昨天下午 3 点到 4 点的数据呢",我只能说"那会儿断网了"。
[图 2] 断网续传的基本结构 采样 ──> 打时间戳 ──> 本地环形队列(内存 + 掉电保存到 Flash/SQLite) │ 联网?───────┤ 是 ─────────> 批量上行 → 收到确认 → 删除队列条目 否 ─────────> 继续攒,到上限就按策略丢最旧的三个要点,每条都是踩过的:① 时间戳要在采样那一刻打,不是上传时打——否则断网一小时后补传的数据全挤在同一时刻,曲线是错的;② 队列要有上限,别把 Flash 写穿(和《17_存储》的写平衡、掉电保护是同一个问题);③ 补传要分批限速——断网两小时后一恢复灌几千条进去,轻则卡死自己,重则被 Broker 限流踢下线。
4.4 本地联动:呼应我在《项目实战_02》做的 BLE 网关
我在《项目实战_02:智能家居环境网关——ESP32 做 BLE 中心 + 本地联动》里干的事,用本篇的语言重述一遍:
[图 3] 同一个项目,两种架构 (a) 我最初的"云上联动"想法 传感器 ──Wi-Fi──> 云规则引擎 ──> 下发命令 ──> 执行器 断网 = 全瘫;每次联动绕一圈,延迟几百毫秒到几秒 (b) 实际做成的"本地联动"(边在起作用) 传感器 ──BLE──> ESP32 网关【规则:温度>28℃ 且 有人 → 开风扇】──> 执行器 └──MQTT──> 云(只负责记录和远程手动控制) 断网 = 只是上不了报,联动照常;延迟是毫秒级这个经历让我彻底理解了"边":判断逻辑离被控对象越近,系统越可靠。云是"全局视角和记忆力",不是"反射神经"——你不会指望大脑皮层负责膝跳反射。
五、数据是怎么流过去的:一次上行 + 一次下行
[图 4] 一次完整闭环,六个阶段 ① 采集 传感器 → 数字量(I2C/SPI/ADC) ② 本地处理 标定、单位换算、滑动平均、剔除跳变、越限标记 ③ 协议封装 组 JSON/CBOR,加设备 ID、时间戳、序号、校验 ④ 上行 MQTT PUBLISH(QoS 1) → Wi-Fi/4G/LoRa → 网关 → Broker ⑤ 平台 解析、鉴权、入库、规则引擎判定、更新设备影子 ⑥ 下行控制 PUBLISH 到 cmd 主题 → 设备校验执行 → 回报新状态(回到 ①) 关键:⑥ 执行完必须回报,形成闭环。否则 App 上永远是"已发送", 你不知道设备到底开没开——而这个"回报"本身就是一次上行。⚠️ 第 ③ 步新手最常踩:把所有东西塞进一个主题。比如
device/data里一会儿温度一会儿电量一会儿心跳,靠解析 payload 区分。后果是没法用主题过滤(云端规则写得又臭又长)、retained 消息也没法用。主题要按"用途"分层,不是按"设备"打包。
六、协议选型:不是四选一,是各管一段
这是我最想纠正的误解。教程常把 MQTT/CoAP/HTTP/LwM2M 摆一排让你"选一个",但真实架构里它们常常同时出现,只是在不同的段上。
[图 5] 协议是分段的,不是互斥的 端 ────────────> 边 ────────────> 云 ──────────> 业务 BLE GATT MQTT over TLS HTTPS / AMQP / Kafka Modbus RTU (也可能直接 CoAP)数据库驱动 Zigbee / LoRa 私有帧 └─ 根本不是 IP 网络, └─ 这一段才是 └─ 跟嵌入式关系不大, 轮不到 MQTT MQTT 的主战场 是后端的事所以听到"这设备用什么协议",第一个该问的是"从哪儿到哪儿"。
| 协议 | 跑在 | 报文开销 | 模型 | 该用的地方 | 别用的地方 |
|---|---|---|---|---|---|
| MQTT | TCP(+TLS) | 固定头最小2 B | 发布/订阅,长连接,支持推送 | 边↔云主力;遥测 + 下行命令;有市电设备 | 电池 + NB-IoT 的极低频上报(心跳本身就费电,见《无线_03》) |
| CoAP | UDP | 固定头4 B | 请求/响应(像精简 HTTP) | 电池设备 + 极受限网络;NB-IoT/LoRa 直连 | 需要服务端主动推命令、对丢包零容忍的业务 |
| HTTP(S) | TCP | 文本头,动辄几百字节 | 请求/响应,短连接 | 偶尔取配置、下载固件、对接 Web 后端 | 高频遥测(头比载荷大);下行实时控制 |
| LwM2M | 通常在 CoAP 之上 | 同 CoAP | 面向"资源"(如/3/0/1) | 设备管理:远程配置、固件升级、生命周期 | 当"数据上报协议"用——那不是它的主场 |
💡一句话记忆:MQTT 管来回传数据,CoAP 管极省电地传一包,HTTP 管跟 Web 世界打交道,LwM2M 管管理设备本身。成熟平台里这四个往往同时在跑。
6.1 主题命名:现在多花 5 分钟,以后省 5 天
{产品}/{设备ID}/{方向}/{用途} agri/gw01/up/telemetry 上行:周期遥测 agri/gw01/up/event 上行:事件/告警 agri/gw01/up/reply 上行:命令执行结果(闭环!) agri/gw01/down/cmd 下行:命令 agri/gw01/status 状态(配合 retained + 遗嘱) 订阅侧:agri/gw01/down/#(这台的下行) agri/+/up/telemetry(所有设备遥测)四条规矩:① 不以/开头(会产生空层级);② 主题里不放动态值(时间戳、随机数一律进 payload,否则主题树爆炸);③ 全小写 + 短横线,别混大小写;④ 主题里不放敏感信息,它是明文的,鉴权靠用户名/密码或证书。
七、动手:半小时搭一个最小端边云
这套我自己在笔记本上跑通过,不需要买云、不需要公网 IP,全在本机 Docker 里。
第 1 步:起一个本地 Broker(EMQX)
dockerrun-d--nameemqx\-p1883:1883\# MQTT over TCP-p8083:8083\# MQTT over WebSocket-p8084:8084\# MQTT over WSS-p8883:8883\# MQTT over TLS-p18083:18083\# Dashboard(Web 管理页)emqx/emqx:latestdockerlogs-femqx# 看日志确认起来了浏览器打开http://localhost:18083,默认账号密码admin/public,首次登录会要求改掉默认密码。
⚠️ 后面让 ESP32 连这个 Broker 时,地址不能填
localhost或127.0.0.1——那是 ESP32 自己。要用电脑在局域网里的 IP(ifconfig/ipconfig查)。这是"ESP32 连不上 EMQX"的头号原因,第二号是防火墙没放行 1883。
第 2 步:先用命令行验证 Broker 通了
sudoaptinstall-ymosquitto-clients# macOS: brew install mosquitto# 终端 A:订阅(模拟"云/应用"一侧)mosquitto_sub-h192.168.1.100-t'agri/#'-v# 终端 B:发布(模拟"设备"一侧)mosquitto_pub-h192.168.1.100-t'agri/gw01/up/telemetry'\-m'{"temp":26.5,"humi":61,"ts":1756000000}'-q1终端 A 应立刻打印出这一条。意义:先把云侧打通再调设备,这样设备联不上时你能确定问题在设备侧。
第 3 步:ESP32 上报(最小可用版)
/* ============ ESP32 / ESP-IDF v5.x:最小 MQTT 上报 ============ * 前置:Wi-Fi 已连接并拿到 IP(esp_wifi 或 example_connect 均可) * 配置细节见本系列下一篇《MQTT 深度实战》 */#include<stdbool.h>#include<string.h>#include"mqtt_client.h"#include"esp_log.h"staticconstchar*TAG="app";staticesp_mqtt_client_handle_ts_client=NULL;staticvoidmqtt_event_handler(void*handler_args,esp_event_base_tbase,int32_tevent_id,void*event_data){esp_mqtt_event_handle_tevent=(esp_mqtt_event_handle_t)event_data;switch((esp_mqtt_event_id_t)event_id){caseMQTT_EVENT_CONNECTED:ESP_LOGI(TAG,"broker 已连接");esp_mqtt_client_subscribe(event->client,"agri/gw01/down/cmd",1);break;caseMQTT_EVENT_DATA:{/* ⚠️ event->topic / event->data 不以 '\0' 结尾,必须按长度拷贝 */chartopic[64];inttlen=event->topic_len;if(tlen>=(int)sizeof(topic))tlen=(int)sizeof(topic)-1;memcpy(topic,event->topic,(size_t)tlen);topic[tlen]='\0';ESP_LOGI(TAG,"收到 [%s] qos=%d : %.*s",topic,event->qos,event->data_len,event->data);/* 执行完记得往 .../up/reply 回报一次,形成闭环 */break;}caseMQTT_EVENT_DISCONNECTED:ESP_LOGW(TAG,"连接断开,esp-mqtt 默认会自动重连");break;default:break;}}voidmqtt_app_start(void){constesp_mqtt_client_config_tcfg={.broker.address.uri="mqtt://192.168.1.100",/* 换成你的局域网 IP */.credentials.client_id="esp32-gw01",/* 同 Broker 内必须唯一 */.session.keepalive=60,/* 秒 */.network.reconnect_timeout_ms=5000,.network.timeout_ms=10000,};s_client=esp_mqtt_client_init(&cfg);if(s_client==NULL){ESP_LOGE(TAG,"mqtt init 失败");return;}esp_mqtt_client_register_event(s_client,ESP_EVENT_ANY_ID,mqtt_event_handler,NULL);esp_mqtt_client_start(s_client);}/* 上报一包遥测(在采集任务里调用) */booltelemetry_publish(floattemp,floathumi,int64_tts_ms){if(s_client==NULL)returnfalse;charpayload[128];intn=snprintf(payload,sizeof(payload),"{\"temp\":%.1f,\"humi\":%.1f,\"ts\":%lld}",(double)temp,(double)humi,(longlong)ts_ms);if(n<=0||n>=(int)sizeof(payload))returnfalse;/* 格式化失败或截断 *//* QoS 1:要等 PUBACK。返回 >=0 的 msg_id 才算进了发送队列 */intmsg_id=esp_mqtt_client_publish(s_client,"agri/gw01/up/telemetry",payload,n,1,0);if(msg_id<0){ESP_LOGW(TAG,"publish 失败: %d(-2 = outbox 满,发的比网络快)",msg_id);returnfalse;}returntrue;}第 4 步:用 Node-RED 当那个"云上应用"
npminstall-gnode-red&&node-red# 打开 http://localhost:1880# 或:docker run -d -p 1880:1880 --name nodered nodered/node-red拖四个节点连成一条线,这就是云层在干的事:解析 → 存储 → 展示。
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ mqtt in │──>│ json │──>│ function │──>│ debug / │ │ agri/# │ │ 解析字符串 │ │ 提取字段 │ │ chart │ └──────────┘ └──────────┘ └──────────┘ └─────────┘mqtt in 节点填 Server192.168.1.100:1883、Topicagri/#、QoS 1;function 节点只写一行:
// Node-RED function 节点内(JavaScript)msg.payload={value:msg.payload.temp,ts:msg.payload.ts};returnmsg;点 Deploy,再用mosquitto_pub发一次,debug 面板应立刻出现数据。到这一步你已跑通完整的"端 → 云 → 应用"链路——只不过"边"目前是空的。
第 5 步:把"边"加回来,做对比实验
- 不聚合:ESP32 每 2 秒发一包,去 EMQX Dashboard 看消息速率;
- 加上边:在 ESP32 里做 10 秒滑动窗口,只有"温度变化超 0.5 ℃ 或超过 30 秒没发"才上报;
- 对比 Dashboard 上的「消息数 / 分钟」。
你会看到一个数量级的差别,且数据几乎没有信息损失——这就是 4.1 那笔账的实物版。
八、新手必踩的 8 个坑
| # | 坑 | 现象 / 后果 | 正确做法 |
|---|---|---|---|
| 1 | 所有设备直连云,不考虑网关 | 连接数爆炸、流量费高、电池撑不过一周 | 先问"有没有市电、数量多少",10 个以上电池节点就该考虑边 |
| 2 | ESP32 里 Broker 地址填localhost/127.0.0.1 | 连不上,日志一片超时 | 填电脑的局域网 IP;确认防火墙放行 1883 |
| 3 | 断网时直接丢数据 | 网络一抖,这段时间数据永久缺失 | 本地队列 + 采样时打时间戳 + 队列有上限 + 补传限速 |
| 4 | 时间戳在上传时打,不是采样时打 | 补传的历史数据全挤同一时刻,曲线是假的 | 采样那一刻就打;或维护序号由平台还原 |
| 5 | 所有消息塞进一个主题 | 云端规则写不动、没法过滤、retained 用不了 | 按{产品}/{设备}/{方向}/{用途}分层,一用途一主题 |
| 6 | 主题以/开头,或把时间戳拼进主题 | 多出空层级;主题树爆炸撑爆 Broker 内存 | 不以/开头;主题只放稳定标识,动态值进 payload |
| 7 | 下行命令发出去就不管 | App 显示"已发送",不知设备有没有执行 | 设备执行完回报.../up/reply,形成闭环 |
| 8 | 边这层硬件选型拍脑袋 | 树莓派做电池场景几小时没电;ESP32 硬扛一周缓存撑不住 | 先算"缓存多大、要不要跑容器、功耗预算多少"再选 |
九、动手练一练
按顺序做。第 3 步和第 5 步是故意搞破坏——只有亲眼见过故障现象,以后才认得出它。
练习 1:把本机端边云跑通(30 分钟)
按第七章做完。验收标准:ESP32 上电后 Node-RED 的 debug 面板持续收到数据,EMQX Dashboard 的 Clients 页能看到esp32-gw01在线。
练习 2:用命令行手动模拟"设备"和"应用"
不开 ESP32,只用两个终端互发。要求:你能说出这一包从"发布者 → Broker → 订阅者"经过哪些环节,并指出 Broker 在里面的作用。
练习 3(故意试错):掐断网络,看数据会不会丢
让 ESP32 正常上报,然后直接关掉电脑 Wi-Fi(或docker stop emqx),等 30 秒再恢复。没有本地队列的话,这 30 秒的数据不见了,而且日志里可能什么都看不出来。然后加一个最简单的队列(哪怕只是内存里的环形数组 + 补传),再试一次。
意义:这是"能演示的 demo"和"能交付的系统"之间的分水岭。
练习 4:改主题结构,体会过滤的威力
先把所有数据都发到agri/data,在 Node-RED 里写过滤逻辑;再改成agri/gw01/up/telemetry的分层结构,用agri/+/up/telemetry订阅。对比两种写法下 Node-RED 流程的复杂度,你就明白为什么要分层。
练习 5(故意试错):把 Broker 地址写错,学会看错误
把.broker.address.uri改成mqtt://192.168.1.999重新烧录,看串口日志里MQTT_EVENT_ERROR的error_type,然后对比两种情况:IP 不存在是传输层(TCP)失败且反复重试;IP 存在但端口没开(如填 1884)是连接被拒,重连间隔由reconnect_timeout_ms决定。
意义:以后遇到"设备离线",你能第一时间分清是"网络不通"还是"Broker 不认我",而不是盲目重启。
小结
- 端-边-云是三次分工:端接触物理世界,边就地解决本地该解决的事,云负责全局和记忆。
- 边的价值能折算成钱和可靠性:省带宽(聚合)、省电(重活从电池节点拿走)、断网续传(数据不丢)、本地联动(断网也能控)。每条都能讲出一个具体场景。
- 协议是分段的:端↔边常常根本不是 IP 网络,边↔云才是 MQTT 的主场。别问"MQTT 和 CoAP 哪个好",要问"这一段用哪个"。
- 主题设计要趁早:
{产品}/{设备}/{方向}/{用途},一用途一主题,动态值进 payload。现在偷懒,以后要还。 - 下行必须有回报,否则你的系统永远只有开环。
参考:MQTT 3.1.1 / 5.0 协议规范(OASIS);EMQX 官方文档(Docker 部署与 Dashboard 端口);乐鑫 ESP-IDF 编程指南中 esp-mqtt 组件的配置说明。文中协议开销与流量估算为典型值,实际请以抓包(Wireshark)或 Broker 侧统计为准。
下一篇:MQTT 深度实战——QoS、保留消息、遗嘱与订阅树,ESP32 对接 Broker