【从零搭建物联网智能充电桩系统】3、MQTT 客户端:设备接入云端的第一道关口

设备接入云端,靠的是 MQTT。本文讲解本项目中最核心的 MQTT 客户端模块:如何订阅设备上报、如何解析二进制报文、如何通过 Spring Integration 与 EMQX 建立连接,以及下行指令如何转发给设备。


一、模块定位

charge-mqtt-client设备接入的核心模块,全项目实现最完整(14 个 Java 文件,成熟度 ★★★★☆)。职责:

  • 上行:订阅 EMQX 的 charge/stat,接收设备二进制报文 → 解析 → 投 RabbitMQ
  • 下行:消费 RabbitMQ 的下行指令 → MQTT publish 给设备

端口:HTTP 8080 | MQTT over WebSocket: ws://emqx:8083


二、模块结构(14 个类)

复制代码
charge-mqtt-client/src/main/java/com/yeseesion/SmartChargeStation/mqtt/client/
├── MqttApplication.java            启动类
├── api/SendApi.java                发送/模拟 REST 接口(/mqtt/send、/mqtt/simulate)
├── conf/MqttProps.java             @ConfigurationProperties(prefix="mqtt") 配置映射
├── factory/
│   ├── FactoryBuilder.java         MqttPahoClientFactory 构建
│   ├── InBoundMessageRev.java      ★★核心:入站消息分发(471 行,全项目最大类)
│   ├── InBoundSubscribe.java       入站适配器/订阅配置
│   └── OutBoundSend.java           出站 Handler 配置
├── model/MqttConstants.java        通道常量 OUT_CHANNEL="out" / In_CHANNEL="in"
├── mq/
│   ├── ChargeCmdConsumer.java      消费下行指令 → MQTT
│   ├── ChargeStatConsumers.java    三路 Fanout 消费者(手动 ACK)
│   ├── ChargeStatProducer.java     Fanout 广播生产者
│   └── RabbitConfig.java           Fanout 交换机 + 3 队列 + TTL
├── service/MqttService.java        @MessagingGateway 网关接口
└── utils/TransformerUtils.java     objectToHex / hexStringToByteArray / bytesToHex

三、上行链路核心:InBoundMessageRev(471 行)

按 Topic 分发逻辑

Topic 处理
charge/stat hex → ChargeStatData.fromHexString() 解析 24 字节帧 → buildStatJson() → 投 Fanout
charge/stat/json 调试通道,直接收 JSON(跳过二进制解析)
charge/cmd 指令回环/日志

三个 HTTP 回调(均带 X-Internal-Key 头)

java 复制代码
callNettyPush()   → POST http://netty:8085/netty/push      // 推小程序
cacheStatToRedis()→ SET charge:stat:{chargeRecordId} TTL 24h
callIotDBSave()   → POST http://iotdb-service:8087/iotdb/charge/stat // 写时序库

代码 172-174 行有一段被注释掉的旧逻辑------原先收到 MQTT 后同步串行调用三个下游,重构为 RabbitMQ Fanout 后异步解耦,但旧代码未删除。


四、Spring Integration MQTT 集成(常见疑问)

关键概念

概念 本项目用法
MqttPahoClientFactory FactoryBuilder 构建,配置 broker 地址/账号/密码
MqttPahoMessageDrivenChannelAdapter 入站适配器(InBoundSubscribe),订阅 charge/stat 等
MqttPahoMessageHandler 出站 handler(OutBoundSend),publish 到 charge/cmd
@MessagingGateway MqttService 接口,声明式发送消息
Channel 内部消息通道(out / in 两个常量)

为什么用 Spring Integration 而不是裸 Paho?

  • 消息收发的生命周期由 Spring 管理,与 Boot 应用集成度高
  • 通道抽象(Channel)方便后续加拦截器/过滤器/转换器
  • 断线重连由 Paho 客户端 + Spring 适配器协同处理

下行指令的格式选择:为什么不是真·二进制?(重要!)

java 复制代码
// ChargeCmdPayload 只有字段,没有 toBytes()
// 实际发送走 TransformerUtils.objectToHex():
//   整个对象 JSON 序列化 → 转 hex 字符串 → MQTT publish

