# 完整协议说明

## 1. 协议模式总览

本项目在同一条 BLE 透传链路上支持四种工作模式。模式由设置页 `protocolMode` 控制，通讯页和参数页会按当前模式切换 UI、参数分组和自动读取协议。

| 模式 | key | 通讯页 | 参数页 | 说明 |
|---|---|---|---|---|
| 无协议 | `none` | 串口发送卡片、日志卡片 | 不显示协议参数组 | 只做原始字节透传和日志观察 |
| 标准 Modbus | `modbus-rtu` | 标准 Modbus 指令卡片、日志卡片 | Modbus 寄存器组 | 使用从机地址、功能码和 Modbus CRC |
| 存储访问 | `storage-access` | 同步、CodeInfo、特殊指令、读写卡片、日志卡片 | 存储访问结构体组/单变量组 | 按字节访问 DATA、IDATA、XDATA、CODE 或 32 位统一地址空间 |
| Bootloader | 设置页升级工具 | Bootloader 工具卡片 | 不参与参数页 | 独立升级协议，不和 Modbus/存储访问混用 |

切换协议时，参数页会同步切换对应协议的分组集合。自动读取运行中如果检测到协议变化，会停止当前读取循环，避免用旧协议继续访问新模式下的参数组。

## 2. 公共链路规则

所有协议均通过 BLE 透传发送原始字节。发送队列、响应等待、日志和超时由 `transport/ble-core.js` 统一处理。

| 项目 | 规则 |
|---|---|
| 发送单位 | 原始字节数组 |
| 日志显示 | HEX 或文本两种显示方式 |
| 响应识别 | 由 `protocols/transport-helpers.js` 根据待响应协议调用对应解析器 |
| 包长限制 | 设置页最大包长用于 Modbus 和存储访问分片，`0` 表示不限制 |
| 参数自动读取 | 由设置页“自动轮询”开关控制，参数页“读取”只执行一次全量读取 |

## 3. 无协议模式

无协议模式不解析帧格式，也不绑定参数页分组。

| 功能 | 行为 |
|---|---|
| 发送 | 通讯页原始串口发送卡片支持 HEX 或文本 |
| 日志 | 显示发送和接收日志 |
| 响应 | 不要求特定响应帧 |
| 参数页 | 不显示标准 Modbus 或存储访问参数组 |

适用场景：临时调试私有串口命令、观察设备透传输出、验证蓝牙链路连通性。

## 4. 标准 Modbus RTU

标准 Modbus 使用常规 RTU 帧格式：

```text
SLAVE FUNC DATA... CRC_L CRC_H
```

CRC 使用 `CRC16-Modbus`，低字节在前。

| 参数 | 值 |
|---|---|
| 多项式 | `0x8005` 反射形式 |
| 初值 | `0xFFFF` |
| 输入反转 | 是 |
| 输出反转 | 是 |
| 结果异或 | `0x0000` |
| 输出顺序 | 低字节在前 |

支持功能码：

| 功能码 | 名称 | 方向 | 参数页用途 |
|---:|---|---|---|
| `0x01` | 读线圈 | 读 | bit/coil 类型参数组 |
| `0x02` | 读离散输入 | 读 | 只读 bit 类型参数组 |
| `0x03` | 读保持寄存器 | 读 | 可读写寄存器组 |
| `0x04` | 读输入寄存器 | 读 | 只读寄存器组 |
| `0x05` | 写单线圈 | 写 | 单个 bit 写入 |
| `0x06` | 写单寄存器 | 写 | 单个 16 位寄存器写入 |
| `0x10` | 写多个寄存器 | 写 | 连续寄存器批量写入 |

数量限制按当前最大包长计算。默认 64 字节包长下，`0x03/0x04` 单帧最多读取 29 个寄存器，`0x10` 单帧最多写入 27 个寄存器。超过限制时参数页会按连续片段分片读取或写入。

异常响应格式：

```text
SLAVE (FUNC | 0x80) EXCEPTION_CODE CRC_L CRC_H
```

标准 Modbus 与存储访问完全独立，不解析 CodeInfo TLV 信息块，也不使用存储访问的 `CMD/AREA`。

## 5. 存储访问协议

存储访问协议不带从机地址，不属于标准 Modbus。普通内存访问帧格式为：

```text
CMD ADDR LEN DATA... CRC_H CRC_L
```

CRC 使用 `CRC16-CCITT-FALSE`，高字节在前。所有多字节协议字段均为大端序。

`CMD` 位定义：

```text
bit7      ERR       异常标志
bit6      CTL       特殊指令标志位
bit5~bit4 RSV       保留，普通读写保持 0
bit3      RW        读写标志，0=读，1=写
bit2~bit0 MODE      地址模式或存储区域
```

地址模式与存储区域：

