1. 协议接入方式

该水表通过外部 MQTT Broker 接入平台。表端和平台之间传输的不是 JSON,而是完整的二进制协议帧。

典型 topic 约定如下:

方向 Topic 示例 说明
表端发布 upstream/{protocol}/{meterNo} 表具上报日报、告警、命令响应
表端订阅 downstream/{protocol}/{meterNo} 表具接收平台下行命令
平台订阅 upstream/{protocol}/+ 平台接收全部表具上行
平台发布 downstream/{protocol}/{meterNo} 平台向指定表具下发

已验证的普通链路包括:

类型 命令示例 说明
日报上告 01C055 表具上报实时数据和最近 24 条冻结记录
通信结束 04C002 平台在无待下发任务或任务处理完成后下发
读取表具参数 02C045 平台读取阈值、持续时间等参数
设置表具参数 04C045 平台设置阈值、持续时间等参数
读取历史冻结 02C053 平台读取指定历史间隔冻结数据
读取采集参数 022108 平台读取 APN、服务器地址、端口等
设置采集参数 042108 平台设置 APN、服务器地址、端口等
设置密钥参数 04C021 平台设置密钥相关参数,真实密钥切换需谨慎处理

远程升级通常比普通命令复杂,建议单独作为专项处理。

2. 帧格式基准

协议文档定义的完整帧格式如下:

68 A PT PV C L DID MID DATA CRC 16
字段 长度 说明
68 1 帧起始符
A 32 地址域,ASCII;实际表号不足 32 字节时,末尾补 0x00
PT 1 协议类型
PV 1 协议版本
C 1 控制码,包含方向、后续帧、加密标志、功能码
L 2 完整帧总长度,从 6816,低字节在前
DID 2 数据标识,低字节在前
MID 1 消息序号
DATA N 数据域,普通交互使用 AES128 加密
CRC 2 CRC16,从 68 到 DATA 结束
16 1 帧结束符

按 32 字节地址域计算,固定偏移如下:

字段 位置
起始符 0
地址域 1 ~ 32
PT 33
PV 34
C 35
L 36 ~ 37
DID 38 ~ 39
MID 40
DATA 起始 41
最小帧长度 44
总帧长度 44 + DATA长度

这里有一个非常容易踩坑的点:

  • 帧地址域补齐:表号不足 32 字节,补 0x00
  • 设备密钥计算:表号不足 16 字节,补协议约定的补齐字节。

这两个补齐规则作用在不同地方,不能混用。

3. 拆一条日报上告帧

假设表号为:

123456789

MQTT 发布 topic:

upstream/{protocol}/123456789

一条日报上告的完整帧可以抽象成:

68
31 32 33 34 35 36 37 38 39 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00
0A
A1
3C 01
C0 55
00
[272 bytes encrypted DATA]
[2 bytes CRC]
16

逐字段解释:

位置 长度 示例 含义 解析结果
0 1 68 帧起始符 固定
1-32 32 31 32 ... 39 + 23个00 地址域 ASCII 表号 123456789
33 1 00 PT 协议类型
34 1 0A PV 协议版本
35 1 A1 控制码 加密上行,低 4 位功能码为 1
36-37 2 3C 01 帧长度 小端 0x013C = 316
38-39 2 C0 55 DID 小端表示,业务 DID 为 C055
40 1 00 MID 消息序号
41-312 272 [cipher data] 密文 DATA 需要 AES 解密
313-314 2 [crc] CRC 按协议校验
315 1 16 帧结束符 固定

命令码不是帧里直接放一个 01C055,而是由“控制码低 4 位 + DID”组合出来:

C = A1 -> 低 4 位是 1
DID = C055
cmdCode = 01C055

同理:

控制码 DID cmdCode 含义
A1 C055 01C055 日报内容上报
22 C045 02C045 读取表具参数
24 C002 04C002 通信结束帧

4. 设备密钥计算思路

DATA 加密使用设备通信密钥。设备通信密钥通常不是直接写在报文里,而是由主密钥和表号计算出来。

脱敏后的主密钥表示为:

[MASTER_KEY_16_BYTES]

表号 123456789 的 ASCII 为:

31 32 33 34 35 36 37 38 39

密钥计算时,如果表号不足 16 字节,需要按协议约定补齐到 16 字节:

31 32 33 34 35 36 37 38 39 PP PP PP PP PP PP PP

其中 PP 表示协议约定的补齐字节。

然后使用主密钥对补齐后的表号块做 AES 运算,再按协议规则截取设备通信密钥:

deviceKey = AES(masterKey, paddedMeterNo).substring(0, 16 bytes)

因此,同一条业务 DATA 换表号以后,密文 DATA 和 CRC 都会变化,不能只替换 MQTT topic 或地址域。

