Skip to content

Full with position control#8

Open
hyperle wants to merge 19 commits into
mainfrom
full-with-position-control
Open

Full with position control#8
hyperle wants to merge 19 commits into
mainfrom
full-with-position-control

Conversation

@hyperle
Copy link
Copy Markdown
Contributor

@hyperle hyperle commented Apr 26, 2026

概述

本PR实现了完整的位置控制功能,通过引入新的动作(Action)和任务(Task)系统、视觉辅助发射准备流程、视频录制和力传感器数据记录等功能,为飞镖发射机制提供精细的位置与力度控制能力。

核心改动

构建系统与插件

  • CMakeLists.txt: 新增5个GoogleTest测试目标,配置条件编译(BUILD_TESTING)和测试依赖
  • package.xml: 添加ament_cmake_gtest测试依赖
  • plugins.xml: 移除DartManagerV2组件,新增ImageDisplayerForceSensorRecorder测试组件

动作系统(Action)扩展

新增动作类

  • AutoAimAction: 自动瞄准,支持像素误差反馈、多层死区控制(就绪死区、接受死区)、超时机制和PID增益计算
  • BeltConstantVelocityMoveAction: 传送带恒速移动,基于目标距离和速度执行,包含过冲检测和失速检测
  • BeltHoldTorqueAction: 传送带恒扭矩保持,维持指定扭矩直到超时
  • ZeroCalibrationAction: 零点标定,持续指定周期后完成
  • ForceScrewCalibrationAction: 力螺杆标定,支持原始传感器和卡尔曼滤波两种工作模式,使用PID闭环控制
  • VisionYawCalibration: 视觉偏航标定,基于距离和映射速率计算控制角度

修改现有动作

  • BeltMoveAction: 添加timeout_returns_success参数,使超时可选返回成功或失败
  • FillingLiftAction: 新增require_motion_before_stall参数,灵活控制失速检测逻辑
  • FillingLimitServoAction: 移除可配置的fill_ticks参数,采用固定常数(200)阈值
  • DartManualForceControlAction: 新增力螺杆速度/扭矩反馈输入支持,优化力控制速度计算公式
  • ActionSequence: 改进控制流,支持跳过空动作,在同一update()周期内连续执行多个动作

反馈与配置管理

  • AutoAimFeedback: 新增自动瞄准反馈类,管理控制速度、就绪状态和目标误差的统一绑定和重置
  • DartLaunchSequence: 新增发射序列管理器,支持多发轮次控制和单发偏差补偿

任务系统(Task)重构

  • DartManager: 从DartManagerV2更名,大幅扩展输入/输出接口,增加传送带角度、力控制反馈等新传感器,集成零点标定和力传感器标定流程

  • LaunchPreparationTask: 完全重构,采用新的BeltConstantVelocityMoveActionBeltHoldTorqueAction,支持条件化提升和力螺杆标定(兼容原始传感器和卡尔曼滤波输入)

  • FireAndPreloadTask: 简化控制逻辑,移除旧的填充时序参数,改用FillingLimitServoAction直接进行舵机控制

  • CancelLaunchTask: 完善多阶段控制序列,使用恒速移动和扭矩保持动作,支持条件化提升操作

  • SliderInitTask: 扩展为包含零点标定和视觉偏航标定(可选)的完整初始化序列

  • VisionAssistedLaunchPreparationTask: 新增任务,组合自动瞄准和发射准备的两阶段流程

视觉与录制模块

  • DartVisionCore: 新增视频录制功能,支持原始/处理帧分别录制,添加目标距离偏移输出
  • VideoRecorder: 新增异步视频录制类,支持H.264和MJPEG编码,支持定时文件分割

测试与调试组件

新增组件

  • ImageDisplayer: 图像显示组件,支持实时显示原始/处理图像,目标标注和帧保存功能
  • ForceSensorRecorder: 力传感器记录组件,异步记录双通道力数据到文件,支持发数跟踪和循环轮次计数

新增单元测试

  • auto_aim_action_test.cpp: 验证自动瞄准的目标跟踪、死区切换和超时行为
  • dart_launch_sequence_test.cpp: 验证发射序列的推进和重置逻辑
  • manual_force_control_test.cpp: 验证力控制动作的速度设置和失速检测
  • launch_preparation_task_test.cpp / vision_assisted_launch_preparation_task_test.cpp: 任务集成测试(代码暂为注释)

影响范围

  • 新增~2300行代码和测试用例
  • 重构核心管理器和任务系统逻辑
  • 完整集成视频录制和数据记录功能
  • 支持多发发射的完整工作流

floatpigeon and others added 19 commits March 19, 2026 03:31
- Added belt angle input interfaces for position feedback
- Added belt_zero_calibration output for zero point calibration
- Added belt_down_distance and belt_pulley_radius parameters
- Added BeltConstantVelocityMoveAction for position-based control
- Added ZeroCalibrationAction for zero point recording
- Updated LaunchPreparationTask to use position control instead of stall detection
- Updated CancelLaunchTask with position control
- Updated SliderInitTask to trigger zero calibration after homing

Key features preserved:
- Downward movement uses position feedback to determine arrival
- Distance (m) to angle (rad) conversion using pulley radius
- Zero point calibration on each upward stall (mechanical limit)
- Added fire_count increment after fire task completion
- Added fire_count reset after cancel_launch task completion
- Restored belt_velocity_ramp_action.hpp (BeltModerateAction)
- Added logging for fire_count changes

Task flow now works correctly:
1. slider_init: calibrates zero point
2. launch_prepare: uses fire_count to select velocity (first vs subsequent)
3. fire: increments fire_count
4. cancel_launch: resets fire_count to 0

This ensures proper position control behavior across multiple launch cycles.
BeltMoveAction changes:
- Added timeout_returns_success parameter (default: false)
- Updated update() to use this parameter for timeout handling
- Added member variable declaration

Task updates:
- silder_init_task: Already correct (timeout_returns_success=true)
- launch_preparation_task: Added timeout_returns_success=false to belt_reset_up
- cancel_launch_task: Added timeout_returns_success=false to belt_reset

This ensures all BeltMoveAction calls have the correct number of parameters
and proper timeout behavior.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Walkthrough

本次变更对飞镖引导系统进行了全面重构,引入自动瞄准动作、新型传送带常速控制动作、力传感器标定动作等多个新动作类,重构了任务框架和管理器核心逻辑,添加了视频录制功能,并扩展了测试覆盖。插件注册表更新,配置依赖项调整。

Changes

Cohort / File(s) Summary
Build Configuration
CMakeLists.txt, package.xml
引入test源文件自动扫描(CONFIGURE_DEPENDS)、注册gtest测试目标、配置包含路径和依赖项、添加ament_cmake_gtest依赖。
Plugin Registry
plugins.xml
移除manager::DartManagerV2,新增ImageDisplayertest::ForceSensorRecorder组件注册。
新增动作类 - 自动瞄准
src/manager/action/auto_aim_action.hpp
实现AutoAimAction,支持像素误差追踪、死区管理、增益控制、可配参数化、超时处理和节流日志。
新增动作类 - 传送带常速
src/manager/action/belt_constant_velocity_move_action.hpp
BeltConstantVelocityMoveAction以恒定速度控制传送带移动,支持距离目标、扭矩偏置、停滞检测。
新增动作类 - 传送带保持扭矩
src/manager/action/belt_hold_torque_action.hpp
BeltHoldTorqueAction维持指定扭矩一段时间直至超时。
新增动作类 - 传送带零点标定
src/manager/action/belt_zero_calibration.hpp
ZeroCalibrationAction通过设置标志位并等待配置时间实现零点标定。
新增动作类 - 力传感器标定
src/manager/action/force_screw_calibration_action.hpp
ForceScrewCalibrationAction使用PID闭环控制力传感器误差,支持原始传感器和卡尔曼滤波两种输入模式。
新增动作类 - 视觉偏航标定
src/manager/action/vision_yaw_calibration.hpp
VisionYawCalibration根据距离和映射率计算偏航控制角度。
修改传送带控制动作
src/manager/action/belt_move_action.hpp
添加可选参数timeout_returns_success,控制超时时返回SUCCESS或FAILURE。
修改提升动作
src/manager/action/filling_lift_action.hpp
新增require_motion_before_stall参数,灵活控制停滞检测时是否要求先观察运动。
修改伺服动作
src/manager/action/filling_limit_servo_action.hpp
移除可配fill_ticks参数,改用固定常数阈值(200刻度)。
修改手动控制
src/manager/action/manual_angle_control.hpp, src/manager/action/manual_force_control.hpp
manual_angle_control格式化调整;manual_force_control新增力螺杆反馈指针参数,改进控制逻辑和停滞检测。
动作框架更新
src/manager/action/action_sequence.hpp
ActionSequence::update()改用循环跳过空动作,SUCCESS状态不再直接返回而是继续处理序列中的后续动作。
命令处理
src/manager/command.cpp
移除未使用的头文件和注释调试代码。
管理器核心
src/manager/dart_manager.cpp
DartManagerV2重命名为DartManager,注册新传感器输入(传送带角度、力螺杆反馈、双通道力传感器、卡尔曼力/速率),新增控制输出(扭矩偏置、零点标定标志、力控参数),扩展任务构造和控制流。
自动瞄准反馈
src/manager/auto_aim_feedback.hpp
新增AutoAimFeedback工具类,管理瞄准相关状态的绑定和访问。
发射序列管理
src/manager/dart_launch_sequence.hpp
新增DartLaunchSequence类,支持配置飞镖序列、跟踪当前飞镖索引、计算目标像素偏置。
任务框架重构
src/manager/task/silder_init_task.hpp, src/manager/task/launch_preparation_task.hpp, src/manager/task/cancel_launch_task.hpp, src/manager/task/fire_and_preload_task.hpp
整体重构任务流程,使用新的动作原语(常速移动、保持扭矩、力标定、零点标定),支持可配的提升和力标定逻辑,集成AutoAimAction
新增视觉辅助任务
src/manager/task/vision_assisted_launch_preparation_task.hpp
新增VisionAssistedLaunchPreparationTask,整合自动瞄准与发射准备逻辑。
视觉系统扩展
src/vision/dart_vision_core.cpp
添加视频录制配置和控制,新增目标距离输出,改进目标位置计算。
视频录制组件
src/vision/video_recorder.hpp
新增VideoRecorder类,异步录制OpenCV帧到视频文件,支持多类型、自动轮换、队列管理。
测试组件
test/force_sensor_recorder.cpp, test/image_displayer.cpp
新增ForceSensorRecorder组件记录力传感器数据及发射统计,新增ImageDisplayer组件实时显示和保存图像。
测试套件
tests/auto_aim_action_test.cpp, tests/dart_launch_sequence_test.cpp, tests/launch_preparation_task_test.cpp, tests/manual_force_control_test.cpp, tests/vision_assisted_launch_preparation_task_test.cpp
新增自动瞄准、发射序列、发射准备任务、手动力控、视觉辅助任务的单元测试(部分为骨架代码)。

Sequence Diagram(s)

sequenceDiagram
    participant Manager as DartManager
    participant VisionTask as VisionAssistedLaunchPreparationTask
    participant AutoAim as AutoAimAction
    participant LaunchPrep as LaunchPreparationTask
    participant BeltControl as BeltConstantVelocityMoveAction
    participant ForceCal as ForceScrewCalibrationAction
    participant Hardware as 硬件控制

    Manager->>VisionTask: 执行视觉辅助发射准备
    VisionTask->>AutoAim: tick() - 自动瞄准
    activate AutoAim
    AutoAim->>Hardware: 读取目标位置/跟踪状态
    Hardware-->>AutoAim: 返回像素误差
    alt 误差在可接受范围
        AutoAim-->>VisionTask: SUCCESS
    else 误差超范围
        AutoAim-->>VisionTask: RUNNING<br/>设置控制速度
    end
    deactivate AutoAim
    
    VisionTask->>LaunchPrep: then() - 发射准备
    activate LaunchPrep
    LaunchPrep->>BeltControl: 执行传送带下行
    activate BeltControl
    BeltControl->>Hardware: 恒速控制传送带
    Hardware-->>BeltControl: 返回角度/速度反馈
    alt 位置误差<阈值
        BeltControl-->>LaunchPrep: SUCCESS
    end
    deactivate BeltControl
    
    LaunchPrep->>ForceCal: 执行力标定
    activate ForceCal
    ForceCal->>Hardware: 读取力传感器
    Hardware-->>ForceCal: 返回力值
    alt 力误差<容差持续确认
        ForceCal-->>LaunchPrep: SUCCESS
    end
    deactivate ForceCal
    LaunchPrep-->>VisionTask: SUCCESS
    deactivate LaunchPrep
    
    VisionTask-->>Manager: 任务完成