| MODE | 名称 | 地址宽度 | 读 | 写 | 说明 |
|---:|---|---|---|---|---|
| `0x00` | 保留 | - | 禁止 | 禁止 | 保留 |
| `0x01` | DATA | 16 位 | 支持 | 支持 | 内部直接寻址 RAM |
| `0x02` | IDATA | 16 位 | 支持 | 支持 | 内部间接寻址 RAM |
| `0x03` | XDATA | 16 位 | 支持 | 支持 | 外部数据空间或扩展 RAM |
| `0x04` | CODE | 16 位 | 支持 | 禁止 | 程序存储区 |
| `0x05..0x06` | 保留 | - | 禁止 | 禁止 | 保留 |
| `0x07` | ADDR32 | 32 位 | 支持 | 支持 | 统一 32 位字节地址 |

普通读请求：

```text
CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L CRC_H CRC_L
或
CMD ADDR_H ADDR_L LEN_H LEN_L CRC_H CRC_L
```

普通读响应回显 `CMD`、`ADDR`、`LEN`，然后携带 `LEN` 字节数据。

普通写请求：

```text
CMD ADDR_3 ADDR_2 ADDR_1 ADDR_0 LEN_H LEN_L DATA... CRC_H CRC_L
或
CMD ADDR_H ADDR_L LEN_H LEN_L DATA... CRC_H CRC_L
```

普通写响应只回显 `CMD`、`ADDR`、`LEN`。写 CODE 区必须返回写保护异常。

特殊指令使用固定 `CMD=0x4F`：

```text
请求: 4F OP DATA... CRC_H CRC_L
响应: 4F OP STATUS DATA... CRC_H CRC_L
异常: CF EXCEPTION_CODE CRC_H CRC_L
```

特殊指令表：

| OP | 名称 | 请求 DATA | 成功响应 DATA | 用途 |
|---:|---|---|---|---|
| `0x01` | RESET | 无 | 无 | 复位 |
| `0x02` | START | 无 | 无 | 启动 |
| `0x03` | STOP | 无 | 无 | 停止 |
| `0x04` | SET_CONTROL_REF | `REF_H REF_L` | 无 | 控制参考值，`int16_t` 大端 |
| `0x05` | READ_CODE_INFO_DESCRIPTOR | 无 | `CODE_ADDR32 + CODE_LEN16 + ADDR_WIDTH8 + MEMORY_ENDIAN16 + MAX_PACKET16` | 读取 CodeInfo bootstrap 描述符 |

完整帧细节、异常码、示例和从机参考见 `存储访问协议.md`。

## 6. CodeInfo 与参数组同步

存储访问同步固定分两步：

1. 发送特殊指令 `4F 05 CRC_H CRC_L`，读取 CodeInfo bootstrap 描述符。
2. 根据 bootstrap 返回的 `CODE_ADDR32 + CODE_LEN16 + ADDR_WIDTH8 + MEMORY_ENDIAN16 + MAX_PACKET16` 读取完整 CodeInfo TLV 信息块。`ADDR_WIDTH=32` 使用 `MODE=0x07 ADDR32`，`ADDR_WIDTH=16` 使用 `MODE=0x04 CODE`；`MAX_PACKET` 非 0 时与设置页最大包长取较小值。

bootstrap 中 `MEMORY_ENDIAN16` 使用原始字节标记目标内存变量字节序：`55 AA` 表示变量值大端，`AA 55` 表示变量值小端。多字节协议控制字段始终固定大端，包括 `CMD`、`ADDR`、普通读写 `LEN`、`CODE_LEN` 和 `MAX_PACKET`；TLV `LEN` 为单字节字段，不涉及大小端。

CodeInfo 信息块使用纯 TLV 格式，不再包含固定头、`format_version`、`board_info_format`、`struct_entry_len` 或 `struct_table`：

```text
TYPE LEN VALUE...
```

常用 TLV：

| TYPE | 名称 | VALUE |
|---:|---|---|
| `0x01` | `cave_freq` | `uint8_t`，KHz |
| `0x02` | `ref_volt` | `uint8_t`，实际参考电压乘 10 |
| `0x03` | `amp_gain` | `uint8_t` |
| `0x04` | `rs_shunt` | `uint16_t`，mΩ |
| `0x05` | `bus_div` | `float32`，大端 |
| `0x06` | `along_div` | `float32`，大端 |
| `0x07` | `chip_model` | UTF-8 或 ASCII 字符串 |
| `0x08` | `model` | UTF-8 或 ASCII 字符串 |
| `0x20` | 16 位地址结构体实例 | `mem_type + byte_addr16 + byte_len16 + type_name` |
| `0x21` | 16 位地址单独变量 | `mem_type + byte_addr16 + byte_len16 + type_name` |
| `0x28` | 32 位地址结构体实例 | `byte_addr32 + byte_len16 + type_name` |
| `0x29` | 32 位地址单独变量 | `byte_addr32 + byte_len16 + type_name` |