5. DATA 解密

协议只加密 DATA,不加密整帧。帧头、地址域、PT、PV、控制码、长度、DID、MID、CRC、结束符都是明文。

日报上告中,DATA 区域可以表示为:

[272 bytes encrypted DATA]

使用设备通信密钥解密后,得到 259 字节明文 DATA。脱敏后的结构如下:

[6 bytes meter clock]
[15 bytes IMEI]
[15 bytes IMSI]
[2 bytes RSRP]
[2 bytes RSSI]
[2 bytes SNR]
[8 bytes total cumulative flow]
[8 bytes forward cumulative flow]
[8 bytes reverse cumulative flow]
[2 bytes instantaneous flow]
[2 bytes water temperature]
[...]
[24 records interval freeze data]

从这里开始,才进入字段定义文件或编解码规则的逐项解析。

6. 日报明文 DATA 映射

日报明文 DATA 长度为 259 字节。前 115 字节是实时信息和基础状态,后 144 字节是最近 24 条间隔冻结记录,每条 6 字节。

6.1 实时信息

DATA 偏移 长度 示例 HEX 字段 解析方式 示例解析值
0-5 6 26 05 27 10 20 30 当前表具时钟 HEX_STR 260527102030
6-20 15 31 32 ... IMEI ASCII [masked_imei]
21-35 15 34 36 ... IMSI ASCII [masked_imsi]
36-37 2 00 32 RSRP HEX 50
38-39 2 00 46 RSSI HEX 70
40-41 2 00 1E SNR HEX 30
42-49 8 00 00 00 00 00 01 86 A1 总累积量 HEX,3 位小数 100.001
50-57 8 00 00 00 00 00 01 86 A1 正向累积量 HEX,3 位小数 100.001
58-65 8 00 00 00 00 00 00 00 00 逆向累积量 HEX,3 位小数 0.000
66-67 2 00 7B 瞬时流量 HEX,3 位小数 0.123
68-69 2 09 E6 瞬时水温 HEX,2 位小数 25.34
80-81 2 0E 10 计量电池电压 HEX 3600
82-83 2 0C E4 通信电池电压 HEX 3300
85-86 2 15 01 表具状态字 HEX_STR 1501
87 1 00 上告类型 枚举 定时上报
104 1 0F 口径 枚举 DN15
105-106 2 01 02 版本号 HEX_STR 0102
107-108 2 03 04 固件信息 HEX_STR 0304
109-114 6 26 05 27 00 00 00 最后一次 OTA 更新日期 HEX_STR 260527000000

几个典型换算:

  • 00 00 00 00 00 01 86 A1 是十进制 100001,按 3 位小数解析为 100.001
  • 09 E6 是十进制 2534,按 2 位小数解析为 25.34
  • 状态字通常需要在业务层再映射成告警项,例如存储异常、反流、低电压等。

6.2 最近 24 条间隔冻结信息

从 DATA 偏移 115 开始,是最近 24 条间隔冻结信息。

每条冻结记录固定 6 字节:

4 字节间隔累积量 + 2 字节历史报警信息

前几条示例:

序号 DATA 偏移 原始 HEX 间隔累积量 HEX 解析值 历史报警信息
1 115-120 00 00 30 39 01 00 00003039 12.345 0100
2 121-126 00 00 30 3A 80 00 0000303A 12.346 8000
3 127-132 00 00 30 3B 01 00 0000303B 12.347 0100

日报上告并不是单纯的实时上报,而是:

当前表具状态
+ 当前读数
+ 当前信号
+ 当前电池
+ 当前告警状态
+ 最近 24 条间隔冻结数据

冻结频率可以由表端配置决定,但单次上报携带多少条冻结记录,应以协议定义为准。

7. 通信结束帧

日报上告后,平台通常不需要再额外回复一个独立 ACK。更常见的处理规则是:

表具上报日报
    |
    |-- 有待下发任务:平台先下发业务命令
    |       |
    |       |-- 表具上报命令响应
    |       |
    |       |-- 无更多任务后,平台下发通信结束帧
    |
    |-- 无待下发任务:平台直接下发通信结束帧

通信结束帧的结构仍然遵循完整帧格式:

68
[32 bytes address]
PT
PV
C
L
DID = C002
MID
[encrypted DATA]
CRC
16

解密后的 DATA 通常包含平台时间和预留字段:

DATA 偏移 长度 字段 示例值
0-5 6 时间 YYMMDDhhmmss
6-25 20 预留 全 0

实际联调时,通信结束帧的时间由平台下发时生成,因此完整帧每次都会变化,这是正常现象。

8. 普通命令闭环

普通命令不是平台任意时刻强推给表,而是在表具上告触发时处理待执行任务。

平台创建任务
    |
表具上报日报
    |
平台查询待执行任务
    |
平台发布业务下行到 downstream/{protocol}/{meterNo}
    |
表具响应同一个 DID
    |
平台解密响应 DATA,并按字段定义解码
    |
平台更新任务状态和回执
    |
无更多任务,下发通信结束帧

以“读取表具参数”为例,平台下发时明文 DATA 为空。因为 DATA 加密使用 Padding,即使明文为空,加密后也会产生 16 字节密文 DATA。

表具响应解密后的明文 DATA 示例结构:

00 00                                      // 协议通信错误代码
00 00 00 00 00 00 00 00                    // 预留
09 C4                                      // 超温阈值 25.00
00 3C                                      // 超温持续时间 60
00 00 00 00                                // 预留
07 D0                                      // 过流阈值 2.000
01 2C                                      // 过流持续时间 300
00 0A                                      // 滴漏阈值 10
00 00 0E 10                                // 滴漏持续时间 3600

字段映射:

DATA 偏移 原始 HEX 字段 解析值
0-1 00 00 协议通信错误代码 0000
10-11 09 C4 超温阈值 25.00
12-13 00 3C 超温持续时间 60
18-19 07 D0 过流阈值 2.000
20-21 01 2C 过流持续时间 300
22-23 00 0A 滴漏阈值 10
24-27 00 00 0E 10 滴漏持续时间 3600

最终任务回执可以整理成类似结构:

{
  "functionId": "02C045",
  "success": true,
  "output": {
    "errorCode": "0000",
    "overTemperatureThreshold": "25.00",
    "overTemperatureDuration": "60",
    "overCurrentThreshold": "2.000",
    "overCurrentDuration": "300",
    "leakThreshold": "10",
    "leakDuration": "3600"
  }
}

其他普通命令遵循同一套时序,只是 DATA 定义不同。

命令 当前处理
设置表具参数 响应 errorCode=0000 后任务成功
读取历史间隔冻结数据 响应结果写入任务回执
读取采集服务参数 响应 APN、服务器地址、端口写入任务回执
设置采集服务参数 响应 errorCode=0000 后任务成功
设置密钥参数 成功回执链路可验证;真实密钥切换和异常补偿需要谨慎设计

9. 上告数据如何进入业务

协议插件的核心职责是把二进制协议帧转成平台能理解的设备消息。

典型流程:

MQTT Broker
-> MQTT 网关
-> 协议插件 decode()
-> 帧校验
-> DATA 解密
-> 字段解码
-> 组装设备属性、告警、冻结数据
-> 写入业务系统

常见业务数据包括:

类型 内容
主消息记录 每次上告的完整消息
最新状态 最新读数、电池、温度、状态字
冻结记录 最近 24 条间隔冻结数据
告警记录 当前激活告警及明细
命令任务 命令下发内容、响应结果、任务状态

命令读取类结果是否额外写入业务表,要结合业务需求决定。很多场景只写任务回执即可。

10. 仍需关注的边界

项目 风险点 建议
远程升级 可能涉及分包、多帧、文件校验 单独专项实现和联调
多帧续传 普通命令一般不触发 大数据量场景单独验证
设置密钥 成功后密钥切换会影响后续加解密 设计失败补偿和回滚策略
模拟报文 模拟侧和解析侧可能同时错 必须逐字节对照协议文档

11. 推荐复盘方法

协议开发完成后,建议按下面步骤复盘:

  1. 先按协议文档写出完整帧字段表和字节偏移。
  2. 用一条完整 HEX 报文逐字节标注位置。
  3. 单独截出 DATA 密文。
  4. 按协议规则计算设备通信密钥。
  5. 解密 DATA,得到明文。
  6. 明文 DATA 再按字段定义逐项映射。
  7. 最后验证业务入库、任务回执和前端展示。

只有这七步能完全自洽,协议实现才算真正可解释、可联调、可维护。

12. 总结

这类 NB-IoT 表计协议看起来只是“收一段 HEX、解一段 DATA”,但真正容易出问题的地方往往在细节:

  • 地址域到底是 12 字节、16 字节,还是 32 字节。
  • 长度字段是整帧长度,还是 DATA 长度。
  • 多字节字段是高字节在前,还是低字节在前。
  • 加密的是 DATA,还是整帧。
  • 表号补齐用于地址域,还是用于密钥计算。
  • 命令码是独立字段,还是由控制码和 DID 组合出来。

如果只是写代码,很容易被“测试能跑通”误导。把报文拆到每一个字节,再和协议文档逐项对照,才是协议开发里最可靠的办法。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