字段名(start_tag 等 snake_case)也会进 payload。这是刻意为之 :下行指令由小程序发起 ,用 JSON 序列化转 hex,小程序端和抓包工具都能直接看到请求参数 (目标桩 ID、充电时长等),调试体验远优于二进制帧。上行(设备→云端)走真·二进制是设备端省流量,下行走 JSON 是前端要可读------同一个项目里按方向选协议,这正是"按场景做取舍"的体现。

注意:若未来设备端指令下发频率变高或需严格省流,可再评估是否统一为二进制------当前下行频率低(用户手动点击),JSON 方案是合理的。


五、消息可靠性设计

层级 措施
设备→EMQX MQTT QoS 1(至少一次)
EMQX→MQTT客户端 订阅 QoS 1
MQTT客户端→下游 RabbitMQ 手动 ACK + 失败重入队(当前无重试上限,见下面缺陷)
RabbitMQ 队列 持久化队列(重启不丢)

已知缺陷basicNack(requeue=true) 且未配置死信队列(DLQ)→ 毒药消息无限重投(见 05 篇)。


六、常见问题与解答

  1. MQTT 的 QoS 0/1/2 有什么区别?为什么用 QoS 1 不用 2?(QoS1 至少一次、有重复;QoS2 恰好一次、开销大;充电状态可容忍重复上报)
  2. MQTT 的 retain 消息有什么用?(保留最近一条,新订阅者立即收到;本项目未用)
  3. buildStatJson() 把二进制解析结果转 JSON 的字段有哪些?(chargingTime/chargedEnergy/currentPower/currentCost/chargingProgress/state;注意 chargeRecordId 是 URL 参数,不在 JSON 内;state 对应"充电中/已完成"等状态枚举)
  4. 如果三个下游全部宕机,MQTT 客户端会发生什么?(RabbitMQ 消息堆积,队列 TTL 到期丢弃;MQTT 客户端本身不阻塞)
  5. Spring Integration 的 Channel 和 MQ 的 Queue 是一回事吗?(不是:Channel 是应用内消息管道,Queue 是跨应用的消息存储)

小结

MQTT 客户端是设备接入的咽喉:上行把二进制报文解析后交给 RabbitMQ 分发,下行把指令发布到 MQTT。它本身不直接连数据库,靠消息中间件与下游解耦。下一篇 【从零搭建物联网智能充电桩系统】4、Netty WebSocket 服务 讲解与小程序保持长连接的 Netty 服务端。

相关推荐
sunywz2 小时前
【从零搭建物联网智能充电桩系统】4、Netty WebSocket 服务:小程序实时推送背后的长连接管理
物联网·websocket·小程序
sunywz2 小时前
【从零搭建物联网智能充电桩系统】选型篇:Netty、MQTT、RabbitMQ、IoTDB 为什么这么选?
物联网·rabbitmq·iotdb
桐盛科技2 小时前
智慧公厕厕位监测技术选型实战:激光测距、毫米波雷达、红外测距的优缺点对比与场景适配
物联网·智慧城市
sunywz4 小时前
【从零搭建物联网智能充电桩系统】系列导读:从硬件到小程序的完整链路
物联网·小程序
microrain11 小时前
从数据烟囱到融合中心:SagooIoT统一数据处理平台的设计实践
物联网·golang·开源·sagooiot
W.W.H.12 小时前
将驱动编译成模块并安装到rootfs
linux·物联网·wifi·rootfs·驱动
单片机杂货铺17 小时前
基于单片机的智能粮仓控制系统设计与实现
stm32·单片机·嵌入式硬件·物联网·计算机外设·51单片机·课程设计
指尖的爷17 小时前
RKNN转化环境搭建(rknn_toolkit2新版本)
嵌入式硬件·深度学习·物联网·目标检测
老孙讲技术17 小时前
周界枪响了、电梯脸却进了物业群:一个 callback,怎么把 alarm 和 faceAnalysis 拆成两条值班线?
后端·物联网