Loading
sequenceDiagram
    participant User as 用户/手动控制
    participant ManualForce as DartManualForceControlAction
    participant Feedback as 力螺杆反馈
    participant Motor as 力螺杆电机

    User->>ManualForce: 摇杆输入
    ManualForce->>ManualForce: 计算力控速度<br/>= 摇杆×scale
    ManualForce->>Motor: 设置目标速度
    alt 摇杆命令≈0
        ManualForce->>ManualForce: 清空停滞计数器
    else 持续非零命令>100刻度
        ManualForce->>Feedback: 读取速度/扭矩反馈
        Feedback-->>ManualForce: 返回反馈值
        alt 速度/扭矩检测到停滞
            ManualForce->>ManualForce: 递增停滞计数
        else 无停滞
            ManualForce->>ManualForce: 重置停滞计数
        end
        alt 停滞计数≥100
            ManualForce-->>User: SUCCESS<br/>任务完成
        else 计数<100
            ManualForce-->>User: RUNNING
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 传送带舞动新节奏,力传感器轻声诉说,
自动瞄准眼眸闪烁,视觉引导梦想起飞,
测试守护每个动作,飞镖将要准确落座!✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 标题「Full with position control」过于宽泛且含义不够具体,无法清晰概括主要变更内容。变更涉及多个行动类、任务、视觉反馈等大量新增和修改,但标题未能反映具体改动的核心目标。 建议使用更具体的标题以反映主要变更,例如「Add auto-aim and belt position control actions」或「Refactor launch sequence with vision-assisted control」。
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch full-with-position-control
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch full-with-position-control

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/manager/action/filling_limit_servo_action.hpp (1)

9-9: ⚠️ Potential issue | 🟡 Minor

注释与新签名不一致。

Line 9 的中文注释仍写“等待 fill_ticks,再下发 LOCK”,但 fill_ticks 参数已被移除。建议同步更新为“等待固定 200 ticks”或说明实际时序,避免误导后续读者。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/filling_limit_servo_action.hpp` at line 9, The file-level
comment for the FillingLimitServoAction is out of sync with the new API: it
still mentions waiting for `fill_ticks` which no longer exists; update the
comment above the FillingLimitServoAction (or the function/constructor with the
new signature) to state that after sending the FREE semantic command the action
waits a fixed 200 ticks (or the precise timing used) before sending LOCK, and
remove any mention of the removed `fill_ticks` parameter; ensure the comment
text around the class/constructor/method name FillingLimitServoAction reflects
the actual timing and is consistent with the current signature so future readers
are not misled.
src/manager/task/fire_and_preload_task.hpp (1)

15-71: ⚠️ Potential issue | 🟠 Major

类注释与实现严重不一致,声称的三个步骤中仅实现其一,且传入的参数被完全忽略。

  • 类注释声称执行 "1. 解锁扳机;2. 填装升降上行;3. 限位舵机执行预装填动作",但实现中仅执行第 3 步,前两步整体被注释掉
  • 构造参数 trigger_lock_enablelifting_commandlifting_left_vel_fblifting_right_vel_fb 在当前实现中完全未被使用,仅在注释代码中出现
  • is_first_shot 参数已传递(在 dart_manager.cpp 中调用时明确设置 is_first_shot = (fire_count_ == 0)),但对应的条件分支完全被注释,参数形同虚设
  • 调用方在 dart_manager.cpp 传递这些参数时实际上其参数会被静默忽略,导致行为与预期不符

需明确化处理:

  1. 若确实要简化为仅限位舵机操作:删除未使用的参数,修改类注释改为 "仅执行限位舵机预装填动作",清理注释掉的死代码
  2. 若计划保留三步逻辑:将注释掉的 if (is_first_shot) 分支完整解注,确保传入的参数被正确使用

否则 PR 合入后会导致行为变更不可见,调用方在传递参数时无法意识到其参数被忽略。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/fire_and_preload_task.hpp` around lines 15 - 71, The
FireAndPreloadTask implementation currently only enqueues a
FillingLimitServoAction while the constructor parameters (trigger_lock_enable,
lifting_command, lifting_left_vel_fb, lifting_right_vel_fb, is_first_shot) are
ignored and the intended if (is_first_shot) / else branch is commented out;
either (A) remove the unused parameters from FireAndPreloadTask, delete the
commented dead code, and update the class comment to state it only performs the
limiting-servo preload (adjust Task name/description if needed), or (B) restore
and uncomment the original logic in the FireAndPreloadTask constructor:
re-enable the TriggerControlAction and FillingLiftAction sequences (including
the is_first_shot conditional that only unlocks the trigger for the first shot),
ensure lifting_command and velocity feedbacks (lifting_left_vel_fb,
lifting_right_vel_fb) are passed into FillingLiftAction, and keep the final
FillingLimitServoAction; pick one approach and make the constructor, comment,
and callers consistent with that choice.
src/vision/dart_vision_core.cpp (2)

255-268: ⚠️ Potential issue | 🔴 Critical

识别成功分支中 *target_position_ 未更新即用于计算 diff,导致输出陈旧/无效像素差

if (!identifier_.result_status_()) 的 else 分支里,initial_position 已通过 identifier_.get_result() 拿到,但并未赋值给 *target_position_;随后第 265 行 cv::Point2i diff = *target_position_ - image_center_; 使用的仍是上一次状态(很可能是 (-1, -1)),导致 *yaw_pitch_distance_ 输出与真实首帧目标位置不符。下游 auto_aim 等动作消费的是该 yaw_pitch_target_distance,会基于错误的像素误差驱动云台。

🐛 建议的修正
                 is_tracker_stage_ = true;
                 tracker_.Init(initial_position);
 
                 // Output relative position (target - center)
-                cv::Point2i diff = *target_position_ - image_center_;
+                *target_position_ = initial_position;
+                cv::Point2i diff = initial_position - image_center_;
                 *yaw_pitch_distance_ = Eigen::Vector2d(diff.x, diff.y);
                 *tracking_ = true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/dart_vision_core.cpp` around lines 255 - 268, The else branch
after identifier_.result_status_() uses *target_position_ to compute diff but
never updates it; assign *target_position_ = initial_position (where
initial_position = identifier_.get_result()) before computing diff so the
immediate pixel offset is correct, then proceed with
tracker_.Init(initial_position), compute cv::Point2i diff = *target_position_ -
image_center_, set *yaw_pitch_distance_ and *tracking_ as before; this ensures
yaw_pitch_distance_ reflects the newly detected initial position rather than a
stale value.

269-296: ⚠️ Potential issue | 🔴 Critical

跟踪分支未同步更新 yaw_pitch_distance_,下游消费者将持续读取过期值

与 261-267 行识别→跟踪切换时计算 diff 的逻辑相比,跟踪稳定后的成功分支(289-294 行)只更新了 *target_position_*tracking_,没有刷新 *yaw_pitch_distance_。后果是 auto_aim_action 等消费 /dart_guidance/tracker/yaw_pitch_target_distance 的下游始终拿到首帧的差值,无法跟随目标移动。

跟踪丢失分支(278-288 行)建议同时把 *yaw_pitch_distance_ 复位(如 Eigen::Vector2d::Zero()),以避免目标丢失后输出仍残留旧误差。

🐛 建议的修正
             if (!is_tracking) {
                 tracker_loss_count_++;
                 *target_position_ = PointT(-1, -1);
                 *tracking_ = false;
+                *yaw_pitch_distance_ = Eigen::Vector2d::Zero();
 
                 if (tracker_loss_count_ > 100) {
                     ...
                 }
             } else {
                 tracker_loss_count_ = 0;
                 *target_position_ = current_position;
                 *tracking_ = true;
+                cv::Point2i diff = current_position - image_center_;
+                *yaw_pitch_distance_ = Eigen::Vector2d(diff.x, diff.y);
                 cv::circle(display_image, current_position, 20, cv::Scalar(255, 0, 255), 2);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/dart_vision_core.cpp` around lines 269 - 296, When tracking is