TLV `LEN` 为 1 字节，表示单项 `VALUE` 字节数；整段 CodeInfo 总长度仍由 bootstrap `CODE_LEN16` 决定。内存入口 TLV 的地址宽度由 `TYPE` 决定，bootstrap `ADDR_WIDTH` 只决定读取 CodeInfo 信息块本体时使用 16 位 CODE 地址还是 32 位统一地址。`0x20/0x21` 按 `mem_type=0x01..0x04` 使用 DATA/IDATA/XDATA/CODE，`0x28/0x29` 统一使用 `MODE=0x07 ADDR32` 且不携带 `mem_type`。

同步到参数页后的规则：

| 条件 | 处理 |
|---|---|
| `TYPE=0x20/0x28` 且未导入结构体定义 | 创建结构体组，每个字节按 `00`、`01`、`02` 命名 |
| `TYPE=0x20/0x28` 且导入定义名、长度匹配 | 按 C 结构体字段展示，保留多字节字段整体值；字段类型为 enum 时按枚举项显示 |
| `TYPE=0x21/0x29` | 创建单变量组，1/2/4 字节默认推断为 `uint8_t`、`uint16_t`、`uint32_t`；匹配 enum 后按枚举项显示 |
| 相同名称但地址或区域不同 | 视为不同参数组 |
| 当前已有同区域、同地址、同名称、同长度且已导入定义 | 保留当前导入结构，只更新同步来源信息 |
| 未知 TLV `TYPE` | 跳过 |

结构体和 enum 可在同一个 `.h/.c/.txt` 定义文件中导入。单独变量的 enum 匹配支持两种方式：`type_name` 直接等于 enum 类型名，或导入文件中存在 `EnumType variable_name;` 且变量名等于 `type_name`。enum 只影响显示与输入映射，不改变底层字节读写协议。

CodeInfo 卡片始终显示在通讯页存储访问协议卡片内。板卡信息 TLV 不存在时对应字段不显示；电机型号优先按 UTF-8 解析，也兼容纯 ASCII。

## 7. 参数值与转换公式

参数页读回的数据先按字段类型解码为原始值，再根据可选转换公式显示实际值。公式由参数配置自定义，适合标幺值转实际值、比例缩放和偏置换算。

存储访问参数组的多字节变量值按 bootstrap `MEMORY_ENDIAN` 解码和写入。标准 Modbus 寄存器仍保持 Modbus 字/字节规则，不受该字段影响。

公式支持变量：

| 变量 | 含义 |
|---|---|
| `x`、`value`、`rawValue` | 当前字段原始值 |
| `caveFreq` | CodeInfo 载波频率 |
| `refVolt` | CodeInfo 参考电压实际值 |
| `ampGain` | CodeInfo 运放倍数 |
| `rsShunt` | CodeInfo 采样电阻 |
| `busDiv` | CodeInfo 母线电压分压比 |
| `alongDiv` | CodeInfo 模拟输入电压分压比 |
| `maxPacketLength` | 同步描述符声明的最大完整协议帧长度 |
| `addressWidth` | 同步描述符声明的地址长度 |
| `memoryEndian` | 同步 bootstrap 声明的目标内存变量字节序，`big` 或 `little` |

公式只支持数字、括号和 `+ - * /`，不执行任意脚本。

## 8. Bootloader 协议

Bootloader 协议用于固件升级，独立于标准 Modbus 和存储访问。帧头固定为：

```text
46 54 PAYLOAD... CRC_H CRC_L
```

CRC 使用 `CRC16-CCITT-FALSE`，高字节在前。

主要命令：

| 命令 | 载荷 | 响应长度 | 用途 |
|---|---|---:|---|
| 握手 | `39 42 4C` | 15 | 读取 Bootloader 版本和芯片 ID |
| 解锁 | `08 4E 00` | 8 | 进入可编程状态 |
| 编程 | `44 ADDR_L ADDR_H DATA(128B)` | 8 | 写入 128 字节程序块 |
| 全 Flash 校验 | `19 43 43` | 9 | 读取 Flash 校验值 |
| 页擦除开关 | `08 50 45/44` | 8 | 开启或关闭页擦除 |
| 退出 | `08 42 42` | 8 | 退出 Bootloader |

ACK 为 `0x06`，NAK 为 `0x15`。固件文件加载、芯片型号识别、升级地址和 Flash 容量由 `features/bootloader/` 处理。

## 9. 维护约束

1. 标准 Modbus、存储访问、Bootloader 分别只保留一个协议入口：`protocols/modbus-rtu/index.js`、`protocols/storage-access/index.js`、`protocols/bootloader/index.js`。
2. 页面只负责 UI 展示和事件转发，不直接拼帧、不直接解析二进制响应。
3. 通讯页业务聚合在 `features/communication/`，参数页业务聚合在 `features/parameter-groups/`，存储访问业务聚合在 `features/storage-access/service.js`。
4. 可复用且有明确领域边界的逻辑保留在 `domain/parameter-groups/` 和 `domain/storage-access/`。
5. 不再为单个按钮、单个字段、单个导入步骤新增小模块；新增能力优先并入现有协议模块、功能服务或领域模型。
