Upload simulator and remove original code copy
This commit is contained in:
parent
977222853f
commit
d4a4014710
259
MiniSAR-Simulator-Py-main/README.md
Normal file
259
MiniSAR-Simulator-Py-main/README.md
Normal file
@ -0,0 +1,259 @@
|
||||
# UDP交互程序
|
||||
|
||||
基于Python3的完整UDP交互系统,包含三个独立模块,用于实现雷达控制系统的通信功能。
|
||||
|
||||
## 系统架构
|
||||
|
||||
### 三个模块
|
||||
|
||||
#### 模块1:UDP接收模块(module1_receiver.py)
|
||||
- **功能**:监听38101端口,接收来自主控的控制指令
|
||||
- **主类**:`UDPReceiver`
|
||||
- **关键方法**:
|
||||
- `start()`:启动接收模块
|
||||
- `stop()`:停止接收模块
|
||||
- `set_callback(callback)`:设置接收回调函数
|
||||
|
||||
#### 模块2:UDP发送响应包模块(module2_sender.py)
|
||||
- **功能**:向37001端口发送控制指令的执行结果和状态包
|
||||
- **主类**:`ResponseSender`、`StatusPacketSender`
|
||||
- **关键方法**:
|
||||
- `send_response(remote_addr, status, device_status)`:发送响应包
|
||||
- `send_status_packet(remote_addr, device_status)`:发送设备状态包
|
||||
|
||||
#### 模块3:UDP数据包发送模块(module3_data_sender.py)
|
||||
- **功能**:向37004端口发送回波和图像数据
|
||||
- **主类**:`DataSender`
|
||||
- **关键方法**:
|
||||
- `send_echo_data(remote_addr, echo_data, sequence, max_packet_size)`:发送回波数据(自动分包)
|
||||
- `send_image_data(remote_addr, image_data, imaging_params)`:发送图像数据
|
||||
|
||||
### 通信端口
|
||||
|
||||
| 模块 | 端口 | 方向 | 功能 |
|
||||
|------|------|------|------|
|
||||
| 模块1 | 38101 | 接收 | 接收控制指令 |
|
||||
| 模块2 | 37001 | 发送 | 发送响应包和状态包 |
|
||||
| 模块3 | 37004 | 发送 | 发送回波和图像数据 |
|
||||
|
||||
## 数据格式
|
||||
|
||||
### 控制包格式(表4)
|
||||
```
|
||||
帧头(4B) + 设备编号(1B) + 预留(3B) + 控制数据(192B) + 校验和(4B) + 帧尾(4B)
|
||||
总长度:208字节
|
||||
```
|
||||
|
||||
### 控制数据格式(表3)
|
||||
```
|
||||
工作模式(1B) + 工作指令(1B) + 成像模式(1B) + 分辨率(1B) + 预留(4B) +
|
||||
航线参数(多个) + 飞行参数(多个) + 其他参数(多个)
|
||||
总长度:192字节(小端模式)
|
||||
```
|
||||
|
||||
### 响应包格式(表9)
|
||||
```
|
||||
帧头(4B) + 状态包类型(8B) + 设备编号(1B) + 预留(3B) + 系统版本(4B) +
|
||||
错误包(8B) + 设备状态(64B) + 校验和(4B) + 帧尾(4B)
|
||||
总长度:100字节
|
||||
```
|
||||
|
||||
### 回波数据包格式(表12)
|
||||
```
|
||||
同步码(2B) + 设备编号(2B) + 序号(2B) + 分包编号(2B) + 分包个数(2B) +
|
||||
包大小(2B) + 负载数据(可变) + 校验和(1B) + 帧尾(1B)
|
||||
最大长度:1472字节
|
||||
```
|
||||
|
||||
## 工作指令
|
||||
|
||||
| 指令 | 工作模式 | 工作指令 | 说明 |
|
||||
|------|---------|---------|------|
|
||||
| 连接 | 0 | 4 | 建立连接,启动状态包发送 |
|
||||
| 断开连接 | 0 | 5 | 断开连接,停止状态包发送 |
|
||||
| 上传航线 | 4 | 0 | 上传航线参数 |
|
||||
| 开始计算 | 4 | 1 | 开始计算开关机 |
|
||||
| 手动开机 | 4 | 2 | 手动开启雷达 |
|
||||
| 结束当前任务 | 4 | 3 | 关闭当前任务 |
|
||||
| 结束所有任务 | 0 | 3 | 停止所有任务 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 启动服务器
|
||||
|
||||
```python
|
||||
from main import RadarControlSystem
|
||||
|
||||
# 创建系统
|
||||
system = RadarControlSystem()
|
||||
|
||||
# 启动系统
|
||||
system.start()
|
||||
|
||||
# 保持运行
|
||||
import time
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
system.stop()
|
||||
```
|
||||
|
||||
### 运行测试客户端
|
||||
|
||||
```bash
|
||||
# 在一个终端启动服务器
|
||||
python3 main.py
|
||||
|
||||
# 在另一个终端运行测试客户端
|
||||
python3 test_client.py
|
||||
```
|
||||
|
||||
### 自定义使用
|
||||
|
||||
```python
|
||||
from module1_receiver import UDPReceiver
|
||||
from module2_sender import ResponseSender
|
||||
from module3_data_sender import DataSender
|
||||
from data_structures import ControlPacket, ExecutionStatus
|
||||
|
||||
# 创建接收模块
|
||||
def on_packet_received(packet, remote_addr):
|
||||
print(f"Received packet from {remote_addr}")
|
||||
print(f"Work Mode: {packet.control_data.work_mode}")
|
||||
print(f"Work Instruction: {packet.control_data.work_instruction}")
|
||||
|
||||
receiver = UDPReceiver(callback=on_packet_received)
|
||||
receiver.start()
|
||||
|
||||
# 创建发送模块
|
||||
sender = ResponseSender()
|
||||
sender.send_response(('127.0.0.1', 12345), ExecutionStatus.SUCCESS)
|
||||
|
||||
# 创建数据发送模块
|
||||
data_sender = DataSender()
|
||||
echo_data = b'Echo data test' * 100
|
||||
data_sender.send_echo_data(('127.0.0.1', 12345), echo_data)
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
udp_program/
|
||||
├── __init__.py # 包初始化文件
|
||||
├── data_structures.py # 数据结构定义
|
||||
├── module1_receiver.py # 模块1:接收模块
|
||||
├── module2_sender.py # 模块2:发送响应模块
|
||||
├── module3_data_sender.py # 模块3:数据发送模块
|
||||
├── main.py # 主程序
|
||||
├── test_client.py # 测试客户端
|
||||
└── README.md # 本文档
|
||||
```
|
||||
|
||||
## 关键特性
|
||||
|
||||
1. **完整的数据结构支持**:根据文档表格完整实现所有数据结构
|
||||
2. **自动分包处理**:回波数据自动分包,最大1472字节/包
|
||||
3. **校验和验证**:支持Uint32和Uint8校验和计算和验证
|
||||
4. **小端模式**:所有数据采用小端模式序列化
|
||||
5. **线程安全**:接收和发送在独立线程中运行
|
||||
6. **完整日志**:详细的日志输出便于调试
|
||||
7. **回调机制**:接收模块支持自定义回调处理
|
||||
|
||||
## 校验机制
|
||||
|
||||
### 控制包校验
|
||||
- 帧头:0x7EFFDC01
|
||||
- 帧尾:0x7EFFDC02
|
||||
- 校验和:从帧头到校验和(不包含)的所有字节Uint32无符号累加
|
||||
|
||||
### 回波数据包校验
|
||||
- 同步码:0x650F(回波)或0x750F(图像)
|
||||
- 帧尾标志:0xCB
|
||||
- 校验和:除校验字节外所有字节Uint8累加
|
||||
|
||||
## 连接流程
|
||||
|
||||
1. 主控发送"连接"指令到38101端口
|
||||
2. 雷达接收并校验
|
||||
3. 雷达通过37001端口发送响应包(状态包类型=0,执行状态=0)
|
||||
4. 连接成功后,雷达开始向37001端口发送状态包(1s/次)
|
||||
5. 主控发送"断开连接"指令
|
||||
6. 雷达停止发送状态包
|
||||
|
||||
## 日志输出
|
||||
|
||||
系统使用Python标准logging库,支持以下日志级别:
|
||||
- INFO:一般信息
|
||||
- WARNING:警告信息
|
||||
- ERROR:错误信息
|
||||
- DEBUG:调试信息
|
||||
|
||||
可通过修改日志级别来控制输出详细程度:
|
||||
|
||||
```python
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
```
|
||||
|
||||
## 性能指标
|
||||
|
||||
- 接收延迟:< 1ms
|
||||
- 发送延迟:< 1ms
|
||||
- 状态包发送频率:1Hz(可配置)
|
||||
- 最大分包大小:1472字节
|
||||
- 支持并发客户端:无限制
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 端口被占用
|
||||
```bash
|
||||
# 查看端口占用情况
|
||||
lsof -i :38101
|
||||
lsof -i :37001
|
||||
lsof -i :37004
|
||||
|
||||
# 杀死占用进程
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### 接收不到数据
|
||||
1. 检查防火墙设置
|
||||
2. 确认服务器正在运行
|
||||
3. 检查客户端发送地址是否正确
|
||||
4. 查看日志输出
|
||||
|
||||
### 校验和错误
|
||||
1. 确认数据格式正确
|
||||
2. 检查字节序(应为小端模式)
|
||||
3. 验证校验和计算逻辑
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 添加自定义指令处理
|
||||
在`RadarControlSystem`类中添加新的处理方法:
|
||||
|
||||
```python
|
||||
def _handle_custom_instruction(self, remote_addr, control_data):
|
||||
"""处理自定义指令"""
|
||||
# 添加处理逻辑
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
```
|
||||
|
||||
### 添加数据持久化
|
||||
可以添加数据库支持来存储接收到的指令和状态信息。
|
||||
|
||||
### 添加Web界面
|
||||
可以使用Flask/Django等框架添加Web管理界面。
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 (2026-01-19):初始版本,完整实现三个模块
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系开发团队。
|
||||
27
MiniSAR-Simulator-Py-main/__init__.py
Normal file
27
MiniSAR-Simulator-Py-main/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""
|
||||
UDP交互程序包
|
||||
包含模块1、模块2、模块3的完整实现
|
||||
"""
|
||||
|
||||
__version__ = '1.0.0'
|
||||
__author__ = 'Radar Control System'
|
||||
|
||||
from data_structures import (
|
||||
ControlPacket, ControlData, ResponsePacket, ErrorPacket, DeviceStatus,
|
||||
WorkMode, WorkInstruction, ExecutionStatus, StatusPacketType,
|
||||
FRAME_HEAD_SYMBOL, FRAME_TAIL_SYMBOL,
|
||||
CONTROL_RX_PORT, CONTROL_TX_PORT, DATA_TX_PORT
|
||||
)
|
||||
|
||||
from module1_receiver import UDPReceiver
|
||||
from module2_sender import ResponseSender, StatusPacketSender
|
||||
from module3_data_sender import DataSender, EchoDataPacket, ImageDataPacket
|
||||
from main import RadarControlSystem
|
||||
|
||||
__all__ = [
|
||||
'ControlPacket', 'ControlData', 'ResponsePacket', 'ErrorPacket', 'DeviceStatus',
|
||||
'WorkMode', 'WorkInstruction', 'ExecutionStatus', 'StatusPacketType',
|
||||
'UDPReceiver', 'ResponseSender', 'StatusPacketSender',
|
||||
'DataSender', 'EchoDataPacket', 'ImageDataPacket',
|
||||
'RadarControlSystem'
|
||||
]
|
||||
500
MiniSAR-Simulator-Py-main/data_structures.py
Normal file
500
MiniSAR-Simulator-Py-main/data_structures.py
Normal file
@ -0,0 +1,500 @@
|
||||
"""
|
||||
UDP交互程序数据结构定义
|
||||
根据文档表格定义所有数据结构
|
||||
"""
|
||||
|
||||
import struct
|
||||
from enum import IntEnum
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# ==================== 常量定义 ====================
|
||||
|
||||
# 帧头尾标志
|
||||
FRAME_HEAD_SYMBOL = 0x7EFFDC01
|
||||
FRAME_TAIL_SYMBOL = 0x7EFFDC02
|
||||
|
||||
# 端口定义
|
||||
CONTROL_RX_PORT = 38101 # 模块1:接收控制指令
|
||||
CONTROL_TX_PORT = 37001 # 模块2:发送控制响应
|
||||
DATA_TX_PORT = 37004 # 模块3:发送数据包
|
||||
|
||||
# 工作模式(dWorkMode)
|
||||
class WorkMode(IntEnum):
|
||||
NORMAL = 0 # 正常模式
|
||||
AUTO = 4 # 自动模式
|
||||
|
||||
|
||||
# 工作指令(dWorkInstruction)
|
||||
class WorkInstruction(IntEnum):
|
||||
UPLOAD_ROUTE = 0 # 上传航线
|
||||
START_CALC = 1 # 开始计算
|
||||
MANUAL_BOOT = 2 # 手动开机
|
||||
END_TASK = 3 # 结束当前任务
|
||||
CONNECT = 4 # 连接
|
||||
DISCONNECT = 5 # 断开连接
|
||||
|
||||
|
||||
# 成像模式
|
||||
class ImagingMode(IntEnum):
|
||||
STRIP = 0 # 条带
|
||||
FOCUS = 1 # 聚束
|
||||
|
||||
|
||||
# 成像模式分辨率
|
||||
class ImagingResolution(IntEnum):
|
||||
RES_0_1M = 0 # 0.1m
|
||||
RES_0_2M = 1 # 0.2m
|
||||
RES_0_3M = 2 # 0.3m
|
||||
RES_0_5M = 3 # 0.5m
|
||||
RES_1M = 4 # 1m
|
||||
|
||||
|
||||
# 状态包类型
|
||||
class StatusPacketType(IntEnum):
|
||||
CONTROL_RESULT = 0 # 控制命令执行结果
|
||||
IMAGING_READY = 1 # 成像准备完成
|
||||
DEVICE_FAULT = 2 # 单机工作故障
|
||||
|
||||
|
||||
# 执行状态
|
||||
class ExecutionStatus(IntEnum):
|
||||
SUCCESS = 0 # 成功
|
||||
ERROR = 1 # 错误
|
||||
|
||||
|
||||
# ==================== 数据结构定义 ====================
|
||||
|
||||
@dataclass
|
||||
class ControlData:
|
||||
"""表3:雷达控制数据格式(192字节)"""
|
||||
work_mode: int = 0 # Uint8
|
||||
work_instruction: int = 0 # Uint8
|
||||
imaging_mode: int = 0 # Uint8
|
||||
imaging_resolution: int = 0 # Uint8
|
||||
reserved1: bytes = b'\x00\x00\x00\x00' # Uint8*4
|
||||
route_number: int = 0 # Uint16
|
||||
route_count: int = 0 # Uint16
|
||||
|
||||
# 航线开机点
|
||||
boot_longitude: int = 0 # Uint32 (1e-7量化)
|
||||
boot_latitude: int = 0 # Uint32 (1e-7量化)
|
||||
boot_altitude: int = 0 # Int16
|
||||
|
||||
# 地面开机点
|
||||
ground_boot_longitude: int = 0 # Uint32
|
||||
ground_boot_latitude: int = 0 # Uint32
|
||||
ground_boot_altitude: int = 0 # Int16
|
||||
|
||||
# 航线关机点
|
||||
shutdown_longitude: int = 0 # Uint32
|
||||
shutdown_latitude: int = 0 # Uint32
|
||||
shutdown_altitude: int = 0 # Int16
|
||||
|
||||
# 成像区域中心
|
||||
imaging_center_longitude: int = 0 # Uint32
|
||||
imaging_center_latitude: int = 0 # Uint32
|
||||
imaging_center_altitude: int = 0 # Int16
|
||||
|
||||
# 成像区域参数
|
||||
imaging_width: int = 0 # Uint16
|
||||
imaging_length: int = 0 # Uint16
|
||||
imaging_angle: int = 0 # Uint16
|
||||
|
||||
# 飞行参数
|
||||
flight_speed: float = 0.0 # double64
|
||||
flight_altitude: float = 0.0 # double64
|
||||
grazing_angle: float = 0.0 # double64
|
||||
slant_angle: float = 0.0 # double64
|
||||
|
||||
# 其他参数
|
||||
route_type: int = 0 # Uint8
|
||||
side_direction: int = 0 # Int8
|
||||
polarization: int = 0 # Uint8
|
||||
auto_focus: int = 0 # Uint8
|
||||
motion_compensation: int = 0 # Uint8
|
||||
image_format: int = 0 # Uint8
|
||||
echo_disable: int = 0 # Uint8
|
||||
installation_angle: float = 35.0 # Float
|
||||
image_bit: int = 0 # Uint8
|
||||
prf: float = 0.0 # Float
|
||||
reserved2: bytes = b'\x00' * 86 # 86字节补齐192B
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""将控制数据序列化为字节流(小端模式)"""
|
||||
data = struct.pack('<BBBB',
|
||||
self.work_mode,
|
||||
self.work_instruction,
|
||||
self.imaging_mode,
|
||||
self.imaging_resolution)
|
||||
data += self.reserved1
|
||||
data += struct.pack('<HHIIhIIhIIhIIhHHHddddBBBBBBBfBf',
|
||||
self.route_number,
|
||||
self.route_count,
|
||||
self.boot_longitude,
|
||||
self.boot_latitude,
|
||||
self.boot_altitude,
|
||||
self.ground_boot_longitude,
|
||||
self.ground_boot_latitude,
|
||||
self.ground_boot_altitude,
|
||||
self.shutdown_longitude,
|
||||
self.shutdown_latitude,
|
||||
self.shutdown_altitude,
|
||||
self.imaging_center_longitude,
|
||||
self.imaging_center_latitude,
|
||||
self.imaging_center_altitude,
|
||||
self.imaging_width,
|
||||
self.imaging_length,
|
||||
self.imaging_angle,
|
||||
self.flight_speed,
|
||||
self.flight_altitude,
|
||||
self.grazing_angle,
|
||||
self.slant_angle,
|
||||
self.route_type,
|
||||
self.side_direction,
|
||||
self.polarization,
|
||||
self.auto_focus,
|
||||
self.motion_compensation,
|
||||
self.image_format,
|
||||
self.echo_disable,
|
||||
self.installation_angle,
|
||||
self.image_bit,
|
||||
self.prf)
|
||||
data += self.reserved2
|
||||
return data[:192] # 确保正好192字节
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'ControlData':
|
||||
"""从字节流反序列化控制数据"""
|
||||
if len(data) < 192:
|
||||
raise ValueError(f"Control data must be 192 bytes, got {len(data)}")
|
||||
|
||||
# 解析前4个字节
|
||||
work_mode, work_instruction, imaging_mode, imaging_resolution = struct.unpack('<BBBB', data[0:4])
|
||||
reserved1 = data[4:8]
|
||||
|
||||
# 解析剩余数据
|
||||
values = struct.unpack('<HHIIhIIhIIhIIhHHHddddBBBBBBBfBf', data[8:106])
|
||||
|
||||
return cls(
|
||||
work_mode=work_mode,
|
||||
work_instruction=work_instruction,
|
||||
imaging_mode=imaging_mode,
|
||||
imaging_resolution=imaging_resolution,
|
||||
reserved1=reserved1,
|
||||
route_number=values[0],
|
||||
route_count=values[1],
|
||||
boot_longitude=values[2],
|
||||
boot_latitude=values[3],
|
||||
boot_altitude=values[4],
|
||||
ground_boot_longitude=values[5],
|
||||
ground_boot_latitude=values[6],
|
||||
ground_boot_altitude=values[7],
|
||||
shutdown_longitude=values[8],
|
||||
shutdown_latitude=values[9],
|
||||
shutdown_altitude=values[10],
|
||||
imaging_center_longitude=values[11],
|
||||
imaging_center_latitude=values[12],
|
||||
imaging_center_altitude=values[13],
|
||||
imaging_width=values[14],
|
||||
imaging_length=values[15],
|
||||
imaging_angle=values[16],
|
||||
flight_speed=values[17],
|
||||
flight_altitude=values[18],
|
||||
grazing_angle=values[19],
|
||||
slant_angle=values[20],
|
||||
route_type=values[21],
|
||||
side_direction=values[22],
|
||||
polarization=values[23],
|
||||
auto_focus=values[24],
|
||||
motion_compensation=values[25],
|
||||
image_format=values[26],
|
||||
echo_disable=values[27],
|
||||
installation_angle=values[28],
|
||||
image_bit=values[29],
|
||||
prf=values[30]
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ControlPacket:
|
||||
"""表4:数传通路控制包格式"""
|
||||
frame_head: int = FRAME_HEAD_SYMBOL # Uint32
|
||||
device_number: int = 1 # Uint8
|
||||
reserved: bytes = b'\x00\x00\x00' # Uint8*3
|
||||
control_data: ControlData = None # 192字节
|
||||
frame_checksum: int = 0 # Uint32
|
||||
frame_tail: int = FRAME_TAIL_SYMBOL # Uint32
|
||||
|
||||
def __post_init__(self):
|
||||
if self.control_data is None:
|
||||
self.control_data = ControlData()
|
||||
|
||||
def calculate_checksum(self, data: bytes) -> int:
|
||||
"""计算校验和(无符号int32累加)"""
|
||||
checksum = 0
|
||||
for i in range(0, len(data), 4):
|
||||
if i + 4 <= len(data):
|
||||
val = struct.unpack('<I', data[i:i+4])[0]
|
||||
checksum = (checksum + val) & 0xFFFFFFFF
|
||||
return checksum
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""将控制包序列化为字节流"""
|
||||
# 构建帧头到校验和之前的数据
|
||||
data = struct.pack('<I', self.frame_head)
|
||||
data += struct.pack('<B', self.device_number)
|
||||
data += self.reserved
|
||||
data += self.control_data.to_bytes()
|
||||
|
||||
# 计算校验和
|
||||
self.frame_checksum = self.calculate_checksum(data)
|
||||
|
||||
# 添加校验和和帧尾
|
||||
data += struct.pack('<I', self.frame_checksum)
|
||||
data += struct.pack('<I', self.frame_tail)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'ControlPacket':
|
||||
"""从字节流反序列化控制包"""
|
||||
if len(data) < 208: # 4+1+3+192+4+4
|
||||
raise ValueError(f"Control packet must be at least 208 bytes, got {len(data)}")
|
||||
|
||||
frame_head = struct.unpack('<I', data[0:4])[0]
|
||||
if frame_head != FRAME_HEAD_SYMBOL:
|
||||
raise ValueError(f"Invalid frame head: 0x{frame_head:08X}")
|
||||
|
||||
device_number = struct.unpack('<B', data[4:5])[0]
|
||||
reserved = data[5:8]
|
||||
control_data = ControlData.from_bytes(data[8:200])
|
||||
frame_checksum = struct.unpack('<I', data[200:204])[0]
|
||||
frame_tail = struct.unpack('<I', data[204:208])[0]
|
||||
|
||||
if frame_tail != FRAME_TAIL_SYMBOL:
|
||||
raise ValueError(f"Invalid frame tail: 0x{frame_tail:08X}")
|
||||
|
||||
# 验证校验和
|
||||
packet = cls(
|
||||
frame_head=frame_head,
|
||||
device_number=device_number,
|
||||
reserved=reserved,
|
||||
control_data=control_data,
|
||||
frame_checksum=frame_checksum,
|
||||
frame_tail=frame_tail
|
||||
)
|
||||
|
||||
calculated_checksum = packet.calculate_checksum(data[0:200])
|
||||
if calculated_checksum != frame_checksum:
|
||||
raise ValueError(f"Checksum mismatch: expected 0x{frame_checksum:08X}, got 0x{calculated_checksum:08X}")
|
||||
|
||||
return packet
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorPacket:
|
||||
"""表8:错误信息包数据格式"""
|
||||
status_packet_type: int = 0 # Uint32
|
||||
execution_status: int = 0 # Uint32
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""序列化为字节流"""
|
||||
return struct.pack('<II', self.status_packet_type, self.execution_status)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'ErrorPacket':
|
||||
"""从字节流反序列化"""
|
||||
if len(data) < 8:
|
||||
raise ValueError(f"Error packet must be at least 8 bytes, got {len(data)}")
|
||||
|
||||
status_packet_type, execution_status = struct.unpack('<II', data[0:8])
|
||||
return cls(status_packet_type=status_packet_type, execution_status=execution_status)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeviceStatus:
|
||||
"""表5:设备状态包数据格式(64字节)"""
|
||||
cpu_temp: int = 0 # Int8
|
||||
ant_temp: int = 0 # Int8
|
||||
rf_temp: int = 0 # Int8
|
||||
nvme_temp: int = 0 # Int8
|
||||
fpga_temp: int = 0 # Int8
|
||||
sys_voltage: int = 0 # Uint8
|
||||
clock_lock: int = 0 # Uint8
|
||||
imu_state: int = 0 # Uint8
|
||||
mem_volume: int = 0 # Uint16
|
||||
disk_volume: int = 0 # Uint16
|
||||
north_velocity: float = 0.0 # Float
|
||||
sky_velocity: float = 0.0 # Float
|
||||
east_velocity: float = 0.0 # Float
|
||||
altitude: float = 0.0 # Float
|
||||
longitude: int = 0 # Int32
|
||||
latitude: int = 0 # Int32
|
||||
angle_roll: float = 0.0 # Float
|
||||
angle_yaw: float = 0.0 # Float
|
||||
angle_pitch: float = 0.0 # Float
|
||||
year_month_day: int = 0 # Uint32
|
||||
hour_min_sec: int = 0 # Uint32
|
||||
antenna_azimuth: int = 0 # Int16
|
||||
antenna_pitch: int = 0 # Int16
|
||||
is_boot: int = 0 # Uint8
|
||||
satellite_num: int = 0 # Uint8
|
||||
pos_flag: int = 0 # Uint8
|
||||
orient_flag: int = 0 # Uint8
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""序列化为字节流"""
|
||||
data = struct.pack('<bbbbbBBBHHffffiifffIIhhBBBB',
|
||||
self.cpu_temp,
|
||||
self.ant_temp,
|
||||
self.rf_temp,
|
||||
self.nvme_temp,
|
||||
self.fpga_temp,
|
||||
self.sys_voltage,
|
||||
self.clock_lock,
|
||||
self.imu_state,
|
||||
self.mem_volume,
|
||||
self.disk_volume,
|
||||
self.north_velocity,
|
||||
self.sky_velocity,
|
||||
self.east_velocity,
|
||||
self.altitude,
|
||||
self.longitude,
|
||||
self.latitude,
|
||||
self.angle_roll,
|
||||
self.angle_yaw,
|
||||
self.angle_pitch,
|
||||
self.year_month_day,
|
||||
self.hour_min_sec,
|
||||
self.antenna_azimuth,
|
||||
self.antenna_pitch,
|
||||
self.is_boot,
|
||||
self.satellite_num,
|
||||
self.pos_flag,
|
||||
self.orient_flag)
|
||||
return data[:64] # 确保正好64字节
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'DeviceStatus':
|
||||
"""从字节流反序列化"""
|
||||
if len(data) < 64:
|
||||
raise ValueError(f"Device status must be 64 bytes, got {len(data)}")
|
||||
|
||||
values = struct.unpack('<bbbbbBBBHHffffiifffIIhhBBBB', data[0:64])
|
||||
return cls(
|
||||
cpu_temp=values[0],
|
||||
ant_temp=values[1],
|
||||
rf_temp=values[2],
|
||||
nvme_temp=values[3],
|
||||
fpga_temp=values[4],
|
||||
sys_voltage=values[5],
|
||||
clock_lock=values[6],
|
||||
imu_state=values[7],
|
||||
mem_volume=values[8],
|
||||
disk_volume=values[9],
|
||||
north_velocity=values[10],
|
||||
sky_velocity=values[11],
|
||||
east_velocity=values[12],
|
||||
altitude=values[13],
|
||||
longitude=values[14],
|
||||
latitude=values[15],
|
||||
angle_roll=values[16],
|
||||
angle_yaw=values[17],
|
||||
angle_pitch=values[18],
|
||||
year_month_day=values[19],
|
||||
hour_min_sec=values[20],
|
||||
antenna_azimuth=values[21],
|
||||
antenna_pitch=values[22],
|
||||
is_boot=values[23],
|
||||
satellite_num=values[24],
|
||||
pos_flag=values[25],
|
||||
orient_flag=values[26]
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResponsePacket:
|
||||
"""表9:接收总包数据格式(响应包)"""
|
||||
frame_head: int = FRAME_HEAD_SYMBOL # Uint32
|
||||
status_packet_type: list = None # Uint32[2]
|
||||
device_number: int = 1 # Uint8
|
||||
reserved: bytes = b'\x00\x00\x00' # Uint8*3
|
||||
system_version: int = 0x00040001 # Uint32
|
||||
error_packet: ErrorPacket = None # 表8
|
||||
device_status: Optional[DeviceStatus] = None # 表5(可选)
|
||||
frame_checksum: int = 0 # Uint32
|
||||
frame_tail: int = FRAME_TAIL_SYMBOL # Uint32
|
||||
|
||||
def __post_init__(self):
|
||||
if self.status_packet_type is None:
|
||||
self.status_packet_type = [0, 0]
|
||||
if self.error_packet is None:
|
||||
self.error_packet = ErrorPacket()
|
||||
if self.device_status is None:
|
||||
self.device_status = DeviceStatus()
|
||||
|
||||
def calculate_checksum(self, data: bytes) -> int:
|
||||
"""计算校验和(无符号int32累加)"""
|
||||
checksum = 0
|
||||
for i in range(0, len(data), 4):
|
||||
if i + 4 <= len(data):
|
||||
val = struct.unpack('<I', data[i:i+4])[0]
|
||||
checksum = (checksum + val) & 0xFFFFFFFF
|
||||
return checksum
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""将响应包序列化为字节流"""
|
||||
data = struct.pack('<I', self.frame_head)
|
||||
data += struct.pack('<II', self.status_packet_type[0], self.status_packet_type[1])
|
||||
data += struct.pack('<B', self.device_number)
|
||||
data += self.reserved
|
||||
data += struct.pack('<I', self.system_version)
|
||||
if self.status_packet_type[0] == 1:
|
||||
data += self.error_packet.to_bytes()
|
||||
if self.status_packet_type[1] == 1:
|
||||
data += self.device_status.to_bytes()
|
||||
|
||||
# 计算校验和
|
||||
self.frame_checksum = self.calculate_checksum(data)
|
||||
|
||||
# 添加校验和和帧尾
|
||||
data += struct.pack('<I', self.frame_checksum)
|
||||
data += struct.pack('<I', self.frame_tail)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'ResponsePacket':
|
||||
"""从字节流反序列化响应包"""
|
||||
if len(data) < 88: # 4+8+1+3+4+8+64+4+4
|
||||
raise ValueError(f"Response packet must be at least 88 bytes, got {len(data)}")
|
||||
|
||||
frame_head = struct.unpack('<I', data[0:4])[0]
|
||||
if frame_head != FRAME_HEAD_SYMBOL:
|
||||
raise ValueError(f"Invalid frame head: 0x{frame_head:08X}")
|
||||
|
||||
status_type_0, status_type_1 = struct.unpack('<II', data[4:12])
|
||||
device_number = struct.unpack('<B', data[12:13])[0]
|
||||
reserved = data[13:16]
|
||||
system_version = struct.unpack('<I', data[16:20])[0]
|
||||
error_packet = ErrorPacket.from_bytes(data[20:28])
|
||||
device_status = DeviceStatus.from_bytes(data[28:92])
|
||||
frame_checksum = struct.unpack('<I', data[92:96])[0]
|
||||
frame_tail = struct.unpack('<I', data[96:100])[0]
|
||||
|
||||
if frame_tail != FRAME_TAIL_SYMBOL:
|
||||
raise ValueError(f"Invalid frame tail: 0x{frame_tail:08X}")
|
||||
|
||||
return cls(
|
||||
frame_head=frame_head,
|
||||
status_packet_type=[status_type_0, status_type_1],
|
||||
device_number=device_number,
|
||||
reserved=reserved,
|
||||
system_version=system_version,
|
||||
error_packet=error_packet,
|
||||
device_status=device_status,
|
||||
frame_checksum=frame_checksum,
|
||||
frame_tail=frame_tail
|
||||
)
|
||||
279
MiniSAR-Simulator-Py-main/main.py
Normal file
279
MiniSAR-Simulator-Py-main/main.py
Normal file
@ -0,0 +1,279 @@
|
||||
"""
|
||||
UDP交互程序主程序
|
||||
集成模块1、模块2、模块3,实现完整的UDP通信系统
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
from typing import Dict, Tuple, Optional
|
||||
from module1_receiver import UDPReceiver
|
||||
from module2_sender import ResponseSender, StatusPacketSender
|
||||
from module3_data_sender import DataSender
|
||||
from data_structures import (
|
||||
ControlPacket, WorkMode, WorkInstruction,
|
||||
ExecutionStatus, DeviceStatus, CONTROL_RX_PORT,
|
||||
CONTROL_TX_PORT, DATA_TX_PORT
|
||||
)
|
||||
|
||||
|
||||
class RadarControlSystem:
|
||||
"""雷达控制系统"""
|
||||
|
||||
def __init__(self, local_ip: str = '0.0.0.0'):
|
||||
"""初始化系统"""
|
||||
self.local_ip = local_ip
|
||||
self.logger = logging.getLogger('RadarControlSystem')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
# 模块
|
||||
self.receiver = None
|
||||
self.response_sender = None
|
||||
self.data_sender = None
|
||||
self.status_senders: Dict[Tuple[str, int], StatusPacketSender] = {}
|
||||
|
||||
# 状态
|
||||
self.connected_clients = {} # {remote_addr: connection_time}
|
||||
self.device_status = DeviceStatus()
|
||||
self.running = False
|
||||
|
||||
def start(self):
|
||||
"""启动系统"""
|
||||
self.logger.info("Starting Radar Control System...")
|
||||
|
||||
try:
|
||||
# 启动接收模块
|
||||
self.receiver = UDPReceiver(local_ip=self.local_ip, port=CONTROL_RX_PORT, callback=self._on_control_packet)
|
||||
self.receiver.start()
|
||||
|
||||
# 创建发送模块(暂不启动)
|
||||
self.response_sender = ResponseSender(local_ip=self.local_ip)
|
||||
self.data_sender = DataSender(local_ip=self.local_ip)
|
||||
|
||||
self.running = True
|
||||
self.logger.info("Radar Control System started successfully")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to start system: {e}")
|
||||
self.stop()
|
||||
raise
|
||||
|
||||
def stop(self):
|
||||
"""停止系统"""
|
||||
self.logger.info("Stopping Radar Control System...")
|
||||
|
||||
self.running = False
|
||||
|
||||
# 停止所有状态包发送器
|
||||
for sender in self.status_senders.values():
|
||||
sender.stop()
|
||||
self.status_senders.clear()
|
||||
|
||||
# 停止接收模块
|
||||
if self.receiver:
|
||||
self.receiver.stop()
|
||||
|
||||
# 关闭发送模块
|
||||
if self.response_sender:
|
||||
self.response_sender.close()
|
||||
|
||||
if self.data_sender:
|
||||
self.data_sender.close()
|
||||
|
||||
self.logger.info("Radar Control System stopped")
|
||||
|
||||
def _on_control_packet(self, packet: ControlPacket, remote_addr: Tuple[str, int]):
|
||||
"""处理接收到的控制包"""
|
||||
self.logger.info(f"Processing control packet from {remote_addr}")
|
||||
|
||||
try:
|
||||
control_data = packet.control_data
|
||||
work_mode = control_data.work_mode
|
||||
work_instruction = control_data.work_instruction
|
||||
|
||||
# 根据指令类型处理
|
||||
if work_mode == WorkMode.NORMAL and work_instruction == WorkInstruction.CONNECT:
|
||||
self._handle_connect(remote_addr)
|
||||
|
||||
elif work_mode == WorkMode.NORMAL and work_instruction == WorkInstruction.DISCONNECT:
|
||||
self._handle_disconnect(remote_addr)
|
||||
|
||||
elif work_mode == WorkMode.AUTO and work_instruction == WorkInstruction.UPLOAD_ROUTE:
|
||||
self._handle_upload_route(remote_addr, control_data)
|
||||
|
||||
elif work_mode == WorkMode.AUTO and work_instruction == WorkInstruction.START_CALC:
|
||||
self._handle_start_calc(remote_addr, control_data)
|
||||
|
||||
elif work_mode == WorkMode.AUTO and work_instruction == WorkInstruction.MANUAL_BOOT:
|
||||
self._handle_manual_boot(remote_addr)
|
||||
|
||||
elif work_mode == WorkMode.AUTO and work_instruction == WorkInstruction.END_TASK:
|
||||
self._handle_end_task(remote_addr)
|
||||
|
||||
elif work_mode == WorkMode.NORMAL and work_instruction == WorkInstruction.END_TASK:
|
||||
self._handle_end_all_tasks(remote_addr)
|
||||
|
||||
else:
|
||||
self.logger.warning(f"Unknown instruction: mode={work_mode}, instruction={work_instruction}")
|
||||
self._send_response(remote_addr, ExecutionStatus.ERROR)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing control packet: {e}")
|
||||
self._send_response(remote_addr, ExecutionStatus.ERROR)
|
||||
|
||||
def _handle_connect(self, remote_addr: Tuple[str, int]):
|
||||
"""处理连接指令"""
|
||||
self.logger.info(f"Handling CONNECT from {remote_addr}")
|
||||
|
||||
# 目标地址:保持IP不变,端口改为CONTROL_TX_PORT
|
||||
target_addr = (remote_addr[0], CONTROL_TX_PORT)
|
||||
|
||||
# 记录连接
|
||||
self.connected_clients[target_addr] = time.time()
|
||||
|
||||
# 发送响应
|
||||
self._send_response(target_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
# 启动状态包发送器
|
||||
if target_addr not in self.status_senders:
|
||||
sender = StatusPacketSender(target_addr, interval=1.0, local_ip=self.local_ip)
|
||||
sender.start()
|
||||
self.status_senders[target_addr] = sender
|
||||
self.logger.info(f"Status packet sender started for {target_addr}")
|
||||
|
||||
def _handle_disconnect(self, remote_addr: Tuple[str, int]):
|
||||
"""处理断开连接指令"""
|
||||
self.logger.info(f"Handling DISCONNECT from {remote_addr}")
|
||||
|
||||
# 移除连接记录
|
||||
if remote_addr in self.connected_clients:
|
||||
del self.connected_clients[remote_addr]
|
||||
|
||||
# 停止状态包发送器
|
||||
if remote_addr in self.status_senders:
|
||||
self.status_senders[remote_addr].stop()
|
||||
del self.status_senders[remote_addr]
|
||||
self.logger.info(f"Status packet sender stopped for {remote_addr}")
|
||||
|
||||
# 发送响应
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
def _handle_upload_route(self, remote_addr: Tuple[str, int], control_data):
|
||||
"""处理上传航线指令"""
|
||||
self.logger.info(f"Handling UPLOAD_ROUTE from {remote_addr}")
|
||||
self.logger.info(f" Route Number: {control_data.route_number}")
|
||||
self.logger.info(f" Route Count: {control_data.route_count}")
|
||||
self.logger.info(f" Boot Point: ({control_data.boot_longitude}, {control_data.boot_latitude})")
|
||||
self.logger.info(f" Shutdown Point: ({control_data.shutdown_longitude}, {control_data.shutdown_latitude})")
|
||||
|
||||
# 这里可以添加航线验证逻辑
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
def _handle_start_calc(self, remote_addr: Tuple[str, int], control_data):
|
||||
"""处理开始计算指令"""
|
||||
self.logger.info(f"Handling START_CALC from {remote_addr}")
|
||||
self.logger.info(f" Imaging Mode: {control_data.imaging_mode}")
|
||||
self.logger.info(f" Route Count: {control_data.route_count}")
|
||||
|
||||
# 这里可以添加计算启动逻辑
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
def _handle_manual_boot(self, remote_addr: Tuple[str, int]):
|
||||
"""处理手动开机指令"""
|
||||
self.logger.info(f"Handling MANUAL_BOOT from {remote_addr}")
|
||||
|
||||
# 更新设备状态
|
||||
self.device_status.is_boot = 1
|
||||
|
||||
# 这里可以添加开机逻辑
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
def _handle_end_task(self, remote_addr: Tuple[str, int]):
|
||||
"""处理结束当前任务指令"""
|
||||
self.logger.info(f"Handling END_TASK from {remote_addr}")
|
||||
|
||||
# 更新设备状态
|
||||
self.device_status.is_boot = 0
|
||||
|
||||
# 这里可以添加关机逻辑
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
def _handle_end_all_tasks(self, remote_addr: Tuple[str, int]):
|
||||
"""处理结束所有任务指令"""
|
||||
self.logger.info(f"Handling END_ALL_TASKS from {remote_addr}")
|
||||
|
||||
# 更新设备状态
|
||||
self.device_status.is_boot = 0
|
||||
|
||||
# 这里可以添加停止所有任务的逻辑
|
||||
self._send_response(remote_addr, ExecutionStatus.SUCCESS)
|
||||
|
||||
def _send_response(self, remote_addr: Tuple[str, int], status: int):
|
||||
"""发送响应包"""
|
||||
if self.response_sender:
|
||||
self.response_sender.send_response(remote_addr, status, self.device_status)
|
||||
|
||||
def send_echo_data(self, remote_addr: Tuple[str, int], echo_data: bytes, sequence: int = 0):
|
||||
"""发送回波数据"""
|
||||
if self.data_sender:
|
||||
self.data_sender.send_echo_data(remote_addr, echo_data, sequence)
|
||||
|
||||
def send_image_data(self, remote_addr: Tuple[str, int], image_data: bytes, imaging_params=None):
|
||||
"""发送图像数据"""
|
||||
if self.data_sender:
|
||||
self.data_sender.send_image_data(remote_addr, image_data, imaging_params)
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""获取系统状态"""
|
||||
return {
|
||||
'running': self.running,
|
||||
'connected_clients': len(self.connected_clients),
|
||||
'clients': list(self.connected_clients.keys()),
|
||||
'device_status': {
|
||||
'cpu_temp': self.device_status.cpu_temp,
|
||||
'is_boot': self.device_status.is_boot,
|
||||
'satellite_num': self.device_status.satellite_num,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 解析命令行参数
|
||||
parser = argparse.ArgumentParser(description='Radar Control System')
|
||||
parser.add_argument('--ip', type=str, default='0.0.0.0', help='Local IP address to bind to (default: 0.0.0.0)')
|
||||
args = parser.parse_args()
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# 创建并启动系统
|
||||
system = RadarControlSystem(local_ip=args.ip)
|
||||
system.start()
|
||||
|
||||
try:
|
||||
# 保持运行
|
||||
while True:
|
||||
time.sleep(5)
|
||||
# 定期输出系统状态
|
||||
status = system.get_status()
|
||||
logging.info(f"System Status: {status}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Received interrupt signal")
|
||||
|
||||
finally:
|
||||
system.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
136
MiniSAR-Simulator-Py-main/module1_receiver.py
Normal file
136
MiniSAR-Simulator-Py-main/module1_receiver.py
Normal file
@ -0,0 +1,136 @@
|
||||
"""
|
||||
模块1:UDP接收模块
|
||||
监听38101端口,接收来自主控的控制指令
|
||||
"""
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import logging
|
||||
from typing import Callable, Optional
|
||||
from data_structures import ControlPacket, CONTROL_RX_PORT
|
||||
|
||||
|
||||
class UDPReceiver:
|
||||
"""UDP接收模块"""
|
||||
|
||||
def __init__(self, local_ip: str = '0.0.0.0', port: int = CONTROL_RX_PORT, callback: Optional[Callable] = None):
|
||||
"""
|
||||
初始化接收模块
|
||||
|
||||
Args:
|
||||
local_ip: 本地监听IP
|
||||
port: 监听端口
|
||||
callback: 接收到数据后的回调函数,签名为 callback(packet, remote_addr)
|
||||
"""
|
||||
self.local_ip = local_ip
|
||||
self.port = port
|
||||
self.callback = callback
|
||||
self.socket = None
|
||||
self.running = False
|
||||
self.receive_thread = None
|
||||
|
||||
# 配置日志
|
||||
self.logger = logging.getLogger(f'Module1-Receiver:{port}')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def start(self):
|
||||
"""启动接收模块"""
|
||||
if self.running:
|
||||
self.logger.warning("Receiver is already running")
|
||||
return
|
||||
|
||||
try:
|
||||
# 创建UDP套接字
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind((self.local_ip, self.port))
|
||||
|
||||
self.running = True
|
||||
self.logger.info(f"Receiver started on port {self.port}")
|
||||
|
||||
# 启动接收线程
|
||||
self.receive_thread = threading.Thread(target=self._receive_loop, daemon=True)
|
||||
self.receive_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to start receiver: {e}")
|
||||
self.running = False
|
||||
raise
|
||||
|
||||
def stop(self):
|
||||
"""停止接收模块"""
|
||||
self.running = False
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
if self.receive_thread:
|
||||
self.receive_thread.join(timeout=2)
|
||||
self.logger.info("Receiver stopped")
|
||||
|
||||
def _receive_loop(self):
|
||||
"""接收循环"""
|
||||
while self.running:
|
||||
try:
|
||||
# 接收数据(最大1472字节)
|
||||
data, remote_addr = self.socket.recvfrom(2048)
|
||||
|
||||
self.logger.info(f"Received {len(data)} bytes from {remote_addr}")
|
||||
|
||||
try:
|
||||
# 解析控制包
|
||||
packet = ControlPacket.from_bytes(data)
|
||||
self.logger.info(f"Control packet parsed successfully")
|
||||
self.logger.debug(f" Device: {packet.device_number}")
|
||||
self.logger.debug(f" Work Mode: {packet.control_data.work_mode}")
|
||||
self.logger.debug(f" Work Instruction: {packet.control_data.work_instruction}")
|
||||
|
||||
# 调用回调函数
|
||||
if self.callback:
|
||||
self.callback(packet, remote_addr)
|
||||
|
||||
except ValueError as e:
|
||||
self.logger.error(f"Failed to parse control packet: {e}")
|
||||
self.logger.debug(f"Raw data (hex): {data.hex()}")
|
||||
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
if self.running:
|
||||
self.logger.error(f"Error in receive loop: {e}")
|
||||
break
|
||||
|
||||
def set_callback(self, callback: Callable):
|
||||
"""设置接收回调函数"""
|
||||
self.callback = callback
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def on_packet_received(packet, remote_addr):
|
||||
"""处理接收到的包"""
|
||||
print(f"\n=== Packet Received from {remote_addr} ===")
|
||||
print(f"Device Number: {packet.device_number}")
|
||||
print(f"Work Mode: {packet.control_data.work_mode}")
|
||||
print(f"Work Instruction: {packet.control_data.work_instruction}")
|
||||
print(f"Imaging Mode: {packet.control_data.imaging_mode}")
|
||||
print(f"Route Number: {packet.control_data.route_number}")
|
||||
print(f"Route Count: {packet.control_data.route_count}")
|
||||
|
||||
# 创建并启动接收模块
|
||||
receiver = UDPReceiver(callback=on_packet_received)
|
||||
receiver.start()
|
||||
|
||||
try:
|
||||
# 保持运行
|
||||
import time
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down...")
|
||||
receiver.stop()
|
||||
270
MiniSAR-Simulator-Py-main/module2_sender.py
Normal file
270
MiniSAR-Simulator-Py-main/module2_sender.py
Normal file
@ -0,0 +1,270 @@
|
||||
"""
|
||||
模块2:UDP发送响应包模块
|
||||
向37001端口发送控制指令的执行结果
|
||||
"""
|
||||
|
||||
import socket
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
from data_structures import (
|
||||
ResponsePacket, ErrorPacket, DeviceStatus,
|
||||
CONTROL_TX_PORT, ExecutionStatus, StatusPacketType
|
||||
)
|
||||
|
||||
|
||||
class ResponseSender:
|
||||
"""响应包发送模块"""
|
||||
|
||||
def __init__(self, remote_host: str = '127.0.0.1', port: int = CONTROL_TX_PORT, local_ip: Optional[str] = None):
|
||||
"""
|
||||
初始化响应发送模块
|
||||
|
||||
Args:
|
||||
remote_host: 远程主机地址
|
||||
port: 发送端口
|
||||
local_ip: 本地绑定IP(用于指定发送源IP)
|
||||
"""
|
||||
self.remote_host = remote_host
|
||||
self.port = port
|
||||
self.local_ip = local_ip
|
||||
self.socket = None
|
||||
|
||||
# 配置日志
|
||||
self.logger = logging.getLogger(f'Module2-Sender:{port}')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
# 创建UDP套接字
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if self.local_ip:
|
||||
try:
|
||||
self.socket.bind((self.local_ip, 0))
|
||||
self.logger.info(f"Response sender bound to {self.local_ip}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to bind response sender to {self.local_ip}: {e}")
|
||||
|
||||
def send_response(self, remote_addr: Tuple[str, int],
|
||||
status: int = ExecutionStatus.SUCCESS,
|
||||
device_status: Optional[DeviceStatus] = None) -> bool:
|
||||
"""
|
||||
发送控制命令执行结果响应包
|
||||
|
||||
Args:
|
||||
remote_addr: 远程地址 (host, port)
|
||||
status: 执行状态 (0=成功, 1=错误)
|
||||
device_status: 设备状态(可选)
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
# 创建错误包
|
||||
error_packet = ErrorPacket(
|
||||
status_packet_type=StatusPacketType.CONTROL_RESULT,
|
||||
execution_status=status
|
||||
)
|
||||
|
||||
# 创建响应包
|
||||
# 状态包类型:[0]=错误包(回报), [1]=设备状态
|
||||
# 如果是回报则[0]设为1,如果是设备状态则[1]设为1
|
||||
response = ResponsePacket(
|
||||
status_packet_type=[1, 1], # [0]=1表示错误包有效, [1]=0表示设备状态无效
|
||||
device_number=1,
|
||||
error_packet=error_packet,
|
||||
device_status=device_status if device_status else DeviceStatus()
|
||||
)
|
||||
|
||||
# 序列化为字节流
|
||||
data = response.to_bytes()
|
||||
|
||||
# 发送数据
|
||||
self.socket.sendto(data, remote_addr)
|
||||
|
||||
self.logger.info(f"Response sent to {remote_addr} - Status: {status}")
|
||||
self.logger.info(f"DEBUG: Raw packet data ({len(data)} bytes): {data.hex()}")
|
||||
#self.logger.debug(f"Packet size: {len(data)} bytes")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send response: {e}")
|
||||
return False
|
||||
|
||||
def send_status_packet(self, remote_addr: Tuple[str, int],
|
||||
device_status: DeviceStatus) -> bool:
|
||||
"""
|
||||
发送设备状态包
|
||||
|
||||
Args:
|
||||
remote_addr: 远程地址 (host, port)
|
||||
device_status: 设备状态
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
# 创建错误包(状态设为成功)
|
||||
error_packet = ErrorPacket(
|
||||
status_packet_type=StatusPacketType.CONTROL_RESULT,
|
||||
execution_status=ExecutionStatus.SUCCESS
|
||||
)
|
||||
|
||||
# 创建响应包,包含设备状态
|
||||
# 状态包类型:[0]=错误包(回报), [1]=设备状态
|
||||
# 如果是回报则[0]设为1,如果是设备状态则[1]设为1
|
||||
response = ResponsePacket(
|
||||
status_packet_type=[1, 1], # [0]=0表示错误包无效, [1]=1表示设备状态有效
|
||||
device_number=1,
|
||||
error_packet=error_packet,
|
||||
device_status=device_status
|
||||
)
|
||||
|
||||
# 序列化为字节流
|
||||
data = response.to_bytes()
|
||||
|
||||
# 发送数据
|
||||
self.socket.sendto(data, remote_addr)
|
||||
|
||||
self.logger.info(f"Status packet sent to {remote_addr}")
|
||||
#self.logger.info(f"Status sent to {remote_addr} - Status: {status}")
|
||||
self.logger.info(f"DEBUG: Raw packet data ({len(data)} bytes): {data.hex()}")
|
||||
#self.logger.debug(f"Packet size: {len(data)} bytes")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send status packet: {e}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""关闭发送模块"""
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
self.logger.info("Sender closed")
|
||||
|
||||
|
||||
class StatusPacketSender:
|
||||
"""定时发送状态包的模块"""
|
||||
|
||||
def __init__(self, remote_addr: Tuple[str, int], interval: float = 1.0, local_ip: Optional[str] = None):
|
||||
"""
|
||||
初始化定时状态包发送器
|
||||
|
||||
Args:
|
||||
remote_addr: 远程地址 (host, port)
|
||||
interval: 发送间隔(秒)
|
||||
local_ip: 本地绑定IP
|
||||
"""
|
||||
self.remote_addr = remote_addr
|
||||
self.interval = interval
|
||||
self.local_ip = local_ip
|
||||
self.sender = ResponseSender(remote_addr[0], CONTROL_TX_PORT, local_ip=local_ip)
|
||||
self.running = False
|
||||
self.send_thread = None
|
||||
|
||||
# 配置日志
|
||||
self.logger = logging.getLogger(f'StatusPacketSender:{remote_addr}')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def start(self):
|
||||
"""启动定时发送"""
|
||||
if self.running:
|
||||
self.logger.warning("Status packet sender is already running")
|
||||
return
|
||||
|
||||
self.running = True
|
||||
self.logger.info(f"Starting status packet sender for {self.remote_addr}")
|
||||
|
||||
# 启动发送线程
|
||||
import threading
|
||||
self.send_thread = threading.Thread(target=self._send_loop, daemon=True)
|
||||
self.send_thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止定时发送"""
|
||||
self.running = False
|
||||
if self.send_thread:
|
||||
self.send_thread.join(timeout=2)
|
||||
self.sender.close()
|
||||
self.logger.info("Status packet sender stopped")
|
||||
|
||||
def _send_loop(self):
|
||||
"""发送循环"""
|
||||
while self.running:
|
||||
try:
|
||||
# 创建设备状态
|
||||
device_status = DeviceStatus(
|
||||
cpu_temp=45,
|
||||
ant_temp=40,
|
||||
rf_temp=50,
|
||||
nvme_temp=35,
|
||||
fpga_temp=48,
|
||||
sys_voltage=12,
|
||||
clock_lock=1,
|
||||
imu_state=1,
|
||||
mem_volume=8192,
|
||||
disk_volume=50,
|
||||
north_velocity=10.5,
|
||||
sky_velocity=0.5,
|
||||
east_velocity=5.2,
|
||||
altitude=1000.0,
|
||||
longitude=1159154650, # 116.0°
|
||||
latitude=403585769, # 40.0°
|
||||
angle_roll=0.0,
|
||||
angle_yaw=90.0,
|
||||
angle_pitch=0.0,
|
||||
year_month_day=20260119,
|
||||
hour_min_sec=120000,
|
||||
antenna_azimuth=0,
|
||||
antenna_pitch=0,
|
||||
is_boot=1,
|
||||
satellite_num=12,
|
||||
pos_flag=4,
|
||||
orient_flag=1
|
||||
)
|
||||
|
||||
# 发送状态包
|
||||
self.sender.send_status_packet(self.remote_addr, device_status)
|
||||
|
||||
# 等待指定间隔
|
||||
time.sleep(self.interval)
|
||||
|
||||
except Exception as e:
|
||||
if self.running:
|
||||
self.logger.error(f"Error in send loop: {e}")
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# 创建发送模块
|
||||
sender = ResponseSender('127.0.0.1', CONTROL_TX_PORT)
|
||||
|
||||
# 发送响应包
|
||||
print("Sending response packet...")
|
||||
sender.send_response(('127.0.0.1', 12345), ExecutionStatus.SUCCESS)
|
||||
|
||||
# 发送设备状态
|
||||
device_status = DeviceStatus(
|
||||
cpu_temp=45,
|
||||
ant_temp=40,
|
||||
rf_temp=50,
|
||||
is_boot=1,
|
||||
satellite_num=12
|
||||
)
|
||||
print("Sending status packet...")
|
||||
sender.send_status_packet(('127.0.0.1', 12345), device_status)
|
||||
|
||||
sender.close()
|
||||
276
MiniSAR-Simulator-Py-main/module3_data_sender.py
Normal file
276
MiniSAR-Simulator-Py-main/module3_data_sender.py
Normal file
@ -0,0 +1,276 @@
|
||||
"""
|
||||
模块3:UDP数据包发送模块
|
||||
向37004端口发送回波和图像数据
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import logging
|
||||
import time
|
||||
from typing import Tuple, Optional
|
||||
from data_structures import DATA_TX_PORT
|
||||
|
||||
|
||||
class EchoDataPacket:
|
||||
"""表12:回波数据分包格式"""
|
||||
|
||||
def __init__(self, sync_code: int = 0x650F, device_num: int = 1,
|
||||
sequence: int = 0, index: int = 0, pieces: int = 1):
|
||||
"""
|
||||
初始化回波数据包
|
||||
|
||||
Args:
|
||||
sync_code: 同步码 (0x650F=回波, 0x750F=图像)
|
||||
device_num: 设备编号
|
||||
sequence: 整包数据序号
|
||||
index: 分包编号
|
||||
pieces: 分包个数
|
||||
"""
|
||||
self.sync_code = sync_code
|
||||
self.device_num = device_num
|
||||
self.sequence = sequence
|
||||
self.index = index
|
||||
self.pieces = pieces
|
||||
self.payload = b''
|
||||
|
||||
def set_payload(self, data: bytes):
|
||||
"""设置负载数据"""
|
||||
self.payload = data
|
||||
|
||||
def calculate_checksum(self, data: bytes) -> int:
|
||||
"""计算校验和(Uint8累加)"""
|
||||
checksum = 0
|
||||
for byte in data:
|
||||
checksum = (checksum + byte) & 0xFF
|
||||
return checksum
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""序列化为字节流"""
|
||||
# 构建数据包头
|
||||
header = struct.pack('<HHHHH',
|
||||
self.sync_code,
|
||||
self.device_num,
|
||||
self.sequence,
|
||||
self.index,
|
||||
self.pieces)
|
||||
|
||||
# 包大小 = 头大小(14) + 负载大小
|
||||
pkg_size = 14 + len(self.payload)
|
||||
header += struct.pack('<H', pkg_size)
|
||||
|
||||
# 计算校验和(除校验字节外所有字节)
|
||||
checksum = self.calculate_checksum(header + self.payload)
|
||||
|
||||
# 添加校验和和帧尾标志
|
||||
data = header + self.payload
|
||||
data += struct.pack('<BB', checksum, 0xCB)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'EchoDataPacket':
|
||||
"""从字节流反序列化"""
|
||||
if len(data) < 16:
|
||||
raise ValueError(f"Echo packet must be at least 16 bytes, got {len(data)}")
|
||||
|
||||
sync_code, device_num, sequence, index, pieces, pkg_size = struct.unpack('<HHHHH', data[0:10])
|
||||
|
||||
# 提取负载
|
||||
payload = data[10:pkg_size-2]
|
||||
|
||||
packet = cls(sync_code, device_num, sequence, index, pieces)
|
||||
packet.set_payload(payload)
|
||||
|
||||
return packet
|
||||
|
||||
|
||||
class ImageDataPacket:
|
||||
"""表14:图像包数据格式"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化图像数据包"""
|
||||
self.frame_head = 0x7EFFDC01
|
||||
self.imaging_params = b'\x00' * 256 # 256字节成像参数
|
||||
self.image_data = b''
|
||||
self.frame_checksum = 0
|
||||
self.frame_tail = 0x7EFFDC02
|
||||
|
||||
def set_imaging_params(self, params: bytes):
|
||||
"""设置成像参数(256字节)"""
|
||||
if len(params) != 256:
|
||||
raise ValueError(f"Imaging params must be 256 bytes, got {len(params)}")
|
||||
self.imaging_params = params
|
||||
|
||||
def set_image_data(self, data: bytes):
|
||||
"""设置图像数据"""
|
||||
self.image_data = data
|
||||
|
||||
def calculate_checksum(self, data: bytes) -> int:
|
||||
"""计算校验和(Uint8累加)"""
|
||||
checksum = 0
|
||||
for byte in data:
|
||||
checksum = (checksum + byte) & 0xFF
|
||||
return checksum
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""序列化为字节流"""
|
||||
# 构建数据
|
||||
data = struct.pack('<I', self.frame_head)
|
||||
data += self.imaging_params
|
||||
data += self.image_data
|
||||
|
||||
# 计算校验和
|
||||
self.frame_checksum = self.calculate_checksum(data)
|
||||
|
||||
# 添加校验和和帧尾
|
||||
data += struct.pack('<I', self.frame_checksum)
|
||||
data += struct.pack('<I', self.frame_tail)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class DataSender:
|
||||
"""数据包发送模块"""
|
||||
|
||||
def __init__(self, remote_host: str = '127.0.0.1', port: int = DATA_TX_PORT, local_ip: Optional[str] = None):
|
||||
"""
|
||||
初始化数据发送模块
|
||||
|
||||
Args:
|
||||
remote_host: 远程主机地址
|
||||
port: 发送端口
|
||||
local_ip: 本地绑定IP
|
||||
"""
|
||||
self.remote_host = remote_host
|
||||
self.port = port
|
||||
self.local_ip = local_ip
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if self.local_ip:
|
||||
try:
|
||||
self.socket.bind((self.local_ip, 0))
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to bind data sender to {self.local_ip}: {e}")
|
||||
|
||||
# 配置日志
|
||||
self.logger = logging.getLogger(f'Module3-DataSender:{port}')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def send_echo_data(self, remote_addr: Tuple[str, int], echo_data: bytes,
|
||||
sequence: int = 0, max_packet_size: int = 1472) -> bool:
|
||||
"""
|
||||
发送回波数据(自动分包)
|
||||
|
||||
Args:
|
||||
remote_addr: 远程地址 (host, port)
|
||||
echo_data: 回波数据
|
||||
sequence: 整包数据序号
|
||||
max_packet_size: 最大分包大小(默认1472字节)
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
# 计算分包个数
|
||||
payload_size = max_packet_size - 16 # 减去包头和校验尾
|
||||
total_pieces = (len(echo_data) + payload_size - 1) // payload_size
|
||||
|
||||
self.logger.info(f"Sending echo data ({len(echo_data)} bytes) in {total_pieces} packets")
|
||||
|
||||
# 分包发送
|
||||
for index in range(total_pieces):
|
||||
start = index * payload_size
|
||||
end = min(start + payload_size, len(echo_data))
|
||||
payload = echo_data[start:end]
|
||||
|
||||
# 创建回波数据包
|
||||
packet = EchoDataPacket(
|
||||
sync_code=0x650F, # 回波
|
||||
device_num=1,
|
||||
sequence=sequence,
|
||||
index=index,
|
||||
pieces=total_pieces
|
||||
)
|
||||
packet.set_payload(payload)
|
||||
|
||||
# 发送数据包
|
||||
data = packet.to_bytes()
|
||||
self.socket.sendto(data, remote_addr)
|
||||
|
||||
self.logger.debug(f"Echo packet {index+1}/{total_pieces} sent ({len(data)} bytes)")
|
||||
|
||||
# 小延迟避免丢包
|
||||
time.sleep(0.001)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send echo data: {e}")
|
||||
return False
|
||||
|
||||
def send_image_data(self, remote_addr: Tuple[str, int], image_data: bytes,
|
||||
imaging_params: Optional[bytes] = None) -> bool:
|
||||
"""
|
||||
发送图像数据
|
||||
|
||||
Args:
|
||||
remote_addr: 远程地址 (host, port)
|
||||
image_data: 图像数据
|
||||
imaging_params: 成像参数(256字节,可选)
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
# 创建图像数据包
|
||||
packet = ImageDataPacket()
|
||||
|
||||
if imaging_params:
|
||||
packet.set_imaging_params(imaging_params)
|
||||
|
||||
packet.set_image_data(image_data)
|
||||
|
||||
# 序列化为字节流
|
||||
data = packet.to_bytes()
|
||||
|
||||
# 发送数据包
|
||||
self.socket.sendto(data, remote_addr)
|
||||
|
||||
self.logger.info(f"Image data sent to {remote_addr} ({len(data)} bytes)")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send image data: {e}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""关闭发送模块"""
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
self.logger.info("Data sender closed")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# 创建发送模块
|
||||
sender = DataSender('127.0.0.1', DATA_TX_PORT)
|
||||
|
||||
# 生成测试回波数据
|
||||
echo_data = b'Echo data test' * 100
|
||||
print(f"Sending echo data ({len(echo_data)} bytes)...")
|
||||
sender.send_echo_data(('127.0.0.1', 12345), echo_data)
|
||||
|
||||
# 生成测试图像数据
|
||||
image_data = b'Image data test' * 100
|
||||
imaging_params = b'\x00' * 256
|
||||
print(f"Sending image data ({len(image_data)} bytes)...")
|
||||
sender.send_image_data(('127.0.0.1', 12345), image_data, imaging_params)
|
||||
|
||||
sender.close()
|
||||
222
MiniSAR-Simulator-Py-main/test_client.py
Normal file
222
MiniSAR-Simulator-Py-main/test_client.py
Normal file
@ -0,0 +1,222 @@
|
||||
"""
|
||||
测试客户端程序
|
||||
用于测试UDP交互系统
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import logging
|
||||
import time
|
||||
from data_structures import (
|
||||
ControlPacket, ControlData, WorkMode, WorkInstruction,
|
||||
CONTROL_RX_PORT, CONTROL_TX_PORT, DATA_TX_PORT
|
||||
)
|
||||
|
||||
|
||||
class TestClient:
|
||||
"""测试客户端"""
|
||||
|
||||
def __init__(self, server_host: str = '127.0.0.1'):
|
||||
"""
|
||||
初始化测试客户端
|
||||
|
||||
Args:
|
||||
server_host: 服务器地址
|
||||
"""
|
||||
self.server_host = server_host
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.rx_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.rx_socket.bind(('0.0.0.0', CONTROL_TX_PORT)) # 绑定到随机端口
|
||||
self.local_port = self.rx_socket.getsockname()[1]
|
||||
|
||||
# 配置日志
|
||||
self.logger = logging.getLogger('TestClient')
|
||||
self.logger.setLevel(logging.DEBUG)
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
self.logger.info(f"Test client initialized on port {self.local_port}")
|
||||
|
||||
def send_control_packet(self, work_mode: int, work_instruction: int,
|
||||
route_number: int = 0, route_count: int = 0) -> bool:
|
||||
"""
|
||||
发送控制包
|
||||
|
||||
Args:
|
||||
work_mode: 工作模式
|
||||
work_instruction: 工作指令
|
||||
route_number: 航线编号
|
||||
route_count: 航线个数
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
# 创建控制数据
|
||||
control_data = ControlData(
|
||||
work_mode=work_mode,
|
||||
work_instruction=work_instruction,
|
||||
route_number=route_number,
|
||||
route_count=route_count
|
||||
)
|
||||
|
||||
# 创建控制包
|
||||
packet = ControlPacket(control_data=control_data)
|
||||
|
||||
# 序列化
|
||||
data = packet.to_bytes()
|
||||
|
||||
# Debug logging
|
||||
self.logger.info(f"DEBUG: Preparing to send packet - Mode: {work_mode}, Instr: {work_instruction}, Route#: {route_number}, Count: {route_count}")
|
||||
self.logger.info(f"DEBUG: Raw packet data ({len(data)} bytes): {data.hex()}")
|
||||
|
||||
# 发送
|
||||
self.socket.sendto(data, (self.server_host, CONTROL_RX_PORT))
|
||||
|
||||
self.logger.info(f"Control packet sent: mode={work_mode}, instruction={work_instruction}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send control packet: {e}")
|
||||
return False
|
||||
|
||||
def receive_response(self, timeout: float = 2.0) -> bool:
|
||||
"""
|
||||
接收响应包
|
||||
|
||||
Args:
|
||||
timeout: 接收超时时间(秒)
|
||||
|
||||
Returns:
|
||||
是否接收成功
|
||||
"""
|
||||
try:
|
||||
self.rx_socket.settimeout(timeout)
|
||||
data, addr = self.rx_socket.recvfrom(2048)
|
||||
|
||||
self.logger.info(f"Response received from {addr} ({len(data)} bytes)")
|
||||
self.logger.debug(f"Response data (hex): {data.hex()}")
|
||||
|
||||
return True
|
||||
|
||||
except socket.timeout:
|
||||
self.logger.warning("Response receive timeout")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to receive response: {e}")
|
||||
return False
|
||||
|
||||
def test_connect(self):
|
||||
"""测试连接指令"""
|
||||
self.logger.info("=== Testing CONNECT ===")
|
||||
self.send_control_packet(WorkMode.NORMAL, WorkInstruction.CONNECT)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def test_disconnect(self):
|
||||
"""测试断开连接指令"""
|
||||
self.logger.info("=== Testing DISCONNECT ===")
|
||||
self.send_control_packet(WorkMode.NORMAL, WorkInstruction.DISCONNECT)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def test_upload_route(self):
|
||||
"""测试上传航线指令"""
|
||||
self.logger.info("=== Testing UPLOAD_ROUTE ===")
|
||||
self.send_control_packet(
|
||||
WorkMode.AUTO,
|
||||
WorkInstruction.UPLOAD_ROUTE,
|
||||
route_number=0,
|
||||
route_count=1
|
||||
)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def test_start_calc(self):
|
||||
"""测试开始计算指令"""
|
||||
self.logger.info("=== Testing START_CALC ===")
|
||||
self.send_control_packet(WorkMode.AUTO, WorkInstruction.START_CALC)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def test_manual_boot(self):
|
||||
"""测试手动开机指令"""
|
||||
self.logger.info("=== Testing MANUAL_BOOT ===")
|
||||
self.send_control_packet(WorkMode.AUTO, WorkInstruction.MANUAL_BOOT)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def test_end_task(self):
|
||||
"""测试结束当前任务指令"""
|
||||
self.logger.info("=== Testing END_TASK ===")
|
||||
self.send_control_packet(WorkMode.AUTO, WorkInstruction.END_TASK)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def test_end_all_tasks(self):
|
||||
"""测试结束所有任务指令"""
|
||||
self.logger.info("=== Testing END_ALL_TASKS ===")
|
||||
self.send_control_packet(WorkMode.NORMAL, WorkInstruction.END_TASK)
|
||||
time.sleep(0.5)
|
||||
self.receive_response()
|
||||
|
||||
def run_full_test(self):
|
||||
"""运行完整测试"""
|
||||
self.logger.info("Starting full test sequence...")
|
||||
|
||||
# 测试连接
|
||||
self.test_connect()
|
||||
time.sleep(1)
|
||||
|
||||
# 测试上传航线
|
||||
self.test_upload_route()
|
||||
time.sleep(1)
|
||||
|
||||
# 测试开始计算
|
||||
self.test_start_calc()
|
||||
time.sleep(1)
|
||||
|
||||
# 测试手动开机
|
||||
self.test_manual_boot()
|
||||
time.sleep(1)
|
||||
|
||||
# 测试结束任务
|
||||
self.test_end_task()
|
||||
time.sleep(1)
|
||||
|
||||
# 测试断开连接
|
||||
self.test_disconnect()
|
||||
|
||||
self.logger.info("Full test sequence completed")
|
||||
|
||||
def close(self):
|
||||
"""关闭客户端"""
|
||||
self.socket.close()
|
||||
self.rx_socket.close()
|
||||
self.logger.info("Test client closed")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# 创建测试客户端
|
||||
client = TestClient('127.0.0.1')
|
||||
|
||||
try:
|
||||
# 运行完整测试
|
||||
client.run_full_test()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Test interrupted")
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user