# 山外山透析机当前通讯解码说明 > 本文档基于当前项目代码实现、现场抓包校验结果和 `schema.json` 物模型整理。 > > 适用项目:`SWS-Communication` > > 主要对应文件: > - `src/protocol.js` > - `src/tcpServer.js` > - `schema.json` ## 1. 总体说明 当前网关通过 TCP 接收山外山透析机上报的数据帧,按帧类型拆分并解码为结构化对象,然后写入缓存、输出本地日志,并按配置转发到 MQTT / 阿里云。 当前已实现并验证的帧类型: - `0x1F`:运行参数帧 - `0x26`:报警帧 - `0x29`:血压测量帧 ## 2. 帧结构 ### 2.1 公共头结构 每一帧的公共头长度固定为 `20` 字节: | 偏移 | 长度 | 含义 | |---|---:|---| | 0 | 4 | 帧头,固定为 `55 55 55 55` | | 4 | 1 | 机器类型 | | 5 | 5 | 机器编号 | | 10 | 1 | 机器运行模式 | | 11 | 1 | 数据帧类型 | | 12 | 1 | 协议版本 | | 13 | 7 | 保留 | ### 2.2 机器类型 当前代码映射如下: | 值 | 机型 | |---|---| | `0x01` | `SWS-4000` | | `0x02` | `SWS-4000A` | | `0x31` | `SWS-6000` | | `0x32` | `SWS-6000A` | ### 2.3 机器编号 当前实现已根据现场数据确认: - 机器编号为 **5 字节无符号整数** - 编码方式为 **小端** 例如: - 字节:`E1 73 CB 72 01` - 解码后:`6220903393` 项目中对应字段: - `n`:机器编号字符串 ### 2.4 运行模式 当前已实现的运行模式映射: | 值 | 含义 | |---|---| | `0x00` | 待机 | | `0x01` | 透析 | | `0x02` | 滤过 | | `0x03` | 透析滤过 | | `0x04` | 序贯-透析 | | `0x05` | 单纯超滤 | | `0x06` | 序贯-单超 | | `0x07` | 预充 | | `0x08` | 清洗 | | `0x09` | 清洗消毒 | | `0x14` | 透析结束 | | `0x15` | 清洗结束 | | `0x16` | 透析滤过结束 | | `0x17` | 单纯超滤结束 | ## 3. 帧长度与拆包规则 当前代码和现场抓包已对齐的长度如下: | 帧类型 | 含义 | 整帧长度 | 数据区长度 | |---|---|---:|---:| | `0x1F` | 运行参数帧 | `220` 字节 | `200` 字节 | | `0x26` | 报警帧 | `150` 字节 | `130` 字节 | | `0x29` | 血压帧 | `150` 字节 | `130` 字节 | 说明: - 当前拆包逻辑按 `20 字节公共头 + 数据区长度` 计算整帧。 - 若当前 buffer 中没有找到帧头,则只保留最后 `3` 字节,防止垃圾数据导致缓冲区无限增长。 - 每个连接当前有 buffer 上限保护,避免异常设备把内存打满。 ## 4. 字节序说明 当前项目中经过现场验证后的字节序规则如下: ### 4.1 运行参数帧 `0x1F` - 多字节字段按 **小端** 解码 - 压力类字段按 **有符号小端 16 位** 解码 对应: - `readUInt16LE` - `readUInt32LE` - `readInt16LE` ### 4.2 报警帧 `0x26` - 报警编号:**小端** - 年份:**小端** - 月/日/时/分/秒:单字节 ### 4.3 血压帧 `0x29` - 年份、收缩压、舒张压、脉搏、平均动脉压:均按 **小端** 解码 ## 5. 运行参数帧 `0x1F` ## 5.1 当前解码字段 当前代码 `parseRunParams()` 中已经实现并使用的字段如下: | 偏移 | 长度 | 协议含义 | 当前字段 | 当前保存方式 | |---|---:|---|---|---| | 0 | 4 | 设置治疗时间(秒) | `SetTreatmentTime` | **转换为分钟后保存** | | 4 | 4 | 已治疗时间(秒) | `K` | **转换为分钟后保存** | | 8 | 2 | 血泵流量(ml/min) | `D` | 原值 | | 10 | 1 | 血泵运行标志 | `xlyxbj` | 原值 | | 11 | 1 | 抗凝方式 | `klfs` | 原值 | | 12 | 1 | 肝素泵运行标志 | `z` | 原值 | | 13 | 2 | 肝素泵流量(放大 10 倍) | `E` | 除以 `10` | | 15 | 2 | 肝素提前结束时间(min) | `gstqjssj` | 原值 | | 17 | 4 | 超滤总量(ml) | `A` | **转换为 L,保留 3 位小数** | | 21 | 4 | 已超滤量(ml) | `B` | **转换为 L,保留 3 位小数** | | 25 | 4 | 超滤率(ml/h) | `C` | 原值 | | 29 | 1 | 超滤泵运行标志 | `cllyxbj` | 原值 | | 30 | 1 | 旁路标志 | `plbj` | 原值 | | 31 | 2 | 透析液流量(ml/min) | `L` | 原值 | | 33 | 2 | 透析液实际温度(放大 10 倍) | `F` | 除以 `10` | | 35 | 2 | 透析液电导值(放大 100 倍) | `G` | 除以 `100` | | 37 | 4 | 补液总量(ml) | `pyzl` | 原值 | | 41 | 4 | 已补入置换液量(ml) | `ypyzhyl` | 原值 | | 45 | 1 | 补液补入模式 | `pyprfs` | 原值 | | 46 | 2 | 内毒素滤器1使用时间(h) | `ldslq` | 原值 | | 48 | 2 | 内毒素滤器2使用时间(h) | `ldslq2sysj` | 原值 | | 50 | 4 | 机器总运行时间(min) | `jqzyxsj` | 原值 | | 54 | 2 | 动脉压(mmHg) | `o` | `readInt16LE` | | 56 | 2 | 静脉压(mmHg) | `H` | `readInt16LE` | | 58 | 2 | 跨膜压(mmHg) | `J` | `readInt16LE` | | 60 | 2 | 透析液压(kPa × 10) | `I` | `readInt16LE` 后除以 `10` | | 62 | 1 | 尿素下降率(%) | `lsxjl` | 原值 | | 64 | 2 | 实时清除率值(放大 100 倍) | `ssqclz` | 除以 `100` | | 66 | 2 | 静脉血温(放大 10 倍) | `jmyxh` | 除以 `10` | | 68 | 2 | 动脉血温(放大 10 倍) | `dmyxw` | 除以 `10` | | 69 | 1 | 相对血容量(%) | `xdxrl` | 原值 | ## 5.2 当前业务换算说明 ### 时间字段 协议原始值单位为“秒”,但当前项目保存为“分钟”: - `SetTreatmentTime = Math.round(秒 / 60)` - `K = Math.round(秒 / 60)` 说明: - 日志和上报中看到的 `SetTreatmentTime`、`K` 现在是“分钟值”。 - `schema.json` 中字段名仍保留旧命名(如 `已透析时间s`),但当前代码语义已改为“分钟”。 ### 超滤量字段 协议原始值单位为 `ml`,当前保存为 `L`: - `A = (ml / 1000).toFixed(3)` - `B = (ml / 1000).toFixed(3)` 示例: - `1800 ml` -> `1.800` - `500 ml` -> `0.500` ## 5.3 压力字段说明 现场抓包已确认以下字段必须按 **有符号数** 解析: - `o`:动脉压 - `H`:静脉压 - `J`:跨膜压 - `I`:透析液压 原因: - 动脉压、透析液压等字段可能出现负值 - 若按无符号解析,会出现类似 `65411` 这样的异常大正数 ## 6. 报警帧 `0x26` 当前代码 `parseAlarm()` 解码规则: | 偏移 | 长度 | 含义 | 当前字段 | |---|---:|---|---| | 0 | 2 | 报警编号(小端) | `alarmCode` | | 2 | 1 | 报警类型(0=解除,1=产生) | `bjlx` | | 3 | 2 | 年(小端) | `bjsj` 组成部分 | | 5 | 1 | 月 | `bjsj` 组成部分 | | 6 | 1 | 日 | `bjsj` 组成部分 | | 7 | 1 | 时 | `bjsj` 组成部分 | | 8 | 1 | 分 | `bjsj` 组成部分 | | 9 | 1 | 秒 | `bjsj` 组成部分 | 输出字段: ```json { "alarmCode": 283, "bjlx": 1, "bjsj": "2026-03-16 10:30:45" } ``` 当前已通过现场帧验证: - 报警编号需按小端读取 - 年份需按小端读取 - 报警产生/解除状态可以正确区分 ## 7. 血压帧 `0x29` 当前代码 `parseBloodPressure()` 解码规则: | 偏移 | 长度 | 含义 | 当前字段 | |---|---:|---|---| | 0 | 1 | 测量模式(0/1=手动/自动) | `bpMode` | | 1 | 1 | 测量结果(0/1=失败/成功) | `bpResult` | | 2 | 2 | 年(小端) | `M` 组成部分 | | 4 | 1 | 月 | `M` 组成部分 | | 5 | 1 | 日 | `M` 组成部分 | | 6 | 1 | 时 | `M` 组成部分 | | 7 | 1 | 分 | `M` 组成部分 | | 8 | 1 | 秒 | `M` 组成部分 | | 9 | 2 | 收缩压(小端) | `N` | | 11 | 2 | 舒张压(小端) | `O` | | 13 | 2 | 脉搏(小端) | `P` | | 15 | 2 | 平均动脉压(小端) | `BPMPJDMY` | 输出字段: ```json { "bpMode": 1, "bpResult": 1, "M": "2026-03-16 10:32:15", "N": 143, "O": 98, "P": 80, "BPMPJDMY": 113 } ``` 当前已通过现场帧验证: - 收缩压、舒张压、脉搏、平均压均为小端 - 偏移为 `9 / 11 / 13 / 15` ## 8. 网关输出字段 每次成功解析后,当前统一输出的基础字段包括: | 字段 | 含义 | |---|---| | `suedtime` | 网关接收时间 | | `deviceType` | 机器类型 | | `IPAddress` | 设备来源 IP | | `n` | 机器编号 | | `jqyxms` | 机器运行模式(中文) | | `jqyxmsRaw` | 机器运行模式原始值 | | `frameType` | 帧类型 | | `protocolVersion` | 协议版本 | 然后按帧类型追加: - 运行参数字段 - 报警字段 - 血压字段 ## 9. 日志追踪 当前项目已经接入本地日志,并可追踪以下关键链路: - `Device connected`:设备什么时间连上 - `Raw TCP data received`:设备发来了什么原始十六进制数据 - `Parsed frame` / `Dialysis frame parsed`:报文解析成功 - `Dialysis frame payload`:完整解析对象 - `MQTT publish success`:MQTT 上报成功 - `Aliyun postProps success`:阿里云上报成功 - `Device disconnected`:设备何时断开、在线多久、收了多少字节和帧 ## 10. 当前注意事项 ### 10.1 `schema.json` 与当前业务语义存在部分差异 当前代码已经做了业务换算,但 `schema.json` 的部分名称还是旧含义: - `K` 的名称仍写成“已透析时间s”,但当前保存值是“分钟” - `SetTreatmentTime` 当前也已经改为“分钟” - `A/B` 在 `schema.json` 中是文本类型,当前保存为升值字符串,例如 `1.800` 如果后续要进一步规范,建议: - 把时间字段名称改成“分钟” - 把 `A/B` 改成 `float/double` 类型 ### 10.2 当前文档以“现有实现”为准 本文档的目标是说明:**当前程序实际是怎么解码和上报的**,便于联调、排障、交付。 如果后续协议理解继续修正,建议同步维护: - `src/protocol.js` - `schema.json` - `docs/current-protocol-decoding.md` ## 11. 建议的后续维护方式 每次现场确认新字段时,建议按下面顺序更新: 1. 在 `src/protocol.js` 中补偏移和解码逻辑 2. 在 `schema.json` 中补 identifier 与字段说明 3. 在本文档中追加“偏移 / 长度 / 含义 / 当前字段 / 单位换算” 4. 保留一条真实抓包样例,方便后续回归验证 --- 如果需要,我下一步还可以继续给你输出一份: 1. **更适合交付甲方的版本**(偏业务说明,少代码痕迹) 2. **更适合开发维护的版本**(带完整偏移表和字段映射) 3. **带真实示例帧的版本**(把你已经验证过的十六进制样例附进去)