feat: 自动化标定车间v1.0.....
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(vehicle_agent_windows CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# nlohmann/json —— 优先查找系统安装,不存在则 FetchContent 自动下载
|
||||
# ---------------------------------------------------------------------------
|
||||
find_package(nlohmann_json 3.9 QUIET)
|
||||
if(NOT nlohmann_json_FOUND)
|
||||
message(STATUS "未找到系统 nlohmann/json,使用 FetchContent 下载")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
nlohmann_json
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||
GIT_TAG v3.11.3
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(nlohmann_json)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 可执行文件
|
||||
# ---------------------------------------------------------------------------
|
||||
add_executable(vehicle_agent_windows
|
||||
src/main.cpp
|
||||
src/chassis_domain_server.cpp
|
||||
src/control_domain_server.cpp
|
||||
)
|
||||
|
||||
target_include_directories(vehicle_agent_windows PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(vehicle_agent_windows PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
# Windows 需要链接 Winsock
|
||||
if(WIN32)
|
||||
target_link_libraries(vehicle_agent_windows PRIVATE ws2_32)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 编译选项
|
||||
# ---------------------------------------------------------------------------
|
||||
if(MSVC)
|
||||
target_compile_options(vehicle_agent_windows PRIVATE /W4 /utf-8)
|
||||
else()
|
||||
target_compile_options(vehicle_agent_windows PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
@@ -302,3 +302,280 @@ Windows 在动作完成后返回,包含质量评估结果供编排器做自动
|
||||
| `artifacts` | array | 关联数据文件列表 |
|
||||
|
||||
**自动验收规则**:编排器要求 `data_quality_passed == true && suitable_for_commit == true`
|
||||
|
||||
---
|
||||
|
||||
## 5. 运控域 payload 格式(端口 9002)
|
||||
|
||||
### 5.1 CONTROL_GET_READINESS_REQ(type=11)
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_name": "control",
|
||||
"include_details": true,
|
||||
"target_resource_ids": []
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 CONTROL_GET_READINESS_RSP(type=12)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"error_code": {"code": 0},
|
||||
"message": "运控就绪。",
|
||||
"agent_ready": true,
|
||||
"trajectory_executor_ready": true,
|
||||
"vehicle_feedback_ready": true,
|
||||
"control_output_ready": true,
|
||||
"estop_released": true,
|
||||
"vehicle_safe_to_move": true,
|
||||
"checked_timestamp_us": 1711234567000000
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `trajectory_executor_ready` | bool | 轨迹执行器是否就绪 |
|
||||
| `vehicle_feedback_ready` | bool | 车辆反馈链路是否就绪 |
|
||||
| `control_output_ready` | bool | 控制输出链路是否就绪 |
|
||||
| `estop_released` | bool | 急停是否释放(**必须为 true**)|
|
||||
| `vehicle_safe_to_move` | bool | 是否允许移动(**必须为 true**)|
|
||||
|
||||
### 5.3 CONTROL_EVALUATION_REQ(type=13)
|
||||
|
||||
Ubuntu 发送,让车端按当前控制参数执行一次评估任务。`selected_task` 决定哪个子对象有效。
|
||||
|
||||
```json
|
||||
{
|
||||
"header": {
|
||||
"session_id": "workshop_v2_1711234567000000_1",
|
||||
"task_id": "stage_control_lateral_001",
|
||||
"vehicle_id": "AGV-001",
|
||||
"request_id": "chassis_req_1711234568000000",
|
||||
"client_send_timestamp_us": 1711234568000000,
|
||||
"operator_id": "op_001",
|
||||
"workshop_host": "ubuntu-workshop-pc"
|
||||
},
|
||||
"test_case_id": "lateral_tracking_v1",
|
||||
"task_purpose": 1,
|
||||
"selected_task": 1,
|
||||
"source_iteration_id": "iter_001",
|
||||
"trajectory_tracking": {
|
||||
"path": [
|
||||
{"x_m": 0.0, "y_m": 0.0, "yaw_rad": 0.0, "target_speed_ms": 0.5},
|
||||
{"x_m": 5.0, "y_m": 0.0, "yaw_rad": 0.0, "target_speed_ms": 0.5},
|
||||
{"x_m": 10.0, "y_m": 0.0, "yaw_rad": 0.0, "target_speed_ms": 0.0}
|
||||
],
|
||||
"stop_at_end": true,
|
||||
"timeout_sec": 60.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `selected_task` 枚举值
|
||||
|
||||
| 值 | 名称 | 有效子对象 | 说明 |
|
||||
|----|------|----------|------|
|
||||
| 0 | UNSPECIFIED | — | — |
|
||||
| 1 | TRAJECTORY_TRACKING | `trajectory_tracking` | 跟踪给定轨迹,评估横向/航向误差 |
|
||||
| 2 | VELOCITY_STEP | `velocity_step` | 速度阶跃,评估纵向动态响应 |
|
||||
| 3 | ACCELERATION_DECELERATION | `accel_decel` | 加减速,评估纵向平顺性与 jerk |
|
||||
| 4 | STOP_ACCURACY | `stop_accuracy` | 评估停车位置精度 |
|
||||
|
||||
#### 各任务子对象格式
|
||||
|
||||
**trajectory_tracking**
|
||||
```json
|
||||
{
|
||||
"path": [
|
||||
{"x_m": 0.0, "y_m": 0.0, "yaw_rad": 0.0, "target_speed_ms": 0.5}
|
||||
],
|
||||
"stop_at_end": true,
|
||||
"timeout_sec": 60.0
|
||||
}
|
||||
```
|
||||
|
||||
**velocity_step**
|
||||
```json
|
||||
{
|
||||
"target_velocity_ms": 1.0,
|
||||
"hold_time_sec": 5.0,
|
||||
"settle_before_step_sec": 2.0
|
||||
}
|
||||
```
|
||||
|
||||
**accel_decel**
|
||||
```json
|
||||
{
|
||||
"start_velocity_ms": 0.0,
|
||||
"target_velocity_ms": 1.5,
|
||||
"target_accel_ms2": 0.5,
|
||||
"hold_time_sec": 3.0
|
||||
}
|
||||
```
|
||||
|
||||
**stop_accuracy**
|
||||
```json
|
||||
{
|
||||
"target_stop_x_m": 10.0,
|
||||
"target_stop_y_m": 0.0,
|
||||
"target_stop_yaw_rad": 0.0,
|
||||
"timeout_sec": 30.0
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 CONTROL_EVALUATION_RSP(type=14)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"error_code": {"code": 0},
|
||||
"message": "轨迹跟踪评估完成。",
|
||||
"job_id": "chassis_req_1711234568000000",
|
||||
"data_quality_passed": true,
|
||||
"suitable_for_commit": true,
|
||||
"recommended_parameter_version": "control_lateral_pid_v2",
|
||||
"validation_summary": {
|
||||
"rms_lateral_error_m": 0.015,
|
||||
"rms_heading_error_rad": 0.010,
|
||||
"rms_speed_error_ms": 0.05,
|
||||
"overshoot_ratio": 0.08,
|
||||
"settle_time_sec": 1.2,
|
||||
"stop_position_error_m": 0.02,
|
||||
"max_jerk": 0.3,
|
||||
"saturation_ratio": 0.05,
|
||||
"auto_acceptance_passed": true
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"file_name": "control_eval_001.csv",
|
||||
"file_uri": "C:/calib_data/control_eval_001.csv",
|
||||
"size_bytes": 204800,
|
||||
"description": "控制评估误差时序数据"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data_quality_passed` | bool | 数据质量是否达标 |
|
||||
| `suitable_for_commit` | bool | 参数是否适合写入 |
|
||||
| `validation_summary` | object | 验收关键指标 |
|
||||
| `validation_summary.rms_lateral_error_m` | float64 | 横向误差 RMS(m)|
|
||||
| `validation_summary.rms_heading_error_rad` | float64 | 航向误差 RMS(rad)|
|
||||
| `validation_summary.rms_speed_error_ms` | float64 | 速度误差 RMS(m/s)|
|
||||
| `validation_summary.overshoot_ratio` | float64 | 超调比例(0~1)|
|
||||
| `validation_summary.settle_time_sec` | float64 | 收敛时间(s)|
|
||||
| `validation_summary.stop_position_error_m` | float64 | 停车位置误差(m)|
|
||||
| `validation_summary.max_jerk` | float64 | 最大 jerk(m/s³)|
|
||||
| `validation_summary.saturation_ratio` | float64 | 控制饱和占比(0~1)|
|
||||
| `validation_summary.auto_acceptance_passed` | bool | 综合自动验收是否通过 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 交互时序
|
||||
|
||||
### 6.1 底盘标定完整流程
|
||||
|
||||
```
|
||||
Ubuntu (orchestrator) Ubuntu (gateway) Windows (vehicle_agent)
|
||||
│ │ │
|
||||
│── execute_goal ───────────►│ │
|
||||
│ │── TCP connect :9001 ──────►│
|
||||
│ │── CHASSIS_GET_READINESS_REQ│
|
||||
│ │◄─ CHASSIS_GET_READINESS_RSP│
|
||||
│ │── TCP close ───────────────│
|
||||
│ │ │
|
||||
│ (readiness check passed) │ │
|
||||
│ │── TCP connect :9001 ──────►│
|
||||
│ │── CHASSIS_MOTION_PRIM_REQ ─│ (车辆开始运动)
|
||||
│ │ ... 等待车辆完成 ... │
|
||||
│ │◄─ CHASSIS_MOTION_PRIM_RSP ─│ (含验收指标)
|
||||
│ │── TCP close ───────────────│
|
||||
│◄── action result ─────────│ │
|
||||
```
|
||||
|
||||
### 6.2 注意事项
|
||||
|
||||
1. **每次请求建立新 TCP 连接**:Ubuntu 侧 `TcpClient` 每次 `send_recv` 都会新建、使用、关闭连接。Windows 侧不需要维护长连接状态。
|
||||
2. **超时控制**:Ubuntu 侧默认等待 5000ms(可通过 ROS2 参数 `chassis_timeout_ms` / `control_timeout_ms` 调整)。动作原语执行时间可能远超此值,Windows 侧应在动作完成后才回复 RSP 帧,不要提前响应。
|
||||
3. **急停处理**:Ubuntu 侧 orchestrator 在收到取消请求时会关闭 socket 连接。Windows 侧检测到连接断开即应停止当前动作并触发安全停车。
|
||||
4. **并发**:底盘域和运控域在不同端口,理论上可并发,但编排器保证同一时刻只有一个域的任务在执行。
|
||||
|
||||
---
|
||||
|
||||
## 7. Windows 侧实现最小要求
|
||||
|
||||
### 7.1 必须实现的接口
|
||||
|
||||
| 端口 | REQ type | 行为 |
|
||||
|------|----------|------|
|
||||
| 9001 | type=1 READINESS | 检查底盘状态,返回 type=2 |
|
||||
| 9001 | type=3 MOTION_PRIMITIVE | 执行指定动作,完成后返回 type=4(含验收指标)|
|
||||
| 9002 | type=11 READINESS | 检查运控状态,返回 type=12 |
|
||||
| 9002 | type=13 EVALUATION | 执行控制评估,完成后返回 type=14(含验收指标)|
|
||||
|
||||
### 7.2 错误响应格式
|
||||
|
||||
执行失败时,`success` 设为 `false`,`error_code.code` 填对应错误码,`message` 填人读错误说明:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error_code": {"code": 5},
|
||||
"message": "底盘驱动离线,无法执行动作。",
|
||||
"job_id": "chassis_req_1711234567100000",
|
||||
"data_quality_passed": false,
|
||||
"suitable_for_commit": false
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 联调工具
|
||||
|
||||
Ubuntu 侧提供了 Python stub 服务器用于 Windows 侧联调前的自测:
|
||||
|
||||
```bash
|
||||
# 在 Ubuntu 上运行,模拟 Windows 车端(返回空 payload 的成功响应)
|
||||
python3 src/win_ubuntu_bridge/vehicle_agent_gateway/scripts/windows_stub_server.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:ErrorCode 值
|
||||
|
||||
| 值 | 名称 | 含义 |
|
||||
|----|------|------|
|
||||
| 0 | UNSPECIFIED | 未指定 |
|
||||
| 1 | OK | 成功 |
|
||||
| 2 | INVALID_ARGUMENT | 参数非法 |
|
||||
| 3 | INVALID_STATE | 当前状态不允许此操作 |
|
||||
| 4 | VEHICLE_BUSY | 车辆当前忙 |
|
||||
| 5 | NOT_READY | 未准备好 |
|
||||
| 6 | TIMEOUT | 超时 |
|
||||
| 7 | NETWORK_LOSS | 网络断开 |
|
||||
| 8 | SAFETY_TRIGGERED | 安全保护触发 |
|
||||
| 9 | HARDWARE_FAULT | 硬件故障 |
|
||||
| 10 | FILE_NOT_FOUND | 文件不存在 |
|
||||
| 11 | CHECKSUM_MISMATCH | 校验失败 |
|
||||
| 12 | INTERNAL_ERROR | 内部错误 |
|
||||
| 13 | UNSUPPORTED_CAPABILITY | 当前车辆不支持该能力 |
|
||||
| 14 | RESOURCE_LOCKED | 资源已被其他会话占用 |
|
||||
| 15 | MANUAL_CONFIRM_REQUIRED | 需要人工确认 |
|
||||
| 16 | APPROVAL_REQUIRED | 需要审批 |
|
||||
| 17 | VALIDATION_FAILED | 验证失败 |
|
||||
| 18 | ROLLBACK_REQUIRED | 需要回滚 |
|
||||
| 19 | DATA_QUALITY_INSUFFICIENT | 数据质量不足 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 B:快速验证清单
|
||||
|
||||
Windows 侧自测时依次验证:
|
||||
|
||||
- [ ] 监听 9001 端口,收到 type=1 帧,回复 type=2 帧(`success=true`, `estop_released=true`, `vehicle_safe_to_move=true`)
|
||||
- [ ] 收到 type=3 帧(`selected_primitive=1` 直线),车辆执行,完成后回复 type=4(`success=true`, `data_quality_passed=true`, `suitable_for_commit=true`)
|
||||
- [ ] 监听 9002 端口,收到 type=11 帧,回复 type=12 帧
|
||||
- [ ] 收到 type=13 帧(`selected_task=1` 轨迹跟踪),执行,回复 type=14
|
||||
- [ ] 模拟急停:收到 type=3 帧后主动关闭连接,验证车辆停车
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
"""frame_codec.py
|
||||
|
||||
TCP 帧编解码层。
|
||||
|
||||
帧格式(与 Ubuntu 侧 proto_frame.cpp 完全一致):
|
||||
[4字节 LE uint32: msg_type][4字节 LE uint32: payload_len][payload_len字节: UTF-8 JSON]
|
||||
|
||||
所有公共 JSON 字段的键名与 proto 字段名对齐,Ubuntu 侧 gateway_codec.hpp 是权威参考。
|
||||
"""
|
||||
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
from enum import IntEnum
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 消息类型枚举(与 proto_frame.hpp MsgType 完全一致)
|
||||
# ---------------------------------------------------------------------------
|
||||
class MsgType(IntEnum):
|
||||
# 底盘域
|
||||
CHASSIS_GET_READINESS_REQ = 1
|
||||
CHASSIS_GET_READINESS_RSP = 2
|
||||
CHASSIS_MOTION_PRIMITIVE_REQ = 3
|
||||
CHASSIS_MOTION_PRIMITIVE_RSP = 4
|
||||
CHASSIS_EMERGENCY_BRAKE_REQ = 5
|
||||
CHASSIS_EMERGENCY_BRAKE_RSP = 6
|
||||
# 运控域
|
||||
CONTROL_GET_READINESS_REQ = 11
|
||||
CONTROL_GET_READINESS_RSP = 12
|
||||
CONTROL_EVALUATION_REQ = 13
|
||||
CONTROL_EVALUATION_RSP = 14
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 底盘动作原语类型(与 ChassisMotionPrimitiveType.msg 一致)
|
||||
# ---------------------------------------------------------------------------
|
||||
class ChassisMotionPrimitiveType(IntEnum):
|
||||
UNSPECIFIED = 0
|
||||
STRAIGHT_LINE = 1
|
||||
ARC = 2
|
||||
IN_PLACE_ROTATION = 3
|
||||
STEERING_SWEEP = 4
|
||||
LATERAL_TRANSLATION = 5
|
||||
DIAGONAL_MOTION = 6
|
||||
MODULE_ALIGNMENT = 7
|
||||
COORDINATED_STEERING = 8
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 运控评估任务类型(与 ControllerEvaluationTaskType.msg 一致)
|
||||
# ---------------------------------------------------------------------------
|
||||
class ControlEvaluationTaskType(IntEnum):
|
||||
UNSPECIFIED = 0
|
||||
TRAJECTORY_TRACKING = 1
|
||||
VELOCITY_STEP = 2
|
||||
ACCELERATION_DECELERATION = 3
|
||||
STOP_ACCURACY = 4
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ErrorCode(与 ErrorCode.msg 一致)
|
||||
# ---------------------------------------------------------------------------
|
||||
class ErrorCode(IntEnum):
|
||||
UNSPECIFIED = 0
|
||||
OK = 1
|
||||
INVALID_ARGUMENT = 2
|
||||
INVALID_STATE = 3
|
||||
VEHICLE_BUSY = 4
|
||||
NOT_READY = 5
|
||||
TIMEOUT = 6
|
||||
NETWORK_LOSS = 7
|
||||
SAFETY_TRIGGERED = 8
|
||||
HARDWARE_FAULT = 9
|
||||
FILE_NOT_FOUND = 10
|
||||
CHECKSUM_MISMATCH = 11
|
||||
INTERNAL_ERROR = 12
|
||||
UNSUPPORTED_CAPABILITY = 13
|
||||
RESOURCE_LOCKED = 14
|
||||
MANUAL_CONFIRM_REQUIRED = 15
|
||||
APPROVAL_REQUIRED = 16
|
||||
VALIDATION_FAILED = 17
|
||||
ROLLBACK_REQUIRED = 18
|
||||
DATA_QUALITY_INSUFFICIENT = 19
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 底层 I/O 工具
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _recv_exactly(conn: socket.socket, n: int) -> bytes:
|
||||
"""从 conn 读取恰好 n 字节,连接关闭时抛出 ConnectionError。"""
|
||||
buf = bytearray()
|
||||
while len(buf) < n:
|
||||
chunk = conn.recv(n - len(buf))
|
||||
if not chunk:
|
||||
raise ConnectionError("连接已关闭")
|
||||
buf.extend(chunk)
|
||||
return bytes(buf)
|
||||
|
||||
|
||||
def recv_frame(conn: socket.socket) -> Tuple[MsgType, Dict[str, Any]]:
|
||||
"""从 conn 读一帧,返回 (msg_type, payload_dict)。
|
||||
|
||||
payload_dict 为空 dict 表示 payload_len=0 的帧。
|
||||
"""
|
||||
header = _recv_exactly(conn, 8)
|
||||
msg_type_val, payload_len = struct.unpack_from("<II", header)
|
||||
msg_type = MsgType(msg_type_val)
|
||||
if payload_len == 0:
|
||||
return msg_type, {}
|
||||
raw = _recv_exactly(conn, payload_len)
|
||||
payload = json.loads(raw.decode("utf-8"))
|
||||
return msg_type, payload
|
||||
|
||||
|
||||
def send_frame(conn: socket.socket, msg_type: MsgType, payload: Dict[str, Any]) -> None:
|
||||
"""向 conn 发一帧。payload 为空 dict 时 payload_len=0。"""
|
||||
if payload:
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
else:
|
||||
data = b""
|
||||
header = struct.pack("<II", int(msg_type), len(data))
|
||||
conn.sendall(header + data)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 公共响应构造辅助
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def make_error_code(code: ErrorCode) -> Dict[str, int]:
|
||||
return {"code": int(code)}
|
||||
|
||||
|
||||
def make_success_response(message: str = "") -> Dict[str, Any]:
|
||||
return {
|
||||
"success": True,
|
||||
"error_code": make_error_code(ErrorCode.OK),
|
||||
"message": message,
|
||||
}
|
||||
|
||||
|
||||
def make_failure_response(message: str, code: ErrorCode = ErrorCode.INTERNAL_ERROR) -> Dict[str, Any]:
|
||||
return {
|
||||
"success": False,
|
||||
"error_code": make_error_code(code),
|
||||
"message": message,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Readiness 响应构造辅助
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def make_chassis_readiness_response(
|
||||
*,
|
||||
success: bool,
|
||||
message: str,
|
||||
agent_ready: bool,
|
||||
chassis_driver_online: bool,
|
||||
motion_control_ready: bool,
|
||||
estop_released: bool,
|
||||
vehicle_safe_to_move: bool,
|
||||
telemetry_available: bool,
|
||||
checked_timestamp_us: int,
|
||||
error_code: ErrorCode = ErrorCode.OK,
|
||||
) -> Dict[str, Any]:
|
||||
"""构造底盘就绪响应 payload(对应 ChassisReadinessResponse.msg)。"""
|
||||
return {
|
||||
"success": success,
|
||||
"error_code": make_error_code(error_code),
|
||||
"message": message,
|
||||
"agent_ready": agent_ready,
|
||||
"chassis_driver_online": chassis_driver_online,
|
||||
"motion_control_ready": motion_control_ready,
|
||||
"estop_released": estop_released,
|
||||
"vehicle_safe_to_move": vehicle_safe_to_move,
|
||||
"telemetry_available": telemetry_available,
|
||||
"checked_timestamp_us": checked_timestamp_us,
|
||||
}
|
||||
|
||||
|
||||
def make_control_readiness_response(
|
||||
*,
|
||||
success: bool,
|
||||
message: str,
|
||||
agent_ready: bool,
|
||||
trajectory_executor_ready: bool,
|
||||
vehicle_feedback_ready: bool,
|
||||
control_output_ready: bool,
|
||||
estop_released: bool,
|
||||
vehicle_safe_to_move: bool,
|
||||
checked_timestamp_us: int,
|
||||
error_code: ErrorCode = ErrorCode.OK,
|
||||
) -> Dict[str, Any]:
|
||||
"""构造运控就绪响应 payload(对应 ControlReadinessResponse.msg)。"""
|
||||
return {
|
||||
"success": success,
|
||||
"error_code": make_error_code(error_code),
|
||||
"message": message,
|
||||
"agent_ready": agent_ready,
|
||||
"trajectory_executor_ready": trajectory_executor_ready,
|
||||
"vehicle_feedback_ready": vehicle_feedback_ready,
|
||||
"control_output_ready": control_output_ready,
|
||||
"estop_released": estop_released,
|
||||
"vehicle_safe_to_move": vehicle_safe_to_move,
|
||||
"checked_timestamp_us": checked_timestamp_us,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 任务结果响应构造辅助
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def make_chassis_job_result(
|
||||
*,
|
||||
success: bool,
|
||||
message: str,
|
||||
job_id: str,
|
||||
data_quality_passed: bool,
|
||||
suitable_for_commit: bool,
|
||||
recommended_parameter_version: str = "",
|
||||
estimated_straight_line_bias: float = 0.0,
|
||||
max_lateral_error_m: float = 0.0,
|
||||
max_yaw_error_rad: float = 0.0,
|
||||
rms_lateral_error_m: float = 0.0,
|
||||
rms_yaw_error_rad: float = 0.0,
|
||||
repeatability_error_m: float = 0.0,
|
||||
curvature_error: float = 0.0,
|
||||
module_consistency_error: float = 0.0,
|
||||
auto_acceptance_passed: bool = False,
|
||||
artifacts: list = None,
|
||||
error_code: ErrorCode = ErrorCode.OK,
|
||||
) -> Dict[str, Any]:
|
||||
"""构造底盘任务结果 payload(对应 ChassisJobResult.msg)。"""
|
||||
return {
|
||||
"success": success,
|
||||
"error_code": make_error_code(error_code if success else ErrorCode.INTERNAL_ERROR),
|
||||
"message": message,
|
||||
"job_id": job_id,
|
||||
"data_quality_passed": data_quality_passed,
|
||||
"suitable_for_commit": suitable_for_commit,
|
||||
"recommended_parameter_version": recommended_parameter_version,
|
||||
"estimated_straight_line_bias": estimated_straight_line_bias,
|
||||
"validation_summary": {
|
||||
"max_lateral_error_m": max_lateral_error_m,
|
||||
"max_yaw_error_rad": max_yaw_error_rad,
|
||||
"rms_lateral_error_m": rms_lateral_error_m,
|
||||
"rms_yaw_error_rad": rms_yaw_error_rad,
|
||||
"repeatability_error_m": repeatability_error_m,
|
||||
"curvature_error": curvature_error,
|
||||
"module_consistency_error": module_consistency_error,
|
||||
"auto_acceptance_passed": auto_acceptance_passed,
|
||||
},
|
||||
"artifacts": artifacts or [],
|
||||
}
|
||||
|
||||
|
||||
def make_control_job_result(
|
||||
*,
|
||||
success: bool,
|
||||
message: str,
|
||||
job_id: str,
|
||||
data_quality_passed: bool,
|
||||
suitable_for_commit: bool,
|
||||
recommended_parameter_version: str = "",
|
||||
rms_lateral_error_m: float = 0.0,
|
||||
rms_heading_error_rad: float = 0.0,
|
||||
rms_speed_error_ms: float = 0.0,
|
||||
overshoot_ratio: float = 0.0,
|
||||
settle_time_sec: float = 0.0,
|
||||
stop_position_error_m: float = 0.0,
|
||||
max_jerk: float = 0.0,
|
||||
saturation_ratio: float = 0.0,
|
||||
auto_acceptance_passed: bool = False,
|
||||
artifacts: list = None,
|
||||
error_code: ErrorCode = ErrorCode.OK,
|
||||
) -> Dict[str, Any]:
|
||||
"""构造运控任务结果 payload(对应 ControlJobResult.msg)。"""
|
||||
return {
|
||||
"success": success,
|
||||
"error_code": make_error_code(error_code if success else ErrorCode.INTERNAL_ERROR),
|
||||
"message": message,
|
||||
"job_id": job_id,
|
||||
"data_quality_passed": data_quality_passed,
|
||||
"suitable_for_commit": suitable_for_commit,
|
||||
"recommended_parameter_version": recommended_parameter_version,
|
||||
"validation_summary": {
|
||||
"rms_lateral_error_m": rms_lateral_error_m,
|
||||
"rms_heading_error_rad": rms_heading_error_rad,
|
||||
"rms_speed_error_ms": rms_speed_error_ms,
|
||||
"overshoot_ratio": overshoot_ratio,
|
||||
"settle_time_sec": settle_time_sec,
|
||||
"stop_position_error_m": stop_position_error_m,
|
||||
"max_jerk": max_jerk,
|
||||
"saturation_ratio": saturation_ratio,
|
||||
"auto_acceptance_passed": auto_acceptance_passed,
|
||||
},
|
||||
"artifacts": artifacts or [],
|
||||
}
|
||||
|
||||
|
||||
def make_artifact(file_name: str, file_uri: str, size_bytes: int = 0, description: str = "") -> Dict[str, Any]:
|
||||
"""构造文件引用条目(对应 FileReference.msg)。"""
|
||||
return {
|
||||
"file_name": file_name,
|
||||
"file_uri": file_uri,
|
||||
"size_bytes": size_bytes,
|
||||
"description": description,
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// chassis_domain_server.hpp
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include "frame_codec.hpp"
|
||||
#include "chassis_handler.hpp"
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
class ChassisDomainServer
|
||||
{
|
||||
public:
|
||||
explicit ChassisDomainServer(uint16_t port, ChassisHandler* handler);
|
||||
~ChassisDomainServer();
|
||||
|
||||
// 阻塞运行,直到 stop() 被调用
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void handle_connection(sock_t conn);
|
||||
|
||||
uint16_t port_;
|
||||
ChassisHandler* handler_;
|
||||
std::atomic<bool> running_;
|
||||
sock_t listen_sock_;
|
||||
};
|
||||
|
||||
} // namespace vaw
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
// chassis_handler.hpp
|
||||
// 底盘处理器虚基类。
|
||||
// 子类覆盖各方法后即可接入真实底盘驱动;默认实现返回 stub 成功响应。
|
||||
|
||||
#include <chrono>
|
||||
#include "data_types.hpp"
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
class ChassisHandler
|
||||
{
|
||||
public:
|
||||
virtual ~ChassisHandler() = default;
|
||||
|
||||
// 查询底盘就绪状态
|
||||
// req: CHASSIS_GET_READINESS_REQ 解析结果(AgentReadinessRequest 字段)
|
||||
virtual ChassisReadinessResponse get_readiness(const nlohmann::json& req)
|
||||
{
|
||||
ChassisReadinessResponse rsp;
|
||||
rsp.success = true;
|
||||
rsp.error_code = ErrorCode::OK;
|
||||
rsp.message = "底盘就绪(stub)";
|
||||
rsp.agent_ready = true;
|
||||
rsp.chassis_driver_online = true;
|
||||
rsp.motion_control_ready = true;
|
||||
rsp.estop_released = true;
|
||||
rsp.vehicle_safe_to_move = true;
|
||||
rsp.telemetry_available = true;
|
||||
rsp.checked_timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 执行底盘动作原语
|
||||
// req: 已解析的 MotionPrimitiveRequest
|
||||
virtual ChassisJobResult execute_motion_primitive(const MotionPrimitiveRequest& req)
|
||||
{
|
||||
(void)req;
|
||||
ChassisJobResult result;
|
||||
result.success = true;
|
||||
result.error_code = ErrorCode::OK;
|
||||
result.message = "动作原语执行完成(stub)";
|
||||
result.job_id = req.test_case_id;
|
||||
result.data_quality_passed = true;
|
||||
result.suitable_for_commit = true;
|
||||
result.validation_summary.auto_acceptance_passed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 紧急制动
|
||||
// req: CHASSIS_EMERGENCY_BRAKE_REQ payload
|
||||
virtual nlohmann::json emergency_brake(const nlohmann::json& req)
|
||||
{
|
||||
(void)req;
|
||||
return nlohmann::json{
|
||||
{"success", true},
|
||||
{"error_code", {"code", 0}},
|
||||
{"message", "紧急制动已执行(stub)"},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace vaw
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// control_domain_server.hpp
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include "frame_codec.hpp"
|
||||
#include "control_handler.hpp"
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
class ControlDomainServer
|
||||
{
|
||||
public:
|
||||
explicit ControlDomainServer(uint16_t port, ControlHandler* handler);
|
||||
~ControlDomainServer();
|
||||
|
||||
// 阻塞运行,直到 stop() 被调用
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void handle_connection(sock_t conn);
|
||||
|
||||
uint16_t port_;
|
||||
ControlHandler* handler_;
|
||||
std::atomic<bool> running_;
|
||||
sock_t listen_sock_;
|
||||
};
|
||||
|
||||
} // namespace vaw
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
// control_handler.hpp
|
||||
// 运控处理器虚基类。
|
||||
// 子类覆盖各方法后即可接入真实运控驱动;默认实现返回 stub 成功响应。
|
||||
|
||||
#include <chrono>
|
||||
#include "data_types.hpp"
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
class ControlHandler
|
||||
{
|
||||
public:
|
||||
virtual ~ControlHandler() = default;
|
||||
|
||||
// 查询运控就绪状态
|
||||
virtual ControlReadinessResponse get_readiness(const nlohmann::json& req)
|
||||
{
|
||||
(void)req;
|
||||
ControlReadinessResponse rsp;
|
||||
rsp.success = true;
|
||||
rsp.error_code = ErrorCode::OK;
|
||||
rsp.message = "运控就绪(stub)";
|
||||
rsp.agent_ready = true;
|
||||
rsp.trajectory_executor_ready = true;
|
||||
rsp.vehicle_feedback_ready = true;
|
||||
rsp.control_output_ready = true;
|
||||
rsp.estop_released = true;
|
||||
rsp.vehicle_safe_to_move = true;
|
||||
rsp.checked_timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return rsp;
|
||||
}
|
||||
|
||||
// 执行控制评估任务
|
||||
// req: 已解析的 ControllerEvaluationRequest
|
||||
virtual ControlJobResult execute_evaluation(const ControllerEvaluationRequest& req)
|
||||
{
|
||||
(void)req;
|
||||
ControlJobResult result;
|
||||
result.success = true;
|
||||
result.error_code = ErrorCode::OK;
|
||||
result.message = "控制评估完成(stub)";
|
||||
result.job_id = req.test_case_id;
|
||||
result.data_quality_passed = true;
|
||||
result.suitable_for_commit = true;
|
||||
result.validation_summary.auto_acceptance_passed = true;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace vaw
|
||||
@@ -0,0 +1,630 @@
|
||||
#pragma once
|
||||
// data_types.hpp
|
||||
// 所有消息数据结构定义,JSON key 与 proto 字段名及 gateway_codec.hpp 完全一致。
|
||||
// 使用 nlohmann/json ADL 钩子(from_json / to_json)实现序列化。
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
// ===========================================================================
|
||||
// 枚举
|
||||
// ===========================================================================
|
||||
|
||||
enum class ErrorCode : uint32_t {
|
||||
UNSPECIFIED = 0,
|
||||
OK = 1,
|
||||
INVALID_ARGUMENT = 2,
|
||||
INVALID_STATE = 3,
|
||||
VEHICLE_BUSY = 4,
|
||||
NOT_READY = 5,
|
||||
TIMEOUT = 6,
|
||||
NETWORK_LOSS = 7,
|
||||
SAFETY_TRIGGERED = 8,
|
||||
HARDWARE_FAULT = 9,
|
||||
FILE_NOT_FOUND = 10,
|
||||
CHECKSUM_MISMATCH = 11,
|
||||
INTERNAL_ERROR = 12,
|
||||
UNSUPPORTED_CAPABILITY = 13,
|
||||
RESOURCE_LOCKED = 14,
|
||||
MANUAL_CONFIRM_REQUIRED = 15,
|
||||
APPROVAL_REQUIRED = 16,
|
||||
VALIDATION_FAILED = 17,
|
||||
ROLLBACK_REQUIRED = 18,
|
||||
DATA_QUALITY_INSUFFICIENT = 19,
|
||||
};
|
||||
|
||||
enum class ChassisMotionPrimitiveType : uint32_t {
|
||||
UNSPECIFIED = 0,
|
||||
STRAIGHT_LINE = 1,
|
||||
ARC = 2,
|
||||
IN_PLACE_ROTATION = 3,
|
||||
STEERING_SWEEP = 4,
|
||||
LATERAL_TRANSLATION = 5,
|
||||
DIAGONAL_MOTION = 6,
|
||||
MODULE_ALIGNMENT = 7,
|
||||
COORDINATED_STEERING = 8,
|
||||
};
|
||||
|
||||
enum class ControlEvaluationTaskType : uint32_t {
|
||||
UNSPECIFIED = 0,
|
||||
TRAJECTORY_TRACKING = 1,
|
||||
VELOCITY_STEP = 2,
|
||||
ACCELERATION_DECELERATION = 3,
|
||||
STOP_ACCURACY = 4,
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// 公共类型
|
||||
// ===========================================================================
|
||||
|
||||
struct RequestHeader {
|
||||
std::string session_id;
|
||||
std::string task_id;
|
||||
std::string vehicle_id;
|
||||
std::string request_id;
|
||||
int64_t client_send_timestamp_us{0};
|
||||
std::string operator_id;
|
||||
std::string workshop_host;
|
||||
};
|
||||
|
||||
inline void from_json(const nlohmann::json& j, RequestHeader& h)
|
||||
{
|
||||
h.session_id = j.value("session_id", std::string{});
|
||||
h.task_id = j.value("task_id", std::string{});
|
||||
h.vehicle_id = j.value("vehicle_id", std::string{});
|
||||
h.request_id = j.value("request_id", std::string{});
|
||||
h.client_send_timestamp_us = j.value("client_send_timestamp_us", int64_t{0});
|
||||
h.operator_id = j.value("operator_id", std::string{});
|
||||
h.workshop_host = j.value("workshop_host", std::string{});
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json& j, const RequestHeader& h)
|
||||
{
|
||||
j = {
|
||||
{"session_id", h.session_id},
|
||||
{"task_id", h.task_id},
|
||||
{"vehicle_id", h.vehicle_id},
|
||||
{"request_id", h.request_id},
|
||||
{"client_send_timestamp_us", h.client_send_timestamp_us},
|
||||
{"operator_id", h.operator_id},
|
||||
{"workshop_host", h.workshop_host},
|
||||
};
|
||||
}
|
||||
|
||||
struct FileReference {
|
||||
std::string file_name;
|
||||
std::string file_uri;
|
||||
int64_t size_bytes{0};
|
||||
std::string description;
|
||||
};
|
||||
|
||||
inline void from_json(const nlohmann::json& j, FileReference& r)
|
||||
{
|
||||
r.file_name = j.value("file_name", std::string{});
|
||||
r.file_uri = j.value("file_uri", std::string{});
|
||||
r.size_bytes = j.value("size_bytes", int64_t{0});
|
||||
r.description = j.value("description", std::string{});
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json& j, const FileReference& r)
|
||||
{
|
||||
j = {
|
||||
{"file_name", r.file_name},
|
||||
{"file_uri", r.file_uri},
|
||||
{"size_bytes", r.size_bytes},
|
||||
{"description", r.description},
|
||||
};
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 底盘命令原语
|
||||
// ===========================================================================
|
||||
|
||||
struct StraightLineCommand {
|
||||
double target_speed_ms{0.0};
|
||||
double target_distance_m{0.0};
|
||||
bool reverse{false};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, StraightLineCommand& c) {
|
||||
c.target_speed_ms = j.value("target_speed_ms", 0.0);
|
||||
c.target_distance_m = j.value("target_distance_m", 0.0);
|
||||
c.reverse = j.value("reverse", false);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const StraightLineCommand& c) {
|
||||
j = {{"target_speed_ms", c.target_speed_ms},
|
||||
{"target_distance_m", c.target_distance_m},
|
||||
{"reverse", c.reverse}};
|
||||
}
|
||||
|
||||
struct ArcCommand {
|
||||
double target_speed_ms{0.0};
|
||||
double radius_m{0.0};
|
||||
double sweep_angle_deg{0.0};
|
||||
bool clockwise{false};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, ArcCommand& c) {
|
||||
c.target_speed_ms = j.value("target_speed_ms", 0.0);
|
||||
c.radius_m = j.value("radius_m", 0.0);
|
||||
c.sweep_angle_deg = j.value("sweep_angle_deg", 0.0);
|
||||
c.clockwise = j.value("clockwise", false);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const ArcCommand& c) {
|
||||
j = {{"target_speed_ms", c.target_speed_ms},
|
||||
{"radius_m", c.radius_m},
|
||||
{"sweep_angle_deg", c.sweep_angle_deg},
|
||||
{"clockwise", c.clockwise}};
|
||||
}
|
||||
|
||||
struct InPlaceRotationCommand {
|
||||
double target_yaw_deg{0.0};
|
||||
double target_angular_vel_deg_s{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, InPlaceRotationCommand& c) {
|
||||
c.target_yaw_deg = j.value("target_yaw_deg", 0.0);
|
||||
c.target_angular_vel_deg_s = j.value("target_angular_vel_deg_s", 0.0);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const InPlaceRotationCommand& c) {
|
||||
j = {{"target_yaw_deg", c.target_yaw_deg},
|
||||
{"target_angular_vel_deg_s", c.target_angular_vel_deg_s}};
|
||||
}
|
||||
|
||||
struct SteeringSweepCommand {
|
||||
double target_angle_deg{0.0};
|
||||
double sweep_amplitude_deg{0.0};
|
||||
double sweep_frequency_hz{0.0};
|
||||
double duration_sec{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, SteeringSweepCommand& c) {
|
||||
c.target_angle_deg = j.value("target_angle_deg", 0.0);
|
||||
c.sweep_amplitude_deg = j.value("sweep_amplitude_deg", 0.0);
|
||||
c.sweep_frequency_hz = j.value("sweep_frequency_hz", 0.0);
|
||||
c.duration_sec = j.value("duration_sec", 0.0);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const SteeringSweepCommand& c) {
|
||||
j = {{"target_angle_deg", c.target_angle_deg},
|
||||
{"sweep_amplitude_deg", c.sweep_amplitude_deg},
|
||||
{"sweep_frequency_hz", c.sweep_frequency_hz},
|
||||
{"duration_sec", c.duration_sec}};
|
||||
}
|
||||
|
||||
struct LateralTranslationCommand {
|
||||
double target_speed_ms{0.0};
|
||||
double target_distance_m{0.0};
|
||||
bool move_left{false};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, LateralTranslationCommand& c) {
|
||||
c.target_speed_ms = j.value("target_speed_ms", 0.0);
|
||||
c.target_distance_m = j.value("target_distance_m", 0.0);
|
||||
c.move_left = j.value("move_left", false);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const LateralTranslationCommand& c) {
|
||||
j = {{"target_speed_ms", c.target_speed_ms},
|
||||
{"target_distance_m", c.target_distance_m},
|
||||
{"move_left", c.move_left}};
|
||||
}
|
||||
|
||||
struct DiagonalMotionCommand {
|
||||
double target_speed_ms{0.0};
|
||||
double target_distance_m{0.0};
|
||||
double heading_deg{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, DiagonalMotionCommand& c) {
|
||||
c.target_speed_ms = j.value("target_speed_ms", 0.0);
|
||||
c.target_distance_m = j.value("target_distance_m", 0.0);
|
||||
c.heading_deg = j.value("heading_deg", 0.0);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const DiagonalMotionCommand& c) {
|
||||
j = {{"target_speed_ms", c.target_speed_ms},
|
||||
{"target_distance_m", c.target_distance_m},
|
||||
{"heading_deg", c.heading_deg}};
|
||||
}
|
||||
|
||||
struct ModuleAlignmentCheckCommand {
|
||||
std::vector<std::string> module_ids;
|
||||
double target_zero_deg{0.0};
|
||||
double tolerance_deg{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, ModuleAlignmentCheckCommand& c) {
|
||||
if (j.contains("module_ids") && j["module_ids"].is_array())
|
||||
c.module_ids = j["module_ids"].get<std::vector<std::string>>();
|
||||
c.target_zero_deg = j.value("target_zero_deg", 0.0);
|
||||
c.tolerance_deg = j.value("tolerance_deg", 0.0);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const ModuleAlignmentCheckCommand& c) {
|
||||
j = {{"module_ids", c.module_ids},
|
||||
{"target_zero_deg", c.target_zero_deg},
|
||||
{"tolerance_deg", c.tolerance_deg}};
|
||||
}
|
||||
|
||||
struct CoordinatedSteeringCommand {
|
||||
std::vector<std::string> module_ids;
|
||||
double target_angle_deg{0.0};
|
||||
double hold_time_sec{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, CoordinatedSteeringCommand& c) {
|
||||
if (j.contains("module_ids") && j["module_ids"].is_array())
|
||||
c.module_ids = j["module_ids"].get<std::vector<std::string>>();
|
||||
c.target_angle_deg = j.value("target_angle_deg", 0.0);
|
||||
c.hold_time_sec = j.value("hold_time_sec", 0.0);
|
||||
}
|
||||
inline void to_json(nlohmann::json& j, const CoordinatedSteeringCommand& c) {
|
||||
j = {{"module_ids", c.module_ids},
|
||||
{"target_angle_deg", c.target_angle_deg},
|
||||
{"hold_time_sec", c.hold_time_sec}};
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 底盘请求聚合
|
||||
// ===========================================================================
|
||||
|
||||
// primitive 字段用 variant,index 对应 ChassisMotionPrimitiveType 枚举值
|
||||
using ChassisMotionPrimitive = std::variant<
|
||||
std::monostate, // UNSPECIFIED (0)
|
||||
StraightLineCommand, // 1
|
||||
ArcCommand, // 2
|
||||
InPlaceRotationCommand, // 3
|
||||
SteeringSweepCommand, // 4
|
||||
LateralTranslationCommand, // 5
|
||||
DiagonalMotionCommand, // 6
|
||||
ModuleAlignmentCheckCommand, // 7
|
||||
CoordinatedSteeringCommand // 8
|
||||
>;
|
||||
|
||||
struct MotionPrimitiveRequest {
|
||||
RequestHeader header;
|
||||
std::string test_case_id;
|
||||
uint32_t task_purpose{0}; // TaskPurpose enum value
|
||||
ChassisMotionPrimitiveType selected_primitive{ChassisMotionPrimitiveType::UNSPECIFIED};
|
||||
ChassisMotionPrimitive primitive; // 对应 selected_primitive 的具体命令
|
||||
bool brake_when_finished{false};
|
||||
double timeout_sec{0.0};
|
||||
std::string source_iteration_id;
|
||||
};
|
||||
|
||||
// 从 JSON 解析 MotionPrimitiveRequest(对应 gateway_codec.hpp encode_motion_primitive_request)
|
||||
inline void from_json(const nlohmann::json& j, MotionPrimitiveRequest& r)
|
||||
{
|
||||
if (j.contains("header")) r.header = j["header"].get<RequestHeader>();
|
||||
r.test_case_id = j.value("test_case_id", std::string{});
|
||||
r.task_purpose = j.value("task_purpose", uint32_t{0});
|
||||
r.selected_primitive = static_cast<ChassisMotionPrimitiveType>(j.value("selected_primitive", uint32_t{0}));
|
||||
r.brake_when_finished = j.value("brake_when_finished", false);
|
||||
r.timeout_sec = j.value("timeout_sec", 0.0);
|
||||
r.source_iteration_id = j.value("source_iteration_id", std::string{});
|
||||
|
||||
switch (r.selected_primitive) {
|
||||
case ChassisMotionPrimitiveType::STRAIGHT_LINE:
|
||||
if (j.contains("straight_line"))
|
||||
r.primitive = j["straight_line"].get<StraightLineCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::ARC:
|
||||
if (j.contains("arc"))
|
||||
r.primitive = j["arc"].get<ArcCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::IN_PLACE_ROTATION:
|
||||
if (j.contains("in_place_rotation"))
|
||||
r.primitive = j["in_place_rotation"].get<InPlaceRotationCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::STEERING_SWEEP:
|
||||
if (j.contains("steering_sweep"))
|
||||
r.primitive = j["steering_sweep"].get<SteeringSweepCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::LATERAL_TRANSLATION:
|
||||
if (j.contains("lateral_translation"))
|
||||
r.primitive = j["lateral_translation"].get<LateralTranslationCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::DIAGONAL_MOTION:
|
||||
if (j.contains("diagonal_motion"))
|
||||
r.primitive = j["diagonal_motion"].get<DiagonalMotionCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::MODULE_ALIGNMENT:
|
||||
if (j.contains("module_alignment"))
|
||||
r.primitive = j["module_alignment"].get<ModuleAlignmentCheckCommand>();
|
||||
break;
|
||||
case ChassisMotionPrimitiveType::COORDINATED_STEERING:
|
||||
if (j.contains("coordinated_steering"))
|
||||
r.primitive = j["coordinated_steering"].get<CoordinatedSteeringCommand>();
|
||||
break;
|
||||
default:
|
||||
r.primitive = std::monostate{};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 底盘响应类型
|
||||
// ===========================================================================
|
||||
|
||||
struct ChassisReadinessResponse {
|
||||
bool success{false};
|
||||
ErrorCode error_code{ErrorCode::OK};
|
||||
std::string message;
|
||||
bool agent_ready{false};
|
||||
bool chassis_driver_online{false};
|
||||
bool motion_control_ready{false};
|
||||
bool estop_released{false};
|
||||
bool vehicle_safe_to_move{false};
|
||||
bool telemetry_available{false};
|
||||
int64_t checked_timestamp_us{0};
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ChassisReadinessResponse& r)
|
||||
{
|
||||
j = {
|
||||
{"success", r.success},
|
||||
{"error_code", {"code", static_cast<uint32_t>(r.error_code)}},
|
||||
{"message", r.message},
|
||||
{"agent_ready", r.agent_ready},
|
||||
{"chassis_driver_online", r.chassis_driver_online},
|
||||
{"motion_control_ready", r.motion_control_ready},
|
||||
{"estop_released", r.estop_released},
|
||||
{"vehicle_safe_to_move", r.vehicle_safe_to_move},
|
||||
{"telemetry_available", r.telemetry_available},
|
||||
{"checked_timestamp_us", r.checked_timestamp_us},
|
||||
};
|
||||
}
|
||||
|
||||
struct ChassisValidationSummary {
|
||||
double straight_line_error_m{0.0};
|
||||
double heading_error_deg{0.0};
|
||||
double arc_radius_error_m{0.0};
|
||||
double rotation_error_deg{0.0};
|
||||
double repeatability_error_m{0.0};
|
||||
double curvature_error{0.0};
|
||||
double module_consistency_error{0.0};
|
||||
bool auto_acceptance_passed{false};
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ChassisValidationSummary& s)
|
||||
{
|
||||
j = {
|
||||
{"straight_line_error_m", s.straight_line_error_m},
|
||||
{"heading_error_deg", s.heading_error_deg},
|
||||
{"arc_radius_error_m", s.arc_radius_error_m},
|
||||
{"rotation_error_deg", s.rotation_error_deg},
|
||||
{"repeatability_error_m", s.repeatability_error_m},
|
||||
{"curvature_error", s.curvature_error},
|
||||
{"module_consistency_error", s.module_consistency_error},
|
||||
{"auto_acceptance_passed", s.auto_acceptance_passed},
|
||||
};
|
||||
}
|
||||
|
||||
struct ChassisJobResult {
|
||||
bool success{false};
|
||||
ErrorCode error_code{ErrorCode::OK};
|
||||
std::string message;
|
||||
std::string job_id;
|
||||
bool data_quality_passed{false};
|
||||
bool suitable_for_commit{false};
|
||||
std::string recommended_parameter_version;
|
||||
double estimated_straight_line_bias{0.0};
|
||||
ChassisValidationSummary validation_summary;
|
||||
std::vector<FileReference> artifacts;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ChassisJobResult& r)
|
||||
{
|
||||
j = {
|
||||
{"success", r.success},
|
||||
{"error_code", {"code", static_cast<uint32_t>(r.error_code)}},
|
||||
{"message", r.message},
|
||||
{"job_id", r.job_id},
|
||||
{"data_quality_passed", r.data_quality_passed},
|
||||
{"suitable_for_commit", r.suitable_for_commit},
|
||||
{"recommended_parameter_version", r.recommended_parameter_version},
|
||||
{"estimated_straight_line_bias", r.estimated_straight_line_bias},
|
||||
{"validation_summary", r.validation_summary},
|
||||
{"artifacts", r.artifacts},
|
||||
};
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 运控命令类型
|
||||
// ===========================================================================
|
||||
|
||||
struct TrajectoryPoint {
|
||||
double x_m{0.0};
|
||||
double y_m{0.0};
|
||||
double yaw_rad{0.0};
|
||||
double target_speed_ms{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, TrajectoryPoint& p) {
|
||||
p.x_m = j.value("x_m", 0.0);
|
||||
p.y_m = j.value("y_m", 0.0);
|
||||
p.yaw_rad = j.value("yaw_rad", 0.0);
|
||||
p.target_speed_ms = j.value("target_speed_ms", 0.0);
|
||||
}
|
||||
|
||||
struct TrajectoryTrackingTask {
|
||||
std::vector<TrajectoryPoint> path;
|
||||
bool stop_at_end{false};
|
||||
double timeout_sec{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, TrajectoryTrackingTask& t) {
|
||||
if (j.contains("path") && j["path"].is_array())
|
||||
t.path = j["path"].get<std::vector<TrajectoryPoint>>();
|
||||
t.stop_at_end = j.value("stop_at_end", false);
|
||||
t.timeout_sec = j.value("timeout_sec", 0.0);
|
||||
}
|
||||
|
||||
struct VelocityStepTask {
|
||||
double target_velocity_ms{0.0};
|
||||
double hold_time_sec{0.0};
|
||||
double settle_before_step_sec{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, VelocityStepTask& t) {
|
||||
t.target_velocity_ms = j.value("target_velocity_ms", 0.0);
|
||||
t.hold_time_sec = j.value("hold_time_sec", 0.0);
|
||||
t.settle_before_step_sec = j.value("settle_before_step_sec", 0.0);
|
||||
}
|
||||
|
||||
struct AccelerationDecelerationTask {
|
||||
double start_velocity_ms{0.0};
|
||||
double target_velocity_ms{0.0};
|
||||
double target_accel_ms2{0.0};
|
||||
double hold_time_sec{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, AccelerationDecelerationTask& t) {
|
||||
t.start_velocity_ms = j.value("start_velocity_ms", 0.0);
|
||||
t.target_velocity_ms = j.value("target_velocity_ms", 0.0);
|
||||
t.target_accel_ms2 = j.value("target_accel_ms2", 0.0);
|
||||
t.hold_time_sec = j.value("hold_time_sec", 0.0);
|
||||
}
|
||||
|
||||
struct StopAccuracyTask {
|
||||
double target_stop_x_m{0.0};
|
||||
double target_stop_y_m{0.0};
|
||||
double target_stop_yaw_rad{0.0};
|
||||
double timeout_sec{0.0};
|
||||
};
|
||||
inline void from_json(const nlohmann::json& j, StopAccuracyTask& t) {
|
||||
t.target_stop_x_m = j.value("target_stop_x_m", 0.0);
|
||||
t.target_stop_y_m = j.value("target_stop_y_m", 0.0);
|
||||
t.target_stop_yaw_rad = j.value("target_stop_yaw_rad", 0.0);
|
||||
t.timeout_sec = j.value("timeout_sec", 0.0);
|
||||
}
|
||||
|
||||
using ControlEvaluationTask = std::variant<
|
||||
std::monostate, // UNSPECIFIED (0)
|
||||
TrajectoryTrackingTask, // 1
|
||||
VelocityStepTask, // 2
|
||||
AccelerationDecelerationTask, // 3
|
||||
StopAccuracyTask // 4
|
||||
>;
|
||||
|
||||
struct ControllerEvaluationRequest {
|
||||
RequestHeader header;
|
||||
std::string test_case_id;
|
||||
uint32_t task_purpose{0};
|
||||
ControlEvaluationTaskType selected_task{ControlEvaluationTaskType::UNSPECIFIED};
|
||||
ControlEvaluationTask task;
|
||||
double timeout_sec{0.0};
|
||||
std::string source_iteration_id;
|
||||
};
|
||||
|
||||
inline void from_json(const nlohmann::json& j, ControllerEvaluationRequest& r)
|
||||
{
|
||||
if (j.contains("header")) r.header = j["header"].get<RequestHeader>();
|
||||
r.test_case_id = j.value("test_case_id", std::string{});
|
||||
r.task_purpose = j.value("task_purpose", uint32_t{0});
|
||||
r.selected_task = static_cast<ControlEvaluationTaskType>(j.value("selected_task", uint32_t{0}));
|
||||
r.timeout_sec = j.value("timeout_sec", 0.0);
|
||||
r.source_iteration_id = j.value("source_iteration_id", std::string{});
|
||||
|
||||
switch (r.selected_task) {
|
||||
case ControlEvaluationTaskType::TRAJECTORY_TRACKING:
|
||||
if (j.contains("trajectory_tracking"))
|
||||
r.task = j["trajectory_tracking"].get<TrajectoryTrackingTask>();
|
||||
break;
|
||||
case ControlEvaluationTaskType::VELOCITY_STEP:
|
||||
if (j.contains("velocity_step"))
|
||||
r.task = j["velocity_step"].get<VelocityStepTask>();
|
||||
break;
|
||||
case ControlEvaluationTaskType::ACCELERATION_DECELERATION:
|
||||
if (j.contains("acceleration_deceleration"))
|
||||
r.task = j["acceleration_deceleration"].get<AccelerationDecelerationTask>();
|
||||
break;
|
||||
case ControlEvaluationTaskType::STOP_ACCURACY:
|
||||
if (j.contains("stop_accuracy"))
|
||||
r.task = j["stop_accuracy"].get<StopAccuracyTask>();
|
||||
break;
|
||||
default:
|
||||
r.task = std::monostate{};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 运控响应类型
|
||||
// ===========================================================================
|
||||
|
||||
struct ControlReadinessResponse {
|
||||
bool success{false};
|
||||
ErrorCode error_code{ErrorCode::OK};
|
||||
std::string message;
|
||||
bool agent_ready{false};
|
||||
bool trajectory_executor_ready{false};
|
||||
bool vehicle_feedback_ready{false};
|
||||
bool control_output_ready{false};
|
||||
bool estop_released{false};
|
||||
bool vehicle_safe_to_move{false};
|
||||
int64_t checked_timestamp_us{0};
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ControlReadinessResponse& r)
|
||||
{
|
||||
j = {
|
||||
{"success", r.success},
|
||||
{"error_code", {"code", static_cast<uint32_t>(r.error_code)}},
|
||||
{"message", r.message},
|
||||
{"agent_ready", r.agent_ready},
|
||||
{"trajectory_executor_ready", r.trajectory_executor_ready},
|
||||
{"vehicle_feedback_ready", r.vehicle_feedback_ready},
|
||||
{"control_output_ready", r.control_output_ready},
|
||||
{"estop_released", r.estop_released},
|
||||
{"vehicle_safe_to_move", r.vehicle_safe_to_move},
|
||||
{"checked_timestamp_us", r.checked_timestamp_us},
|
||||
};
|
||||
}
|
||||
|
||||
struct ControlValidationSummary {
|
||||
double rms_lateral_error_m{0.0};
|
||||
double rms_heading_error_rad{0.0};
|
||||
double rms_speed_error_ms{0.0};
|
||||
double overshoot_ratio{0.0};
|
||||
double settle_time_sec{0.0};
|
||||
double stop_position_error_m{0.0};
|
||||
double max_jerk{0.0};
|
||||
double saturation_ratio{0.0};
|
||||
bool auto_acceptance_passed{false};
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ControlValidationSummary& s)
|
||||
{
|
||||
j = {
|
||||
{"rms_lateral_error_m", s.rms_lateral_error_m},
|
||||
{"rms_heading_error_rad", s.rms_heading_error_rad},
|
||||
{"rms_speed_error_ms", s.rms_speed_error_ms},
|
||||
{"overshoot_ratio", s.overshoot_ratio},
|
||||
{"settle_time_sec", s.settle_time_sec},
|
||||
{"stop_position_error_m", s.stop_position_error_m},
|
||||
{"max_jerk", s.max_jerk},
|
||||
{"saturation_ratio", s.saturation_ratio},
|
||||
{"auto_acceptance_passed", s.auto_acceptance_passed},
|
||||
};
|
||||
}
|
||||
|
||||
struct ControlJobResult {
|
||||
bool success{false};
|
||||
ErrorCode error_code{ErrorCode::OK};
|
||||
std::string message;
|
||||
std::string job_id;
|
||||
bool data_quality_passed{false};
|
||||
bool suitable_for_commit{false};
|
||||
std::string recommended_parameter_version;
|
||||
ControlValidationSummary validation_summary;
|
||||
std::vector<FileReference> artifacts;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ControlJobResult& r)
|
||||
{
|
||||
j = {
|
||||
{"success", r.success},
|
||||
{"error_code", {"code", static_cast<uint32_t>(r.error_code)}},
|
||||
{"message", r.message},
|
||||
{"job_id", r.job_id},
|
||||
{"data_quality_passed", r.data_quality_passed},
|
||||
{"suitable_for_commit", r.suitable_for_commit},
|
||||
{"recommended_parameter_version", r.recommended_parameter_version},
|
||||
{"validation_summary", r.validation_summary},
|
||||
{"artifacts", r.artifacts},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace vaw
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
// frame_codec.hpp
|
||||
// 跨平台 TCP 帧读写工具。
|
||||
// 帧格式(与 Ubuntu 侧 proto_frame.hpp 完全一致):
|
||||
// [4字节 LE uint32: msg_type][4字节 LE uint32: payload_len][payload_len字节: UTF-8 JSON]
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
using sock_t = SOCKET;
|
||||
# define SOCK_INVALID INVALID_SOCKET
|
||||
# define SOCK_CLOSE(s) closesocket(s)
|
||||
#else
|
||||
# include <sys/types.h>
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <unistd.h>
|
||||
using sock_t = int;
|
||||
# define SOCK_INVALID (-1)
|
||||
# define SOCK_CLOSE(s) ::close(s)
|
||||
#endif
|
||||
|
||||
namespace vaw // vehicle_agent_windows
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 枚举:与 Python frame_codec.py / proto_frame.hpp MsgType 完全一致
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class MsgType : uint32_t {
|
||||
// 底盘域
|
||||
CHASSIS_GET_READINESS_REQ = 1,
|
||||
CHASSIS_GET_READINESS_RSP = 2,
|
||||
CHASSIS_MOTION_PRIMITIVE_REQ = 3,
|
||||
CHASSIS_MOTION_PRIMITIVE_RSP = 4,
|
||||
CHASSIS_EMERGENCY_BRAKE_REQ = 5,
|
||||
CHASSIS_EMERGENCY_BRAKE_RSP = 6,
|
||||
// 运控域
|
||||
CONTROL_GET_READINESS_REQ = 11,
|
||||
CONTROL_GET_READINESS_RSP = 12,
|
||||
CONTROL_EVALUATION_REQ = 13,
|
||||
CONTROL_EVALUATION_RSP = 14,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 内部:精确读取 n 字节,不足则抛出 std::runtime_error
|
||||
// ---------------------------------------------------------------------------
|
||||
inline void recv_exactly(sock_t s, char* buf, uint32_t n)
|
||||
{
|
||||
uint32_t received = 0;
|
||||
while (received < n) {
|
||||
int r = static_cast<int>(::recv(s, buf + received, static_cast<int>(n - received), 0));
|
||||
if (r <= 0) {
|
||||
throw std::runtime_error("连接已关闭或读取错误");
|
||||
}
|
||||
received += static_cast<uint32_t>(r);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 读一帧:返回 (msg_type, payload),payload 可为空字符串
|
||||
// ---------------------------------------------------------------------------
|
||||
inline void recv_frame(sock_t s, MsgType& msg_type, std::string& payload)
|
||||
{
|
||||
// 读 8 字节帧头
|
||||
char header[8];
|
||||
recv_exactly(s, header, 8);
|
||||
|
||||
// 小端 uint32 反序列化
|
||||
auto le32 = [](const char* p) -> uint32_t {
|
||||
return static_cast<uint32_t>(static_cast<uint8_t>(p[0]))
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(p[1])) << 8)
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(p[2])) << 16)
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(p[3])) << 24);
|
||||
};
|
||||
|
||||
uint32_t type_val = le32(header);
|
||||
uint32_t payload_len = le32(header + 4);
|
||||
|
||||
msg_type = static_cast<MsgType>(type_val);
|
||||
|
||||
if (payload_len == 0) {
|
||||
payload.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
payload.resize(payload_len);
|
||||
recv_exactly(s, &payload[0], payload_len);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 写一帧:payload 可为空字符串
|
||||
// ---------------------------------------------------------------------------
|
||||
inline void send_frame(sock_t s, MsgType msg_type, const std::string& payload)
|
||||
{
|
||||
uint32_t type_val = static_cast<uint32_t>(msg_type);
|
||||
uint32_t payload_len = static_cast<uint32_t>(payload.size());
|
||||
|
||||
// 小端序列化
|
||||
auto put_le32 = [](char* p, uint32_t v) {
|
||||
p[0] = static_cast<char>(v & 0xFF);
|
||||
p[1] = static_cast<char>((v >> 8) & 0xFF);
|
||||
p[2] = static_cast<char>((v >> 16) & 0xFF);
|
||||
p[3] = static_cast<char>((v >> 24) & 0xFF);
|
||||
};
|
||||
|
||||
char header[8];
|
||||
put_le32(header, type_val);
|
||||
put_le32(header + 4, payload_len);
|
||||
|
||||
// 发帧头
|
||||
::send(s, header, 8, 0);
|
||||
// 发 payload
|
||||
if (payload_len > 0) {
|
||||
::send(s, payload.data(), static_cast<int>(payload_len), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vaw
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// chassis_domain_server.cpp
|
||||
// 底盘域 TCP 服务,监听端口 9001(默认)。
|
||||
// 每个连接处理一帧后关闭(与 Ubuntu 侧 ChassisBridgeNode 行为一致)。
|
||||
|
||||
#include "chassis_domain_server.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "frame_codec.hpp"
|
||||
#include "data_types.hpp"
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
ChassisDomainServer::ChassisDomainServer(uint16_t port, ChassisHandler* handler)
|
||||
: port_(port), handler_(handler), running_(false), listen_sock_(SOCK_INVALID)
|
||||
{}
|
||||
|
||||
ChassisDomainServer::~ChassisDomainServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void ChassisDomainServer::run()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
#endif
|
||||
|
||||
listen_sock_ = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_sock_ == SOCK_INVALID)
|
||||
throw std::runtime_error("底盘:创建 socket 失败");
|
||||
|
||||
int opt = 1;
|
||||
::setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char*>(&opt), sizeof(opt));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port_);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (::bind(listen_sock_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0)
|
||||
throw std::runtime_error("底盘:bind 失败");
|
||||
|
||||
::listen(listen_sock_, 8);
|
||||
running_ = true;
|
||||
std::printf("[chassis] 监听端口 %d\n", port_);
|
||||
|
||||
while (running_) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int addr_len = sizeof(client_addr);
|
||||
#else
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
#endif
|
||||
sock_t conn = ::accept(listen_sock_,
|
||||
reinterpret_cast<sockaddr*>(&client_addr), &addr_len);
|
||||
if (conn == SOCK_INVALID) break;
|
||||
|
||||
// 每个连接起一个线程处理,detach 后自动回收
|
||||
std::thread([this, conn]() { handle_connection(conn); }).detach();
|
||||
}
|
||||
}
|
||||
|
||||
void ChassisDomainServer::stop()
|
||||
{
|
||||
running_ = false;
|
||||
if (listen_sock_ != SOCK_INVALID) {
|
||||
SOCK_CLOSE(listen_sock_);
|
||||
listen_sock_ = SOCK_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void ChassisDomainServer::handle_connection(sock_t conn)
|
||||
{
|
||||
std::printf("[chassis] 新连接\n");
|
||||
try {
|
||||
MsgType msg_type;
|
||||
std::string payload;
|
||||
recv_frame(conn, msg_type, payload);
|
||||
std::printf("[chassis] 收到帧 type=%u\n",
|
||||
static_cast<unsigned>(msg_type));
|
||||
|
||||
nlohmann::json j_payload;
|
||||
if (!payload.empty())
|
||||
j_payload = nlohmann::json::parse(payload);
|
||||
|
||||
switch (msg_type) {
|
||||
|
||||
case MsgType::CHASSIS_GET_READINESS_REQ: {
|
||||
auto rsp = handler_->get_readiness(j_payload);
|
||||
nlohmann::json j_rsp = rsp;
|
||||
send_frame(conn, MsgType::CHASSIS_GET_READINESS_RSP, j_rsp.dump());
|
||||
break;
|
||||
}
|
||||
|
||||
case MsgType::CHASSIS_MOTION_PRIMITIVE_REQ: {
|
||||
MotionPrimitiveRequest req;
|
||||
from_json(j_payload, req);
|
||||
auto rsp = handler_->execute_motion_primitive(req);
|
||||
nlohmann::json j_rsp = rsp;
|
||||
send_frame(conn, MsgType::CHASSIS_MOTION_PRIMITIVE_RSP, j_rsp.dump());
|
||||
break;
|
||||
}
|
||||
|
||||
case MsgType::CHASSIS_EMERGENCY_BRAKE_REQ: {
|
||||
auto j_rsp = handler_->emergency_brake(j_payload);
|
||||
send_frame(conn, MsgType::CHASSIS_EMERGENCY_BRAKE_RSP, j_rsp.dump());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
std::printf("[chassis] 未知帧类型 %u,忽略\n",
|
||||
static_cast<unsigned>(msg_type));
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::printf("[chassis] 处理帧异常: %s\n", e.what());
|
||||
}
|
||||
SOCK_CLOSE(conn);
|
||||
}
|
||||
|
||||
} // namespace vaw
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
// control_domain_server.cpp
|
||||
// 运控域 TCP 服务,监听端口 9002(默认)。
|
||||
|
||||
#include "control_domain_server.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "frame_codec.hpp"
|
||||
#include "data_types.hpp"
|
||||
|
||||
namespace vaw
|
||||
{
|
||||
|
||||
ControlDomainServer::ControlDomainServer(uint16_t port, ControlHandler* handler)
|
||||
: port_(port), handler_(handler), running_(false), listen_sock_(SOCK_INVALID)
|
||||
{}
|
||||
|
||||
ControlDomainServer::~ControlDomainServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void ControlDomainServer::run()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
#endif
|
||||
|
||||
listen_sock_ = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_sock_ == SOCK_INVALID)
|
||||
throw std::runtime_error("运控:创建 socket 失败");
|
||||
|
||||
int opt = 1;
|
||||
::setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char*>(&opt), sizeof(opt));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port_);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (::bind(listen_sock_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0)
|
||||
throw std::runtime_error("运控:bind 失败");
|
||||
|
||||
::listen(listen_sock_, 8);
|
||||
running_ = true;
|
||||
std::printf("[control] 监听端口 %d\n", port_);
|
||||
|
||||
while (running_) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int addr_len = sizeof(client_addr);
|
||||
#else
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
#endif
|
||||
sock_t conn = ::accept(listen_sock_,
|
||||
reinterpret_cast<sockaddr*>(&client_addr), &addr_len);
|
||||
if (conn == SOCK_INVALID) break;
|
||||
|
||||
std::thread([this, conn]() { handle_connection(conn); }).detach();
|
||||
}
|
||||
}
|
||||
|
||||
void ControlDomainServer::stop()
|
||||
{
|
||||
running_ = false;
|
||||
if (listen_sock_ != SOCK_INVALID) {
|
||||
SOCK_CLOSE(listen_sock_);
|
||||
listen_sock_ = SOCK_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlDomainServer::handle_connection(sock_t conn)
|
||||
{
|
||||
std::printf("[control] 新连接\n");
|
||||
try {
|
||||
MsgType msg_type;
|
||||
std::string payload;
|
||||
recv_frame(conn, msg_type, payload);
|
||||
std::printf("[control] 收到帧 type=%u\n",
|
||||
static_cast<unsigned>(msg_type));
|
||||
|
||||
nlohmann::json j_payload;
|
||||
if (!payload.empty())
|
||||
j_payload = nlohmann::json::parse(payload);
|
||||
|
||||
switch (msg_type) {
|
||||
|
||||
case MsgType::CONTROL_GET_READINESS_REQ: {
|
||||
auto rsp = handler_->get_readiness(j_payload);
|
||||
nlohmann::json j_rsp = rsp;
|
||||
send_frame(conn, MsgType::CONTROL_GET_READINESS_RSP, j_rsp.dump());
|
||||
break;
|
||||
}
|
||||
|
||||
case MsgType::CONTROL_EVALUATION_REQ: {
|
||||
ControllerEvaluationRequest req;
|
||||
from_json(j_payload, req);
|
||||
auto rsp = handler_->execute_evaluation(req);
|
||||
nlohmann::json j_rsp = rsp;
|
||||
send_frame(conn, MsgType::CONTROL_EVALUATION_RSP, j_rsp.dump());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
std::printf("[control] 未知帧类型 %u,忽略\n",
|
||||
static_cast<unsigned>(msg_type));
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::printf("[control] 处理帧异常: %s\n", e.what());
|
||||
}
|
||||
SOCK_CLOSE(conn);
|
||||
}
|
||||
|
||||
} // namespace vaw
|
||||
@@ -0,0 +1,74 @@
|
||||
// main.cpp
|
||||
// vehicle_agent_windows 入口。
|
||||
// 启动底盘域(默认 9001)和运控域(默认 9002)两个 TCP 服务线程。
|
||||
//
|
||||
// 用法:
|
||||
// vehicle_agent_windows [--chassis-port <port>] [--control-port <port>]
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
|
||||
#include "chassis_domain_server.hpp"
|
||||
#include "control_domain_server.hpp"
|
||||
#include "chassis_handler.hpp"
|
||||
#include "control_handler.hpp"
|
||||
|
||||
static std::atomic<bool> g_shutdown{false};
|
||||
|
||||
static void sig_handler(int) { g_shutdown = true; }
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
uint16_t chassis_port = 9001;
|
||||
uint16_t control_port = 9002;
|
||||
|
||||
// 简单命令行解析
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--chassis-port") == 0 && i + 1 < argc)
|
||||
chassis_port = static_cast<uint16_t>(std::atoi(argv[++i]));
|
||||
else if (std::strcmp(argv[i], "--control-port") == 0 && i + 1 < argc)
|
||||
control_port = static_cast<uint16_t>(std::atoi(argv[++i]));
|
||||
}
|
||||
|
||||
std::signal(SIGINT, sig_handler);
|
||||
std::signal(SIGTERM, sig_handler);
|
||||
|
||||
// 默认 stub handler,子类化后可接入真实硬件
|
||||
vaw::ChassisHandler chassis_handler;
|
||||
vaw::ControlHandler control_handler;
|
||||
|
||||
vaw::ChassisDomainServer chassis_server(chassis_port, &chassis_handler);
|
||||
vaw::ControlDomainServer control_server(control_port, &control_handler);
|
||||
|
||||
std::thread t_chassis([&]() {
|
||||
try { chassis_server.run(); }
|
||||
catch (const std::exception& e)
|
||||
{ std::fprintf(stderr, "[chassis] 启动失败: %s\n", e.what()); }
|
||||
});
|
||||
|
||||
std::thread t_control([&]() {
|
||||
try { control_server.run(); }
|
||||
catch (const std::exception& e)
|
||||
{ std::fprintf(stderr, "[control] 启动失败: %s\n", e.what()); }
|
||||
});
|
||||
|
||||
std::printf("vehicle_agent_windows 已启动,按 Ctrl+C 退出。\n");
|
||||
|
||||
// 等待退出信号
|
||||
while (!g_shutdown)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
std::printf("\n正在关闭...\n");
|
||||
chassis_server.stop();
|
||||
control_server.stop();
|
||||
|
||||
t_chassis.join();
|
||||
t_control.join();
|
||||
|
||||
std::printf("已退出。\n");
|
||||
return 0;
|
||||
}
|
||||
+123
-23
@@ -1,32 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# Windows 侧 TCP stub server(本机联调用)
|
||||
# 用途:模拟 Windows 车端代理,收到任意帧后回复对应的 RSP 帧(payload 为空,表示成功)。
|
||||
# 用途:模拟 Windows 车端代理,收到任意帧后回复对应的 RSP 帧(含最小合法 JSON payload)。
|
||||
# 启动方式:python3 windows_stub_server.py
|
||||
# 默认监听:
|
||||
# 9001 端口 —— 底盘域
|
||||
# 9002 端口 —— 运控域
|
||||
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="[%(threadName)s] %(message)s")
|
||||
|
||||
# 帧类型枚举(与 proto_frame.hpp 保持一致)
|
||||
MSG_TYPE = {
|
||||
# 底盘域
|
||||
1: 2, # CHASSIS_GET_READINESS_REQ -> CHASSIS_GET_READINESS_RSP
|
||||
3: 4, # CHASSIS_MOTION_PRIMITIVE_REQ -> CHASSIS_MOTION_PRIMITIVE_RSP
|
||||
5: 6, # CHASSIS_EMERGENCY_BRAKE_REQ -> CHASSIS_EMERGENCY_BRAKE_RSP
|
||||
# 运控域
|
||||
11: 12, # CONTROL_GET_READINESS_REQ -> CONTROL_GET_READINESS_RSP
|
||||
13: 14, # CONTROL_EVALUATION_REQ -> CONTROL_EVALUATION_RSP
|
||||
# 帧类型(与 proto_frame.hpp 保持一致)
|
||||
CHASSIS_GET_READINESS_REQ = 1
|
||||
CHASSIS_GET_READINESS_RSP = 2
|
||||
CHASSIS_MOTION_PRIMITIVE_REQ = 3
|
||||
CHASSIS_MOTION_PRIMITIVE_RSP = 4
|
||||
CHASSIS_EMERGENCY_BRAKE_REQ = 5
|
||||
CHASSIS_EMERGENCY_BRAKE_RSP = 6
|
||||
CONTROL_GET_READINESS_REQ = 11
|
||||
CONTROL_GET_READINESS_RSP = 12
|
||||
CONTROL_EVALUATION_REQ = 13
|
||||
CONTROL_EVALUATION_RSP = 14
|
||||
|
||||
# REQ → RSP 类型映射
|
||||
REQ_TO_RSP = {
|
||||
CHASSIS_GET_READINESS_REQ: CHASSIS_GET_READINESS_RSP,
|
||||
CHASSIS_MOTION_PRIMITIVE_REQ: CHASSIS_MOTION_PRIMITIVE_RSP,
|
||||
CHASSIS_EMERGENCY_BRAKE_REQ: CHASSIS_EMERGENCY_BRAKE_RSP,
|
||||
CONTROL_GET_READINESS_REQ: CONTROL_GET_READINESS_RSP,
|
||||
CONTROL_EVALUATION_REQ: CONTROL_EVALUATION_RSP,
|
||||
}
|
||||
|
||||
|
||||
def read_exactly(conn, n):
|
||||
"""从 conn 读取恰好 n 个字节,不足则抛出 ConnectionError。"""
|
||||
def _ts_us() -> int:
|
||||
return int(time.time() * 1_000_000)
|
||||
|
||||
|
||||
def _make_payload(req_type: int, req_payload: dict) -> dict:
|
||||
"""根据请求类型生成最小合法的成功响应 payload。"""
|
||||
if req_type == CHASSIS_GET_READINESS_REQ:
|
||||
return {
|
||||
"success": True,
|
||||
"error_code": {"code": 1},
|
||||
"message": "Stub: 底盘就绪。",
|
||||
"agent_ready": True,
|
||||
"chassis_driver_online": True,
|
||||
"motion_control_ready": True,
|
||||
"estop_released": True,
|
||||
"vehicle_safe_to_move": True,
|
||||
"telemetry_available": True,
|
||||
"checked_timestamp_us": _ts_us(),
|
||||
}
|
||||
elif req_type == CHASSIS_MOTION_PRIMITIVE_REQ:
|
||||
job_id = req_payload.get("header", {}).get("request_id", "stub_job")
|
||||
return {
|
||||
"success": True,
|
||||
"error_code": {"code": 1},
|
||||
"message": "Stub: 动作原语执行完成。",
|
||||
"job_id": job_id,
|
||||
"data_quality_passed": True,
|
||||
"suitable_for_commit": True,
|
||||
"recommended_parameter_version": "stub_v1",
|
||||
"estimated_straight_line_bias": 0.001,
|
||||
"validation_summary": {
|
||||
"max_lateral_error_m": 0.010,
|
||||
"max_yaw_error_rad": 0.005,
|
||||
"rms_lateral_error_m": 0.005,
|
||||
"rms_yaw_error_rad": 0.003,
|
||||
"repeatability_error_m": 0.001,
|
||||
"curvature_error": 0.001,
|
||||
"module_consistency_error": 0.0,
|
||||
"auto_acceptance_passed": True,
|
||||
},
|
||||
"artifacts": [],
|
||||
}
|
||||
elif req_type == CHASSIS_EMERGENCY_BRAKE_REQ:
|
||||
return {
|
||||
"success": True,
|
||||
"error_code": {"code": 1},
|
||||
"message": "Stub: 紧急制动已执行。",
|
||||
}
|
||||
elif req_type == CONTROL_GET_READINESS_REQ:
|
||||
return {
|
||||
"success": True,
|
||||
"error_code": {"code": 1},
|
||||
"message": "Stub: 运控就绪。",
|
||||
"agent_ready": True,
|
||||
"trajectory_executor_ready": True,
|
||||
"vehicle_feedback_ready": True,
|
||||
"control_output_ready": True,
|
||||
"estop_released": True,
|
||||
"vehicle_safe_to_move": True,
|
||||
"checked_timestamp_us": _ts_us(),
|
||||
}
|
||||
elif req_type == CONTROL_EVALUATION_REQ:
|
||||
job_id = req_payload.get("header", {}).get("request_id", "stub_job")
|
||||
return {
|
||||
"success": True,
|
||||
"error_code": {"code": 1},
|
||||
"message": "Stub: 控制评估完成。",
|
||||
"job_id": job_id,
|
||||
"data_quality_passed": True,
|
||||
"suitable_for_commit": True,
|
||||
"recommended_parameter_version": "stub_control_v1",
|
||||
"validation_summary": {
|
||||
"rms_lateral_error_m": 0.015,
|
||||
"rms_heading_error_rad": 0.010,
|
||||
"rms_speed_error_ms": 0.05,
|
||||
"overshoot_ratio": 0.08,
|
||||
"settle_time_sec": 1.2,
|
||||
"stop_position_error_m": 0.02,
|
||||
"max_jerk": 0.3,
|
||||
"saturation_ratio": 0.05,
|
||||
"auto_acceptance_passed": True,
|
||||
},
|
||||
"artifacts": [],
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
def _read_exactly(conn, n: int) -> bytes:
|
||||
buf = b""
|
||||
while len(buf) < n:
|
||||
chunk = conn.recv(n - len(buf))
|
||||
@@ -36,31 +134,33 @@ def read_exactly(conn, n):
|
||||
return buf
|
||||
|
||||
|
||||
def handle_connection(conn, addr):
|
||||
def handle_connection(conn: socket.socket, addr):
|
||||
logging.info(f"新连接来自 {addr}")
|
||||
try:
|
||||
# 读取帧头:8字节(4字节LE类型 + 4字节LE长度)
|
||||
header = read_exactly(conn, 8)
|
||||
header = _read_exactly(conn, 8)
|
||||
msg_type, payload_len = struct.unpack_from("<II", header)
|
||||
payload = read_exactly(conn, payload_len) if payload_len > 0 else b""
|
||||
raw = _read_exactly(conn, payload_len) if payload_len > 0 else b""
|
||||
logging.info(f"收到帧 type={msg_type} payload_len={payload_len}")
|
||||
|
||||
rsp_type = MSG_TYPE.get(msg_type)
|
||||
req_payload = json.loads(raw.decode("utf-8")) if raw else {}
|
||||
|
||||
rsp_type = REQ_TO_RSP.get(msg_type)
|
||||
if rsp_type is None:
|
||||
logging.warning(f"未知帧类型 {msg_type},忽略")
|
||||
return
|
||||
|
||||
# 回复 RSP 帧,payload 为空(stub 固定返回成功)
|
||||
rsp_header = struct.pack("<II", rsp_type, 0)
|
||||
conn.sendall(rsp_header)
|
||||
logging.info(f"已回复帧 type={rsp_type}")
|
||||
except ConnectionError as e:
|
||||
rsp_dict = _make_payload(msg_type, req_payload)
|
||||
rsp_data = json.dumps(rsp_dict, ensure_ascii=False).encode("utf-8")
|
||||
rsp_header = struct.pack("<II", rsp_type, len(rsp_data))
|
||||
conn.sendall(rsp_header + rsp_data)
|
||||
logging.info(f"已回复帧 type={rsp_type} payload_len={len(rsp_data)}")
|
||||
except (ConnectionError, json.JSONDecodeError) as e:
|
||||
logging.warning(f"连接异常:{e}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def start_server(port, name):
|
||||
def start_server(port: int, name: str):
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(("0.0.0.0", port))
|
||||
|
||||
Reference in New Issue
Block a user