293 lines
6.7 KiB
Markdown
293 lines
6.7 KiB
Markdown
# 串口通信协议
|
||
|
||
一个完整的Python串口通信协议实现,支持两台设备之间可靠的数据传输,可传输结构化数据(用户名、线路名称、站点编号等)。
|
||
|
||
## 特性
|
||
|
||
- ✅ **完整的协议帧结构** - 包含帧头、长度、命令、数据、CRC校验、帧尾
|
||
- ✅ **CRC16校验** - 使用CRC-16/MODBUS算法确保数据完整性
|
||
- ✅ **异步接收** - 多线程接收,不阻塞主程序
|
||
- ✅ **多种命令类型** - 心跳、数据传输、控制命令、应答等
|
||
- ✅ **结构化数据** - 支持JSON格式的站点数据传输(用户名、线路名称、站点编号)
|
||
- ✅ **易于使用** - 简洁的API,开箱即用
|
||
|
||
## 协议帧格式
|
||
|
||
```
|
||
+------+------+--------+------+--------+------+
|
||
| HEAD | LEN | CMD | DATA | CRC | TAIL |
|
||
+------+------+--------+------+--------+------+
|
||
| 0xAA | 2B | 1B | N字节 | 2B | 0x55 |
|
||
+------+------+--------+------+--------+------+
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
- **HEAD**: 帧头标识 (1字节, 0xAA)
|
||
- **LEN**: 数据长度 (2字节, 大端序, 包含CMD+DATA)
|
||
- **CMD**: 命令字 (1字节)
|
||
- **DATA**: 数据内容 (N字节)
|
||
- **CRC**: CRC16校验 (2字节, 大端序)
|
||
- **TAIL**: 帧尾标识 (1字节, 0x55)
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
## 命令类型
|
||
|
||
| 命令 | 值 | 说明 |
|
||
| ----------- | ---- | -------------- |
|
||
| HEARTBEAT | 0x01 | 心跳包 |
|
||
| DATA_QUERY | 0x02 | 数据查询 |
|
||
| DATA_RESPONSE | 0x03 | 数据响应 |
|
||
| CONTROL | 0x04 | 控制命令 |
|
||
| ACK | 0x05 | 应答 |
|
||
| NACK | 0x06 | 否定应答 |
|
||
|
||
## 站点数据模型
|
||
|
||
### StationData 类
|
||
|
||
用于传输站点信息的数据模型:
|
||
|
||
```python
|
||
from data_models import StationData
|
||
|
||
# 创建站点数据
|
||
station = StationData(
|
||
username="张三", # 用户名
|
||
line_name="1号线", # 线路名称
|
||
station_no=5 # 第几站
|
||
)
|
||
|
||
# 序列化为字节(用于发送)
|
||
data_bytes = station.to_bytes()
|
||
|
||
# 从字节反序列化(接收后解析)
|
||
restored = StationData.from_bytes(data_bytes)
|
||
```
|
||
|
||
## 使用方法
|
||
|
||
### 基本示例
|
||
|
||
```python
|
||
from serial_protocol import SerialProtocol, Command
|
||
|
||
# 创建协议实例
|
||
device = SerialProtocol(port='COM1', baudrate=115200)
|
||
|
||
# 打开串口
|
||
if device.open():
|
||
# 发送数据
|
||
device.send_data(b"Hello, World!")
|
||
|
||
# 发送心跳
|
||
device.send_heartbeat()
|
||
|
||
# 发送控制命令
|
||
device.send_control(0x10, b"\x01\x02")
|
||
|
||
# 关闭串口
|
||
device.close()
|
||
```
|
||
|
||
### 异步接收数据
|
||
|
||
```python
|
||
def on_receive(cmd, data):
|
||
print(f"收到命令: 0x{cmd:02X}, 数据: {data.hex()}")
|
||
|
||
device = SerialProtocol(port='COM1', baudrate=115200)
|
||
device.open()
|
||
|
||
# 启动接收线程
|
||
device.start_receive(on_receive)
|
||
|
||
# ... 主程序运行 ...
|
||
|
||
device.close()
|
||
```
|
||
|
||
## 完整示例
|
||
|
||
### 设备A (发送端)
|
||
|
||
运行 `device_a.py`,设备A会定期发送:
|
||
- 心跳包
|
||
- **站点数据**(包含用户名、线路名称、站点编号)
|
||
- **接收设备B的字典响应**
|
||
|
||
输出示例:
|
||
```
|
||
============================================================
|
||
循环 1
|
||
============================================================
|
||
✓ 发送心跳包
|
||
|
||
准备发送站点数据:
|
||
用户: 李四, 线路: 2号线, 站点: 第1站
|
||
✓ 站点数据发送成功
|
||
|
||
[设备A] 收到数据:
|
||
命令: 0x03 (DATA_RESPONSE)
|
||
|
||
📥 收到设备B的响应字典:
|
||
1: "李四"
|
||
2: "2号线"
|
||
3: "1"
|
||
4: "已接收"
|
||
5: "设备B确认"
|
||
```
|
||
|
||
运行命令:
|
||
```bash
|
||
python device_a.py
|
||
```
|
||
|
||
### 设备B (接收端)
|
||
|
||
运行 `device_b.py`,设备B会:
|
||
- 接收站点数据并解析显示
|
||
- 自动应答心跳包
|
||
- **返回统一格式的响应字典** `{1:"xxx", 2:"xxx", 3:"xxx", ...}`
|
||
|
||
输出示例:
|
||
```
|
||
============================================================
|
||
[设备B] 收到数据
|
||
============================================================
|
||
命令类型: 0x03 (DATA_RESPONSE)
|
||
|
||
📍 站点数据详情:
|
||
用户名称: 李四
|
||
线路名称: 2号线
|
||
站点编号: 第1站
|
||
|
||
📤 设备B统一响应格式:
|
||
1: "李四"
|
||
2: "2号线"
|
||
3: "1"
|
||
4: "已接收"
|
||
5: "设备B确认"
|
||
|
||
<<< 已发送响应字典
|
||
```
|
||
|
||
运行命令:
|
||
```bash
|
||
python device_b.py
|
||
```
|
||
|
||
## 设备B统一响应格式
|
||
|
||
设备B接收到站点数据后,会返回统一格式的字典响应:
|
||
|
||
```python
|
||
{
|
||
1: "用户名",
|
||
2: "线路名称",
|
||
3: "站点编号",
|
||
4: "已接收",
|
||
5: "设备B确认"
|
||
}
|
||
```
|
||
|
||
**字段说明:**
|
||
- `1`: 接收到的用户名
|
||
- `2`: 接收到的线路名称
|
||
- `3`: 接收到的站点编号(字符串)
|
||
- `4`: 固定值 "已接收"
|
||
- `5`: 固定值 "设备B确认"
|
||
|
||
## 硬件连接
|
||
|
||
### 方案1: 使用两个USB转串口模块
|
||
|
||
```
|
||
设备A (COM1) <---RX/TX交叉---> 设备B (COM2)
|
||
TX ----------------------> RX
|
||
RX <---------------------- TX
|
||
GND <---------------------> GND
|
||
```
|
||
|
||
### 方案2: 使用虚拟串口 (测试用)
|
||
|
||
**Windows**: 使用 com0com 或 Virtual Serial Port Driver
|
||
**Linux**: 使用 socat
|
||
|
||
```bash
|
||
# Linux创建虚拟串口对
|
||
socat -d -d pty,raw,echo=0 pty,raw,echo=0
|
||
# 会创建 /dev/pts/X 和 /dev/pts/Y
|
||
```
|
||
|
||
## API文档
|
||
|
||
### SerialProtocol 类
|
||
|
||
#### 初始化
|
||
|
||
```python
|
||
SerialProtocol(port: str, baudrate: int = 115200, timeout: float = 1.0)
|
||
```
|
||
|
||
#### 方法
|
||
|
||
- `open() -> bool` - 打开串口
|
||
- `close()` - 关闭串口
|
||
- `send_frame(cmd: int, data: bytes) -> bool` - 发送数据帧
|
||
- `receive_frame() -> Optional[dict]` - 接收数据帧(阻塞)
|
||
- `start_receive(callback)` - 启动异步接收
|
||
- `stop_receive()` - 停止异步接收
|
||
- `send_heartbeat() -> bool` - 发送心跳包
|
||
- `send_data(data: bytes) -> bool` - 发送数据
|
||
- `send_control(code: int, params: bytes) -> bool` - 发送控制命令
|
||
- `send_ack() -> bool` - 发送应答
|
||
- `send_nack() -> bool` - 发送否定应答
|
||
|
||
#### 静态方法
|
||
|
||
- `calc_crc16(data: bytes) -> int` - 计算CRC16校验值
|
||
- `build_frame(cmd: int, data: bytes) -> bytes` - 构建数据帧
|
||
- `parse_frame(frame: bytes) -> Optional[dict]` - 解析数据帧
|
||
|
||
## 常见问题
|
||
|
||
### 1. 如何修改串口号?
|
||
|
||
编辑 `device_a.py` 和 `device_b.py`,修改 `port` 参数:
|
||
- Windows: `'COM1'`, `'COM2'`, etc.
|
||
- Linux: `'/dev/ttyUSB0'`, `'/dev/ttyS0'`, etc.
|
||
|
||
### 2. 如何修改波特率?
|
||
|
||
修改 `baudrate` 参数,常用值:9600, 19200, 38400, 57600, 115200
|
||
|
||
### 3. 数据长度限制?
|
||
|
||
理论最大65535字节,但建议单帧数据不超过1024字节以提高可靠性。
|
||
|
||
### 4. 如何处理超时?
|
||
|
||
设置 `timeout` 参数控制读取超时时间。
|
||
|
||
## 应用场景
|
||
|
||
- 🤖 机器人通信
|
||
- 📡 传感器数据采集
|
||
- 🎮 设备控制
|
||
- 📊 工业自动化
|
||
- 🔌 嵌入式系统互联
|
||
|
||
## 许可证
|
||
|
||
MIT License
|
||
|
||
## 作者
|
||
|
||
Created with ❤️ for reliable serial communication
|