feat:完善结构与仿真环境

This commit is contained in:
li-shihao-code
2026-05-04 10:42:25 +08:00
parent f82c3ad12a
commit 1cc1ed1544
491 changed files with 14953 additions and 1336 deletions
+38 -120
View File
@@ -1,141 +1,59 @@
# 🧠 AGV 标定中央大脑
# 源码目录结构
**环境**: Ubuntu 22.04 + ROS 2 Humble | **语言**: C++ | **通信**: gRPC over Wi-Fi 6
`src` 按“仿真、核心逻辑、通信、现场部署”的边界组织,而不是按临时实验文件组织。
> 标定车间的"发令大脑",通过局域网跨平台遥控 Windows 车端执行动作并拉取遥测数据。
## 目录说明
---
- `apps/`
面向操作人员的工具和界面原型。
## 📋 目录
- `communication/`
ROS 2 接口包、TCP 帧协议、车间工控机到车端电脑的 gateway。这个层应该同时服务于仿真和现场部署。
1. [系统依赖安装](#1-系统依赖一键安装)
2. [VS Code 插件配置](#2-vs-code-核心插件配置)
3. [解决 IntelliSense 报错](#3-解决-vs-code-红色波浪线)
4. [编译与运行](#4-编译与运行)
- `core/`
标定流程和标定算法,包括 `workshop_orchestrator`、底盘标定、运控标定、传感器标定、车辆参数管理等可复用核心逻辑。
---
- `simulation/`
部署前仿真验证代码。Isaac 车间、仿真车辆、仿真车端 agent、仿真传感器、仿真标定靶和旧版仿真包都放在这里。
## 1. 系统依赖一键安装
- `deployment/`
部署 profile 和从仿真迁移到现场前的检查清单。这里放配置基准,不放算法实现。
在 Ubuntu 22.04 终端执行以下命令:
- `docs/`
源码树内的设计说明、边界说明和迁移规则。
- `site_deployment/`
真实现场部署代码,例如真实车端电脑适配器、真实车辆 SDK、PLC/CAN 或厂商控制器对接代码。
## 边界规则
- Isaac API 只放在 `simulation/`
- 真实车辆 SDK、PLC、CAN、厂商控制器相关代码只放在 `site_deployment/`
- ROS 2 接口、TCP 协议和 gateway 放在 `communication/`
- 编排流程和标定算法放在 `core/`
- 部署 profile 放在 `deployment/`
- 设计说明和迁移边界说明放在 `docs/`
## 主要入口
Isaac 车间仿真:
```bash
sudo apt update
sudo apt install -y build-essential cmake pkg-config gdb
sudo apt install -y protobuf-compiler-grpc libgrpc++-dev libprotobuf-dev protobuf-compiler
python3 src/simulation/isaac_workshop_sim/scripts/build_calibration_room.py
```
> ⚠️ **警告**: 严禁自行去 GitHub 源码编译 gRPC,直接使用 Ubuntu 官方 APT 源即可,避免浪费时间与报错。
---
## 2. VS Code 核心插件配置
打开 VS Code → 扩展商店 (Extensions)**必须安装**以下 4 个插件:
| 插件名称 | 开发者 | 用途 |
|---------|--------|------|
| **C/C++** | Microsoft | 代码补全与 GDB 调试 |
| **CMake Tools** | Microsoft | 底部快速构建状态栏 |
| **ROS** | Microsoft | 自动识别 `colcon` 工作空间 |
| **vscode-proto3** | zxh404 | `.proto` 文件语法高亮 |
---
## 3. 解决 VS Code 红色波浪线 (IntelliSense 报错)
**问题原因**: gRPC 生成的 `.pb.h` 文件在 `colcon build` 阶段动态生成于 `build/` 目录,VS Code 初始无法识别。
**修复步骤**:
1.`Ctrl+Shift+P` → 输入 `C/C++: Edit Configurations (JSON)`
2. 确保 `c_cpp_properties.json` 包含以下配置:
```json
{
"configurations": [
{
"name": "ROS2",
"includePath": [
"${workspaceFolder}/**",
"/opt/ros/humble/include/**",
"${workspaceFolder}/build/agv_calib_brain/grpc_gen/**"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64"
}
]
}
```
> 💡 **提示**: `grpc_gen` 是 CMakeLists 中配置的自动生成源码路径,请根据实际情况微调。
---
## 4. 编译与运行 (CMake 自动化)
### 4.0 最小联调闭环
当前仓库已经补齐了一个最小可运行闭环:
- `vehicle_profile_manager`:提供默认车辆画像
- `external_localization_service`:提供外部真值校核的最小执行端
- `workshop_orchestrator_v2`:负责编排会话、计划和报告
启动顺序:
仿真车端 agent
```bash
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 launch win_ubuntu_bridge minimal_workshop_demo.launch.py
python3 src/simulation/vehicle_agent_sim/scripts/isaac_vehicle_agent_sim.py
```
如果只想单独跑编排器
按 profile 启动完整仿真链路
```bash
ros2 launch workshop_orchestrator_v2 workshop_orchestrator_v2.launch.py
python3 src/simulation/tools/launch_sim_stack.py
```
可先调用这些接口做联调:
这条链路中,车间工控机与车端电脑之间的底盘、运控、外部真值位姿和传感器数据都通过 TCP/WiFi6 仿真边界传输;Isaac topic 只留在仿真内部。
- `/vehicle_profile_manager/get_vehicle_profile`
- `/vehicle_profile_manager/evaluate_vehicle_calibration_applicability`
- `/external_localization/get_readiness`
- `/external_localization/execute_task`
- `/workshop_v2/create_session`
- `/workshop_v2/execute_session`
- `/workshop_v2/get_report`
最小会话建议至少包含:
- `session.config.localization_source_id = demo_vehicle_001`
- `session.config.workcell_zone_id = demo_workcell`
- 一个 `requested_tasks`,其中 `stage_type = EXTERNAL_REFERENCE_READY_CHECK_STAGE`
- 该任务的 `task_params` 至少包含:
- `external.static_sample_count`
- `external.dynamic_sample_count`
- `external.max_position_stddev_m`
- `external.max_yaw_stddev_rad`
- `external.max_tracking_loss_ratio`
- `external.max_time_sync_offset_ms`
- `external.timeout_sec`
> ✨ **无需手动执行 `protoc`** —— CMakeLists.txt 已配置自动化脚本,编译时自动生成 C++ 网络源码。
### 4.1 编译
```bash
# 回到工作空间根目录(如 ~/agv_ws)
source /opt/ros/humble/setup.bash
colcon build --packages-select agv_calib_brain --symlink-install
```
### 4.2 运行
```bash
source install/setup.bash
ros2 run agv_calib_brain brain_node
```
车间 gateway 和 `workshop_orchestrator` 仍然按 ROS 2 包名启动;源码分别在 `communication/``core/` 下。
@@ -1,24 +0,0 @@
#include "chassis_calibration_service/chassis_calibration_common.hpp"
namespace chassis_calibration_service
{
bool DifferentialChassisAlgorithm::run(
const ChassisCalibrationInput & input,
ChassisCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// TODO: 在这里填写差速底盘标定算法。
// 算法工程师应从这里读取并计算:
// - 任务请求:input.requestrequest_id、任务目的、selected_primitive、straight_line 参数、timeout 等)
// - 底盘反馈:由上层扩展到 ChassisCalibrationInput 中的实时数据(轮速、左右电机反馈、IMU、里程计、定位等)
// - 输出:output.response.resultsuccess、error_code、validation_summary、estimated_params、artifacts 等)
fill_common_result(input, output);
output.response.result.message = "differential chassis template executed.";
output.response.result.recommended_parameter_version = "differential_template_v1";
return true;
}
} // namespace chassis_calibration_service
@@ -1,33 +0,0 @@
#include "control_calibration_service/control_calibration_common.hpp"
namespace control_calibration_service
{
bool PurePursuitControlCalibrationAlgorithm::run(
const ControlCalibrationInput & input,
ControlCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// Pure Pursuit 控制标定模板:
// 适合基于参考轨迹和前视点策略的横向控制评估。
// 算法工程师通常会在这里处理:
// 1. 参考轨迹读取:
// - input.reference_trajectory
// - input.reference_stop_at_end / input.reference_timeout_sec
// 2. 观测输入:
// - input.control_telemetry_history 中的横向误差、航向误差、转向输出
// - input.chassis_telemetry_history 中的速度、姿态和底盘运动状态
// - input.truth_source_diagnostics / 真值历史,用于判断轨迹对齐是否可靠
// 3. 参数输出:
// - 可将前视距离、速度相关增益等写入 output.response.result.estimated_parameter_set
// 4. 验收输出:
// - 将最大误差、均方误差、振荡情况、自动验收结论写入 validation_summary
fill_common_result(input, output);
output.response.result.message = "pure pursuit control calibration template executed.";
output.response.result.recommended_parameter_version = "pure_pursuit_template_v1";
return true;
}
} // namespace control_calibration_service
@@ -1,120 +0,0 @@
#include "external_localization_service/external_localization_service_node.hpp"
#include <chrono>
#include <thread>
#include "calibration_common_interfaces/msg/error_code.hpp"
#include "calibration_common_interfaces/msg/job_state.hpp"
namespace external_localization_service
{
namespace
{
int64_t now_us()
{
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
} // namespace
using calibration_common_interfaces::msg::ErrorCode;
using calibration_common_interfaces::msg::JobState;
ExternalLocalizationServiceNode::ExternalLocalizationServiceNode(const rclcpp::NodeOptions & options)
: Node("external_localization_service", options)
{
readiness_service_ = create_service<ReadinessSrv>(
"/external_localization/get_readiness",
std::bind(&ExternalLocalizationServiceNode::handle_readiness, this, std::placeholders::_1, std::placeholders::_2));
execute_task_action_server_ = rclcpp_action::create_server<ExecuteTask>(
this,
"/external_localization/execute_task",
std::bind(&ExternalLocalizationServiceNode::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&ExternalLocalizationServiceNode::handle_cancel, this, std::placeholders::_1),
std::bind(&ExternalLocalizationServiceNode::handle_accepted, this, std::placeholders::_1));
}
void ExternalLocalizationServiceNode::handle_readiness(
const std::shared_ptr<ReadinessSrv::Request> request,
std::shared_ptr<ReadinessSrv::Response> response)
{
(void)request;
// 这里先保留最小 readiness 逻辑。
// 后续若接入真实外部定位设备/真值源桥接程序,可在这里增加:
// - 真值源在线检查
// - 同步状态检查
// - 覆盖范围检查
// - 观测质量检查
// - 切源稳定性检查
response->response.success = true;
response->response.error_code.code = ErrorCode::OK;
response->response.message = "external_localization_service is ready.";
response->response.agent_ready = true;
response->response.ready_for_reference_validation = true;
response->response.checked_timestamp_us = now_us();
response->response.validation_summary.time_sync_ok = true;
response->response.validation_summary.coverage_ok = true;
response->response.validation_summary.quality_ok = true;
response->response.validation_summary.tracking_stable = true;
response->response.validation_summary.recommended_as_truth_source = true;
response->response.validation_summary.position_stddev_m = 0.0;
response->response.validation_summary.yaw_stddev_rad = 0.0;
response->response.validation_summary.tracking_loss_ratio = 0.0;
response->response.validation_summary.time_sync_offset_ms = 0.0;
}
rclcpp_action::GoalResponse ExternalLocalizationServiceNode::handle_goal(
const rclcpp_action::GoalUUID & /*uuid*/,
std::shared_ptr<const ExecuteTask::Goal> goal)
{
std::string reject_reason;
if (!executor_.validate_goal(*goal, reject_reason)) {
RCLCPP_WARN(get_logger(), "Reject external_localization goal: %s", reject_reason.c_str());
return rclcpp_action::GoalResponse::REJECT;
}
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse ExternalLocalizationServiceNode::handle_cancel(
const std::shared_ptr<GoalHandleExecuteTask> /*goal_handle*/)
{
return rclcpp_action::CancelResponse::ACCEPT;
}
void ExternalLocalizationServiceNode::handle_accepted(
const std::shared_ptr<GoalHandleExecuteTask> goal_handle)
{
std::thread(std::bind(&ExternalLocalizationServiceNode::execute_goal, this, goal_handle)).detach();
}
void ExternalLocalizationServiceNode::execute_goal(
const std::shared_ptr<GoalHandleExecuteTask> goal_handle)
{
auto feedback = std::make_shared<ExecuteTask::Feedback>();
feedback->feedback.job_id = goal_handle->get_goal()->goal.header.request_id;
feedback->feedback.state.state = JobState::RUNNING;
feedback->feedback.progress = 0.5;
feedback->feedback.error_code.code = ErrorCode::OK;
feedback->feedback.message = "external localization task is running.";
feedback->feedback.server_timestamp_us = now_us();
feedback->feedback.safe_to_retry = false;
goal_handle->publish_feedback(feedback);
auto result = std::make_shared<ExecuteTask::Result>();
std::string failure_reason;
if (!executor_.build_result(*goal_handle->get_goal(), *result, failure_reason)) {
result->result.success = false;
result->result.error_code.code = ErrorCode::INVALID_ARGUMENT;
result->result.message = failure_reason;
goal_handle->abort(result);
return;
}
goal_handle->succeed(result);
}
} // namespace external_localization_service
@@ -1,66 +0,0 @@
#include "external_localization_service/external_localization_common.hpp"
namespace external_localization_service
{
bool MarkerAlignmentAlgorithm::run(
const ExternalLocalizationInput & input,
ExternalLocalizationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 标靶对齐模板:
// 【本文件负责什么】
// - 负责标靶检测结果读取、坐标系对齐求解和残差统计相关算法实现。
// - 后续算法工程师应主要修改本文件,不要改 node 层和模板分发层。
//
// 【建议优先读取的输入】
// 1. input.marker_alignment_task
// - target_board_id、min_valid_observation_count、timeout_sec 等关键约束。
// 2. input.latest_external_localization_telemetry / input.external_localization_telemetry_history
// - 读取观测位姿、标准差、丢失率、时间同步偏差和质量评分。
// 3. input.marker_alignment_diagnostics
// - 读取标靶检测失败、角点不足、姿态求解不稳定等问题描述。
// 4. input.sensor_quality_diagnostics
// - 如果标靶检测依赖相机 / LiDAR 质量,可在这里读取辅助质量信息。
//
// 【必写输出】
// 1. output.response.result.result.workshop_to_localization
// - 这是标靶对齐最核心的输出结果。
// 2. output.response.result.result.residual_error_m / residual_error_rad
// - 写回对齐残差,供 orchestrator 判断是否自动验收。
// 3. output.response.result.validation_summary
// - 写位置标准差、航向标准差、时间同步偏差等摘要。
//
// 【可选输出】
// - output.response.result.artifacts
// 可挂标靶检测日志、可视化结果、拟合报告、残差统计文件等。
//
// 【常见失败原因】
// - 有效观测数不足、标靶检测失败、姿态求解不稳定、时间同步异常、质量评分过低。
//
// 【在这里添加真实算法】
// - 请在 fill_external_localization_common_success(...) 之前或之后补充真实对齐求解逻辑。
// - 当前文件仅提供交付模板,不包含真实外部定位算法。
fill_external_localization_common_success(
input,
output,
"标靶对齐完成。",
"demo_external_marker_alignment_v1");
output.response.result.result.workshop_frame_id = "workshop";
output.response.result.result.localization_frame_id = "localization";
output.response.result.result.workshop_to_localization.z_m = 0.0;
output.response.result.result.position_repeatability_m = 0.0;
output.response.result.result.yaw_repeatability_rad = 0.0;
output.response.result.result.residual_error_m = 0.0;
output.response.result.result.residual_error_rad = 0.0;
output.response.result.result.tracking_loss_ratio = 0.0;
output.response.result.result.time_sync_offset_ms = 0.0;
output.response.result.result.validated_as_truth_source = true;
return true;
}
} // namespace external_localization_service
@@ -1,65 +0,0 @@
#include "external_localization_service/external_localization_common.hpp"
namespace external_localization_service
{
bool ReferencePoseCollectionAlgorithm::run(
const ExternalLocalizationInput & input,
ExternalLocalizationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 参考位姿采集模板:
// 【本文件负责什么】
// - 负责参考位姿采集与静态重复性分析相关算法实现。
// - 后续算法工程师应主要修改本文件,不要改 node 层和模板分发层。
//
// 【建议优先读取的输入】
// 1. input.reference_pose_collection_task
// - sample_count、require_vehicle_static、timeout_sec、min_sample_interval_sec 等采样约束。
// 2. input.latest_external_localization_telemetry / input.external_localization_telemetry_history
// - 每帧外部定位位姿、位置标准差、航向标准差、时间同步偏差、质量评分。
// 3. input.latest_chassis_telemetry / input.chassis_telemetry_history
// - 用于判断车辆是否真实静止,避免采入无效参考位姿。
// 4. input.truth_source_diagnostics / input.acquisition_diagnostics
// - 记录时间同步异常、观测缺失、采样不足、落盘失败等问题。
//
// 【必写输出】
// 1. output.response.result.validation_summary
// - 写位置标准差、航向标准差、时间同步偏差、是否推荐为真值源等摘要。
// 2. output.response.result.result
// - 写 workshop_frame_id / localization_frame_id / repeatability / residual 等结果。
// 3. output.response.result.suitable_for_commit
// - 明确当前采集结果是否建议进入下一阶段。
//
// 【可选输出】
// - output.response.result.artifacts
// 可挂采样日志、原始位姿文件、统计报告等文件引用。
//
// 【常见失败原因】
// - 车辆未静止、有效样本不足、时间同步超标、外部定位观测丢失、采样频率不足。
//
// 【在这里添加真实算法】
// - 请在 fill_external_localization_common_success(...) 之前或之后补充真实采样与统计逻辑。
// - 当前文件仅提供交付模板,不包含真实外部定位算法。
fill_external_localization_common_success(
input,
output,
"参考位姿采集完成。",
"demo_external_reference_pose_collection_v1");
output.response.result.result.workshop_frame_id = "workshop";
output.response.result.result.localization_frame_id = "localization";
output.response.result.result.position_repeatability_m = 0.0;
output.response.result.result.yaw_repeatability_rad = 0.0;
output.response.result.result.residual_error_m = 0.0;
output.response.result.result.residual_error_rad = 0.0;
output.response.result.result.tracking_loss_ratio = 0.0;
output.response.result.result.time_sync_offset_ms = 0.0;
output.response.result.result.validated_as_truth_source = true;
return true;
}
} // namespace external_localization_service
@@ -1,77 +0,0 @@
#include "external_localization_service/external_localization_common.hpp"
namespace external_localization_service
{
bool TruthSourceValidationAlgorithm::run(
const ExternalLocalizationInput & input,
ExternalLocalizationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 真值源验证模板:
// 【本文件负责什么】
// - 负责静态重复性、动态稳定性、时间同步与丢失率等真值源验证算法实现。
// - 后续算法工程师应主要修改本文件,不要改 node 层和模板分发层。
//
// 【建议优先读取的输入】
// 1. input.truth_source_validation_task
// - static_sample_count、dynamic_sample_count、require_short_motion_segment 等任务要求。
// 2. input.external_localization_telemetry_history
// - 外部定位历史观测窗口,是稳定性、同步性、重复性分析的核心输入。
// 3. input.chassis_telemetry_history / input.control_telemetry_history
// - 如果要求短运动段验证,需要结合车辆实际运动状态和控制输出做时序对齐。
// 4. input.sensor_telemetry_history
// - 用于判断辅助传感器质量是否影响外部定位观测可信度。
// 5. input.truth_source_diagnostics
// - 记录时间同步超标、观测丢失、切源异常等问题。
//
// 【必写输出】
// 1. output.response.result.validation_summary
// - 这是 orchestrator 自动验收最关键的摘要区域。
// 2. output.response.result.result
// - 写 repeatability、tracking_loss_ratio、time_sync_offset_ms 等核心结果。
// 3. output.response.result.data_quality_passed / suitable_for_commit
// - 明确当前真值源是否可进入后续标定闭环。
//
// 【可选输出】
// - output.response.result.artifacts
// 可挂稳定性分析报告、同步统计图、丢失率分析文件等。
//
// 【常见失败原因】
// - 动态窗口不足、时间同步偏差超阈值、观测丢失率过高、重复性不满足要求。
//
// 【在这里添加真实算法】
// - 请在 fill_external_localization_common_success(...) 之前或之后补充真实验证逻辑。
// - 当前文件仅提供交付模板,不包含真实外部定位算法。
fill_external_localization_common_success(
input,
output,
"真值源验证完成。",
"demo_external_truth_source_validation_v1");
output.response.result.validation_summary.time_sync_ok = true;
output.response.result.validation_summary.coverage_ok = true;
output.response.result.validation_summary.quality_ok = true;
output.response.result.validation_summary.tracking_stable = true;
output.response.result.validation_summary.recommended_as_truth_source = true;
output.response.result.validation_summary.position_stddev_m = 0.0;
output.response.result.validation_summary.yaw_stddev_rad = 0.0;
output.response.result.validation_summary.tracking_loss_ratio = 0.0;
output.response.result.validation_summary.time_sync_offset_ms = 0.0;
output.response.result.result.workshop_frame_id = "workshop";
output.response.result.result.localization_frame_id = "localization";
output.response.result.result.position_repeatability_m = 0.0;
output.response.result.result.yaw_repeatability_rad = 0.0;
output.response.result.result.residual_error_m = 0.0;
output.response.result.result.residual_error_rad = 0.0;
output.response.result.result.tracking_loss_ratio = 0.0;
output.response.result.result.time_sync_offset_ms = 0.0;
output.response.result.result.validated_as_truth_source = true;
return true;
}
} // namespace external_localization_service
@@ -1,167 +0,0 @@
#include "sensor_calibration_service/sensor_calibration_service_node.hpp"
#include <chrono>
#include <thread>
#include "calibration_common_interfaces/msg/error_code.hpp"
#include "calibration_common_interfaces/msg/job_state.hpp"
#include "calibration_sensor_interfaces/msg/sensor_calibration_job_result.hpp"
#include "calibration_sensor_interfaces/msg/sensor_readiness_response.hpp"
namespace sensor_calibration_service
{
namespace
{
int64_t now_us()
{
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
} // namespace
using calibration_common_interfaces::msg::ErrorCode;
using calibration_common_interfaces::msg::JobState;
SensorCalibrationServiceNode::SensorCalibrationServiceNode(const rclcpp::NodeOptions & options)
: Node("sensor_calibration_service", options)
{
readiness_service_ = create_service<ReadinessSrv>(
"/sensor_calibration/get_readiness",
std::bind(&SensorCalibrationServiceNode::handle_readiness, this, std::placeholders::_1, std::placeholders::_2));
execute_task_action_server_ = rclcpp_action::create_server<ExecuteTask>(
this,
"/sensor_calibration/execute_task",
std::bind(&SensorCalibrationServiceNode::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&SensorCalibrationServiceNode::handle_cancel, this, std::placeholders::_1),
std::bind(&SensorCalibrationServiceNode::handle_accepted, this, std::placeholders::_1));
}
void SensorCalibrationServiceNode::handle_readiness(
const std::shared_ptr<ReadinessSrv::Request> request,
std::shared_ptr<ReadinessSrv::Response> response)
{
(void)request;
response->response.success = true;
response->response.error_code.code = ErrorCode::OK;
response->response.message = "sensor_calibration_service is ready.";
response->response.agent_ready = true;
response->response.capture_pipeline_ready = true;
response->response.storage_ready = true;
response->response.telemetry_ready = true;
response->response.vehicle_safe_to_move = true;
response->response.arm_ready = true;
response->response.ready_sensor_ids.push_back("demo_sensor_001");
response->response.checked_timestamp_us = now_us();
}
rclcpp_action::GoalResponse SensorCalibrationServiceNode::handle_goal(
const rclcpp_action::GoalUUID & /*uuid*/,
std::shared_ptr<const ExecuteTask::Goal> goal)
{
if (goal->goal.header.request_id.empty()) {
return rclcpp_action::GoalResponse::REJECT;
}
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse SensorCalibrationServiceNode::handle_cancel(
const std::shared_ptr<GoalHandleExecuteTask> /*goal_handle*/)
{
return rclcpp_action::CancelResponse::ACCEPT;
}
void SensorCalibrationServiceNode::handle_accepted(
const std::shared_ptr<GoalHandleExecuteTask> goal_handle)
{
std::thread(std::bind(&SensorCalibrationServiceNode::execute_goal, this, goal_handle)).detach();
}
void SensorCalibrationServiceNode::execute_goal(
const std::shared_ptr<GoalHandleExecuteTask> goal_handle)
{
// 先回一帧 RUNNING feedback,告诉 orchestrator 当前任务已经进入执行阶段。
auto feedback = std::make_shared<ExecuteTask::Feedback>();
feedback->feedback.state.state = JobState::RUNNING;
goal_handle->publish_feedback(feedback);
// ===== 组装算法输入上下文 =====
// 当前模板阶段先把“算法最常用的任务侧输入”显式展开。
// 后续如果要接真实车辆画像、已生效参数查询、历史遥测缓存、外部定位缓存,
// 也应继续在这里补齐并写入 SensorCalibrationInput。
SensorCalibrationAlgorithmTemplate::Input input;
input.request = *goal_handle->get_goal();
input.task_type = goal_handle->get_goal()->goal.selected_task;
input.task_subtype = goal_handle->get_goal()->goal.task_subtype;
input.target_sensor_id = resolve_target_sensor_id(*goal_handle->get_goal());
input.camera_intrinsic_task = goal_handle->get_goal()->goal.camera_intrinsic;
input.imu_intrinsic_task = goal_handle->get_goal()->goal.imu_intrinsic;
input.sensor_to_base_extrinsic_task = goal_handle->get_goal()->goal.sensor_to_base_extrinsic;
input.hand_eye_task = goal_handle->get_goal()->goal.hand_eye;
input.required_image_count = goal_handle->get_goal()->goal.camera_intrinsic.required_image_count;
input.required_static_segment_count = goal_handle->get_goal()->goal.imu_intrinsic.required_static_segment_count;
input.required_motion_segment_count = goal_handle->get_goal()->goal.imu_intrinsic.required_motion_segment_count;
input.required_sample_count = goal_handle->get_goal()->goal.sensor_to_base_extrinsic.required_sample_count;
input.required_pose_count = goal_handle->get_goal()->goal.hand_eye.required_pose_count;
switch (goal_handle->get_goal()->goal.selected_task.value) {
case TaskType::CAMERA_INTRINSIC:
input.reference_timeout_sec = goal_handle->get_goal()->goal.camera_intrinsic.timeout_sec;
break;
case TaskType::IMU_INTRINSIC:
input.reference_timeout_sec = goal_handle->get_goal()->goal.imu_intrinsic.timeout_sec;
break;
case TaskType::SENSOR_TO_BASE_EXTRINSIC:
input.reference_timeout_sec = goal_handle->get_goal()->goal.sensor_to_base_extrinsic.timeout_sec;
break;
case TaskType::HAND_EYE:
input.reference_timeout_sec = goal_handle->get_goal()->goal.hand_eye.timeout_sec;
break;
default:
input.reference_timeout_sec = 0.0;
break;
}
input.reference_board_id = goal_handle->get_goal()->goal.camera_intrinsic.target_board_id;
input.reference_base_frame_id = goal_handle->get_goal()->goal.sensor_to_base_extrinsic.base_frame_id;
input.reference_arm_id = goal_handle->get_goal()->goal.hand_eye.arm_id;
input.sensor_history_available = false;
input.chassis_history_available = false;
input.control_history_available = false;
input.truth_history_available = false;
SensorCalibrationAlgorithmTemplate::Output output;
std::string failure_reason;
if (!algorithm_.run(input, output, failure_reason)) {
auto result = std::make_shared<ExecuteTask::Result>();
result->result.success = false;
result->result.error_code.code = ErrorCode::INVALID_STATE;
result->result.message = failure_reason;
result->result.job_id = goal_handle->get_goal()->goal.header.request_id;
result->result.data_quality_passed = false;
result->result.suitable_for_commit = false;
goal_handle->abort(result);
return;
}
auto result = std::make_shared<ExecuteTask::Result>(output.response);
goal_handle->succeed(result);
}
std::string SensorCalibrationServiceNode::resolve_target_sensor_id(const ExecuteTask::Goal & goal) const
{
switch (goal.goal.selected_task.value) {
case TaskType::CAMERA_INTRINSIC:
return goal.goal.camera_intrinsic.sensor_id;
case TaskType::IMU_INTRINSIC:
return goal.goal.imu_intrinsic.sensor_id;
case TaskType::SENSOR_TO_BASE_EXTRINSIC:
return goal.goal.sensor_to_base_extrinsic.sensor_id;
case TaskType::HAND_EYE:
return goal.goal.hand_eye.sensor_id;
default:
return "";
}
}
} // namespace sensor_calibration_service
@@ -1,76 +0,0 @@
#include "sensor_calibration_service/sensor_calibration_common.hpp"
namespace sensor_calibration_service
{
bool FrontCameraExtrinsicCalibrationAlgorithm::run(
const SensorCalibrationInput & input,
SensorCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 前视相机到 base_link 外参标定模板。
fill_common_result(input, output);
output.response.result.message = "front camera extrinsic calibration template executed.";
output.response.result.recommended_parameter_version = "front_camera_extrinsic_template_v1";
return true;
}
bool DownwardCameraExtrinsicCalibrationAlgorithm::run(
const SensorCalibrationInput & input,
SensorCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 下视相机到 base_link 外参标定模板。
fill_common_result(input, output);
output.response.result.message = "downward camera extrinsic calibration template executed.";
output.response.result.recommended_parameter_version = "downward_camera_extrinsic_template_v1";
return true;
}
bool ImuExtrinsicCalibrationAlgorithm::run(
const SensorCalibrationInput & input,
SensorCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// IMU 到 base_link 外参标定模板。
fill_common_result(input, output);
output.response.result.message = "imu extrinsic calibration template executed.";
output.response.result.recommended_parameter_version = "imu_extrinsic_template_v1";
return true;
}
bool Lidar2DExtrinsicCalibrationAlgorithm::run(
const SensorCalibrationInput & input,
SensorCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 2D 激光雷达到 base_link 外参标定模板。
fill_common_result(input, output);
output.response.result.message = "2d lidar extrinsic calibration template executed.";
output.response.result.recommended_parameter_version = "lidar_2d_extrinsic_template_v1";
return true;
}
bool Lidar3DExtrinsicCalibrationAlgorithm::run(
const SensorCalibrationInput & input,
SensorCalibrationOutput & output,
std::string & failure_reason) const
{
(void)failure_reason;
// 3D 激光雷达到 base_link 外参标定模板。
fill_common_result(input, output);
output.response.result.message = "3d lidar extrinsic calibration template executed.";
output.response.result.recommended_parameter_version = "lidar_3d_extrinsic_template_v1";
return true;
}
} // namespace sensor_calibration_service
@@ -1,180 +0,0 @@
#include "vehicle_profile_manager/vehicle_profile_manager_node.hpp"
#include <chrono>
#include "calibration_common_interfaces/msg/error_code.hpp"
#include "calibration_vehicle_profile_interfaces/msg/chassis_type.hpp"
#include "calibration_vehicle_profile_interfaces/msg/workflow_stage_type.hpp"
#include "rclcpp_components/register_node_macro.hpp"
namespace vehicle_profile_manager
{
using calibration_common_interfaces::msg::ErrorCode;
using calibration_vehicle_profile_interfaces::msg::ChassisType;
using calibration_vehicle_profile_interfaces::msg::WorkflowStageType;
VehicleProfileManagerNode::VehicleProfileManagerNode(const rclcpp::NodeOptions & options)
: Node("vehicle_profile_manager", options)
{
get_profile_service_ = create_service<GetProfileSrv>(
"/vehicle_profile_manager/get_vehicle_profile",
std::bind(
&VehicleProfileManagerNode::handle_get_profile, this,
std::placeholders::_1, std::placeholders::_2));
register_service_ = create_service<RegisterSrv>(
"/vehicle_profile_manager/register_or_update_vehicle_profile",
std::bind(
&VehicleProfileManagerNode::handle_register, this,
std::placeholders::_1, std::placeholders::_2));
applicability_service_ = create_service<ApplicabilitySrv>(
"/vehicle_profile_manager/evaluate_vehicle_calibration_applicability",
std::bind(
&VehicleProfileManagerNode::handle_applicability, this,
std::placeholders::_1, std::placeholders::_2));
heartbeat_service_ = create_service<HeartbeatSrv>(
"/vehicle_profile_manager/heartbeat",
std::bind(
&VehicleProfileManagerNode::handle_heartbeat, this,
std::placeholders::_1, std::placeholders::_2));
load_demo_profile();
RCLCPP_INFO(get_logger(), "VehicleProfileManagerNode 启动,已预载 demo 车辆画像。");
}
void VehicleProfileManagerNode::handle_get_profile(
const std::shared_ptr<GetProfileSrv::Request> request,
std::shared_ptr<GetProfileSrv::Response> response)
{
const auto & vehicle_id = request->request.vehicle_id;
auto it = profiles_.find(vehicle_id);
if (it == profiles_.end()) {
response->response.success = false;
response->response.error_code.code = ErrorCode::INVALID_ARGUMENT;
response->response.message = "找不到 vehicle_id=[" + vehicle_id + "] 的车辆画像。";
return;
}
response->response.success = true;
response->response.error_code.code = ErrorCode::OK;
response->response.message = "查询成功。";
response->response.profile = it->second;
}
void VehicleProfileManagerNode::handle_register(
const std::shared_ptr<RegisterSrv::Request> request,
std::shared_ptr<RegisterSrv::Response> response)
{
const auto & vehicle_id = request->request.profile.base_info.vehicle_id;
if (vehicle_id.empty()) {
response->response.success = false;
response->response.error_code.code = ErrorCode::INVALID_ARGUMENT;
response->response.message = "vehicle_id 不能为空。";
return;
}
profiles_[vehicle_id] = request->request.profile;
RCLCPP_INFO(get_logger(), "已注册/更新车辆画像 vehicle_id=[%s]", vehicle_id.c_str());
response->response.success = true;
response->response.error_code.code = ErrorCode::OK;
response->response.message = "注册/更新成功。";
}
void VehicleProfileManagerNode::handle_applicability(
const std::shared_ptr<ApplicabilitySrv::Request> request,
std::shared_ptr<ApplicabilitySrv::Response> response)
{
const auto & profile = request->request.profile_snapshot;
auto stages = evaluate_supported_stages(profile);
response->response.success = true;
response->response.error_code.code = ErrorCode::OK;
response->response.message = "适用性评估完成。";
response->response.overall_supported = !stages.empty();
response->response.recommended_workflow_stages = stages;
}
void VehicleProfileManagerNode::handle_heartbeat(
const std::shared_ptr<HeartbeatSrv::Request> /*request*/,
std::shared_ptr<HeartbeatSrv::Response> response)
{
const auto now_us = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
response->response.success = true;
response->response.error_code.code = ErrorCode::OK;
response->response.message = "vehicle_profile_manager 在线。";
response->response.server_timestamp_us = now_us;
response->response.vehicle_ready = true;
}
std::vector<WorkflowStageType>
VehicleProfileManagerNode::evaluate_supported_stages(const VehicleProfile & profile) const
{
std::vector<WorkflowStageType> stages;
auto make_stage = [](uint8_t v) {
WorkflowStageType s;
s.value = v;
return s;
};
// 预检和画像校验始终支持。
stages.push_back(make_stage(WorkflowStageType::PROFILE_VALIDATION_STAGE));
stages.push_back(make_stage(WorkflowStageType::WORKSHOP_PRECHECK_STAGE));
// 底盘标定:底盘类型已指定时支持。
if (profile.chassis_type.value != ChassisType::CHASSIS_TYPE_UNSPECIFIED) {
stages.push_back(make_stage(WorkflowStageType::CHASSIS_CALIBRATION_STAGE));
stages.push_back(make_stage(WorkflowStageType::CONTROL_CALIBRATION_STAGE));
}
// 传感器标定:有传感器配置时支持。
if (!profile.sensors.empty()) {
stages.push_back(make_stage(WorkflowStageType::SENSOR_INTRINSIC_CALIBRATION_STAGE));
stages.push_back(make_stage(WorkflowStageType::SENSOR_EXTRINSIC_CALIBRATION_STAGE));
}
// 手眼标定:有机械臂且有传感器时支持。
if (profile.arm_profile.has_mechanical_arm && !profile.sensors.empty()) {
stages.push_back(make_stage(WorkflowStageType::HAND_EYE_CALIBRATION_STAGE));
}
// 最终阶段始终加入。
stages.push_back(make_stage(WorkflowStageType::PARAMETER_COMMIT_STAGE));
stages.push_back(make_stage(WorkflowStageType::REPORT_ARCHIVE_STAGE));
return stages;
}
void VehicleProfileManagerNode::load_demo_profile()
{
VehicleProfile demo;
// 基础信息
demo.base_info.vehicle_id = "demo_agv_001";
demo.base_info.vehicle_name = "Demo AGV";
demo.base_info.model_name = "DemoModel-X1";
demo.base_info.manufacturer = "Demo Manufacturer";
// 底盘类型:差速
demo.chassis_type.value = ChassisType::DIFFERENTIAL;
// base_link
demo.base_link_frame = "base_link";
// 画像版本
demo.profile_version = "demo_v1";
// 启用的工作流阶段
demo.enabled_workflow_stages = evaluate_supported_stages(demo);
profiles_[demo.base_info.vehicle_id] = demo;
RCLCPP_INFO(get_logger(), "已预载 demo 车辆画像 vehicle_id=[%s]",
demo.base_info.vehicle_id.c_str());
}
} // namespace vehicle_profile_manager
RCLCPP_COMPONENTS_REGISTER_NODE(vehicle_profile_manager::VehicleProfileManagerNode)
@@ -1,13 +0,0 @@
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package="workshop_orchestrator_v2",
executable="workshop_orchestrator_v2_node",
name="workshop_orchestrator_v2",
output="screen",
)
])
@@ -1,153 +0,0 @@
#include "workshop_orchestrator_v2/precheck_runner.hpp"
#include "calibration_workshop_orchestration_interfaces/msg/precheck_item.hpp"
namespace workshop_orchestrator_v2
{
bool PrecheckRunner::has_metadata_key(const StagePlan & stage, const std::string & key) const
{
for (const auto & kv : stage.metadata) {
if (kv.key == key) {
return true;
}
}
return false;
}
bool PrecheckRunner::has_metadata_prefix(const StagePlan & stage, const std::string & prefix) const
{
for (const auto & kv : stage.metadata) {
if (kv.key.rfind(prefix, 0) == 0) {
return true;
}
}
return false;
}
WorkshopPrecheckResponse PrecheckRunner::run(const WorkshopSession & session) const
{
WorkshopPrecheckResponse response;
response.success = true;
response.error_code = make_error_code(ErrorCode::OK);
response.checked_timestamp_us = now_us();
auto append_item = [&](const std::string & code,
const std::string & name,
bool passed,
const std::string & message,
uint8_t stage_type,
uint8_t module_type) {
calibration_workshop_orchestration_interfaces::msg::PrecheckItem item;
item.item_code = code;
item.display_name = name;
item.passed = passed;
item.blocking = true;
item.error_code = make_error_code(passed ? ErrorCode::OK : ErrorCode::INVALID_STATE);
item.message = message;
item.related_stage_type = make_stage_type(stage_type);
item.related_module_type = make_module_type(module_type);
response.items.push_back(item);
if (!passed) {
response.blocking_issue_count += 1;
response.all_passed = false;
}
};
response.all_passed = true;
response.blocking_issue_count = 0;
append_item(
"plan_not_empty",
"Execution plan exists",
!session.stage_plan.empty(),
session.stage_plan.empty() ? "Session has no executable stages." : "Session has executable stages.",
WorkflowStageType::WORKFLOW_STAGE_UNSPECIFIED,
CalibrationModuleType::CALIBRATION_MODULE_UNSPECIFIED);
for (const auto & stage : session.stage_plan) {
if (stage.module_type.value == CalibrationModuleType::CHASSIS_MODULE) {
const bool passed =
has_metadata_key(stage, metadata_keys::CHASSIS_PRIMITIVE_TYPE) &&
has_metadata_key(stage, metadata_keys::CHASSIS_STRAIGHT_LINE_DISTANCE_M) &&
has_metadata_key(stage, metadata_keys::CHASSIS_STRAIGHT_LINE_SPEED_MS);
append_item(
stage.stage_id + ".metadata",
stage.display_name + " metadata",
passed,
passed ? "底盘阶段输入完整。" : "底盘阶段缺少 primitive_type 或直线动作参数。",
stage.stage_type.value,
stage.module_type.value);
} else if (stage.module_type.value == CalibrationModuleType::CONTROL_MODULE) {
const bool passed =
has_metadata_key(stage, metadata_keys::CONTROL_TASK_TYPE) &&
has_metadata_prefix(stage, metadata_keys::CONTROL_TRAJECTORY_PREFIX);
append_item(
stage.stage_id + ".metadata",
stage.display_name + " metadata",
passed,
passed ? "运控阶段输入完整。" : "运控阶段缺少 control.task_type 或轨迹点输入。",
stage.stage_type.value,
stage.module_type.value);
} else if (
stage.module_type.value == CalibrationModuleType::SENSOR_INTRINSIC_MODULE ||
stage.module_type.value == CalibrationModuleType::SENSOR_EXTRINSIC_MODULE ||
stage.module_type.value == CalibrationModuleType::HAND_EYE_MODULE) {
bool passed =
has_metadata_key(stage, metadata_keys::SENSOR_ID) &&
has_metadata_key(stage, metadata_keys::SENSOR_TASK_SUBTYPE);
std::string message = passed ? "传感器阶段基础输入完整。" : "传感器阶段缺少 sensor.sensor_id 或 sensor.task_subtype。";
if (passed && stage.module_type.value == CalibrationModuleType::SENSOR_INTRINSIC_MODULE) {
const bool has_image_count = has_metadata_key(stage, metadata_keys::CAMERA_INTRINSIC_REQUIRED_IMAGE_COUNT);
const bool has_board = has_metadata_key(stage, metadata_keys::CAMERA_INTRINSIC_TARGET_BOARD_ID);
passed = has_image_count || has_board;
message = passed ? "传感器内参阶段输入完整。" : "传感器内参阶段缺少图像数或标定板信息。";
}
if (passed && stage.module_type.value == CalibrationModuleType::SENSOR_EXTRINSIC_MODULE) {
passed =
has_metadata_key(stage, metadata_keys::SENSOR_EXTRINSIC_BASE_FRAME_ID) &&
has_metadata_key(stage, metadata_keys::SENSOR_EXTRINSIC_REQUIRED_SAMPLE_COUNT);
message = passed ? "传感器外参阶段输入完整。" : "传感器外参阶段缺少 base_frame_id 或 required_sample_count。";
}
if (passed && stage.module_type.value == CalibrationModuleType::HAND_EYE_MODULE) {
passed =
has_metadata_key(stage, metadata_keys::HAND_EYE_ARM_ID) &&
has_metadata_key(stage, metadata_keys::HAND_EYE_REQUIRED_POSE_COUNT);
message = passed ? "手眼阶段输入完整。" : "手眼阶段缺少 arm_id 或 required_pose_count。";
}
append_item(
stage.stage_id + ".metadata",
stage.display_name + " metadata",
passed,
message,
stage.stage_type.value,
stage.module_type.value);
} else if (stage.module_type.value == CalibrationModuleType::EXTERNAL_LOCALIZATION_MODULE) {
const bool passed =
has_metadata_key(stage, metadata_keys::EXTERNAL_STATIC_SAMPLE_COUNT) &&
has_metadata_key(stage, metadata_keys::EXTERNAL_DYNAMIC_SAMPLE_COUNT) &&
has_metadata_key(stage, metadata_keys::EXTERNAL_MAX_POSITION_STDDEV_M) &&
has_metadata_key(stage, metadata_keys::EXTERNAL_MAX_YAW_STDDEV_RAD) &&
has_metadata_key(stage, metadata_keys::EXTERNAL_MAX_TRACKING_LOSS_RATIO) &&
has_metadata_key(stage, metadata_keys::EXTERNAL_MAX_TIME_SYNC_OFFSET_MS) &&
has_metadata_key(stage, metadata_keys::EXTERNAL_TIMEOUT_SEC);
append_item(
stage.stage_id + ".metadata",
stage.display_name + " metadata",
passed,
passed ? "external 阶段输入完整。" : "external 阶段缺少真值校核阈值配置。",
stage.stage_type.value,
stage.module_type.value);
}
}
response.ready_for_start = response.all_passed;
response.message = response.ready_for_start ? "Precheck passed." : "Precheck failed.";
return response;
}
} // namespace workshop_orchestrator_v2
@@ -1,5 +1,4 @@
# workshop_ui_pyside6_config_aligned
# 操作员界面原型
这版 PySide6 原型的目标不是单纯展示界面,而是:
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.8)
project(vehicle_internal_interfaces)
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(calibration_vehicle_profile_interfaces REQUIRED)
set(msg_files
"msg/VehicleControlMode.msg"
"msg/AckermannDriveCommand.msg"
"msg/VehicleSafetyCommand.msg"
"msg/AckermannActuatorState.msg"
"msg/VehicleInternalState.msg"
"msg/VehicleHealthStatus.msg"
"msg/VehicleTimeSyncStatus.msg"
"msg/SensorLinkStatus.msg"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
DEPENDENCIES calibration_vehicle_profile_interfaces
)
ament_export_dependencies(rosidl_default_runtime)
ament_package()
@@ -0,0 +1,17 @@
# 车辆内部接口
这个包定义车端电脑和车辆本体之间的内部 ROS2 消息。
它不直接作为车间电脑和车端电脑之间的 WiFi6 协议。WiFi6 对外协议仍由 `calibration_*_interfaces/proto` 定义。这个包用于把真实 Windows 小车的 CAN、串口、厂商 SDK 或仿真 Isaac topic 统一映射成车端内部语义。
当前消息包括:
- `AckermannDriveCommand`:阿克曼底盘速度、转角、制动、超时命令。
- `VehicleSafetyCommand`:上使能、下使能、急停、清故障、标定低速模式。
- `AckermannActuatorState`:速度、转角、轮速、电机电流、制动/油门反馈。
- `VehicleInternalState`:车辆模式、安全状态、底盘执行器状态、电源与运动状态。
- `VehicleHealthStatus`:控制器在线、总线状态、通信质量和车端资源状态。
- `VehicleTimeSyncStatus`:车端、传感器、外部真值之间的时间同步状态。
- `SensorLinkStatus`:车载传感器在线状态、帧率、丢帧和延迟。
仿真中,`vehicle_agent_sim` 会把车间电脑下发的控制请求转换为 `AckermannDriveCommand`,同时继续发布 Isaac 当前需要的 `cmd_vel`。真实部署时,Windows 车端应把这些内部消息映射到实际车辆接口。
@@ -0,0 +1,35 @@
# =========================================================
# 阿克曼底盘内部执行器状态
# 发送方:车辆控制器 / Isaac 底盘执行器适配层
# 接收方:车端电脑
# =========================================================
# 状态时间戳
int64 hardware_timestamp_us
# 实际纵向速度
float64 actual_speed_ms
# 实际纵向加速度
float64 actual_accel_ms2
# 实际前轮等效转角
float64 actual_steering_angle_rad
# 实际转向角速度
float64 actual_steering_rate_rads
# 左后驱动轮速度
float64 rear_left_wheel_speed_ms
# 右后驱动轮速度
float64 rear_right_wheel_speed_ms
# 左前轮等效转角
float64 front_left_steering_angle_rad
# 右前轮等效转角
float64 front_right_steering_angle_rad
# 驱动电机电流
float64 drive_motor_current_amp
# 转向电机电流
float64 steering_motor_current_amp
# 制动压力或制动比例,范围 [0, 1]
float64 brake_pressure
# 驱动控制输出,范围 [0, 1]
float64 throttle_output
@@ -0,0 +1,33 @@
# =========================================================
# 阿克曼底盘内部控制命令
# 发送方:车端电脑
# 接收方:车辆运动控制器 / Isaac 底盘执行器适配层
# =========================================================
# 命令时间戳
int64 command_timestamp_us
# 命令 ID,用于追踪和去重
string command_id
# 命令来源,例如 vehicle_agent_sim / real_vehicle_agent
string source
# 控制模式
vehicle_internal_interfaces/VehicleControlMode control_mode
# 目标纵向速度
float64 target_speed_ms
# 目标纵向加速度;0 表示由车辆控制器默认限幅
float64 target_accel_ms2
# 目标前轮等效转角,左正右负
float64 target_steering_angle_rad
# 目标转向角速度;0 表示由车辆控制器默认限幅
float64 target_steering_rate_rads
# 制动命令,范围 [0, 1]
float64 brake_command
# 油门 / 驱动命令,范围 [0, 1];仿真可选
float64 throttle_command
# 命令超时时间
float64 command_timeout_sec
# 是否要求控制器在超时或任务结束后停车
bool stop_when_timeout
@@ -0,0 +1,33 @@
# =========================================================
# 车端传感器链路状态
# 发送方:车端传感器代理
# 接收方:车端电脑 / 车间电脑桥接层
# =========================================================
# 状态时间戳
int64 hardware_timestamp_us
# 传感器 ID
string sensor_id
# 传感器类型
calibration_vehicle_profile_interfaces/SensorType sensor_type
# 传感器 frame
string frame_id
# 车端订阅或驱动 topic / 通道名
string source_channel
# 是否在线
bool online
# 当前帧率
float64 frame_rate_hz
# 最近一帧年龄
float64 latest_frame_age_ms
# 累计帧数
uint64 frame_count
# 丢帧比例
float64 dropped_frame_ratio
# 最近一帧传输延迟
float64 latest_transport_latency_ms
# 当前链路状态说明
string status_message
@@ -0,0 +1,15 @@
# =========================================================
# 车辆内部控制模式
# 作用:车端电脑与车辆控制器之间约定当前车辆控制状态
# =========================================================
uint8 MODE_UNSPECIFIED=0
uint8 DISABLED=1
uint8 MANUAL=2
uint8 AUTO=3
uint8 CALIBRATION=4
uint8 ESTOP=5
uint8 FAULT=6
# 当前模式
uint8 value
@@ -0,0 +1,40 @@
# =========================================================
# 车辆内部健康诊断状态
# 发送方:车端电脑 / 车辆控制器
# 接收方:车端电脑内部监控或车间电脑桥接层
# =========================================================
# 状态时间戳
int64 hardware_timestamp_us
# 车端电脑进程是否在线
bool vehicle_agent_online
# 车辆主控制器是否在线
bool vehicle_controller_online
# 驱动控制器是否在线
bool drive_controller_online
# 转向控制器是否在线
bool steering_controller_online
# 传感器总线是否在线
bool sensor_bus_online
# CAN 或厂商控制链路是否在线
bool vehicle_bus_online
# 外部真值链路是否在线
bool external_truth_link_online
# 通信质量
float64 vehicle_bus_rx_hz
float64 vehicle_bus_drop_ratio
float64 command_latency_ms
float64 telemetry_latency_ms
# 车端电脑资源
float64 cpu_load_ratio
float64 memory_used_ratio
float64 disk_used_ratio
float64 temperature_c
# 诊断摘要
bool healthy
string diagnostic_code
string diagnostic_message
@@ -0,0 +1,35 @@
# =========================================================
# 车辆内部综合状态
# 发送方:车辆控制器 / Isaac 底盘执行器适配层
# 接收方:车端电脑
# =========================================================
# 状态时间戳
int64 hardware_timestamp_us
# 当前控制模式
vehicle_internal_interfaces/VehicleControlMode current_mode
# 车辆是否已上使能
bool vehicle_enabled
# 急停是否触发
bool estop_engaged
# 是否处于低速标定模式
bool calibration_low_speed_mode
# 是否存在故障
bool fault_active
# 主故障码
string primary_fault_code
# 主故障说明
string primary_fault_message
# 当前底盘状态
vehicle_internal_interfaces/AckermannActuatorState ackermann_state
# 电源状态
float64 battery_voltage_v
float64 battery_current_amp
float64 battery_soc
# 车辆运动状态
float64 yaw_rate_rads
float64 lateral_accel_ms2
@@ -0,0 +1,30 @@
# =========================================================
# 车辆内部安全命令
# 发送方:车端电脑
# 接收方:车辆安全控制器 / 底盘控制器
# =========================================================
# 命令时间戳
int64 command_timestamp_us
# 命令 ID
string command_id
# 命令来源
string source
# 上使能车辆
bool enable_vehicle
# 下使能车辆
bool disable_vehicle
# 触发急停
bool engage_estop
# 解除急停
bool release_estop
# 清除可恢复故障
bool clear_faults
# 切换到标定低速安全模式
bool enter_calibration_low_speed_mode
# 退出标定低速安全模式
bool exit_calibration_low_speed_mode
# 操作原因
string reason
@@ -0,0 +1,27 @@
# =========================================================
# 车辆内部时间同步状态
# 发送方:车端电脑
# 接收方:车间电脑桥接层 / 车端诊断
# =========================================================
# 状态时间戳
int64 hardware_timestamp_us
# 同步源,例如 ptp / ntp / external_truth / sim_clock
string sync_source
# 是否认为已同步
bool synchronized
# 车端系统时钟相对传感器硬件时钟偏差
float64 system_to_sensor_offset_ms
# 车端系统时钟相对外部真值时钟偏差
float64 system_to_external_truth_offset_ms
# 同步抖动
float64 jitter_ms
# 近期最大时间同步误差
float64 max_offset_ms
# 时间同步链路延迟
float64 sync_transport_latency_ms
# 说明
string status_message
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<package format="3">
<name>vehicle_internal_interfaces</name>
<version>0.0.1</version>
<description>ROS 2 interfaces for vehicle-computer to vehicle-controller internal communication.</description>
<maintainer email="user@example.com">user</maintainer>
<license>Proprietary</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>calibration_vehicle_profile_interfaces</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
@@ -3,8 +3,8 @@ syntax = "proto3";
package agv.calibration.common;
// msg/
// Vector3d.msg
// Pose3dEuler.msg
// Vector3D.msg
// Pose3D.msg
// RequestHeader.msg
// ErrorCode.msg
// StandardResponse.msg
@@ -263,4 +263,4 @@ message FileReference {
message KeyValuePair {
string key = 1; //
string value = 2; //
}
}
@@ -0,0 +1,146 @@
syntax = "proto3";
package agv.calibration.transport;
// =========================================================
// 文件作用:WiFi6/TCP 传输契约
// 使用范围:
// 1) Ubuntu 车间工控机与 Windows 车端代理之间的网络边界
// 2) 仿真车端 agent 与真实车端 agent 需要共同遵守的帧号和通道约定
// 3) 业务消息仍由 chassis/control/sensor/external_localization proto 定义
// 说明:
// 1) WiFi6 是承载网络,当前工程传输层使用 TCP 长/短连接
// 2) 当前仿真实现 payload 使用 proto 字段名风格的 JSON
// 3) 现场可切换为 protobuf binary,但必须保持本文件中的通道和帧号不变
// =========================================================
// =========================================================
// WiFi6 逻辑通道
// =========================================================
enum Wifi6Channel {
WIFI6_CHANNEL_UNSPECIFIED = 0;
WIFI6_CHANNEL_CHASSIS = 1; // 底盘标定动作和急停
WIFI6_CHANNEL_CONTROL = 2; // 运控参数评估、轨迹跟踪
WIFI6_CHANNEL_SENSOR = 3; // 车端传感器原始数据
WIFI6_CHANNEL_EXTERNAL_POSE = 4; // 外部真值位姿输入到车端
}
// =========================================================
// 默认端口
// 说明:
// 1) WiFi6 边界默认只暴露一个车端 gateway 端口
// 2) channel 只是协议里的逻辑通道,不应该被理解成“一类功能一个物理端口”
// 3) 9001-9004 仅保留给仿真 router 背后的内部域服务或旧版兼容使用
// =========================================================
enum Wifi6DefaultPort {
WIFI6_DEFAULT_PORT_UNSPECIFIED = 0;
WIFI6_DEFAULT_PORT_VEHICLE_GATEWAY = 9000;
WIFI6_LEGACY_INTERNAL_PORT_CHASSIS = 9001;
WIFI6_LEGACY_INTERNAL_PORT_CONTROL = 9002;
WIFI6_LEGACY_INTERNAL_PORT_SENSOR = 9003;
WIFI6_LEGACY_INTERNAL_PORT_EXTERNAL_POSE = 9004;
}
// =========================================================
// TCP 载荷编码方式
// =========================================================
enum Wifi6PayloadEncoding {
WIFI6_PAYLOAD_ENCODING_UNSPECIFIED = 0;
WIFI6_PAYLOAD_ENCODING_JSON_PROTO_FIELD_NAMES = 1; // 当前仿真实现:JSON key 使用 proto 字段名
WIFI6_PAYLOAD_ENCODING_PROTOBUF_BINARY = 2; // 现场高性能实现可选
}
// =========================================================
// TCP 连接方向
// =========================================================
enum Wifi6FrameDirection {
WIFI6_FRAME_DIRECTION_UNSPECIFIED = 0;
WIFI6_FRAME_DIRECTION_WORKSHOP_TO_VEHICLE = 1;
WIFI6_FRAME_DIRECTION_VEHICLE_TO_WORKSHOP = 2;
}
// =========================================================
// TCP 帧号
// 帧头格式固定为:
// uint32 little-endian msg_type
// uint32 little-endian payload_len
// payload_len bytes payload
// 注意:
// 1) 这个 8 字节帧头不是 protobuf 序列化结果,而是传输层二进制头
// 2) payload 的业务结构由本字段注释中对应的 proto 消息定义
// 3) 逻辑 channel 由 msg_type 映射得到;同一个 gateway 端口根据 msg_type 做路由
// =========================================================
enum Wifi6FrameType {
WIFI6_FRAME_TYPE_UNSPECIFIED = 0;
// 底盘域:chassis_calibration.proto / AgvCalibChassisService
WIFI6_FRAME_CHASSIS_GET_READINESS_REQ = 1; // AgentReadinessRequest
WIFI6_FRAME_CHASSIS_GET_READINESS_RSP = 2; // ChassisReadinessResponse
WIFI6_FRAME_CHASSIS_MOTION_PRIMITIVE_REQ = 3; // MotionPrimitiveRequest
WIFI6_FRAME_CHASSIS_MOTION_PRIMITIVE_RSP = 4; // ChassisJobResult
WIFI6_FRAME_CHASSIS_EMERGENCY_BRAKE_REQ = 5; // Empty 或 EmergencyBrake 请求
WIFI6_FRAME_CHASSIS_EMERGENCY_BRAKE_RSP = 6; // StandardResponse
// 运控域:control_calibration.proto / AgvCalibControlService
WIFI6_FRAME_CONTROL_GET_READINESS_REQ = 11; // AgentReadinessRequest
WIFI6_FRAME_CONTROL_GET_READINESS_RSP = 12; // ControlReadinessResponse
WIFI6_FRAME_CONTROL_EVALUATION_REQ = 13; // ControllerEvaluationRequest
WIFI6_FRAME_CONTROL_EVALUATION_RSP = 14; // ControlJobResult
// 传感器域:sensor_calibration.proto / AgvCalibSensorService
WIFI6_FRAME_SENSOR_GET_READINESS_REQ = 21; // AgentReadinessRequest
WIFI6_FRAME_SENSOR_GET_READINESS_RSP = 22; // SensorReadinessResponse
WIFI6_FRAME_SENSOR_GET_LATEST_FRAME_REQ = 23; // StreamVehicleSensorDataRequest 的轻量轮询形态
WIFI6_FRAME_SENSOR_GET_LATEST_FRAME_RSP = 24; // VehicleSensorFrame
WIFI6_FRAME_SENSOR_LIST_SENSORS_REQ = 25; // Empty 或能力查询请求
WIFI6_FRAME_SENSOR_LIST_SENSORS_RSP = 26; // Sensor 列表响应
// 外部真值位姿域:external_localization.proto / AgvCalibExternalPoseFeedService
WIFI6_FRAME_EXTERNAL_POSE_PUSH_REQ = 31; // ExternalLocalizationTelemetry
WIFI6_FRAME_EXTERNAL_POSE_PUSH_RSP = 32; // StandardResponse
}
// =========================================================
// TCP 帧头的 protobuf 表达
// 说明:
// 1) 这个 message 仅用于文档、测试、代码生成时表达契约
// 2) 实际线上帧头仍是上面注释中定义的 8 字节小端二进制结构
// =========================================================
message Wifi6TcpFrameHeader {
Wifi6FrameType msg_type = 1; // 对应 8 字节帧头中的 uint32 msg_type
uint32 payload_len = 2; // 对应 8 字节帧头中的 uint32 payload_len
}
// =========================================================
// 逻辑通道端点
// =========================================================
message Wifi6ChannelEndpoint {
Wifi6Channel channel = 1;
Wifi6DefaultPort default_port = 2;
Wifi6FrameDirection request_direction = 3;
string canonical_proto_file = 4; // 例如 chassis_calibration.proto
string canonical_service = 5; // 例如 AgvCalibChassisService
}
// =========================================================
// 请求 / 响应帧映射
// =========================================================
message Wifi6FrameMapping {
Wifi6Channel channel = 1;
Wifi6FrameType request_type = 2;
Wifi6FrameType response_type = 3;
Wifi6PayloadEncoding payload_encoding = 4;
string request_message = 5; // proto 消息名
string response_message = 6; // proto 消息名
string rpc_name = 7; // 对齐的 RPC 名称;轻量轮询可为空
}
// =========================================================
// 当前工程默认契约版本
// =========================================================
message Wifi6TransportContract {
string contract_version = 1; // 当前为 "wifi6_tcp_v1"
Wifi6PayloadEncoding default_payload_encoding = 2;
repeated Wifi6ChannelEndpoint endpoints = 3;
repeated Wifi6FrameMapping frame_mappings = 4;
}

Some files were not shown because too many files have changed in this diff Show More