From 7885cede659f3255be56f77c1eef2ada7387d6f1 Mon Sep 17 00:00:00 2001
From: chenyc <501753378@qq.com>
Date: 星期日, 22 三月 2026 16:23:21 +0800
Subject: [PATCH] 初始化项目

---
 docs/current-protocol-decoding.md |  349 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 349 insertions(+), 0 deletions(-)

diff --git a/docs/current-protocol-decoding.md b/docs/current-protocol-decoding.md
new file mode 100644
index 0000000..9ebecaf
--- /dev/null
+++ b/docs/current-protocol-decoding.md
@@ -0,0 +1,349 @@
+# 山外山透析机当前通讯解码说明
+
+> 本文档基于当前项目代码实现、现场抓包校验结果和 `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. **带真实示例帧的版本**(把你已经验证过的十六进制样例附进去)

--
Gitblit v1.8.0