successful or lost you must update the shared yaw_pitch_distance_ the same way
the identification->tracking switch does: in the tracking-success branch (where
tracker_.get_current_position() is used and *target_position_/*tracking_ are
set) recompute and store *yaw_pitch_distance_ using the same computation/utility
used in the identification→tracking transition (reuse the same function/logic
that produced the initial diff) so downstream consumers see up-to-date error; in
the tracking-loss branch also reset *yaw_pitch_distance_ to
Eigen::Vector2d::Zero() (in addition to setting *target_position_ and *tracking_
and incrementing tracker_loss_count_) to avoid stale error values.
🟡 Minor comments (13)
test/force_sensor_recorder.cpp-66-82 (1)

66-82: ⚠️ Potential issue | 🟡 Minor

initialize_output_file 每次启动都 std::ios::trunc,会丢失历史记录。

每次组件构造(节点重启 / 重配置)都会无条件清空 output_file_path_ 指向的文件,这在测试调试场景下很容易把上一轮采集的力传感器数据全部清零。建议至少满足下列之一:

  • 用带时间戳/轮次的文件名(如 force_sensor_record_2025-04-26T10-02.md);
  • 仅在文件不存在时才写入表头,否则 std::ios::app 续写;
  • 或在覆盖前把旧文件改名备份。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/force_sensor_recorder.cpp` around lines 66 - 82, The
initialize_output_file function currently opens output_file_path_ with
std::ios::trunc which always erases prior data; change it so existing data isn't
lost by checking for the file existence first: if the file exists, open with
std::ios::app and only write the header when creating a new file, otherwise
create a new timestamped/round-specific filename or rename the old file as a
backup before truncating; update references to output_file_path_, the local
std::ofstream creation logic in initialize_output_file, and the header-write
block to run only on new-file creation.
test/force_sensor_recorder.cpp-95-100 (1)

95-100: ⚠️ Potential issue | 🟡 Minor

记录上升沿但未做去抖:单条 fire_trigger 异常脉冲会导致总发射次数错位。

当前实现仅以 current_trigger && !last_trigger_state_ 作为上升沿判定,没有最小持续帧数 / 时间戳间隔限制。如果 fire_trigger_ 因任何原因出现“一帧 true 后又落下”的毛刺脉冲(重置 / 节点重启 / 上游 publisher 重连等),都会让 total_shot_count_ 多走一格,进而把后续轮次((total_shot_count_ - 1) % 4 + 1)整体偏移,给后期数据分析带来对齐困难。

建议加入最小高电平确认帧数(例如 ≥ 2 ticks)或与 DartManager 一侧的发射事件对齐(带 sequence id 的消息)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/force_sensor_recorder.cpp` around lines 95 - 100, The code currently
treats any current_trigger && !last_trigger_state_ as an edge and increments
total_shot_count_, which allows single-frame glitches to miscount; modify the
edge-detection in the function handling fire_trigger_ so an up-edge is only
accepted after confirming a minimum high duration (e.g. require current_trigger
to be true for >=2 consecutive ticks or check timestamp delta >= configured
debounce_ms) before incrementing total_shot_count_ and computing round_number =
(total_shot_count_ - 1) % 4 + 1; update/maintain a small counter or
last_high_timestamp variable alongside last_trigger_state_ to implement the
debounce, and ensure async_write_to_file(total_shot_count_, round_number, ...)
is only called after the debounced edge is confirmed.
src/manager/action/action_sequence.hpp-49-77 (1)

49-77: ⚠️ Potential issue | 🟡 Minor

确认 update() 内确实可连续推进多个动作,建议明确文档或调整实现。

代码确实允许在单次 update() 调用中连续执行多个动作:当一个动作返回 SUCCESS 时(第61-65行),代码调用 continue 直接进入下一次循环迭代,可能立即对下一个非空动作调用 tick_first()/tick()

这与现有注释(第16行:"当前动作返回 SUCCESS 后自动推进到下一个动作")的措辞模糊——未明确指出是否在同一帧内推进。带来的风险包括:

  • 新进入的动作在 tick_first() 可能读到陈旧的输入状态(上游传感器/控制量尚未在本帧刷新)
  • 可观察的状态机推进速度高于预期
  • 单帧执行耗时可能上升

建议:

  1. 若设计意图是"一次 update 至多推进一帧":将第65行的 continue 改为 return ActionStatus::RUNNING(或仅在跳过 nullptrcontinue
  2. 若意图就是同帧推进多个动作:在类注释中显式文档化这一语义,例如"可在单次 update() 内连续推进,直到某动作返回 RUNNINGFAILURE",避免后续维护误解
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/action_sequence.hpp` around lines 49 - 77, The update()
loop currently advances immediately to the next action when the current action
returns SUCCESS (using continue), allowing multiple actions to be progressed in
a single update; to limit progress to at most one actionable step per update,
change the SUCCESS branch in update() (the block handling ActionStatus::SUCCESS
around current.on_exit(), ++cursor_, first_tick_of_current_ = true) to return
ActionStatus::RUNNING instead of continue (keep the continue for the nullptr
skip), so the method advances the cursor but yields RUNNING to prevent ticking
the next action in the same call; update the class comment to state this
"one-action-per-update" semantic and reference cursor_, actions_,
first_tick_of_current_, and update().
src/manager/task/silder_init_task.hpp-27-65 (1)

27-65: ⚠️ Potential issue | 🟡 Minor

视觉校准参数当前为死代码,建议明确处理。

yaw_pitch_angleyaw_pitch_distanceyaw_angle_mapping_rate 已纳入构造函数签名,但唯一的使用点(第 61-65 行)整段被注释。这会带来三个问题:

  1. 未使用形参可能触发 -Wunused-parameter 告警;
  2. 调用方(如 dart_manager.cpp)会以为视觉校准已生效,实际并未启用;
  3. vision_yaw_calibration.hpp 当前仅因为这段注释而 include,等价于隐式依赖。

如果暂未准备启用,建议先把视觉相关参数从构造函数移除,待 VisionYawCalibration 调用真正落地时再恢复;或者保留参数但取消注释、用 if (yaw_pitch_angle && yaw_pitch_distance) 守卫调用。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/silder_init_task.hpp` around lines 27 - 65, The constructor
currently accepts yaw_pitch_angle, yaw_pitch_distance, and
yaw_angle_mapping_rate but the VisionYawCalibration call is commented out
causing unused-parameter warnings and misleading API behavior; either remove
these three parameters from the constructor signature (and related includes) if
vision calibration is not supported, or re-enable the guarded calibration call
by uncommenting and adding the runtime guard if (yaw_pitch_angle &&
yaw_pitch_distance)
then(std::make_shared<VisionYawCalibration>(*yaw_pitch_angle,
*yaw_pitch_distance, yaw_angle_mapping_rate)); so that VisionYawCalibration is
invoked only when both pointers are provided; update any callers (e.g., where
this Task is constructed) accordingly to match the chosen approach.
src/manager/dart_launch_sequence.hpp-57-67 (1)

57-67: ⚠️ Potential issue | 🟡 Minor

configurecurrent_desired_target_px 对空 offsets 的处理不一致(小问题)。

configure() 强制 aim_dart_offsets_px.size() == dart_count,因此正常路径上 offsets 永远不可能为空;但 current_desired_target_px() 第 87-89 行又对空 offsets 做了回退(直接返回参考点)。要么这条回退分支永远死代码,要么应放宽 configure 的校验(例如允许 empty() 表示「所有飞镖共用参考点」)。建议二选一以消除歧义,并据此调整 configure_from_parameter_values 的校验条件。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/dart_launch_sequence.hpp` around lines 57 - 67, 当前不一致:configure()
强制 aim_dart_offsets_px.size() == dart_count 导致 offsets 永不为空,但
current_desired_target_px() 又对空 offsets 做回退,造成歧义;请统一逻辑:修改 configure()(及
configure_from_parameter_values 的校验)以允许 aim_dart_offsets_px.size() == 0 或 ==
dart_count(空表示所有飞镖共用参考点),保留 current_desired_target_px() 的空回退分支;在 configure()
中更新错误消息和条件以接受 size()==0,并在 configure_from_parameter_values 中相应放宽验证以匹配此行为。
src/manager/task/silder_init_task.hpp-31-54 (1)

31-54: ⚠️ Potential issue | 🟡 Minor

确认 BeltMoveAction 参数顺序与新签名一致。

第 43 行注释为「右同步带力矩(输出)」,但 BeltMoveAction 中该参数实际为 const double& right_belt_torque(输入),注释与实际相反,请修正。同时第 54 行 timeout_returns_success=true("测试环境无接驳件")会让超时被当作成功上报:在生产环境中,如果接驳件未到位也会静默走到零点校准步骤,可能造成误标定。建议把该开关改为通过构造函数参数透出,由调用方按部署环境显式配置,而不是在任务内硬编码 true

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/silder_init_task.hpp` around lines 31 - 54, The
BeltMoveAction parameter comment for the right belt torque is incorrect—update
the comment near the BeltMoveAction construction in this task to reflect that
right_belt_torque is an input (const double& right_belt_torque) rather than an
output; also stop hardcoding timeout_returns_success=true in the task: add a
constructor or task-level parameter (e.g., bool timeout_returns_success) and
thread it through to the BeltMoveAction invocation so callers can explicitly
configure timeout behavior for production versus test (update the call site
symbol BeltMoveAction(...) and the task/class constructor that creates this
action to accept and forward the new flag).
tests/launch_preparation_task_test.cpp-1-80 (1)

1-80: ⚠️ Potential issue | 🟡 Minor

vision_assisted_launch_preparation_task_test.cpp 同样的问题:整文件被注释。

LaunchPreparationTask 是本 PR 的核心重构对象之一,却没有任何可执行测试。建议要么移除该文件,要么至少打开一个最小 TEST_F 验证 tick_first() 能拿到 RUNNING 与初始指令状态,否则即便 CMake 注册了 tests/*.cpp,覆盖率仍为 0。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/launch_preparation_task_test.cpp` around lines 1 - 80, The entire test
file is commented out so LaunchPreparationTask has no executable tests; either
delete the file or restore a minimal test: uncomment the includes and the
LaunchPreparationTaskTest/Harness definitions and add at least one TEST_F (e.g.,
using LaunchPreparationTaskTest and Harness with
LaunchPreparationTask::Mode::NORMAL) that calls harness.task.tick_first() and
asserts ActionStatus::RUNNING and the expected initial commands (check
harness.belt_command, harness.belt_target_velocity, harness.lifting_command, and
harness.trigger_lock_enable); ensure tick() and tick_first() references and the
LaunchPreparationTask::Mode symbol names match the refactored API so the test
compiles and runs.
src/manager/action/belt_constant_velocity_move_action.hpp-32-32 (1)

32-32: ⚠️ Potential issue | 🟡 Minor

min_running_ticks 参数注释与含义不符。

注释写作「下行最大距离限制(m,正值,防止过度下行)」,但实际参数是用于位置到达判定前的最短运行帧数(ticks)。建议修正注释,避免后续维护者按错误含义传参。

-        uint64_t min_running_ticks = 50) // 下行最大距离限制(m,正值,防止过度下行)
+        uint64_t min_running_ticks = 50) // 最短运行帧数(避免起步阶段误判到位)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/belt_constant_velocity_move_action.hpp` at line 32,
min_running_ticks 的注释目前写成“下行最大距离限制(m,正值,防止过度下行)”,但实际含义是到达位置判定前的最短运行帧数(ticks);请在
BeltConstantVelocityMoveAction 构造函数或参数声明处(标识符
min_running_ticks)将注释改为表明它是“到达判定前的最短运行帧数/最小运行 ticks(uint64_t,默认
50)”,并补充一句说明其用途(防止过早判定到达),以避免调用者误传距离值。
src/manager/action/force_screw_calibration_action.hpp-109-109 (1)

109-109: ⚠️ Potential issue | 🟡 Minor

显式添加 #include <algorithm> 头文件。

std::clamp 在 C++17 中定义于 <algorithm> 头,但当前文件未显式包含。虽然代码可能通过间接传递编译,但这种做法跨编译器、跨标准库版本不可靠,应显式在文件顶部增加 #include <algorithm>

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/force_screw_calibration_action.hpp` at line 109, The file
uses std::clamp (seen in the line with "output = std::clamp(output,
-max_velocity_, max_velocity_);") but does not explicitly include the header
that defines it; add an explicit `#include` <algorithm> at the top of the header
(force_screw_calibration_action.hpp) so std::clamp is reliably available across
compilers and standard library versions.
test/image_displayer.cpp-46-46 (1)

46-46: ⚠️ Potential issue | 🟡 Minor

日志中残留旧类名 DebugDisplayComponent

类已重命名为 ImageDisplayer,但第 46、61 行的日志仍输出旧名,会让排障时混淆是否加载到了正确组件。

📝 建议的修正
-        RCLCPP_INFO(logger_, "DebugDisplayComponent constructed");
+        RCLCPP_INFO(logger_, "ImageDisplayer constructed");
...
-        RCLCPP_INFO(logger_, "Initializing DebugDisplayComponent");
+        RCLCPP_INFO(logger_, "Initializing ImageDisplayer");

Also applies to: 61-61

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/image_displayer.cpp` at line 46, 日志中仍使用旧类名 "DebugDisplayComponent"(出现在
ImageDisplayer 的构造/初始化日志语句 logger_ 输出处),请将这两处日志字符串改为当前类名 "ImageDisplayer":定位到
ImageDisplayer 构造函数/初始化代码中使用 RCLCPP_INFO(logger_, ...) 的日志调用(原文为
"DebugDisplayComponent constructed" 和另一个同类条目)并替换为描述性字符串包含 "ImageDisplayer"(例如
"ImageDisplayer constructed" 或相似一致的文本)。
test/image_displayer.cpp-268-280 (1)

268-280: ⚠️ Potential issue | 🟡 Minor

std::localtime 非线程安全;建议改用 localtime_r

save_current_frame 由 OpenCV 显示线程调用,而 std::localtime 返回的是共享静态缓冲区,与其他线程(或未来扩展)并发调用会发生数据竞争。可参考 src/vision/video_recorder.hpp:206 已经使用的 localtime_r 模式。

🔒 建议的修正
-            auto time_t = std::chrono::system_clock::to_time_t(now);
-            char time_str[100];
-            std::strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", std::localtime(&time_t));
+            auto time_t = std::chrono::system_clock::to_time_t(now);
+            std::tm tm_buf;
+            localtime_r(&time_t, &tm_buf);
+            char time_str[100];
+            std::strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", &tm_buf);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/image_displayer.cpp` around lines 268 - 280, In save_current_frame(),
replace the non-thread-safe std::localtime usage with the reentrant localtime_r:
create a std::tm (or struct tm) instance, call localtime_r(&time_t, &tm), then
use std::strftime with that tm to populate time_str before building filename;
update references in save_current_frame (processed_image_, save_directory_,
logger_) accordingly so the timestamp formatting is thread-safe (matching the
localtime_r pattern used in video_recorder.hpp).
src/vision/video_recorder.hpp-30-46 (1)

30-46: ⚠️ Potential issue | 🟡 Minor

fps / rotation_minutes / codec 的输入未做合法性校验

  • rotation_minutes_ 为 0 或负数时,rotate_video_if_neededelapsed.count() >= rotation_minutes_ 会对每一帧都成立,导致每一帧都新开一个 cv::VideoWriter 文件,瞬间产生大量很小的视频文件并显著抖动 IO。
  • fps 为 0 或负数时会被传给 cv::VideoWriter::open(filename, fourcc, fps, size),多数后端会直接打开失败或写出不可播放的文件。
  • 大小写敏感的字符串比较意味着 "h264""mjpeg" 等会走到默认分支并落回 H264,但只有 RCLCPP_ERROR 提示,建议宽松匹配或显式 reject。

建议在 Init 入口加一层校验并 clamp/reject 非法值。

🛡️ 建议的校验示例
     bool Init(const std::string& save_directory, const std::string& codec, int fps, int rotation_minutes) {
+        if (fps <= 0) {
+            RCLCPP_ERROR(logger_, "Invalid fps=%d, must be > 0", fps);
+            return false;
+        }
+        if (rotation_minutes <= 0) {
+            RCLCPP_ERROR(logger_, "Invalid rotation_minutes=%d, must be > 0", rotation_minutes);
+            return false;
+        }
         save_directory_ = save_directory;
         fps_ = fps;
         rotation_minutes_ = rotation_minutes;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/video_recorder.hpp` around lines 30 - 46, The Init method
currently doesn't validate inputs causing rotate_video_if_needed to reopen files
every frame when rotation_minutes_ <= 0 and cv::VideoWriter to fail when fps_ <=
0, and codec matching is case-sensitive; update the Init(const std::string&
save_directory, const std::string& codec, int fps, int rotation_minutes)
implementation to validate and clamp/reject parameters: ensure fps > 0 (reject
or clamp to a sane default like 30 and log via RCLCPP_ERROR/RCLCPP_WARN), ensure
rotation_minutes_ > 0 (reject or clamp to a minimum like 1 to prevent per-frame
rotation), and perform case-insensitive codec matching for codec strings before
setting fourcc_code_ and file_extension_ (or explicitly reject unsupported
values), logging clear errors; adjust any callers or docs accordingly so
rotate_video_if_needed, fourcc_code_, file_extension_, fps_, and
rotation_minutes_ get only validated values.
src/manager/auto_aim_feedback.hpp-9-66 (1)

9-66: ⚠️ Potential issue | 🟡 Minor

AutoAimFeedback 是未使用的死代码,应移除

仓库内搜索确认:

  • 没有任何文件引用或 include auto_aim_feedback.hpp
  • AutoAimFeedback 类未在任何地方被实例化或使用
  • 相关功能已由 AutoAimAction 直接处理(如 src/manager/task/vision_assisted_launch_preparation_task.hpp 中的用法)

建议删除此类及其所在文件,或如有后续规划需要该中间层接口,应在该计划实现并连接调用点时再引入,避免维护未使用的代码路径。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/auto_aim_feedback.hpp` around lines 9 - 66, The AutoAimFeedback
class is dead code and should be removed: delete the AutoAimFeedback
declaration/implementation (auto_aim_feedback.hpp) and remove any references or
build/include entries that mention AutoAimFeedback or auto_aim_feedback.hpp
(e.g., in headers, source files, and CMake/BUILD lists); if you prefer to keep
the interface for future use, instead add a clear TODO and unit test plan, but
do not leave the unused class orphaned—either remove the file or wire it into
the code paths that use AutoAimAction so it becomes referenced.
🧹 Nitpick comments (20)
src/manager/action/filling_limit_servo_action.hpp (1)

23-29: 硬编码 200 帧切换阈值,建议保留可配置参数(带默认值)。

将原本可配置的 fill_ticks 改为硬编码 200,会把舵机释放→上锁的时序耦合到当前控制频率。两个调用点(launch_preparation_task.hppfire_and_preload_task.hpp)当前都用同一时序虽然没问题,但如果将来需要为某个流程加快/拉长释放时间,就要再改这个头文件。建议保留构造参数并给出默认值 200:

建议的修改
     FillingLimitServoAction(
         rmcs_msgs::DartLimitingServoStatus& limiting_command,
         rmcs_msgs::DartLimitingServoStatus trigger_command,
-        rmcs_msgs::DartLimitingServoStatus lock_command)
+        rmcs_msgs::DartLimitingServoStatus lock_command, uint64_t fill_ticks = 200)
         : IAction("limiting_fill")
         , limiting_command_(limiting_command)
         , trigger_command_(trigger_command)
-        , lock_command_(lock_command) {}
+        , lock_command_(lock_command)
+        , fill_ticks_(fill_ticks) {}
@@
     ActionStatus update() override {
-        if (elapsed_ticks() >= 200) {
+        if (elapsed_ticks() >= fill_ticks_) {
             limiting_command_ = lock_command_;
             return ActionStatus::SUCCESS;
         }
         return ActionStatus::RUNNING;
     }
@@
     rmcs_msgs::DartLimitingServoStatus lock_command_;
+    uint64_t fill_ticks_;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/filling_limit_servo_action.hpp` around lines 23 - 29, The
update() method in filling_limit_servo_action.hpp uses a hard-coded threshold
(200) for elapsed_ticks(); change the class to accept a configurable fill_ticks
parameter with a default value of 200 (exposed in the constructor) and replace
the literal 200 in update() with that member (e.g., fill_ticks_); ensure
existing callers (launch_preparation_task.hpp and fire_and_preload_task.hpp)
either keep using the default or are updated to pass a custom fill_ticks when
needed, and keep behavior of assigning limiting_command_ = lock_command_ and
returning ActionStatus::SUCCESS unchanged.
src/manager/action/belt_zero_calibration.hpp (1)

1-12: 建议显式包含 <cstdint>

hold_ticks_tick_count_ 使用 uint64_t,但本头文件没有显式 #include <cstdint>,目前可能依赖 action.hpp 间接引入,未来重构 action.hpp 时可能编译失败。

建议的修改
 `#include` "action.hpp"

+#include <cstdint>
+
 namespace rmcs_dart_guidance::manager {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/belt_zero_calibration.hpp` around lines 1 - 12, This
header uses uint64_t for members like hold_ticks_ (and likely tick_count_) in
class ZeroCalibrationAction but doesn't include <cstdint>; add an explicit
`#include` <cstdint> at the top of src/manager/action/belt_zero_calibration.hpp so
the use of uint64_t (in the ZeroCalibrationAction constructor and any
tick_count_ declarations) does not rely on transitive includes from action.hpp.
src/manager/action/vision_yaw_calibration.hpp (2)

22-25: 5000 帧硬编码,建议参数化为构造入参。

elapsed_ticks() > 5000 是一个隐式的“校准持续时长”,与控制频率耦合。建议像 ZeroCalibrationAction(hold_ticks) 一样把它做成构造参数(默认值 5000),方便不同 tick 频率下复用,也便于单元测试。

建议的修改
     VisionYawCalibration(
         Eigen::Vector2d& yaw_pitch_angle, const Eigen::Vector2d& yaw_pitch_distance,
-        double yaw_angle_mapping_rate)
+        double yaw_angle_mapping_rate, uint64_t calibration_ticks = 5000)
         : IAction("vision_yaw_calibration")
         , yaw_control_angle_(yaw_pitch_angle.x())
         , yaw_distance_(yaw_pitch_distance.x())
-        , yaw_rate_(yaw_angle_mapping_rate) {}
+        , yaw_rate_(yaw_angle_mapping_rate)
+        , calibration_ticks_(calibration_ticks) {}
@@
     ActionStatus update() override {
-        if (elapsed_ticks() > 5000) {
+        if (elapsed_ticks() > calibration_ticks_) {
             return ActionStatus::SUCCESS;
         }
@@
     double yaw_distance_;
     double yaw_rate_;
+    uint64_t calibration_ticks_;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/vision_yaw_calibration.hpp` around lines 22 - 25, The
hardcoded 5000 tick duration in update() should be parameterized: add a
constructor parameter (e.g., hold_ticks with default 5000) and store it as a
member (e.g., hold_ticks_) in this calibration action class, then change the
condition in ActionStatus update() from elapsed_ticks() > 5000 to
elapsed_ticks() > hold_ticks_; update any callers/constructors to accept the new
parameter or rely on the default so tests and different tick rates can pass a
custom hold duration (see ZeroCalibrationAction(hold_ticks) for the intended
pattern).

5-6: <opencv2/core/types.hpp> 未被使用,可移除。

本文件中只用到 Eigen::Vector2dopencv2/core/types.hpp 是无关引入,建议删除以减少编译依赖。

建议的修改
 `#include` <eigen3/Eigen/Dense>
-#include <opencv2/core/types.hpp>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/vision_yaw_calibration.hpp` around lines 5 - 6, 在
src/manager/action/vision_yaw_calibration.hpp 中移除未使用的 include
<opencv2/core/types.hpp>,因為此檔案僅使用 Eigen::Vector2d(由 `#include`
<eigen3/Eigen/Dense> 提供);確保刪除該 include 後重新編譯以驗證沒有其他依賴於 OpenCV types
的引用(檢查本檔案內與相關函式/類別引用,例如任何使用 Eigen::Vector2d 的函式或類別定義)。
test/force_sensor_recorder.cpp (1)

103-127: “异步写入”实际上仍可能阻塞控制循环:先 join() 再启动新线程。

async_write_to_file 在启动新线程前会先对上一个线程 join()。若上一次写入因为磁盘/IO 卡顿尚未结束,控制线程的 update() 就会同步等待——这违背了注释里 “异步写入,不阻塞控制循环” 的承诺,并且在两次触发间隔很短的场景下尤其明显。

同时,由于这种 “先 join 再 spawn” 的串行化已经保证了同一时间只有一个 writer 线程,file_mutex_ 实际上没有保护任何东西,可以拿掉,或者改为真正的“后台单线程 + 任务队列”:

建议方向(任选其一)
  • 简单兜底:去掉 mutex,并把对“控制线程不应阻塞”的承诺写得更弱(例如“尽量异步,但极端情况下会等待上一次写入”)。
  • 推荐:用一个常驻 worker 线程 + std::queue + std::condition_variable 把记录入队即返回,析构时设置停止标志再 join。
-    // 异步写入
-    std::mutex file_mutex_;
-    std::thread write_thread_;
+    // 异步写入(单生产者单消费者队列)
+    std::mutex queue_mutex_;
+    std::condition_variable queue_cv_;
+    std::queue<RecordEntry> pending_;
+    std::thread writer_thread_;
+    std::atomic<bool> stop_{false};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/force_sensor_recorder.cpp` around lines 103 - 127, The current
async_write_to_file() calls write_thread_.join() before spawning a new thread,
which can block update(); change this by replacing the spawn-on-each-call
pattern with a persistent background worker: create a worker thread (e.g.,
worker_thread_) that runs a loop consuming a thread-safe std::queue of write
tasks guarded by a std::mutex and std::condition_variable, modify
async_write_to_file() to push {shot_number, round_number, ch1_value, ch2_value}
into the queue and notify the condition variable (do not join there), remove the
unnecessary file_mutex_ from per-write code, have the worker open/append to
output_file_path_, and in the class destructor set a stop flag, notify the
worker and join worker_thread_ to cleanly shut down; alternatively, if you
prefer the minimal change, remove file_mutex_ and the join in
async_write_to_file() and update the function comment to state it may block in
rare cases.
src/manager/dart_launch_sequence.hpp (1)

71-78: advance_after_fire 语义建议在文档中明确。

返回 false 时不递增 index——这意味着调用方在最后一发后再次调用仍会停留在最后一个 index。这通常是期望行为,但建议在头部加注释说明「sequence exhausted; caller should call reset() before reusing」,避免误用。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/dart_launch_sequence.hpp` around lines 71 - 78, 在
advance_after_fire() 上方添加一条注释,明确说明其语义:当 current_dart_index_ + 1 >=
config_.dart_count 时函数返回 false 且不会递增
current_dart_index_,表示发射序列已耗尽;调用方若要再次使用序列应先调用 reset()(或其他复位方法),并说明返回 true 时会将
current_dart_index_ 递增一位。标注涉及的标识符:advance_after_fire, current_dart_index_,
config_.dart_count, reset().
tests/manual_force_control_test.cpp (1)

41-61: stall counter 重置测试逻辑依赖具体 tick 数,建议加注释。

第 58-60 行连续 3 次 tick() 才返回 SUCCESS,这一长度与 DartManualForceControlAction 的 stall 确认窗口耦合。一旦后续调整 stall_confirm_ticks 默认值,本测试会成为盲改点。建议在测试上方注释中标注当前依赖的 stall 确认窗口大小,或改为通过构造参数显式传入,避免「神奇数字」。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/manual_force_control_test.cpp` around lines 41 - 61, The test
DartManualForceControlActionTest::ResetsStallCounterWhenCommandDropsToZero
relies on a magic number of consecutive tick() calls (3) to reach SUCCESS which
is coupled to DartManualForceControlAction's internal stall_confirm_ticks;
either add a short comment above the test documenting the expected
stall_confirm_ticks value being relied on, or better, modify the test to pass an
explicit stall_confirm_ticks (or equivalent constructor parameter) into
DartManualForceControlAction so the confirmation window is deterministic and not
tied to defaults—locate the DartManualForceControlAction constructor invocation
in this test and change it to supply the explicit stall_confirm_ticks value (or
add a clarifying comment if you prefer the simpler opt).
src/manager/action/belt_constant_velocity_move_action.hpp (1)

109-109: on_exit 未重置 belt_target_velocity_ / belt_torque_limit_ / belt_command_

当前仅清零 belt_torque_offset_。虽然现有调用链下一步是 BeltHoldTorqueAction 会重新设置这些字段,但一旦后续插入其他动作或异常退出,残留的速度/力矩限幅可能造成意外的传送带运动。建议同时把这三个字段恢复到安全默认(例如 belt_command_=WAIT; belt_target_velocity_=0;)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/belt_constant_velocity_move_action.hpp` at line 109,
on_exit currently only clears belt_torque_offset_ but must also reset
belt_target_velocity_, belt_torque_limit_ and belt_command_ to safe defaults to
avoid leftover motion; update the on_exit() override to set belt_command_ =
WAIT, belt_target_velocity_ = 0.0, belt_torque_limit_ = 0.0 (and keep
belt_torque_offset_ = 0.0) so that if future actions or failures occur these
fields cannot cause unexpected belt movement (references: on_exit,
belt_target_velocity_, belt_torque_limit_, belt_command_, belt_torque_offset_,
BeltHoldTorqueAction).
tests/vision_assisted_launch_preparation_task_test.cpp (1)

1-131: 整文件均为注释,建议明确处理。

该测试文件 100% 是注释代码,CMake 中虽然 tests/*.cpp 会被收集,但本文件实际不贡献任何可执行用例。两种处理方式更佳:

  1. 暂不合入:先从本 PR 中移除该文件,等 VisionAssistedLaunchPreparationTask 的接口稳定后再补充;
  2. 保留占位:保留文件但用 GTEST_SKIP() << "TODO: 启用后再实现" 写一个最小骨架,附 issue 链接,避免被当作已覆盖的测试。

当前形式下,PR 描述中宣称的「扩展了测试覆盖」对该任务并未生效。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/vision_assisted_launch_preparation_task_test.cpp` around lines 1 - 131,
The whole test file is commented out and provides no executable tests; either
remove it from the PR or replace it with a minimal skipped test so CI shows an
intentional placeholder. To fix, either delete this file from the change set, or
re-enable a tiny skeleton using the existing
VisionAssistedLaunchPreparationTaskTest/ Harness symbols: add a single
TEST_F(VisionAssistedLaunchPreparationTaskTest, Placeholder) that calls
GTEST_SKIP() << "TODO: enable after VisionAssistedLaunchPreparationTask API
stabilizes; see <issue-link>"; keep any needed
includes/SetUpTestSuite/TearDownTestSuite so the test compiles and reference the
issue URL in the skip message.
src/manager/task/silder_init_task.hpp (1)

1-12: 缺少 Eigen 头文件包含。

构造函数签名引用了 Eigen::Vector2d*(第 27-28 行),但本文件并未直接包含 Eigen 头文件,目前只是通过其他 include 间接传递。建议显式引入,避免后续重排 include 时产生编译失败。

建议修改
 `#include` "manager/action/belt_move_action.hpp"
 `#include` "manager/action/belt_zero_calibration.hpp"
 `#include` "manager/action/delay_action.hpp"
 `#include` "manager/action/vision_yaw_calibration.hpp"
 `#include` "manager/task/task.hpp"
 `#include` <memory>
 
+#include <eigen3/Eigen/Dense>
 `#include` <rmcs_msgs/dart_limiting_servo_status.hpp>
-
 `#include` <rmcs_msgs/dart_slider_status.hpp>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/silder_init_task.hpp` around lines 1 - 12, The header is
missing an explicit Eigen include used by the constructor signature that
references Eigen::Vector2d* (the SliderInitTask constructor declaration around
lines referencing Eigen::Vector2d*). Add the appropriate Eigen header (e.g.,
`#include` <Eigen/Dense> or `#include` <Eigen/Core>) near the other includes in
silder_init_task.hpp so the Eigen::Vector2d type is defined directly in this
header and will not rely on transitive includes.
src/manager/action/force_screw_calibration_action.hpp (1)

96-106: 积分项缺少抗饱和处理(建议)。

integral_ += error; 没有任何 anti-windup,长时间未达稳态时 integral 会无限增长,配合 kp/ki/kd 会让输出长时间打到 ±max_velocity_ 极限并在到达后产生大幅过冲。建议至少:

  • 在 clamp 输出后做 back-calculation:integral_ -= (raw_output - clamped_output) / ki_;(避免 ki=0 除零);
  • 或对 integral_ 也做硬限幅(如 ±max_velocity_/ki_)。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/force_screw_calibration_action.hpp` around lines 96 - 106,
The integral term integral_ is not protected against windup in
ForceScrewCalibrationAction (integral_ updated with integral_ += error and used
with kp_/ki_/kd_), so clamp-induced saturation can cause runaway integral; fix
by implementing anti-windup: after computing raw output = kp_*error +
ki_*integral_ + kd_*derivative (and applying any rate feedforward when
use_filtered_input_ && use_rate_feedforward_ && force_rate_ != nullptr), clamp
the final output to ±max_velocity_ and then perform back-calculation to roll
back the integral: integral_ -= (raw_output - clamped_output) / ki_ while
guarding against ki_ == 0, or alternatively enforce a hard clamp on integral_
itself (e.g., limit integral_ to ±(max_velocity_/max(ki_, epsilon))) so
integral_ cannot grow unbounded; apply this near the block handling output and
clamping.
src/vision/video_recorder.hpp (2)

30-63: Init 不可重入:重复调用会泄漏前一个 worker 线程

若上层逻辑(例如重新加载参数)二次调用 Init,当前实现会在 is_running_ 已为 true 时再次启动一个 worker_thread_,覆盖原 std::thread 句柄并丢失对前一个线程的 join 入口。建议在入口先 stop(),或显式拒绝重复 Init。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/video_recorder.hpp` around lines 30 - 63, Init currently starts a
new worker_thread_ even if is_running_ is true, leaking the previous thread;
update Init to check is_running_ at the top and either call stop() to cleanly
join/stop the existing worker_thread_ before proceeding or return false to
reject re-entry, ensuring is_running_ and worker_thread_ are correctly
synchronized (use the existing stop() method to join the old thread and reset
state) before launching a new std::thread using &VideoRecorder::worker_loop.

65-77: frame.clone() 在临界区内进行,可在锁外完成以减小持锁时间

push_frame 当前在持有 queue_mutex_ 的情况下做 cv::Mat::clone(),对于 720p/1080p 的彩色图大约要拷贝几 MB,会让生产者(视觉主循环)和消费者(写盘线程)在同一锁上无谓争用。可以先在锁外 clone,再入队。

♻️ 建议的微调
     void push_frame(const cv::Mat& frame, const std::string& type) {
         if (!is_running_) return;
 
-        std::lock_guard<std::mutex> lock(queue_mutex_);
-        if (job_queue_.size() < max_queue_size_) {
-            job_queue_.push({frame.clone(), type, std::chrono::system_clock::now()});
-            queue_cv_.notify_one();
-        } else {
+        RecordJob job{frame.clone(), type, std::chrono::system_clock::now()};
+        std::lock_guard<std::mutex> lock(queue_mutex_);
+        if (job_queue_.size() < max_queue_size_) {
+            job_queue_.push(std::move(job));
+            queue_cv_.notify_one();
+        } else {
             if (dropped_frames_++ % 30 == 0) {
                 RCLCPP_WARN(logger_, "Video frame queue is full, dropping frame (%s)", type.c_str());
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/video_recorder.hpp` around lines 65 - 77, push_frame currently
calls frame.clone() while holding queue_mutex_, causing long lock durations;
move the heavy clone call outside the critical section: create a local cv::Mat
cloned = frame.clone() (and capture the timestamp) before acquiring
queue_mutex_, then lock queue_mutex_, check job_queue_.size() < max_queue_size_,
push the cloned frame with its type and timestamp into job_queue_, notify
queue_cv_, and otherwise increment dropped_frames_ and log as before; keep
symbols: push_frame, queue_mutex_, job_queue_, max_queue_size_, dropped_frames_,
queue_cv_ and preserve existing behavior when queue is full.
src/vision/dart_vision_core.cpp (1)

337-337: image_center_ 硬编码为 {701, 565},建议参数化

图像中心目前是写死的常量,无法适配不同分辨率/不同标定的相机;与上方通过 ROS 参数化的相机配置(曝光、增益、HSV 阈值等)风格不一致。建议改为从 ROS 参数读入,或在收到首帧后由实际 cols/rows 计算(与 192-193 行的 cx/cy 复用同一来源)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/dart_vision_core.cpp` at line 337, image_center_ is hard-coded as
cv::Point2i{701,565}; change it to be configurable by reading a ROS parameter or
computed from the first received frame (using image.cols/rows) and reuse the
same cx/cy source used at lines where cx/cy are derived; specifically replace
the literal cv::Point2i image_center_ initialization so it is assigned either
from a ROS param (e.g., "image_center_x"/"image_center_y") or set to
cv::Point2i(static_cast<int>(cx), static_cast<int>(cy)) after computing cx/cy
from the camera info/first frame, ensuring image_center_ and the existing cx/cy
logic stay consistent.
CMakeLists.txt (1)

53-62: Debug/test 组件被无条件编入生产共享库

DEBUG_COMPONENT_SOURCEtest/*.cpp(包括 image_displayer.cppforce_sensor_recorder.cpp 等带 PLUGINLIB_EXPORT_CLASS 的调试组件)始终编入 ${PROJECT_NAME} 库并随 plugins.xml 一并发布。这意味着即便 release 构建也会带上调试组件,会增大体积、引入运行时依赖(如 HighGUI),并让真实部署可加载到测试节点。建议至少在 if(BUILD_TESTING) 或自定义开关下才合并这部分源。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 53 - 62, DEBUG_COMPONENT_SOURCE (which
glob-matches test/*.cpp/.cc) is being unconditionally added into the production
shared library via add_library(${PROJECT_NAME} SHARED ...
${DEBUG_COMPONENT_SOURCE}), causing test/debug plugins (e.g.,
image_displayer.cpp, force_sensor_recorder.cpp) to be built into release
artifacts; change the CMake flow to only append DEBUG_COMPONENT_SOURCE when a
test/build toggle is enabled (e.g., wrap the glob + the addition in an
if(BUILD_TESTING) or a custom option like if(ENABLE_DEBUG_COMPONENTS)), so
modify where DEBUG_COMPONENT_SOURCE is defined and where
add_library(${PROJECT_NAME} ...) is called to conditionally include
DEBUG_COMPONENT_SOURCE based on BUILD_TESTING or a new option, keeping the rest
of ${PROJECT_SOURCE} always included.
test/image_displayer.cpp (2)

268-280: 保存目录未创建即写入,失败时仅依赖 cv::imwrite 内部错误

save_directory_ 仅作为字符串参数读入,未在初始化时调用 std::filesystem::create_directories,目录不存在或无写权限时 cv::imwrite 会静默失败(仍打印 "Saved frame: ..."),用户难以察觉。建议在 initialize() 里预先创建并校验写权限(参考 video_recorder.hpp:48-58 的处理)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/image_displayer.cpp` around lines 268 - 280, The save_current_frame()
path assumes save_directory_ exists and always logs success even if cv::imwrite
silently fails; update initialization (e.g., initialize()) to call
std::filesystem::create_directories(save_directory_) and verify the directory is
writable, storing a boolean or validated path flag on the class; then in
save_current_frame() (which uses processed_image_, processed_mutex_, logger_)
check that the directory was validated before calling cv::imwrite and inspect
its return value, logging an error via logger_ on failure and only logging
"Saved frame" when imwrite returns true.

304-304: save_on_error_last_target_time_ 已读取/赋值但未被使用

  • save_on_error_(第 34 行读入,第 322 行成员)在整个文件中没有被消费过,建议要么实现“出错时自动保存”的逻辑,要么从参数列表与成员中移除以避免误用。
  • last_target_time_(第 138、304 行)只写不读,可考虑在显示循环中用它判定 target 信息是否过期(与 STALE 标注一致)。

Also applies to: 322-322

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/image_displayer.cpp` at line 304, The member save_on_error_ is read from
params and stored but never used—either implement the "save on error" behavior
(catch errors where images/frames are processed/decoded and, when save_on_error_
is true, write the current frame to disk/emit a dump) or remove the parameter
and the member to avoid dead code; the timestamp last_target_time_ is written
but never consulted—update the code paths that receive/parse target info to set
last_target_time_ and modify the display loop (the frame/render loop, e.g., the
function that processes/display frames) to check
std::chrono::steady_clock::now() - last_target_time_ against the STALE threshold
and treat target data as expired (skip rendering target overlays or mark as
stale) when exceeded.
src/manager/dart_manager.cpp (2)

553-553: target_position_target_position_input_ 重复且未注册。

第 553 行的 target_position_input_ 与第 632 行的 target_position_ 都是 InputInterface<Eigen::Vector2d> 成员,但只有 target_position_input_make_slider_init_task 中被引用,且其对应的 register_input 在第 67 行已被注释;而 target_position_ 既未注册也未被使用。结果是 target_position_input_.ready() 在运行期始终为 false,分支一直走到 default_target_position。请删除冗余成员并明确视觉接入策略:要么恢复 register_input 的注册,要么直接保留 default_target_position 并删除整段条件判断。

Also applies to: 632-632

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/dart_manager.cpp` at line 553, There are two redundant
InputInterface members (target_position_input_ and target_position_) causing
ready() to never be true; either delete the unused target_position_ and
re-enable the input registration for target_position_input_ (restore the
register_input call referenced in register_input) so make_slider_init_task can
use target_position_input_.ready(), or simplify by removing the conditional
logic in make_slider_init_task that checks target_position_input_.ready() and
always use default_target_position, then delete target_position_input_ entirely;
update/remove references to target_position_ and target_position_input_
accordingly.

397-401: prepare_outputs_for_task 是空实现的死代码。

该函数体仅由一个判断 + 空 {} 组成,没有任何副作用,被 dispatch_next_task 调用却不会做任何事情。建议删除该方法及调用点,或补上原计划的输出预清零逻辑(例如根据任务类型重置 belt_*_/lifting_command_/force_control_velocity_),避免误导后来阅读者。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/dart_manager.cpp` around lines 397 - 401, The function
prepare_outputs_for_task(const Task& task) is a no-op (empty body) and should be
fixed: either remove prepare_outputs_for_task and its callers (e.g.,
dispatch_next_task) to eliminate dead code, or implement the intended output
reset logic inside prepare_outputs_for_task by switching on task.name() for the
listed task types ("launch_preparation", "fire", "cancel_launch", "slider_init",
"manual_angle", "manual_force") and clearing/resetting the relevant output state
variables (e.g., belt_* variables, lifting_command_ fields,
force_control_velocity_) to their safe default values before dispatching the
task so downstream code receives deterministic outputs.
src/manager/action/auto_aim_action.hpp (1)

184-201: std::abs(x) <= 0.0 等价于 x == 0.0,写法有歧义。

std::abs(error_px) <= 0.0std::abs(gain) <= 0.0 在数学上仅当数值严格为 0 才成立,使用 abs() <= 0.0 形式既冗余又会让阅读者误以为存在容差。建议改为显式的 == 0.0 比较,或引入一个明确的小阈值(如 1e-9)让"忽略极小误差"的意图变清晰。

♻️ 建议改写
-    double compute_axis_velocity(double error_px, double gain) const {
-        if (std::abs(error_px) <= 0.0 || std::abs(gain) <= 0.0) {
-            return 0.0;
-        }
+    double compute_axis_velocity(double error_px, double gain) const {
+        constexpr double kEpsilon = 1e-9;
+        if (std::abs(error_px) <= kEpsilon || std::abs(gain) <= kEpsilon) {
+            return 0.0;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/auto_aim_action.hpp` around lines 184 - 201, In
compute_axis_velocity, the checks using std::abs(... ) <= 0.0 are misleading
(equivalent to == 0.0) — change them to explicit zero comparisons or use a small
epsilon to express "near zero"; specifically replace the initial guard
(std::abs(error_px) <= 0.0 || std::abs(gain) <= 0.0) with either (error_px ==
0.0 || gain == 0.0) or a tolerance check like (std::abs(error_px) <= kEpsilon ||
std::abs(gain) <= kEpsilon), and likewise replace the bounded_magnitude check
(bounded_magnitude <= 0.0) with (bounded_magnitude == 0.0) or (bounded_magnitude
<= kEpsilon); introduce a named constant (e.g. constexpr double kEpsilon = 1e-9)
if using a tolerance so intent is clear and use these changes inside
compute_axis_velocity to preserve existing behavior while improving readability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8f3a6d6-3e11-4460-8c09-020a38ba639f

📥 Commits

Reviewing files that changed from the base of the PR and between 5dcb425 and 422a431.

📒 Files selected for processing (33)
  • CMakeLists.txt
  • package.xml
  • plugins.xml
  • src/manager/action/action_sequence.hpp
  • src/manager/action/auto_aim_action.hpp
  • src/manager/action/belt_constant_velocity_move_action.hpp
  • src/manager/action/belt_hold_torque_action.hpp
  • src/manager/action/belt_move_action.hpp
  • src/manager/action/belt_zero_calibration.hpp
  • src/manager/action/filling_lift_action.hpp
  • src/manager/action/filling_limit_servo_action.hpp
  • src/manager/action/force_screw_calibration_action.hpp
  • src/manager/action/manual_angle_control.hpp
  • src/manager/action/manual_force_control.hpp
  • src/manager/action/vision_yaw_calibration.hpp
  • src/manager/auto_aim_feedback.hpp
  • src/manager/command.cpp
  • src/manager/dart_launch_sequence.hpp
  • src/manager/dart_manager.cpp
  • src/manager/task/cancel_launch_task.hpp
  • src/manager/task/fire_and_preload_task.hpp
  • src/manager/task/launch_preparation_task.hpp
  • src/manager/task/silder_init_task.hpp
  • src/manager/task/vision_assisted_launch_preparation_task.hpp
  • src/vision/dart_vision_core.cpp
  • src/vision/video_recorder.hpp
  • test/force_sensor_recorder.cpp
  • test/image_displayer.cpp
  • tests/auto_aim_action_test.cpp
  • tests/dart_launch_sequence_test.cpp
  • tests/launch_preparation_task_test.cpp
  • tests/manual_force_control_test.cpp
  • tests/vision_assisted_launch_preparation_task_test.cpp
💤 Files with no reviewable changes (1)
  • src/manager/command.cpp

Comment on lines +49 to +67
void on_enter() override {
stall_counter_ = 0;
initial_angle_ = left_belt_angle_;
double target_angle_offset =
target_distance_ / 0.0195; // 将目标距离换算为角度偏移(rad),滑轮半径为0.0195m
target_position_ = initial_angle_ + target_angle_offset;

double distance_to_target = target_position_ - initial_angle_;
if (distance_to_target > 0) {
belt_command_ = rmcs_msgs::DartSliderStatus::DOWN;
belt_target_velocity_ = std::abs(velocity_);
} else {
belt_command_ = rmcs_msgs::DartSliderStatus::UP;
belt_target_velocity_ = std::abs(velocity_);
}

belt_torque_offset_ = torque_offset_value_;
belt_torque_limit_ = torque_limit_;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

initial_angle_ 与终止判断使用的角度口径不一致。

on_enterinitial_angle_ = left_belt_angle_;(仅左电机),而 update() 中却用 avg_angle = (left + right) / 2.0 来评估位置误差。一旦左右电机存在初始相位差或运行中产生差异(双电机通常会),target_position_avg_angle 就处于不同口径,会引入恒定的位置偏置,可能导致提前/滞后到位甚至误判 overshoot。

建议修改
-        initial_angle_ = left_belt_angle_;
+        initial_angle_ = (left_belt_angle_ + right_belt_angle_) / 2.0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void on_enter() override {
stall_counter_ = 0;
initial_angle_ = left_belt_angle_;
double target_angle_offset =
target_distance_ / 0.0195; // 将目标距离换算为角度偏移(rad),滑轮半径为0.0195m
target_position_ = initial_angle_ + target_angle_offset;
double distance_to_target = target_position_ - initial_angle_;
if (distance_to_target > 0) {
belt_command_ = rmcs_msgs::DartSliderStatus::DOWN;
belt_target_velocity_ = std::abs(velocity_);
} else {
belt_command_ = rmcs_msgs::DartSliderStatus::UP;
belt_target_velocity_ = std::abs(velocity_);
}
belt_torque_offset_ = torque_offset_value_;
belt_torque_limit_ = torque_limit_;
}
void on_enter() override {
stall_counter_ = 0;
initial_angle_ = (left_belt_angle_ + right_belt_angle_) / 2.0;
double target_angle_offset =
target_distance_ / 0.0195; // 将目标距离换算为角度偏移(rad),滑轮半径为0.0195m
target_position_ = initial_angle_ + target_angle_offset;
double distance_to_target = target_position_ - initial_angle_;
if (distance_to_target > 0) {
belt_command_ = rmcs_msgs::DartSliderStatus::DOWN;
belt_target_velocity_ = std::abs(velocity_);
} else {
belt_command_ = rmcs_msgs::DartSliderStatus::UP;
belt_target_velocity_ = std::abs(velocity_);
}
belt_torque_offset_ = torque_offset_value_;
belt_torque_limit_ = torque_limit_;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/belt_constant_velocity_move_action.hpp` around lines 49 -
67, The initial angle used to compute target_position_ is taken from only
left_belt_angle_ in on_enter(), while update() evaluates progress using
avg_angle = (left_belt_angle_ + right_belt_angle_) / 2.0, causing a consistent
bias when left/right differ; change on_enter() to set initial_angle_ using the
same averaging as update() (use both left_belt_angle_ and right_belt_angle_ to
compute initial_angle_), then compute target_position_ from that averaged
initial_angle_ so initial_angle_, target_position_, and the avg_angle used in
update() share the same reference frame (adjust references to initial_angle_,
left_belt_angle_, right_belt_angle_, target_position_, and on_enter()).

Comment on lines +51 to +52
, current_force_ch1_(*(int*)nullptr) // dummy, won't be used
, current_force_ch2_(*(int*)nullptr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

严重未定义行为:将引用绑定到空指针解引用结果。

, current_force_ch1_(*(int*)nullptr) // dummy, won't be used
, current_force_ch2_(*(int*)nullptr)

C++ 标准规定引用必须绑定到合法对象,对空指针解引用属于 UB(即使后续不读取)。GCC/Clang 在开启优化或 UBSan 时可能直接生成 trap 指令或基于「引用永远非空」做激进优化,进而导致随机崩溃或被裁剪。

建议将 current_force_ch1_/ch2_ 改为指针成员,或采用静态哨兵对象绑定:

建议修改:改用指针并在 update 中按 mode 分支
 private:
     double& force_screw_velocity_;
-    const int& current_force_ch1_;
-    const int& current_force_ch2_;
+    const int* current_force_ch1_;
+    const int* current_force_ch2_;

构造函数相应改为:

-        , current_force_ch1_(current_force_ch1)
-        , current_force_ch2_(current_force_ch2)
+        , current_force_ch1_(&current_force_ch1)
+        , current_force_ch2_(&current_force_ch2)
...
-        , current_force_ch1_(*(int*)nullptr) // dummy, won't be used
-        , current_force_ch2_(*(int*)nullptr)
+        , current_force_ch1_(nullptr)
+        , current_force_ch2_(nullptr)

update() 中的取值改为:

-            current_force =
-                static_cast<double>(force_channel_ == 2 ? current_force_ch2_ : current_force_ch1_) *
-                0.00981;
+            const int raw =
+                force_channel_ == 2 ? *current_force_ch2_ : *current_force_ch1_;
+            current_force = static_cast<double>(raw) * 0.00981;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/force_screw_calibration_action.hpp` around lines 51 - 52,
The constructor currently binds references current_force_ch1_ and
current_force_ch2_ to a null-dereference which is undefined behavior; change
these members to either pointers (e.g., int* current_force_ch1_,
current_force_ch2_) and initialize them to nullptr in the constructor and
dereference only after checking in update(), or replace the references with
references bound to a static sentinel int (e.g., a file-static kDummy) to
guarantee a valid object; update the constructor, the class member declarations,
and the update() logic in ForceScrewCalibrationAction (and any uses of
current_force_ch1_/current_force_ch2_) so they safely read via pointer checks or
from the sentinel depending on chosen approach.

Comment on lines +75 to +77
if (elapsed_ticks() >= timeout_ticks_) {
return ActionStatus::SUCCESS;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

超时返回 SUCCESS 容易掩盖标定失败。

elapsed_ticks() >= timeout_ticks_ 直接返回 SUCCESS,会让上层任务以为已经完成稳定校准,但实际可能力值从未进入容差。建议改为 FAILURE,或将「超时是否视为成功」做成构造参数(与 BeltMoveAction::timeout_returns_success 保持一致风格),由调用方按场景决定。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/force_screw_calibration_action.hpp` around lines 75 - 77,
The code currently treats a timeout in ForceScrewCalibrationAction as success
(the elapsed_ticks() >= timeout_ticks_ branch returns ActionStatus::SUCCESS),
which can hide failed calibrations; modify the logic to mirror BeltMoveAction by
adding a constructor parameter (e.g. bool timeout_returns_success_ defaulting to
false) stored as a member and change the timeout branch to return
timeout_returns_success_ ? ActionStatus::SUCCESS : ActionStatus::FAILURE;
alternatively, if you prefer a minimal change, change the hardcoded return to
ActionStatus::FAILURE; update the constructor and member declarations in
ForceScrewCalibrationAction and use the elapsed_ticks()/timeout_ticks_ check to
consult timeout_returns_success_ when deciding the return value.

Comment on lines +12 to +33
VisionYawCalibration(
Eigen::Vector2d& yaw_pitch_angle, const Eigen::Vector2d& yaw_pitch_distance,
double yaw_angle_mapping_rate)
: IAction("vision_yaw_calibration")
, yaw_control_angle_(yaw_pitch_angle.x())
, yaw_distance_(yaw_pitch_distance.x())
, yaw_rate_(yaw_angle_mapping_rate) {}

void on_enter() override {}

ActionStatus update() override {
if (elapsed_ticks() > 5000) {
return ActionStatus::SUCCESS;
}
yaw_control_angle_ = yaw_distance_ * yaw_rate_;
return ActionStatus::RUNNING;
}

private:
double& yaw_control_angle_;
double yaw_distance_;
double yaw_rate_;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 找到 VisionYawCalibration 的所有构造点,确认传入的 Vector2d 来源与生命周期
rg -nP -C5 '\bVisionYawCalibration\s*\('
# 找到 IAction 默认 elapsed_ticks() 的实现,确认每次 on_enter 后是否会重置
rg -nP -C3 '\belapsed_ticks\b'

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 9915


🏁 Script executed:

# Find the full IAction class definition to see how elapsed_ticks_ is managed
fd -t f action.hpp | head -5 | xargs cat -n | head -100

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 3418


🏁 Script executed:

# Search for VisionYawCalibration instantiation/usage
rg -n 'VisionYawCalibration' --type cpp --type hpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 109


🏁 Script executed:

# Find and examine slider_init_task.hpp mentioned in the review
fd -t f 'slider_init_task.hpp' | xargs cat -n

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

# Correct search for VisionYawCalibration instantiation
rg -n 'VisionYawCalibration' -A 5 -B 5

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 1984


🏁 Script executed:

# Find slider_init_task file
fd 'slider_init_task' -type f

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 252


🏁 Script executed:

# Read silder_init_task.hpp to understand the lifetime of yaw_pitch_angle and yaw_pitch_distance
cat -n src/manager/task/silder_init_task.hpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 3756


yaw_distance_ 按值捕获导致后续每帧使用的是构造期的初值,无法反映视觉系统的动态更新。

构造参数 const Eigen::Vector2d& yaw_pitch_distance 被复制为成员 double yaw_distance_;,仅在初始化时拷贝一次值。而 yaw_control_angle_ 采用 double& 引用绑定,每帧都能获取最新值。这种不对称在每帧调用 yaw_distance_ * yaw_rate_ 时会导致使用过时的距离值——即使视觉系统在 5000 帧内更新了 yaw_pitch_distance

建议将 yaw_distance_ 改为 const double& 以保持与 yaw_control_angle_ 一致的引用语义:

建议的修改
 private:
     double& yaw_control_angle_;
-    double yaw_distance_;
+    const double& yaw_distance_;
     double yaw_rate_;

参考 silder_init_task.hpp 第 27–28 行,传入的 yaw_pitch_angleyaw_pitch_distance 为指针参数,指向的对象生命周期贯穿整个任务执行,不存在悬垂引用的风险。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/action/vision_yaw_calibration.hpp` around lines 12 - 33, The
constructor copies yaw_pitch_distance into yaw_distance_ causing stale values
each frame; change the member from double yaw_distance_ to const double&
yaw_distance_ and bind it in VisionYawCalibration's initializer so that update()
reads the live yaw_pitch_distance each tick (mirror how yaw_control_angle_ is a
reference); adjust any initializer lists and class declaration accordingly to
avoid dangling references only if the passed-in yaw_pitch_distance lifetime is
guaranteed (per silder_init_task usage).

Comment on lines +433 to +484
if (left_belt_angle_.ready() && right_belt_angle_.ready()) {
RCLCPP_INFO(
logger_, "[DartManager] Belt angles: left=%.4f, right=%.4f", *left_belt_angle_,
*right_belt_angle_);
} else {
RCLCPP_WARN(logger_, "[DartManager] Belt angle feedback NOT READY!");
}
RCLCPP_INFO(
logger_, "[DartManager] Belt params: down_distance=%.4f m, pulley_radius=%.4f m",
belt_down_distance_, down_velocity);

// 读取力值用于力矩闭环(不在此处记录)
bool ch1_ready = current_force_ch1_.ready();
bool ch2_ready = current_force_ch2_.ready();

int force_ch1_value = ch1_ready ? *current_force_ch1_ : 0;
int force_ch2_value = ch2_ready ? *current_force_ch2_ : 0;

// 根据 force_channel_ 选择主控通道
if (force_channel_ == 2 && ch2_ready) {
last_fire_force_ = static_cast<double>(force_ch2_value);
} else if (force_channel_ == 1 && ch1_ready) {
last_fire_force_ = static_cast<double>(force_ch1_value);
}

// Prepare Kalman filter pointers (if enabled and ready)
const double* kalman_force_ptr = nullptr;
const double* kalman_rate_ptr = nullptr;
if (use_kalman_force_ && kalman_filtered_force_.ready() && kalman_force_rate_.ready()) {
kalman_force_ptr = &(*kalman_filtered_force_);
kalman_rate_ptr = &(*kalman_force_rate_);
RCLCPP_INFO(
logger_, "[DartManager] Using Kalman force: F_filt=%.1fN, dF/dt=%.1fN/s",
*kalman_force_ptr, *kalman_rate_ptr);
} else if (use_kalman_force_) {
RCLCPP_WARN(
logger_,
"[DartManager] use_kalman_force=true but Kalman inputs not ready, falling back "
"to raw sensors");
}

return std::make_shared<LaunchPreparationTask>(
*belt_command_, *belt_target_velocity_, *belt_torque_limit_, *belt_hold_torque_,
*belt_wait_zero_velocity_,
*left_belt_velocity_, *right_belt_velocity_,
*left_belt_torque_, *right_belt_torque_,
*trigger_lock_enable_,
*lifting_command_,
*lifting_left_vel_fb_, *lifting_right_vel_fb_,
lifting_stall_threshold_, lifting_stall_confirm_ticks_,
lifting_stall_min_run_ticks_, lifting_stall_timeout_ticks_);
*belt_wait_zero_velocity_, *belt_torque_offset_, *left_belt_angle_,
*right_belt_angle_, *left_belt_velocity_, *right_belt_velocity_,
*trigger_lock_enable_, belt_down_distance_, down_velocity, require_lifting_down,
*lifting_command_, *lifting_left_vel_fb_, *lifting_right_vel_fb_,
*belt_zero_calibration_, *force_control_velocity_, *current_force_ch1_,
*current_force_ch2_, force_channel_, last_fire_force_, enable_force_calibration_,
force_tolerance_, force_settle_ticks_, force_timeout_ticks_, force_kp_, force_ki_,
force_kd_, is_first_shot, use_kalman_force_, kalman_force_ptr, kalman_rate_ptr,
kalman_rate_feedforward_, kalman_rate_gain_);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify InputInterface dereference behavior when not ready
ast-grep --pattern 'class InputInterface { $$$ }'
rg -nP --type=cpp -C2 '\bInputInterface<.*>::operator\*' 
rg -nP --type=cpp -C3 '\.ready\(\)' src/manager/dart_manager.cpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 2569


🏁 Script executed:

sed -n '430,520p' src/manager/dart_manager.cpp | cat -n

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 5575


🏁 Script executed:

fd -t f -name "*.hpp" -o -name "*.h" | xargs rg -l "class InputInterface" | head -5

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 312


🏁 Script executed:

rg -A 10 "operator\*" --type=cpp | grep -A 10 "InputInterface"

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

find . -name "*.hpp" -o -name "*.h" | xargs rg -l "class InputInterface" | head -3

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

rg -B5 -A15 "class InputInterface" --type=cpp --type=hpp | head -60

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 109


🏁 Script executed:

rg "class InputInterface" --type-list | head -20

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 471


🏁 Script executed:

rg "class InputInterface" -g "*.h" -g "*.hpp" -g "*.cpp"

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

find . -name "InputInterface*" -o -name "*input*interface*"

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

rg "InputInterface" --type=c --type=cpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 2419


🏁 Script executed:

rg -B2 -A8 "operator\s*\*" --type=c --type=cpp | grep -A10 -B2 "InputInterface"

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

git ls-files | head -30

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 1202


🏁 Script executed:

rg "#include.*InputInterface" --type=cpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 63


🏁 Script executed:

cat package.xml

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 1142


🏁 Script executed:

rg -B3 "\.ready\(\)" src/manager/dart_manager.cpp | head -40

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 1643


未就绪输入被无条件解引用,构造调用前缺少返回防护。

代码在第 433–439 行显式检查 left_belt_angle_right_belt_angle_ready() 状态并在 else 分支打印警告,但随后在第 476–481 行的 LaunchPreparationTask 构造调用中无条件解引用 *left_belt_angle_, *right_belt_angle_ 以及 *current_force_ch1_, *current_force_ch2_(这两个虽在第 445–446 行检查过,但 ch1_ready/ch2_ready 标志并未被用来防护构造参数)。同样问题出现在第 489–495 行的 CancelLaunchTask 调用中(*left_belt_angle_, *right_belt_angle_, *lifting_left_vel_fb_, *lifting_right_vel_fb_),后两个接口甚至没有任何 ready() 检查。

相比之下,同文件其他位置采用了安全的条件解引用模式(如第 410 行的三元表达式、第 520–522 行的空指针检查),说明 InputInterface 的非就绪状态需要特殊处理。第 433 行的 ready() 检查和警告日志明确承认了这个风险,但代码继续执行而非提前返回,违反了防御性编程原则。建议在 ready() 检查失败时添加 return nullptr; 并打 ERROR 日志,或将所有 InputInterface 参数改为安全的三元表达式/条件指针形式。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/dart_manager.cpp` around lines 433 - 484, The code
unconditionally dereferences InputInterface values (e.g., *left_belt_angle_,
*right_belt_angle_, *current_force_ch1_, *current_force_ch2_,
*lifting_left_vel_fb_, *lifting_right_vel_fb_) when constructing
LaunchPreparationTask and CancelLaunchTask despite earlier ready() checks, so
either (A) perform strict readiness guarding: if any required input for the
upcoming task is not ready, log an RCLCPP_ERROR with context and return nullptr
immediately from the function, or (B) make the constructor calls safe by passing
guarded/nullable values (use ternary-style safe dereference or pass
nullptr/optional pointers) for each possibly-unready InputInterface (references:
LaunchPreparationTask, CancelLaunchTask, variables left_belt_angle_,
right_belt_angle_, current_force_ch1_, current_force_ch2_, lifting_left_vel_fb_,
lifting_right_vel_fb_), ensuring no raw dereference occurs unless ready() was
true.

Comment on lines +38 to 40
// 立即停止力丝杆电机(直接设置速度为0)
force_screw_control_velocity = 0.0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

构造期写入输出可能引发竞态。

force_screw_control_velocity = 0.0; 在构造函数体中立即执行,而 CancelLaunchTask 的实际入队/调度发生在 dart_manager.cppsubmit_task 之后。如果队列中尚有未完成的任务(例如 LaunchPreparationTask 仍在使用力丝杆),构造该取消任务时就会被立刻清零,可能导致前置任务行为异常。建议把"立即停止力丝杆"封装为一个动作(如轻量的 then(std::make_shared<SetValueAction>(force_screw_control_velocity, 0.0))),放在动作链最前面,使其在调度到本任务时才生效。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/cancel_launch_task.hpp` around lines 38 - 40, The
constructor for CancelLaunchTask currently writes force_screw_control_velocity =
0.0 immediately, which can race with already-queued tasks like
LaunchPreparationTask; instead, remove the direct write from the
CancelLaunchTask constructor and wrap the stop into a scheduled action so it
executes when the cancel task runs (e.g., create a lightweight action such as
std::make_shared<SetValueAction>(force_screw_control_velocity, 0.0) and prepend
it to the CancelLaunchTask action chain via then(...)); update CancelLaunchTask
to build and enqueue that action in its action list rather than mutating
force_screw_control_velocity at construction time, ensuring submit_task in
dart_manager.cpp schedules the stop only when the cancel task is dispatched.

Comment on lines 43 to +48
then(
std::make_shared<BeltMoveAction>(
"belt_move_down", // 动作名称
belt_command, // 同步带目标状态(输出)
belt_target_velocity, // 同步带目标速度(输出)
belt_torque_limit, // 同步带力矩限制(输出)
belt_hold_torque, // 同步带保持力矩(输出)
belt_wait_zero_velocity, // Wait 时使用零速闭环还是保留力矩
left_belt_velocity, // 左同步带反馈(输入)
right_belt_velocity, // 右同步带反馈(输入)
left_belt_torque, // 左同步带力矩(输入)
right_belt_torque, // 右同步带力矩(输入)
rmcs_msgs::DartSliderStatus::DOWN, // 指令状态
10.0, // 设定速度
torque_limit, // 设定力矩限制
hold_torque, // 设定保持力矩
10000, // 超时帧数
1.0, // 堵转速度阈值
0.5, // 堵转力矩阈值
100, // 堵转确认帧数
50, // 最短运行帧数
BeltMoveAction::ExitMode::WAIT_HOLD_TORQUE));

std::make_shared<FillingLimitServoAction>(
limiting_command, // 限位舵机状态(输出)
rmcs_msgs::DartLimitingServoStatus::FREE, // 先释放
rmcs_msgs::DartLimitingServoStatus::LOCK // 再锁回
));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm limiting_command is undeclared in launch_preparation_task.hpp
rg -nP --type=cpp '\blimiting_command\b' src/manager/task/launch_preparation_task.hpp
rg -nP --type=cpp 'LaunchPreparationTask\s*\(' src/manager/dart_manager.cpp -A 12

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Read the launch_preparation_task.hpp file to see full constructor
cat -n src/manager/task/launch_preparation_task.hpp | head -70

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 4272


🏁 Script executed:

#!/bin/bash
# Search for LaunchPreparationTask class definition and constructor more broadly
rg -nP --type=cpp 'class LaunchPreparationTask' -A 50 src/manager/task/launch_preparation_task.hpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 3389


🏁 Script executed:

#!/bin/bash
# Search for LaunchPreparationTask constructor calls in dart_manager.cpp
rg -nP --type=cpp 'LaunchPreparationTask' src/manager/dart_manager.cpp -B 2 -A 15

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 1292


🏁 Script executed:

#!/bin/bash
# Check if limiting_command_ is a member variable in LaunchPreparationTask
rg -nP --type=cpp 'limiting_command' src/manager/task/launch_preparation_task.hpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Check the class definition for member variables
cat -n src/manager/task/launch_preparation_task.hpp | tail -50

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 2892


🏁 Script executed:

#!/bin/bash
# Check dart_manager.cpp for limiting_command_
rg -nP --type=cpp 'limiting_command' src/manager/dart_manager.cpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 534


limiting_command 在构造函数中未声明,无法编译。

第 45 行使用 limiting_command,但该变量未在构造函数参数列表(26–40 行)中声明,也不是类成员变量。需要添加为构造函数参数:

修改位置
-        bool& trigger_lock_enable, double belt_down_distance, double down_velocity,
-        bool require_lifting_down, rmcs_msgs::DartSliderStatus& lifting_command,
+        bool& trigger_lock_enable, rmcs_msgs::DartLimitingServoStatus& limiting_command,
+        double belt_down_distance, double down_velocity,
+        bool require_lifting_down, rmcs_msgs::DartSliderStatus& lifting_command,

并在 src/manager/dart_manager.cpp 第 474–484 行的 LaunchPreparationTask 实例化调用中,在 *trigger_lock_enable_ 后传入 *limiting_command_

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/launch_preparation_task.hpp` around lines 43 - 48, The
constructor for LaunchPreparationTask is using limiting_command in the then(...
FillingLimitServoAction(...)) call but limiting_command is not declared; add a
parameter (e.g., const rmcs_msgs::DartLimitingServoCommand& limiting_command or
a pointer/reference type matching usages) to the LaunchPreparationTask
constructor signature and initializer list so the member is available in the
constructor body, and update the LaunchPreparationTask instantiation in
DartManager (the call that currently passes *trigger_lock_enable_ ) to also pass
*limiting_command_ so the new constructor parameter is provided; ensure the
symbol names LaunchPreparationTask, limiting_command, and
FillingLimitServoAction are consistent.

Comment on lines +190 to +212
if (enable_force_calibration) {
double target_force_value = is_first_shot ? 21800.0 : target_force;
double tolerance_value = is_first_shot ? 0.5 : force_tolerance;

if (use_kalman_force && kalman_filtered_force != nullptr
&& kalman_force_rate != nullptr) {
// Use Kalman-filtered force (already in Newtons)
then(
std::make_shared<ForceScrewCalibrationAction>(
"force_calibration_kalman", force_screw_velocity, *kalman_filtered_force,
*kalman_force_rate, target_force_value, tolerance_value, force_settle_ticks,
force_timeout_ticks, force_kp, force_ki, force_kd, 5.0,
kalman_rate_feedforward, kalman_rate_gain));
} else {
// Use raw force sensor readings (in grams)
then(
std::make_shared<ForceScrewCalibrationAction>(
"force_calibration_raw", force_screw_velocity, current_force_ch1,
current_force_ch2, force_channel, target_force_value, tolerance_value,
force_settle_ticks, force_timeout_ticks, force_kp, force_ki, force_kd,
5.0));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

首发力标定的目标值/容差与单位语义不一致。

第 191–192 行无论后续走 Kalman 分支还是原始传感器分支,都对 is_first_shot 强制覆盖为 target_force_value = 21800.0tolerance_value = 0.5

  • 在 197–202 的 Kalman 分支中,注释明确指出"already in Newtons",因此 21800.0 N 显然不是首发期望值(按字面值,约 ~2 吨力)。
  • 在 205–211 的原始通道分支中,传感器读数是 grams(参见调用点 force_ch1_value/force_ch2_valueint 克数),21800 g 才符合预期,但此时 tolerance_value = 0.5 又看起来像 Newtons 量级(0.5 g 容差几乎不可能稳态)。

无论是哪种解释,这两个量纲在两个分支间无法同时成立。建议为 Kalman 与原始模式分别提供首发覆盖值,或在调用方就把单位归一化后再传入;否则首发标定要么永远超时要么过早完成。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/launch_preparation_task.hpp` around lines 190 - 212, The
is_first_shot override uses a single hardcoded pair (target_force_value =
21800.0, tolerance_value = 0.5) but your branches use different units: the
Kalman branch (use_kalman_force, kalman_filtered_force, kalman_force_rate)
expects Newtons while the raw branch (current_force_ch1, current_force_ch2,
force_channel) expects grams; update the logic so that when is_first_shot is
true you set unit-appropriate defaults (e.g., separate
kalman_target_force_newtons / kalman_tolerance_newtons for the Kalman path and
raw_target_force_grams / raw_tolerance_grams for the raw path) or normalize
units before constructing ForceScrewCalibrationAction; adjust the assignment of
target_force_value and tolerance_value (or create two variables) and use the
correct ones in each then(...) call to ForceScrewCalibrationAction
("force_calibration_kalman" vs "force_calibration_raw").

Comment on lines +22 to +62
VisionAssistedLaunchPreparationTask(
rmcs_msgs::DartSliderStatus& belt_command, double& belt_target_velocity,
double& belt_torque_limit, double& belt_hold_torque, bool& belt_wait_zero_velocity,
double& belt_torque_offset, const double& left_belt_angle, const double& right_belt_angle,
const double& left_belt_velocity, const double& right_belt_velocity,
const double& left_belt_torque, const double& right_belt_torque, bool& trigger_lock_enable,
double belt_down_distance, double belt_pulley_radius, double down_velocity,
double torque_limit, double up_torque_limit, uint64_t down_ramp_ticks,
double down_torque_offset, double down_hold_torque, double down_zero_velocity_threshold,
uint64_t down_zero_confirm_ticks, uint64_t down_ramp_timeout_ticks,
bool require_lifting_down, rmcs_msgs::DartSliderStatus& lifting_command,
const double& lifting_left_vel_fb, const double& lifting_right_vel_fb,
bool& belt_zero_calibration, double belt_up_distance, double up_velocity,
double up_decel_target_velocity, double up_decel_torque_offset,
double up_stall_velocity_threshold, uint64_t up_stall_confirm_ticks,
uint64_t up_stall_min_run_ticks, uint64_t up_decel_timeout_ticks,
Eigen::Vector2d& yaw_pitch_control_velocity, bool& aim_ready, Eigen::Vector2d& aim_error_px,
Eigen::Vector2d& desired_target_px_output, const cv::Point2i& target_position,
const bool& target_tracking, const rclcpp::Logger& logger, AutoAimParams auto_aim_params,
bool is_first_shot = false)
: Task("launch_preparation", "视觉辅助滑块发射准备") {

then(
std::make_shared<AutoAimAction>(
yaw_pitch_control_velocity, aim_ready, aim_error_px, desired_target_px_output,
target_position, target_tracking, logger, std::move(auto_aim_params)));

then(
std::make_shared<LaunchPreparationTask>(
belt_command, belt_target_velocity, belt_torque_limit, belt_hold_torque,
belt_wait_zero_velocity, belt_torque_offset, left_belt_angle, right_belt_angle,
left_belt_velocity, right_belt_velocity, left_belt_torque, right_belt_torque,
trigger_lock_enable, belt_down_distance, belt_pulley_radius, down_velocity,
torque_limit, up_torque_limit, down_ramp_ticks, down_torque_offset,
down_hold_torque, down_zero_velocity_threshold, down_zero_confirm_ticks,
down_ramp_timeout_ticks, require_lifting_down, lifting_command, lifting_left_vel_fb,
lifting_right_vel_fb, belt_zero_calibration, belt_up_distance, up_velocity,
up_decel_target_velocity, up_decel_torque_offset, up_stall_velocity_threshold,
up_stall_confirm_ticks, up_stall_min_run_ticks, up_decel_timeout_ticks,
is_first_shot));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm constructor signature mismatch and check for any usage of VisionAssistedLaunchPreparationTask
rg -nP --type=cpp -C2 'VisionAssistedLaunchPreparationTask'
rg -nP --type=cpp -C0 'class\s+LaunchPreparationTask\b' src/manager/task/launch_preparation_task.hpp
ast-grep --pattern 'LaunchPreparationTask($$$)'

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 3381


🏁 Script executed:

# Read the VisionAssistedLaunchPreparationTask constructor to see what's being passed
cat -n src/manager/task/vision_assisted_launch_preparation_task.hpp | head -70

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 4037


🏁 Script executed:

# Read the LaunchPreparationTask constructor signature
cat -n src/manager/task/launch_preparation_task.hpp | head -50

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 2923


🏁 Script executed:

# Check if LaunchPreparationTask header exists and get its full signature
fd launch_preparation_task.hpp

Repository: Alliance-Algorithm/rmcs_dart_guidance

Length of output: 187


LaunchPreparationTask 构造调用参数签名不匹配,将无法编译。

第 50–61 行对 LaunchPreparationTask 的调用传入了 38 个参数,但其实际构造函数签名(launch_preparation_task.hpp 第 26–40 行)仅期望 35 个参数。

传入但不期望的参数(13 个):

  • left_belt_torque, right_belt_torque, belt_pulley_radius, torque_limit, up_torque_limit, down_ramp_ticks, down_torque_offset, down_hold_torque, down_zero_velocity_threshold, down_zero_confirm_ticks, down_ramp_timeout_ticks, belt_up_distance, up_velocity, up_decel_target_velocity, up_decel_torque_offset, up_stall_velocity_threshold, up_stall_confirm_ticks, up_stall_min_run_ticks, up_decel_timeout_ticks

期望但未传入的参数(12 个):

  • force_screw_velocity, current_force_ch1, current_force_ch2, force_channel, target_force, enable_force_calibration, force_tolerance, force_settle_ticks, force_timeout_ticks, force_kp, force_ki, force_kd
  • 可选参数:use_kalman_force, kalman_filtered_force, kalman_force_rate, kalman_rate_feedforward, kalman_rate_gain

需要修正 VisionAssistedLaunchPreparationTask 的构造参数和转发逻辑,使其与新版 LaunchPreparationTask 接口对齐。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/manager/task/vision_assisted_launch_preparation_task.hpp` around lines 22
- 62, The LaunchPreparationTask call in VisionAssistedLaunchPreparationTask is
mismatched: remove the unexpected belt/torque/pulley parameters
(left_belt_torque, right_belt_torque, belt_pulley_radius, torque_limit,
up_torque_limit, down_ramp_ticks, down_torque_offset, down_hold_torque,
down_zero_velocity_threshold, down_zero_confirm_ticks, down_ramp_timeout_ticks,
belt_up_distance, up_velocity, up_decel_target_velocity, up_decel_torque_offset,
up_stall_velocity_threshold, up_stall_confirm_ticks, up_stall_min_run_ticks,
up_decel_timeout_ticks) and instead add and forward the force-related parameters
that LaunchPreparationTask now expects (force_screw_velocity, current_force_ch1,
current_force_ch2, force_channel, target_force, enable_force_calibration,
force_tolerance, force_settle_ticks, force_timeout_ticks, force_kp, force_ki,
force_kd and the optional use_kalman_force, kalman_filtered_force,
kalman_force_rate, kalman_rate_feedforward, kalman_rate_gain) by updating
VisionAssistedLaunchPreparationTask's parameter list and the
std::make_shared<LaunchPreparationTask>(...) call to match the exact order and
names in launch_preparation_task.hpp (use the same symbols to locate where to
add/remove arguments).

: false;

// Video recording parameters
enable_video_recording_ = has_parameter("enable_video_recording");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

enable_video_recording_ 仅判断参数是否声明,未读取其值

与上方 enable_image_saving_(第 48-50 行)等参数读取方式不一致:此处只调用了 has_parameter("enable_video_recording"),没有进一步通过 get_parameter(...).as_bool() 读取实际值。后果是只要参数被声明(即使被设为 false),录制就会被启用;反之则永远禁用。

🐛 建议的修正
-        // Video recording parameters
-        enable_video_recording_ = has_parameter("enable_video_recording");
+        // Video recording parameters
+        enable_video_recording_ = has_parameter("enable_video_recording")
+                                    ? get_parameter("enable_video_recording").as_bool()
+                                    : false;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
enable_video_recording_ = has_parameter("enable_video_recording");
// Video recording parameters
enable_video_recording_ = has_parameter("enable_video_recording")
? get_parameter("enable_video_recording").as_bool()
: false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vision/dart_vision_core.cpp` at line 64, The assignment to
enable_video_recording_ incorrectly uses has_parameter("enable_video_recording")
instead of reading the parameter value; change it to read the declared parameter
value like enable_image_saving_ does (use
get_parameter("enable_video_recording").as_bool() or equivalent) so that
enable_video_recording_ reflects the actual boolean value passed rather than
mere presence; ensure to check has_parameter first if needed and fall back to a
default consistent with enable_image_saving_.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants