feat: 自动化标定车间v1.0.....

This commit is contained in:
li-shihao-code
2026-03-30 12:41:13 +08:00
parent 672c77726d
commit f82c3ad12a
13 changed files with 2016 additions and 24 deletions
@@ -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_REQtype=11
```json
{
"agent_name": "control",
"include_details": true,
"target_resource_ids": []
}
```
### 5.2 CONTROL_GET_READINESS_RSPtype=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_REQtype=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_RSPtype=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 | 横向误差 RMSm|
| `validation_summary.rms_heading_error_rad` | float64 | 航向误差 RMSrad|
| `validation_summary.rms_speed_error_ms` | float64 | 速度误差 RMSm/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 | 最大 jerkm/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
```
---
## 附录 AErrorCode 值
| 值 | 名称 | 含义 |
|----|------|------|
| 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,
}
@@ -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
@@ -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 字段用 variantindex 对应 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
@@ -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
@@ -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;
}
@@ -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))