网关不就是转发数据吗?来,拆一个MQTT聚合网关看看

📅 2026/7/2 8:48:20
网关不就是转发数据吗?来,拆一个MQTT聚合网关看看
你有没有想过一个问题市面上卖的那些IoT网关从几十块钱的透传模块到几千块钱的工业网关差价能差两个数量级它们到底差在哪透传模块做的事很简单串口收啥网口发啥。但一个真正的IoT网关核心工作不是转发而是消化。我们今天就拆一个实际跑过的方案。场景是3个Modbus RTU传感器挂在RS485总线上数据要统一上报到云端MQTT Broker。目标很明确——不让每一路传感器各自连云而是由网关做聚合。先看数据怎么从物理层到应用层。RS485只解决物理传输Modbus协议在应用层定义了一帧数据的格式地址码(1byte) 功能码(1byte) 数据区(N bytes) CRC校验(2bytes)。MCU收到一串字节流第一步就是拆帧。这里有一个常见的误区——很多人直接用环形缓冲区收完一帧就开始解析但Modbus RTU帧之间没有固定的分隔符协议规定的是3.5个字符时间空闲作为帧结束标志。所以代码里不能简单地 read 到\n就收工。正确的做法是#define T35_US 1750 // 9600bps下3.5字符约3.65ms取整1750us uint8_t frame_buf[256]; uint16_t frame_len 0; uint64_t last_byte_time 0; void uart_rx_callback(uint8_t byte) { uint64_t now micros(); if ((now - last_byte_time) T35_US frame_len 0) { process_modbus_frame(frame_buf, frame_len); frame_len 0; } frame_buf[frame_len] byte; last_byte_time now; }收到完整帧之后真正的麻烦才开始。3个传感器每路要读保持寄存器、输入寄存器、线圈状态每5秒轮询一遍。如果网关只是把收到的原始Modbus帧直接封装成MQTT payload发出去云端那边就得自己解析Modbus那云端的业务代码就耦合了链路层逻辑——架构上说不通。更好的做法是网关内部做协议终结protocol termination。MCU解析完Modbus帧提取出有意义的数值——温度、压力、流量——然后用结构体统一组织再序列化成JSON发到MQTT Topic。这样云端消费的是干净的key-value数据。typedef struct { float temperature; float pressure; uint16_t flow_rate; uint8_t coil_status; uint32_t timestamp; } sensor_data_t; // 解析后的数据直接以JSON格式发布 // topic: gateway/sensor/01/data // payload: {temp:25.3,press:101.2,flow:45,coil:1,ts:1719876543}这里有一个有意思的设计Topic 层级怎么规划。我们当时踩了一圈最后定的是{gateway_id}/{sensor_type}/{sensor_addr}/{data_type}。好处是后端用 MQTT wildcard 做订阅时非常灵活——/sensor//data可以收所有传感器数据gateway-A/temperature//data只收温度数据。如果一上来就把所有东西拍平到 Topic 里后面加 sensor 就要改订阅逻辑可扩展性差很多。网关还需要处理一个容易被忽略的问题断线重连后的数据补传。Wi-Fi/4G不稳定网关离线时传感器还在跑数据是丢了还是缓存起来我们的方案是用一个循环队列最多缓存2000条记录。重连后按时间戳顺序补发Topic 上用/history后缀跟实时数据区分开。#define HISTORY_CAPACITY 2000 typedef struct { sensor_data_t buf[HISTORY_CAPACITY]; uint16_t head; uint16_t tail; uint16_t count; } history_queue_t; // 重连成功后按先进先出补发 while (queue.count 0) { mqtt_publish(topic_history, queue.buf[queue.tail], ...); queue.tail (queue.tail 1) % HISTORY_CAPACITY; queue.count--; }缓存容量2000条按每5秒一条算大约撑2.8小时。这是基于现场实测的中位断网时长约40分钟乘以安全系数算出来的不是拍脑袋定的。MQTT的心跳保活也值得多说一句。很多方案把 keepalive 设成60秒觉得越短越可靠。其实不对——每60秒发一次PINGREQ如果网络有瞬时抖动重传加上QoS1的消息累积反而会加剧拥塞。我们在现场试下来120秒的 keepalive 配合 30秒的 socket 超时检测稳定性最高。关键是要在应用层加一个数据健康度监控如果超过 N 个周期没收到任何 sensor 数据主动重建连接不等底层 TCP 超时。网关的另一个角色是边缘规则引擎。举个具体的例子温度超过75度时需要立刻关闭阀门这个动作如果走云端——传感器→网关→云→规则判断→命令下发的回路——延迟在蜂窝网络下可能3到5秒太慢了。我们在网关里写了一条简单的规则表rule_t rules[] { {temp 75.0, relay_off(valve_id)}, {press 50.0, led_red_on}, };MCU上跑个轻量的表达式求值器每次采集完数据先扫一遍本地规则命中就直接 GPIO 操作。云端只做记录和告警。这样本地响应在毫秒级断网也能正常工作。网关的开发调试和上层应用开发有个很大的区别你很难 printf 大法。串口被Modbus占了网络断开会丢上下文。我们当时花了一整天做了一个调试模式——长按板子上的按键3秒网关切换角色通过蓝牙BLE输出内部状态。这个功能后来成为客户验收时最常说还挺专业的功能。如果想自己动手试一下可以用ESP32 MAX485模块搭一个最简单的原型成本不到50块。固件层面不提具体RTOSFreeRTOS或者裸机都行——关键是把上面说的协议终结 本地缓存 边缘规则这三层的边界画清楚层次之间用队列解耦。架构对了后面换硬件只是换驱动层的事。