protocol-observations.md 19 KB

故障录波 MQTT 协议观察

整理日期:2026-09-29;最近修订:2026-09-30。本文只记录报文证据、协议描述和待验证项。功能目标见 requirements.md,整体设计见 technical-design.md。协议描述可能过时;发生冲突时以真实报文为准。

2026-10-12 补充:逐字段结构和本轮用户确认见 协议详细描述。用户已明确 377/378 不能依靠 snno 关联;服务端实现的 379/380 可使用 snno 关联,具体赋值规则仍待补充。下文保留原样本事实和当时的观察,不将新约定改写为旧抓包行为。

2026-10-14 补充:业务层字段已由 FaultFilter_MQTT协议文档 定义。下文中与之冲突的旧描述已更正并标注;实报样本事实不变,且与该文档一致。

2026-10-16 补充:新增 2026-10-14 样本,包含文件上传消息 607~610 和可解析的 hexData。用户确认下载地址规律,并决定本工具只监听平台发起的上传。

证据与范围

标记 含义
实报 用户提供的 CloudLog_2026-09-29_063029.csv 中的 8 条真实通讯记录(附件未纳入仓库);以及 CloudLog_2026-10-14_104644.csv 中的 24 条记录
已验证代码 用户确认 service/repositories/mqtt/fwh_mqtt/fwh_mqtt_fault_record.go 已实现的部分协议结构可信
FaultFilter 文档 设备侧故障录波服务的业务协议文档;定义 dataArea 内的业务字段,不覆盖 377~380 外层
描述 用户提供的旧协议说明;与 FaultFilter 文档冲突的部分已作废,其余未被实报或已验证代码覆盖的细节仍待验证

本工具只发送 377,其余消息(378、379、380、607~610)全部监听;380、607、610 由 FWH 平台发出(设计决策,2026-10-16,见 technical-design.md)。2026-09-29 日志只覆盖 cmd=0 和 cmd=2、设备类型 aGate、成功结果。cmd=1、cmd=3、pe 设备类型及失败结果没有实报样本。

报文外层与消息方向

实报中的四类消息均有 cmdType、equipNo、type、timeStamp、snno、len、crc、dataArea 字段。样本的 equipNo 均为 10060010A01X26310190,type=0。现有发送代码将 len 设为序列化后 dataArea 的字节长度,并计算其 CRC32 IEEE 大写十六进制值;接收报文是否始终采用同一计算方式,尚未单独验证。

cmdType 方向 作用 dataArea
377 服务端 → 设备 故障录波操作指令 cmd、data
378 设备 → 服务端 对 377 的应答 cmd、result
379 设备 → 服务端 故障录波信息上传请求 cmd、result、按命令出现的 list / filename
380 服务端 → 设备 对 379 的应答 cmd、result

378 是对指令的应答,业务数据或文件处理结果出现在后续 379。不能只凭 378.result=0 判定录波数据已经取得。CSV 没有 MQTT topic;当前客户端代码发送到 fwh/s2c/mng/{sn},等待 fwh/c2s/mng/{sn} 的应答,但 379/380 的实际 topic 仍需确认。

379.dataArea 的 result/list/filename 与 FaultFilter 文档回复中 data 对象的内容一致,只是没有 data 包裹,推断 379 承载的就是 FaultFilter 回复。

377:服务端请求

dataArea 的形状为 { "cmd": <整数>, "data": { "list": [...] } }。list 和每项的 info 均为数组。以下字段按每个 list[] / info[] 项列出。

cmd 作用 list[].type list[].info[] 证据
0 获取实时数据;code=3 即故障录波总目录 实报为 aGate sn 字符串;codeList 整数数组(1~3;4/5 只能经 cmd=2 获取) aGate、codeList=[3] 为实报;code 字典来自 FaultFilter 文档
1 设置参数 已验证代码发送 pe 或 aGate sn 字符串;paraList 为 {id: 功能码, setVal: 十六进制字符串} 数组 已验证代码;本次无实报
2 获取记录 实报为 aGate sn 字符串;index 整数数组,元素为目录中的记录序号 aGate、index=[9] 为实报;旧描述的范围 1~16 未获 FaultFilter 文档支持,以目录为准
3 查询参数 — sn 字符串;idList 功能码数组 仅 FaultFilter 文档,无实报
4 / 90 / 99 退出/连通性测试 — data 为 {} 仅 FaultFilter 文档,无实报

旧描述将部分 type 或 sn 写为 string[],而真实报文及已验证代码对应的是单个字符串字段。旧描述列出的 agate、mppt、apbox 不在 FaultFilter 文档中;业务层设备类型只有 pe 和 aGate。

378:设备对 377 的应答

dataArea 包含 cmd 和整数 result。描述称 result=0 为成功,result=-1 为失败。实报覆盖 cmd=0 和 cmd=2 的 result=0;失败值及其后续行为待验证。FaultFilter 文档不定义 378。

379:设备上传

dataArea.result 为总结果字符串:"ok" 全部子项成功,"error" 存在子项失败;命令无法进入处理时为错误说明(如 busy、pe sn not find)。实报中的 cmd=0 和 cmd=2 均为 "ok"。dataArea.list 是数组,每项包含 type 和 info[]。

cmd dataArea 中的业务字段 证据
0 list[].type;info[].sn;info[].hexList[],每项含 code 整数、resultOne 字符串、hexData 十六进制字符串 实报覆盖:type="aGate"、code=3、resultOne="ok",且有 hexData。hexData 为子设备原始字节,code=3 的结构为 1 字节条数 + N × 14 字节目录项(FaultFilter 文档 5.1.1)
1 list[].type、info[].sn、info[].setResultList[{id, resultOne}] FaultFilter 文档。旧描述的 resultList[{id, result}](result 为 string[])有误;resultOne 为字符串
2 filename 字符串;list[].type;info[].sn;info[].indexInfo[],每项含 index 整数及 resultOne 字符串 实报覆盖:filename="2026-09-29_14_18_29",type="aGate",index 为 4 和 5,resultOne 均为 "ok"。index 是数据类型码(4=故障数据,5=波形数据),不是记录序号
3 list[].type、info[].sn、info[].idInfo[{id, idData, resultOne}] 仅 FaultFilter 文档

旧描述称 379 的设备类型可为 aPower,FaultFilter 文档未定义该值,实报仅见 aGate。旧描述将 cmd=2 的 sn、resultOne 写为数组,实报中二者都是字符串。cmd=2 的 379 只显示文件名和逐项结果,没有录波文件字节或下载地址:文件保存在设备本地 /home/root/ExportFile/faultFilterRecode/<filename>/<sn>/index_<记录序号>/,随后由平台经 607~610 触发上传,见 2026-10-14 样本。

380:服务端对 379 的应答

dataArea 包含 cmd 和整数 result。描述称 result=0 成功、result=-1 失败。实报覆盖 cmd=0 和 cmd=2 的 result=0;cmd=1 及失败值待验证。FaultFilter 文档不定义 380。

实报时间线

CSV 倒序展示;下表按 logTime 正序排列。logTime 比同一行的 time 晚 8 小时,与本地时间和 UTC 的差值一致。

本地时间 消息 关键字段
14:17:35 377 cmd=0,aGate,codeList=[3],snno=7529
14:17:35 378 cmd=0,result=0,snno=13
14:17:38 379 cmd=0,result="ok",hexList 中 code=3、resultOne="ok"、hexData,snno=14
14:17:46 380 cmd=0,result=0,snno=7536
14:17:47 377 cmd=2,aGate,index=[9],snno=7539
14:17:47 378 cmd=2,result=0,snno=22
14:18:31 379 cmd=2,result="ok",filename="2026-09-29_14_18_29",indexInfo 为 4/ok、5/ok,snno=26
14:18:33 380 cmd=2,result=0,snno=7559

请求 cmd=0 后约 3 秒出现 379;请求 cmd=2 后约 44 秒出现 379。这些是单次样本的观察值,不是超时规则。请求、应答和上传的 snno 不相同;尚不能用它直接关联消息。

该样本没有记录 607~610;当时是否发生上传无法从该日志判断。

2026-10-14 样本

来源:CloudLog_2026-10-14_104644.csv,测试服,网关 AFCI_TEST_0713,aGate,外层 equipNo 与 info[].sn 相同。包含两轮完全相同的操作(05:36 和 05:45),每轮依次为 cmd=0, codeList=[3] 和 cmd=2, index=[7]。日志未标明 377 的发送方。

第二轮时间线(logTime,UTC):

时间 消息 关键字段
05:45:25 377 cmd=0,codeList=[3],snno=6008
05:45:25 378 cmd=0,result=0
05:45:28 379 cmd=0,result="ok",hexList 中 code=3、resultOne="ok",hexData 见下
05:45:36 380 cmd=0,result=0
05:45:38 377 cmd=2,index=[7]
05:45:38 378 cmd=2,result=0
05:46:21 379 cmd=2,result="ok",filename="2026-10-14_05_46_20",indexInfo 为 4/ok、5/ok
05:46:21 607 faultRecordID=5226,fileName="2026-10-14_05_46_20",url="http://test.franklinwh.com/hes-upload/v1/file/upload",logTime="2026-10-14"
05:46:21 608 {"result":0,"filename":"2026-10-14_05_46_20","result":5226}(第二个 result 实为误写的 id,用户确认,2026-10-16)
05:46:22 380 cmd=2,result=0
05:46:33 609 result=0,fileName="2026-10-14_05_46_20.tar.bz2"
05:46:33 610 result=1

第一轮(05:36~05:37)顺序与字段结构相同,faultRecordID=5224、filename="2026-10-14_05_37_32",607→609 约 11 秒。

观察:

  • 607 在 379(cmd=2) 同一秒发出,早于 380,带平台召唤记录 ID,推断平台收到 379 后自动发起上传。用户确认本工具只监听这一过程(2026-10-16)。
  • 下载地址由用户确认为 https://test.franklinwh.com/hes-upload/v1/file/download/logger/AFCI_TEST_0713/2026-10-14_05_46_20.tar.bz2,即 logger/<equipNo>/<609.fileName>;生产服替换域名。
  • 610.result=1,而 378/380 成功为 0,不同消息的成功值不一致。
  • 两轮 cmd=0 的 hexData 完全相同。

code=3 目录解析(211 字节 = 1 + 15 × 14,结构与 FaultFilter 文档 5.1.1 一致):

序号 发生时间 参数列表版本 触发源
1 2024-01-01 00:00:34 00000001 0x1002
2 2026-07-14 02:53:35 00000001 0x1001
3 2026-08-12 02:03:05 00000001 0x1001
4 2026-08-13 21:40:40 00000001 0x1001
5 2026-08-14 03:24:16 00000001 0x1001
6 2026-08-16 03:58:05 00000001 0x1001
7 2026-08-18 03:03:08 00000001 0x1001
8 2025-11-24 01:11:46 00000001 0x1001
9 2025-11-26 21:44:06 00000001 0x1001
10 2023-05-04 22:01:03 00000001 0x1002
11 2023-05-04 22:02:33 00000001 0x1002
12 2023-05-04 22:03:40 00000001 0x1002
13 2023-05-04 22:04:46 00000001 0x1002
14 2023-05-04 22:28:14 00000001 0x1002
15 2026-06-29 15:07:35 00000001 0x1001

目录不按时间排序,像是循环覆盖存储;第 1 条时间疑似设备未校时。

2026-10-14 录波文件解析

来源:2026-10-14_05_46_20.tar.bz2,即上表 609.fileName 对应的文件,按 logger/<equipNo>/<fileName> 规律下载。用户确认下载不需要 token(2026-10-16)。

压缩包

  • 扩展名是 .tar.bz2,实际是 gzip 压缩的 tar(文件头 1f 8b),用 bzip2 解压会失败。解压时应按文件头判断格式,不能信任扩展名。
  • 目录结构与 FaultFilter 文档 5.2.1 一致,但没有 faultFilterRecode 根目录:

    2026-10-14_05_46_20/AFCI_TEST_0713/index_7/failuredata    1024 字符(512 字节)
    2026-10-14_05_46_20/AFCI_TEST_0713/index_7/waveformdata  98304 字符(49152 字节)
    
  • 文件内容为大写十六进制 ASCII 文本,无换行,与文档一致。<sn> 为 info[].sn(本例与网关 SN 相同)。

failuredata(按 FaultFilter 5.2.2 解析)

字段 值 说明
单一故障信息数据长度 0x0200 = 512 等于整个文件的字节数(含本字段和末尾填充),不是有效数据长度
基本信息 CAN 协议版本 1;存储格式版本 1;参数列表版本 00000001;最大采样频率 4500;采样频率 4500;采样点总数 1536;故障前 1024;故障后 512 与 code=2 结构一致
位置索引 7 与请求 index=[7] 一致
故障发生时间 2026-08-18 03:03:08 与目录第 7 条一致
事件触发源 0x1001 并网发生状态切换故障 与目录一致
DCDC 版本 EMS_SL:V10R80B21D00,NUL 填充 aGate 中为从 DSP 版本
INV/FPGA 版本 字节 01 02 03 … 40 aGate 中为填充,填充方式不限定(用户确认,2026-10-16);解析时跳过
快照通道 总数 6,已用 6:0x10=2049、0x11=0、0x12=0、0x13=4096、0x05=1、0x04=0
采样通道 总数 16,已用 16:0x10 0x11 0x12 0x13 0x3016 0x3017 0x3013 0x3014 0x3010 0x3011 0x3022 0x3023 0x01 0x03 0x03 0x01 最后 4 项与可配采样通道的类型码相同,见下
可配快照通道 总数 0
可配采样通道 总数 4,已配 4:Uint16@0x00014990、float32@0x00001510、float32@0x00001512、Uint16@0x0001476E
结构结束位置 第 308 字节 剩余 204 字节全部为 0xFF 填充

waveformdata(按 FaultFilter 5.2.3 解析)

  • 49152 字节 = 16 通道 × 1536 点 × 2 字节,恰好整除。通道数 n = failuredata 的“采样通道总数”(16),点数 m = 基本信息的“采样点总数”(1536)。
  • “采样点优先”排列得到确认:按此排列,相邻点平均变化约 18;若按通道优先排列,平均变化约 2388(无意义)。
  • 采样通道列表的最后 4 项对应 4 个可配采样通道,即可配采样通道已包含在 16 个采样通道中,不另外追加。其“含义”位置填的是类型码(0x01/0x03),不是 F14 参数代码。
  • 每个通道每点固定 2 字节,包括类型为 float32 的可配通道。第 13、14 通道(float32)的值范围为 -248~249,看起来像 16 位整数,float32 如何压成 16 位待确认。
  • 数值应按有符号 int16 解释:第 4~7 通道(0x3016/0x3017/0x3013/0x3014)是幅值约 ±2200 的正弦波,按 Uint16 解释会在过零处跳变。文档写的是 Uint16。
  • 采样频率得到验证:第 4、5 通道过零周期约 90 点,在 4500 Hz 下即 50 Hz。
  • 故障时刻对齐得到验证:第 0 通道(0x10)在第 1024 点从 0x0000 变为 0x0801,正好等于故障前采样点数。快照 0x10=2049(0x801)等于故障后的值,快照 0x13=4096 等于第 3 通道故障前的值 0x1000。
通道 含义代码 特征
0~3 0x10~0x13 状态/故障字,只有少数几个离散值
4~7 0x3016 0x3017 0x3013 0x3014 50 Hz 正弦,±2200 左右
8~11 0x3010 0x3011 0x3022 0x3023 小幅值噪声(-36~31)
12 可配 Uint16 0x14990 3 个离散值(0x3020、0x3900、0x1900)
13~14 可配 float32 0x1510/0x1512 -248~249
15 可配 Uint16 0x1476E 4 或 5

各通道的物理量和换算系数需要 F14《参数代码表》,目前只能展示原始值。

2026-10-19 测试服实测:607~610 的 topic

测试服 EamonAux 的 record_fetch(05:20 UTC):mng/{SN} 上收到 378、379、380,但 5 分钟内没有 607~610,最终靠超时后按规则直接下载完成,faultRecordId 为空。用户确认 607~610 走 debug topic:607、610 为 fwh/s2c/debug/{SN},608、609 为 fwh/c2s/debug/{SN}。已改为录波获取时同时订阅这两个 topic。

待验证事项

  1. cmd=2 请求 index=[9],随后上传的 indexInfo 却是索引 4、5。 已由 FaultFilter 文档解释:每个记录序号在回复中展开为类型码 4(故障数据)和 5(波形数据),两者对应同一请求。
  2. cmd=0 的 hexData 代表什么。 已由 FaultFilter 文档定义,并已用 2026-10-14 样本验证 code=3 结构。code=1/2 仍无样本。
  3. cmd=2 的录波文件从设备传到服务端的通道。 已由 2026-10-14 样本确认为 607~610 触发的 HTTP 上传,下载地址规律已确认。下载不需要 token,压缩包结构已确认(见 2026-10-14 录波文件解析)。仍待确认:平台对本工具直连发送的 377 是否同样自动上传。
  4. 录波文件解析中与 FaultFilter 文档不一致或文档未说明的地方:压缩格式实为 gzip;failuredata 长度字段含义;采样值有无符号;float32 可配通道如何存成 16 位。(aGate 的 INV/FPGA 版本字段已确认为填充,解析时跳过。)见 2026-10-14 录波文件解析。
  5. 379 上传和 380 应答的实际 topic、应答时限、重发机制及消息关联规则。
  6. cmd=1/cmd=3 的 379 实报,以及各命令的失败报文在外层的表现。结构本身已由 FaultFilter 文档定义(setResultList/idInfo)。
  7. pe 设备的实际寻址(网关 SN 与子设备 SN 不同时的报文);批量记录序号时多组 4/5 的顺序。

下一轮实测需要记录的内容

优先级 实测场景 必须保留的证据 要解决的问题
1 pe 子设备、单个 code 的完整 cmd=0 查询 操作时间、网关 SN、子设备类型/SN、双向 topic、377~380 原始报文和设备界面中的对应记录 按 FaultFilter 文档解析 hexData 验证;网关与子设备 SN 关系
1 本工具直连发送 377(cmd=2),测试服和生产服各一次 全部双向报文(含 607~610)、topic、按规律下载的 tar.bz2 平台是否自动上传;s2c 能否订阅;下载认证与压缩包结构
1 cmd=3 读取一项已知功能码(如 id=5 采样频率) 请求/返回报文、功能码和当前值 cmd=3 经外层转发的实际表现
2 cmd=1 设置一项参数并用 cmd=3 复读确认 377~380 全链路、设置前后值、单项结果 378 与 379 各自的成功语义,setResultList 实际结构
2 设备忙、SN 不存在、参数设置失败及超时 完整错误报文、重发次数与间隔 busy 等错误字符串在外层的表现、378 失败值、超时和重试规则

新增样本时,先保留原始报文、topic、时间和触发操作,再将“待验证”条目逐项升级为已确认事实。当前代码入口和实现边界见 implementation-status.md。