Uh oh!
There was an error while loading. Please reload this page.
dev: Robots adaptation after rmuc 2026 - #101
Conversation
2、interfere clean 3、six_friction: rename and reset parameter 4、putter return
…for temporary debugging by qzhhhi · Pull Request #55 · Allia)
- Sync both omni and omni-b to BottomBoard/TopBoard/ImuBoard architecture - Update board serial filters and DeformableSuspension config in YAMLs - Remove Vt13/RemoteControl; use Dr16 direct /remote/* outputs - Clean up unrelated changes from feat/deformable-infantry
* feat: Add spin stuck detection for chassis and develop nav gimbal control * feat: Getter for node mixin * wip: Chassis stuck detection * feat: Support nav fusion control with joystick * feat: Update remote control timeout logic * chore: Update sentry config * build: Add detect path to update-image workflow * chore: Update auto aim v2 * wip: Fill basic context and utils for climber * refactor: Climber for sentry * feat(climber): add kRetracted state for stick auto-retract with stall detection - StickGroup: add kRetracted state with speed_rise + rise_torque_limit + hold_torque - kRetracted performs PID ascent, switches to constant hold_torque (0.25Nm) on stall - release_chassis() now sets track→kFree + stick→kRetracted for safe idle state - Add hold_torque param (default 0.25) to YAML config * wip: Cleanup code * feat: Adapt climb request from navigation * chore: Update auto aim v2 * wip: Improve climber --------- Co-authored-by: zlq040222 <1542498005@qq.com>
* chore: Clean up code * feat: Add mavlink to dockerfile * feat: Adapt autoaim on flight * feat: Add flight odin support by mavlink component * fix: Add px4_serial output (forgot in flight.cpp) * chore: Update autoaim offset * chore: Add submodule odin and hikcamera * fix: Add autoaim param degraded_angle_speed,refactor:odin management in px4_vision_bridge * chore: Remove hikcamera and odin_ros_driver submodules * chore: Apply format * chore: Remove unused cmake config --------- Co-authored-by: creeper5820 <cqq1960180796@163.com>
* wip: Develop sentry climb and decision * feat: Enable navigation and add referee enemy status outputs - Enable SentryDecision and Navigation plugins in sentry config - Parameterize steering wheel controller PID gains - Add enemy outpost/base hp and damage difference outputs * wip: Clean up pr impl and update auto aim v2 --------- Co-authored-by: creeper5820 <cqq1960180796@163.com>
* wip(robots): Enable sentry navigation, add navigation supercap boost and refine climber status * wip(robots): Add leave phase after sentry climber landing
Walkthrough本次变更扩展 RMCS 的开发工具、硬件接入、底盘与云台控制、射击控制、裁判交互、系统辨识和运行配置,并更新消息接口、插件注册、SDK 依赖及公共工具。 ChangesRMCS 工具与运行环境
控制与硬件集成
配置、消息与裁判界面
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Note
Due to the large number of review comments, Critical 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 (5)
rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp (1)
285-297: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win下降沿相机同步的 GPIO 回调缺少电平确认。 三个硬件文件都用
falling_edge = true, rising_edge = false的 GPIO 配置来捕获相机曝光时间戳,但只有sentry.cpp的GimbalBoard::gpio_digital_read_result_callback(第 230-231 行)在使用时间戳之前检查了if (data.high) return;。另外两处缺少该检查。如果板卡在配置生效前或因其他原因上报一次高电平采样,这两处会发出错误的曝光时间戳,导致自瞄的时间对齐出错。
rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp#L285-L297:在第 287 行的 GPIO 描述符判断之后、时间戳判空之前,增加if (data.high) return;。rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp#L501-L515:在第 506 行kUart1Tx分支内,于第 507 行的时间戳判空之前增加if (data.high) return;。注意不要影响第 504-505 行kUart1Rx分支,那里data.high是有效的传感器电平。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp` around lines 285 - 297, 在 omni_infantry.cpp 的 gpio_digital_read_result_callback(285-297)中,于 kUart0Tx 判断后、时间戳判空前增加 data.high 的提前返回;在 steering-hero-little-six-friction.cpp 的 gpio_digital_read_result_callback(501-515)中,仅对 kUart1Tx 分支执行同样检查,勿修改 kUart1Rx 分支。rmcs_ws/src/rmcs_core/src/controller/chassis/hero_chassis_controller.cpp (1)
181-194: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAUTO 分支覆盖计算结果,导致共享 PID 积分饱和。
第 182 行至第 190 行计算
angular_velocity,第 192 行把它无条件置为 0。计算结果被丢弃,但update_following_angular_velocity有副作用:它调用following_velocity_controller_.update(err)。该 PID 对象在STEP_DOWN、ALIGNMENT、ALIGNMENT_POWERED、CLIMB、LAUNCH_RAMP分支以及攀爬分支(第 173 行)中共用。因此在 AUTO 模式下 PID 会持续积分而输出被丢弃。切换到上述任一模式时,第一帧输出会包含累积的积分项,产生突发角速度。
请在 AUTO 分支重置 PID,并移除已失效的计算。
🐛 建议的修复
switch (*mode_) { case rmcs_msgs::ChassisMode::AUTO: { - angular_velocity =- update_following_angular_velocity(StepDownFacing::BACK, chassis_control_angle);-- // Keep AUTO rear-following gentle at low translation speed and fully enabled at max.- const double measured_translational_speed =- chassis_velocity_feedback_ready_ ? chassis_velocity_->vector.head<2>().norm()- : translational_velocity.norm();- angular_velocity *=- std::clamp(measured_translational_speed / translational_velocity_max, 0.0, 0.3);-- angular_velocity = 0.0;-+ // AUTO 模式不做随动,重置 PID 以避免切换模式时积分突变。+ following_velocity_controller_.reset();+ angular_velocity = 0.0; } break;如果保留随动是后续计划,请改用条件开关而不是直接覆盖。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/hero_chassis_controller.cpp` around lines 181 - 194, 在英雄底盘控制器的 AUTO 分支中移除无效的跟随角速度计算及其速度缩放逻辑,并在该分支通过 following_velocity_controller_ 重置共享 PID,确保切换到其他模式时不会携带 AUTO 期间累积的积分项;保留 angular_velocity 为零的 AUTO 行为。rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp (1)
61-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winE 键与 C 键从 ENCODER 切回 IMU 的行为不一致。
第 72-75 行的 C 键切换会设置
switch_encoder_to_imu_by_c,第 81 行随后调用enter_imu_hold_current_pose()保持当前姿态。第 61-68 行的 E 键在
gimbal_mode_keyboard_ == GimbalMode::IMU为假时也会切回 IMU,但不设置该标志。此时imu_gimbal_solver_已在 ENCODER 期间被SetDisabled(第 91 行),因此update_imu_control()第 130-131 行会执行SetToLevel{},云台立刻跳到水平位置。请确认该差异是有意设计。如果两个按键都表示"退出编码器模式",建议让 E 键也走
enter_imu_hold_current_pose()。🐛 建议修复
+ bool switch_encoder_to_imu_by_c = false;+ if (!last_keyboard_.e && keyboard_->e) { if (gimbal_mode_keyboard_ == GimbalMode::IMU) { encoder_init_pitch_ = keyboard_->ctrl ? kCtrlEInitPitch : kEInitPitch; gimbal_mode_keyboard_ = GimbalMode::ENCODER; } else { gimbal_mode_keyboard_ = GimbalMode::IMU; + switch_encoder_to_imu_by_c = true; } } -- bool switch_encoder_to_imu_by_c = false;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp` around lines 61 - 82, 使 E 键从 ENCODER 切换回 IMU 时与 C 键保持一致:在 gimbal_mode_keyboard_ 的 E 键切换逻辑中设置 switch_encoder_to_imu_by_c 所对应的切换标志,确保后续 IMU 控制调用 enter_imu_hold_current_pose() 而不是 update_imu_control();保留 E 键从 IMU 切换到 ENCODER 的现有行为。rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp (1)
70-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
friction_wheel_count_ != 6时会写入空行。
log_text初始化为空字符串。如果friction_wheel_count_参数不是 6,条件不成立,第 78 行仍然写入日志文件并打印空行。日志文件会被无内容的行填充。请让日志格式与轮数解耦,或者在计数不受支持时提前返回并打印一次告警。
🐛 建议修改
- if (friction_wheel_count_ == 6) {- log_text = fmt::format(- "{},{},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f}", *initial_speed_,- (int)velocities.size(), //- velocity_, excellence_rate_, pass_rate_, range_, range2_, velocity_max,- velocity_min);- }+ log_text = fmt::format(+ "{},{},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f}", *initial_speed_,+ (int)velocities.size(), //+ velocity_, excellence_rate_, pass_rate_, range_, range2_, velocity_max,+ velocity_min);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp` around lines 70 - 78, Update the logging flow around the friction_wheel_count_ check so unsupported counts do not write an empty line. Either generate a valid log format independent of friction_wheel_count_ or return before log_stream_ writes and emit a warning once for unsupported values; preserve normal logging for count 6.rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp (1)
195-207: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win弹丸击发检测中的轮索引不一致,并且
target_friction_velocity(2)可能越界。第 196 行和第 207 行改用
friction_velocities_[0],但第 201 行仍然用target_friction_velocity(2)作为阈值基准。若两轮的目标速度不同,判据会失真。另外
target_friction_velocity(i)直接对std::vector使用operator[]。当friction_wheels配置为 1 或 2 个轮时,索引 2 越界,属于未定义行为。请把阈值基准改为索引 0。🐛 建议修改
if (primary_friction_velocity_decrease_integral_ < -14.0 - && last_primary_friction_velocity_ < target_friction_velocity(2) - 25.0)+ && last_primary_friction_velocity_ < target_friction_velocity(0) - 25.0) fired = true;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp` around lines 195 - 207, Update the firing detection condition in the friction-wheel controller to use target_friction_velocity(0) alongside friction_velocities_[0]. Replace the current target_friction_velocity(2) reference in the primary friction velocity decrease check to avoid an out-of-bounds access and keep the threshold aligned with the monitored wheel.
🟠 Major comments (31)
.script/local-context-76-89 (1)
76-89: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win写入 FIFO 可能永久阻塞。
第 87 行向命名管道写入数据。如果此时没有进程打开该 FIFO 的读端,
printf会一直阻塞,脚本不会返回,也不会给出提示。第 77 行的-p检查只确认 FIFO 存在,不能确认存在读者。建议加上超时保护,让用户在读端未就绪时得到明确的错误。
🔧 建议的修改
-printf '%s' "${value}" >"${fifo}"+if ! timeout 5 bash -c 'printf "%s" "$1" >"$2"' _ "${value}" "${fifo}"; then+ echo "Timed out writing to ${fifo}. Is rmcs-navigation reading the FIFO?" >&2+ exit 1+fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.script/local-context around lines 76 - 89, Add timeout protection around the FIFO write in the local-context flow, using the existing fifo and value variables, so printf cannot block indefinitely when no reader is connected. On timeout, emit a clear error to stderr and exit nonzero; preserve the current successful write and confirmation behavior..script/rmcs-cli-31-36 (1)
31-36: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
_list_pane_ids未检查 tmux 是否成功,窗格不足时会抛出IndexError。
subprocess.run失败时result.stdout为空字符串,"".strip().split("\n")返回[""]。第 76-79 行的final[1]、final[3]会抛出IndexError;即使不越界,pane ID 也会是空字符串,后续send-keys -t ""静默失败。同样的假设出现在第 152-156 行的
run_control_panel。🛡️ 建议的校验
def _list_pane_ids(): result = subprocess.run( ["tmux", "list-panes", "-t", SESSION, "-F", "#{pane_id}"], capture_output=True, text=True ) - return result.stdout.strip().split("\n")+ if result.returncode != 0:+ sys.exit(f"错误: 无法列出 tmux 窗格: {result.stderr.strip()}")+ ids = [line for line in result.stdout.strip().split("\n") if line]+ if not ids:+ sys.exit("错误: 未找到 tmux 窗格")+ return ids并在第 75 行后校验数量:
final = _list_pane_ids() + if len(final) < 4:+ sys.exit(f"错误: 期望 4 个窗格, 实际 {len(final)} 个")Also applies to: 75-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.script/rmcs-cli around lines 31 - 36, Update _list_pane_ids to detect unsuccessful tmux execution and return only non-empty pane IDs instead of splitting an empty stdout into [""]. In the callers that index the result, including the final pane setup around final[1] and final[3] and run_control_panel, validate that the required pane count exists before indexing; handle insufficient panes explicitly so no empty target reaches send-keys..script/scan-remote-595-604 (1)
595-604: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win行数减少时光标记账错误,下一帧渲染会错位。
分析当前帧的光标移动:
- 第 596 行上移
self._rendered_lines(记为old)行,回到渲染区起点。- 第 597-600 行输出
len(lines)行,光标下移len(lines)行。- 第 601-602 行为清理多余行再输出
old - len(lines)个换行,光标继续下移。- 光标最终位于起点下方
old行,但第 604 行把_rendered_lines记为len(lines)。下一帧只上移
len(lines)行,比实际少old - len(lines)行。渲染区会向下漂移并留下残留文本。结果列表在选择过程中不会减少,但网络列表为空或_prompt被clear_prompt清除时会触发。清理多余行后,请把光标移回内容末尾。
🐛 建议的修复
for line in lines: sys.stdout.write("\033[2K\r") sys.stdout.write(line) sys.stdout.write("\n") - for _ in range(max(0, self._rendered_lines - len(lines))):+ extra = max(0, self._rendered_lines - len(lines))+ for _ in range(extra): sys.stdout.write("\033[2K\r\n") + if extra:+ sys.stdout.write(f"\033[{extra}A") sys.stdout.flush() self._rendered_lines = len(lines)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.script/scan-remote around lines 595 - 604, Update the rendering logic around the line-clearing loop in the scan UI so that, after clearing surplus lines when the new result has fewer entries, the cursor is moved back to the end of the current rendered content. Keep _rendered_lines synchronized with len(lines), ensuring the next frame’s cursor-up operation starts from the correct position..script/rmcs-cli-90-102 (1)
90-102: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win编译等待循环没有超时,并且状态判定不可靠。
两个风险:
tmux capture-pane -p只返回窗格当前可见区域。编译输出较多时,Summary:行会滚出可见区域。循环随后永久执行,控制面板卡死。"failed" in result.stdout对整屏文本做子串匹配。任意包含failed的编译日志行都会被判为失败,Summary: 0 packages failed之类的正常输出同样命中。建议改为轮询
.script/build-rmcs的退出码(例如写入标记文件),并加入超时上限。♻️ 建议的最小改动:增加超时
log(" 等待编译完成...") + deadline = time.time() + 1800 while True: result = subprocess.run( ["tmux", "capture-pane", "-t", _pane_build, "-p"], capture_output=True, text=True ) if "Summary:" in result.stdout or "failed" in result.stdout: break + if time.time() > deadline:+ log("✗ 编译超时")+ return time.sleep(1)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.script/rmcs-cli around lines 90 - 102, Update the build-wait loop in the script around the tmux capture logic to use a bounded timeout and reliable build completion status instead of scanning visible pane text for “Summary:” or “failed”. Prefer polling the `.script/build-rmcs` process exit state via an appropriate marker or status file, then preserve distinct success and failure handling after completion.rmcs_ws/src/rmcs_auto_aim_v2-1-1 (1)
1-1: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win更新未检出子模块到目标提交。
rmcs_ws/src/rmcs_auto_aim_v2当前检出的是66863258...,与.gitmodules指向的远程 SHA387b384e...不一致,合并后会被当作脏工作树/未保存变更。清理或重置该子模块并重新提交到目标 gitlink。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_auto_aim_v2` at line 1, 将 rmcs_auto_aim_v2 子模块重置或更新到 .gitmodules 指向的目标提交 387b384e...,确保不再停留在 66863258...;清理子模块工作树后重新提交对应的 gitlink,避免合并后被识别为未保存变更。rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp-170-182 (1)
170-182: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win限制格式化距离文本的写入长度。
std::format_to会一直写入格式化结果,而text只分配 30 字节;robot_center_->norm()只要值有限就会进入该路径,较大值会写出缓冲区。改用std::format_to_n(..., kMaxTextLength, "{} | {:.1f}m", type, *distance),无距离时保留原"{} | NONE"或"{} {}"格式串即可;末尾嵌入的\0不是安全终止符。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpp` around lines 170 - 182, 限制 set_distance_text 中的格式化写入长度:将有距离分支的 std::format_to 改为使用 kMaxTextLength 限制输出的 std::format_to_n,并保留 "{} | {:.1f}m" 格式;无距离分支继续使用现有格式但移除格式串末尾嵌入的 "\0",确保缓冲区不会越界且由字符串存储负责终止。rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp-141-141 (1)
141-141: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift前爬升机构与底盘共用同一个功率上限值,会导致总功率超限。
第 140-141 行把同一个
power_limit同时写入/chassis/control_power_limit和/chassis/climber/front/control_power_limit。这两个通道由两个独立的限功器消费:底盘功率控制器限制轮组,ChassisClimberFrontPowerLimiter(chassis_climber_controller.cpp第 170 行注册该输入)限制前爬升电机。两者各自按满额上限工作,实际总功率可达该上限的两倍。爬坡时轮组和爬升机构同时出力,会超出裁判系统功率上限并造成扣血。
请在两者之间分配功率预算,例如按爬坡激活状态划分比例,或从底盘预算中扣除分给爬升机构的份额。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp` at line 141, 调整 chassis power limit 分配逻辑,避免 chassis 与 front climber 各自获得完整的 power_limit;在相关控制更新函数中根据前爬升机构激活状态划分或扣除爬升功率份额,并分别写入 control_power_limit_ 与前爬升限功器输入,确保两者合计不超过总功率上限。rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp-188-193 (1)
188-193: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
ALIGNMENT模式读取的是上一拍的底盘速度,且复位后为 NaN。
update_velocity_control()先调用update_translational_velocity_control(),再调用update_angular_velocity_control(),最后才写入chassis_control_velocity_->vector。因此第 263 行chassis_control_velocity_->vector.head<2>()读到的是上一拍的值。
reset_all_controls()把*chassis_control_velocity_设为{kNaN, kNaN, kNaN},并把模式设为ALIGNMENT。当遥控器恢复后的第一拍进入ALIGNMENT分支时,speed为 NaN,signed_angle返回 NaN,following_velocity_controller_.update(-min)得到 NaN。PID 的积分项会被 NaN 污染,后续输出会持续为 NaN,直到再次复位。请把本拍计算出的平移速度传入角速度计算,并对非有限值做保护。
🐛 建议修复
void update_velocity_control() { auto translational_velocity = update_translational_velocity_control(); - auto angular_velocity = update_angular_velocity_control();+ auto angular_velocity = update_angular_velocity_control(translational_velocity); chassis_control_velocity_->vector << translational_velocity, angular_velocity; }- double update_angular_velocity_control() {+ double update_angular_velocity_control(const Eigen::Vector2d& translational_velocity) { double angular_velocity = 0.0; @@ case ChassisMode::ALIGNMENT: { - const auto speed = chassis_control_velocity_->vector.head<2>();+ if (!translational_velocity.array().isFinite().all())+ break;+ const auto speed = translational_velocity; const auto line1 = Eigen::Vector2d{speed.x(), 0};Also applies to: 262-275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp` around lines 188 - 193, 修复 update_velocity_control 与 update_angular_velocity_control 的数据流:将本拍计算出的 translational_velocity 传入角速度计算,避免 ALIGNMENT 读取 chassis_control_velocity_->vector 的上一拍或 NaN 值;同时在 ALIGNMENT 相关计算及传入 following_velocity_controller_ 前校验速度和角度等中间结果的有限性,遇到非有限值时使用安全的有限值并阻止 NaN 污染 PID 积分项。rmcs_ws/src/rmcs_core/plugins.xml-6-9 (1)
6-9: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win补充
rmcs_core::test::output::AngleOutput的插件导出注册。
plugins.xml中该类型条目缺少对应的PLUGINLIB_EXPORT_CLASS(...),后续插件加载会找不到该组件,需要补齐导出语句并确认对应源文件已编译。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/plugins.xml` around lines 6 - 9, 在插件注册配置中补充 rmcs_core::test::output::AngleOutput 的导出注册,并在其实现源文件中确保存在对应的 PLUGINLIB_EXPORT_CLASS 声明且该源文件已加入构建目标,使插件能够被正常加载。rmcs_ws/src/rmcs_core/src/controller/chassis/climber/co_schduler.hpp-181-220 (1)
181-220: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift协程内调用
stop_all()会导致迭代器失效。
spin_once()在std::erase_if(slots_, ...)内部调用slot->handle.resume()。如果被恢复的协程体内调用了stop_all(),slots_.clear()会释放 vector 的存储,erase_if随后继续使用已失效的迭代器,产生未定义行为。同时slot所指的Slot会被销毁,而resume()尚未返回,协程帧已被Slot析构函数destroy()。急停清场是该调度器的既定用途,任务体内触发急停是自然用法。请增加重入保护,例如用标志记录“正在 spin”,在 spin 中调用
stop_all()时只置位待清标志,在spin_once()结束后再执行清理。🛡️ 建议方向
auto spin_once() { + spinning_ = true; slots_.insert(slots_.end(), pending_.begin(), pending_.end()); pending_.clear(); @@ + spinning_ = false;+ if (stop_requested_) {+ stop_requested_ = false;+ slots_.clear();+ pending_.clear();+ return;+ }+ if (error) std::rethrow_exception(error); } // 急停清场:销毁全部任务帧,协程局部变量正常析构 auto stop_all() { + if (spinning_) {+ stop_requested_ = true;+ return;+ } slots_.clear(); pending_.clear(); } private: + bool spinning_ = false;+ bool stop_requested_ = false; std::vector<std::shared_ptr<Slot>> slots_{};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/climber/co_schduler.hpp` around lines 181 - 220, 为 spin_once() 增加“正在执行”状态保护,并让 stop_all() 在 spin 期间仅记录待清理请求,不直接清空 slots_ 或 pending_;在 spin_once() 完成遍历、异常处理前后选择安全时机执行延迟清理,并确保协程恢复期间 Slot 及其协程帧保持有效,非 spin 期间仍立即清理。rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp-102-110 (1)
102-110: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win超时互锁在只注册一路遥控设备的机器人上会完全禁用失联保护。 根因在
RemoteControl::update_timeout_interlock():它只在对方设备当前valid()时才允许本设备启用超时。本 PR 中有三个机器人只注册了 DR16,从不注册 VT13。对这些机器人,vt13_恒为nullptr,vt13_ok恒为false,因此 DR16 的超时检测被永久关闭。DR16 链路断开后valid()继续返回true,build_snapshot()持续输出断线前最后一帧的摇杆值与拨杆值,底盘和云台会按最后的指令持续运动。
rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp#L102-L110:修改互锁逻辑。仅当dr16_与vt13_都已注册时才做互锁;只注册一路时对该路调用set_timeout_enabled(true),保留失联保护。rmcs_ws/src/rmcs_core/src/hardware/flight.cpp#L88-L89:该组件只调用register_dr16,属于受影响配置。请在互锁修正后验证 DR16 断线时输出会归零。rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp#L137-L138:同样只调用register_dr16,请做同一项断线验证。rmcs_ws/src/rmcs_core/src/hardware/sentry.cpp#L370-L372:ChassisBoard只调用register_dr16,且哨兵为自主运行平台,失联保护失效的后果更严重,请优先验证。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp` around lines 102 - 110, Update RemoteControl::update_timeout_interlock() in rmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpp#L102-L110 so mutual interlock is applied only when both dr16_ and vt13_ are registered; when only one is present, call set_timeout_enabled(true) for that device. Validate the resulting DR16 disconnect behavior in rmcs_ws/src/rmcs_core/src/hardware/flight.cpp#L88-L89, rmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpp#L137-L138, and rmcs_ws/src/rmcs_core/src/hardware/sentry.cpp#L370-L372; these sites require no direct code change.rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_controller.cpp-151-168 (1)
151-168: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win外部设定速度有效时 TD 不更新,回退到 TD 时会产生跳变。
第 156 行只有在
reference_velocity非有限时才调用td_.update()。当上游持续提供有效的setpoint_velocity时,td_的内部状态停留在最后一次更新的值。如果上游随后停止提供速度(NaN 或ready()变为 false),控制器会立刻回退到陈旧的 TD 状态,reference_angle与reference_velocity会出现阶跃,进而产生扭矩尖峰。请在每个周期都推进 TD,只在选择参考量时区分两种来源。
🐛 每周期推进 TD
bool run_joint_servo_(const InputSnapshot& inputs, double& control_torque) { const auto eso_out = eso_.update(inputs.measurement_angle, last_u_); - double reference_angle = inputs.setpoint_angle;- double reference_velocity = inputs.setpoint_velocity;- if (!std::isfinite(reference_velocity)) {- const auto td_out = td_.update(inputs.setpoint_angle);- reference_angle = td_out.x1;- reference_velocity = td_out.x2;- }+ const auto td_out = td_.update(inputs.setpoint_angle);++ double reference_angle = td_out.x1;+ double reference_velocity = td_out.x2;+ if (std::isfinite(inputs.setpoint_velocity)) {+ reference_angle = inputs.setpoint_angle;+ reference_velocity = inputs.setpoint_velocity;+ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_controller.cpp` around lines 151 - 168, 在 run_joint_servo_ 中每个控制周期都调用 td_.update(inputs.setpoint_angle) 推进 TD 状态;随后仅在选择参考量时,根据 setpoint_velocity 是否有限决定使用外部设定值或 TD 输出,避免回退到过期状态并保持现有扭矩计算流程不变。rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp-437-452 (1)
437-452: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick winGPIO 读取配置在每个控制周期都重新下发。
第 437-452 行位于
command_update()中。该函数由SteeringHeroLittleCommand::update()在每个控制周期调用。因此每个周期都会向板卡发送两条 GPIO 配置命令。
omni_infantry.cpp第 95 行与sentry.cpp第 132 行都把gpio_digital_read放在构造函数中,只配置一次。持续重复配置会占用 CAN/串口下行带宽,也可能重置 GPIO 的采样状态。请把这段配置移到
TopBoard的构造函数中,紧跟第 317 行的uart_config之后。🐛 移动到构造函数
board_->start_transmit().uart_config(Spec::kUarts.kUart0, {.baudrate = 921600}); ++ board_->start_transmit()+ .gpio_digital_read(+ Spec::kGpios.kUart1Rx,+ {+ .period_ms = 20,+ .pull = librmcs::data::GpioPull::kUp,+ })+ .gpio_digital_read(+ Spec::kGpios.kUart1Tx, {+ .period_ms = 0,+ .asap = false,+ .rising_edge = false,+ .falling_edge = true,+ .capture_timestamp = true,+ .pull = librmcs::data::GpioPull::kUp,+ });并从
command_update()中删除第 437-452 行。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp` around lines 437 - 452, Move the two gpio_digital_read configurations from command_update() into the TopBoard constructor immediately after the existing uart_config call, preserving all GPIO settings. Remove the corresponding per-cycle configuration calls from SteeringHeroLittleCommand::update() while leaving command_update() behavior otherwise unchanged.rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp-227-270 (1)
227-270: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win为可变形关节配置 TD 速度/加速度限幅。
deformable_joint_controller.cpp将td_max_vel/td_max_acc默认为无穷大,但deformable-infantry-omni.yaml和deformable-infantry-omni-b.yaml没有设置这两项;td.hpp的无穷大可被fhan转换为整型INT_MIN,导致阶跃设定值产生过大fh输入。在配置中为每个可变形关节设置有限的td_max_vel与td_max_acc。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp` around lines 227 - 270, 在 deformable-infantry-omni.yaml 和 deformable-infantry-omni-b.yaml 中,为每个可变形关节补充有限且有效的 td_max_vel 与 td_max_acc 配置值,避免继续使用 deformable_joint_controller.cpp 的无穷大默认值。确保两份配置的所有可变形关节都设置这两个参数,并保持现有关节配置结构不变。rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp-49-73 (1)
49-73: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
reset()会永久丢弃active_suspension_base_angle参数值。构造函数在第 39-42 行从节点参数
active_suspension_base_angle读取初值,并 clamp 到[min_angle_ - 5.0, max_angle_]。第 62 行的reset()把该成员无条件写成max_angle_。类中没有任何路径会重新读取参数。因此第一次复位之后,参数配置被永久丢弃,主动悬挂的基准角度只能在min_angle_与max_angle_之间切换。请保存构造时的初值,并在
reset()中恢复该初值。🐛 保存并恢复参数初值
, active_suspension_base_angle_( std::clamp( node.get_parameter_or("active_suspension_base_angle", max_angle_), min_angle_ - 5.0, max_angle_)) + , active_suspension_base_angle_default_(active_suspension_base_angle_) , suspension_enable_(node.get_parameter_or("active_suspension_enable", false)) {current_target_angle_ = max_angle_; - active_suspension_base_angle_ = max_angle_;+ active_suspension_base_angle_ = active_suspension_base_angle_default_;并在成员区新增:
double active_suspension_base_angle_; + double active_suspension_base_angle_default_; bool suspension_enable_;注意
active_suspension_base_angle_default_的声明必须位于active_suspension_base_angle_之后,以匹配初始化列表顺序。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpp` around lines 49 - 73, Update the deformable-mode class constructor and reset() to preserve the clamped constructor value of active_suspension_base_angle_. Add active_suspension_base_angle_default_ immediately after active_suspension_base_angle_ in the member declarations and initialize it in matching initialization-list order; have reset() restore active_suspension_base_angle_ from this saved default instead of max_angle_.rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp-128-157 (1)
128-157: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win用
static_assert固定 VT13 协议包尺寸。
RemoteControlData混用位域底层类型,sizeof(RemoteControlData)和sizeof(RefereeFrameHeader)可能因实现定义行为偏离实际包长。read_remote_control_data()依赖sizeof读取整个包并计算 CRC16,若尺寸不一致,校验将失败且VT13::valid()会一直为 false。在协议结构附近增加尺寸断言,把协议帧长固化。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp` around lines 128 - 157, 在 RemoteControlData 定义附近增加 static_assert,固定其 sizeof(RemoteControlData) 与 VT13 协议实际帧长一致,并同时校验 RefereeFrameHeader 的尺寸;确保 read_remote_control_data() 使用 sizeof 读取和计算 CRC 时不会因位域布局差异产生错误。rmcs_ws/src/rmcs_core/src/hardware/flight.cpp-80-86 (1)
80-86: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win为 PX4 串口补充接收路径。
flight.cpp:80-84向/px4/serial注册接口时把read固定为返回 0,且uart_receive_callback没有消费Spec::kUarts.kUart0;px4_vision_bridge.cpp只使用px4_serial_->write发送 MAVLink。PX4 应答会进入 RX 中断后丢弃,导致心跳/链路应答或协商数据丢失。为kUart0增加环形缓冲区,并在read中从中弹出数据。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/flight.cpp` around lines 80 - 86, 为 PX4 串口补充接收数据路径:在 flight.cpp 的串口初始化及 uart_receive_callback 中为 Spec::kUarts.kUart0 维护环形缓冲区,将 RX 数据写入该缓冲区;把 px4_serial_->read 从固定返回 0 改为按请求长度弹出并返回缓冲区中的数据,同时保留现有 write 发送逻辑。rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp-629-660 (1)
629-660: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win核对非转向电机的 CAN id 配置与实际接线。
DjiMotor::generate_command()的字节位置由motor.id()决定:(id - 1) % 4;recv_id()根据该 id 映射到对应的反馈 ID。这些 motor 配置 id 与当前槽位/反馈 ID 映射不一致时,命令会和反馈落到不同的电机。
chassis_wheel_motors_[0]、[1]、[2]、[3]按实际反馈 ID 应为 1、2、3、4;当前配置为 4、1、3、4。chassis_front_climber_motor_当前 id 为 1、2;按 0x201/0x203 反馈应为 1、3。chassis_back_climber_motor_当前 id 为 2、4;按 0x202/0x204 反馈,若对应电机 id 为 2、4 则配置可自洽;若实际 id 为 3、4,则需要确认并调整。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp` around lines 629 - 660, 核对并修正该配置块中非转向电机的 CAN ID,使命令槽位与实际反馈 ID 一致:将 chassis_wheel_motors_[0..3] 配置为 1、2、3、4,并将 chassis_front_climber_motor_ 配置为 1、3。确认 chassis_back_climber_motor_[0..1] 的实际接线与反馈 ID;若对应为 3、4,则调整其 ID,否则保持当前 2、4 配置。rmcs_ws/src/rmcs_core/src/controller/chassis/sentry_climber.cpp-637-648 (1)
637-648: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win常驻协程异常后需要重建或持续捕获错误。
CoSchduler::spin_once()会保存并跳过已完成的异常协程,一旦spin_context()或spin_groups()抛出,后续调用不会再恢复这两个常驻任务。当前update()会打印一次异常日志并释放攀爬状态,但不会重建spin_context()和spin_groups(),导致攀爬逻辑静默终止。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/sentry_climber.cpp` around lines 637 - 648, Update the update() exception path so that after schduler.spin_once() throws, the persistent spin_context() and spin_groups() tasks are rebuilt or the scheduler continues capturing and recovering their exceptions; do not leave either resident coroutine permanently completed. Preserve the existing error logging, task cancellation, state reset, and release_climber() cleanup.rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_17mm.cpp-64-65 (1)
64-65: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win避免对字面量使用
bind_directly。
bind_directly(false)会将模板对象绑定为const T&参数的地址。第 63 行和第 65 行传入字面量false时,解引用会指向临时值并导致悬垂引用;第 65 行新增调用会直接访问解引用结果,风险更大。改用make_and_bind_directly(false, ...)或绑定到组件内部/自有存储的false。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_17mm.cpp` around lines 64 - 65, Update the shooting logic around single_shoot_.ready() to avoid calling bind_directly with the false literal; replace it with make_and_bind_directly(false, ...) or bind to component-owned storage so the bound value remains valid when dereferenced.rmcs_ws/src/rmcs_core/src/controller/flight/px4_vision_bridge.cpp-116-116 (1)
116-116: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift需确保发送到 PX4 的视觉时间戳与 EKF2 使用的时间基准一致。
当前
VISION_POSITION_ESTIMATE.usec直接来自 ROS 消息头时间戳;ROS 时间基准依赖于 NTP/PTP(或use_sim_time等配置),与 PX4 的 IMU/EKF 时钟不一定一致。若部署环境未做时间同步或 EV 延迟补偿,PX4 EKF2 可能拒绝外部视觉数据或产生错误融合结果。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/flight/px4_vision_bridge.cpp` at line 116, 更新生成 VISION_POSITION_ESTIMATE.usec 的时间戳逻辑,不要直接使用 sample.stamp.nanoseconds();改用与 PX4 IMU/EKF2 一致的 PX4 单调时钟时间基准,并在必要时应用视觉延迟补偿或 ROS/PX4 时钟偏移,确保发送的时间戳与 EKF2 使用的时钟一致。rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_controller.cpp-38-59 (1)
38-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win为
SteeringWheelController补齐新增 PID 参数。
SteeringWheelController在chassis_translation_*/chassis_angular_velocity_**:steering_*/steering_angle_*/wheel_velocity_**处使用get_parameter(name).as_double(),而现有sentry.yaml只包含这 3 个chassis_translation_*。rmcs_bringup config中其他 YAML 也没有这些参数的配置块;需要每个加载该组件的配置都补齐这 15 个 PID 键,否则SentryClimber相关启动文件会因ParameterNotDeclaredException加载失败。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_controller.cpp` around lines 38 - 59, 补齐所有加载 SteeringWheelController 的 YAML 配置,为 chassis_translational_velocity_pid_、chassis_angular_velocity_pid_、steering_velocity_pid_、steering_angle_pid_ 和 wheel_velocity_pid_ 各添加 kp、ki、kd 三个参数,共 15 个 PID 键;确保 sentry.yaml 及 rmcs_bringup config 中相关配置都包含这些参数,并使用有效的 double 值,避免启动时因参数未声明而失败。rmcs_ws/src/rmcs_core/src/controller/gimbal/player_viewer.cpp-108-108 (1)
108-108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winclamp 参数顺序修正正确;但相邻的限位判断条件恒为真。
第 108 行把
std::clamp的参数顺序改为(value, lower_limit_, upper_limit_),修正了此前lo > hi的未定义行为。这个修改正确。请同时检查紧随其后的第 112 行至第 113 行:
if (norm_angle(*viewer_control_angle_) >= lower_limit_ || norm_angle(*viewer_control_angle_) <= upper_limit_) {当
lower_limit_ <= upper_limit_时,任意数值都至少满足其中一个条件,因此该判断恒为真。结果是*viewer_delta_angle_by_mouse_wheel_每帧都被置 0,该输出接口对下游始终为 0。从命名判断,意图应为“到达限位时清零增量”。请改为超出范围的判断。
🐛 建议的修复
- if (norm_angle(*viewer_control_angle_) >= lower_limit_- || norm_angle(*viewer_control_angle_) <= upper_limit_) {+ const double normalized = norm_angle(*viewer_control_angle_);+ if (normalized <= lower_limit_ || normalized >= upper_limit_) { *viewer_delta_angle_by_mouse_wheel_ = 0; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/player_viewer.cpp` at line 108, 修正 gimbal viewer 控制逻辑中紧随 std::clamp 的限位判断:将 norm_angle(*viewer_control_angle_) 与 lower_limit_、upper_limit_ 的条件从恒真的或(||)改为表示超出有效范围的判断,并仅在到达限位时将 *viewer_delta_angle_by_mouse_wheel_ 清零;保留正常范围内增量输出。rmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw.cpp-129-141 (1)
129-141: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
kGimbalFree哨兵值使用double::min()并做精确相等比较,属于跨文件契约风险。
std::numeric_limits<double>::min()是最小正规化正数(约2.2e-308),不是最小可表示值。第 132 行用==精确比较该值。只要上游/rmcs_navigation/gimbal_toward的生产者写入的位模式有任何差异(例如经过序列化、单精度转换或使用了lowest()),该分支就静默失效,云台不会进入自由状态。请将该哨兵约定提取到共享头文件中,让生产者与消费者引用同一常量;或改用
NaN加std::isnan判定,与第 136、138 行已有的isfinite检查保持一致。#!/bin/bash# 查找 gimbal_toward 的生产者及其使用的哨兵常量 rg -n -C5 'gimbal_toward' rmcs_ws/src rg -n -C3 'numeric_limits<double>::min\(\)|kGimbalFree' rmcs_ws/src🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw.cpp` around lines 129 - 141, Replace the local kGimbalFree sentinel and exact equality checks in the navigation block of EccentricDualYawController with a shared sentinel contract, preferably NaN checked via std::isnan consistently with the existing finite-value handling. Update both the gimbal_toward producer and consumer to use the same representation, while preserving enter_disabled_state() when both navigation coordinates indicate free mode.rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp-27-44 (1)
27-44: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win把输入、输出和内部状态成员移到
private段。第 20 行的
public:一直延续到第 129 行。因此sentry_events_、requests_、cached_events_、last_fb_posture_等全部对外暴露。这些是实现细节。请把它们移到private:段,只保留构造函数、before_updating()和update()为公开。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp` around lines 27 - 44, 将 SentryDecision 类中当前由 public 继承的输入、输出及内部状态成员移入 private: 段,包括 sentry_events_、requests_、cached_events_ 和 last_fb_posture_ 等;仅保留构造函数、before_updating() 与 update() 位于 public: 段。rmcs_ws/src/rmcs_core/src/referee/status/field.hpp-144-148 (1)
144-148: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win姿态反馈位宽与姿态命令枚举取值范围不匹配。
SentryInfo::posture定义为 2 位(取值 0-3),而SentryCommand::Posture定义了 7 个取值(0-6)。两者在反馈校验中被直接比较,因此所有POWERED_*姿态请求都无法确认完成,会永久停留在请求集合中并阻塞后续命令。
rmcs_ws/src/rmcs_core/src/referee/status/field.hpp#L144-L148:核对裁判协议手册中posture字段的实际位宽。如果是 3 位,把std::uint16_t posture : 2改为: 3,并把同组的energy_core_activatable与is_powered之后的位分配同步调整,保持static_assert(sizeof(SentryInfo) == 14)成立。rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp#L210-L217:在位宽修正后,确认static_cast<uint8_t>(it->second) == fb_posture_id能匹配POWERED_ATTACK、POWERED_DEFENSE、POWERED_MOVE。如果协议确实只提供 2 位反馈,请为这三个事件改用其他完成判据(例如结合is_powered位),并增加超时清除以避免请求永久驻留。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/referee/status/field.hpp` around lines 144 - 148, 修正 SentryInfo::posture 与 SentryCommand::Posture 的反馈取值范围不匹配:在 rmcs_ws/src/rmcs_core/src/referee/status/field.hpp:144-148 核对协议位宽,若为 3 位则将 posture 扩展为 3 位并同步调整后续字段,保持 static_assert(sizeof(SentryInfo) == 14);在 rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp:210-217 确认反馈比较可匹配 POWERED_ATTACK、POWERED_DEFENSE 和 POWERED_MOVE,若协议仅支持 2 位则改用包含 is_powered 的完成判据并增加超时清除,避免请求永久驻留。rmcs_ws/src/rmcs_core/src/controller/gimbal/deformable_infantry_gimbal_controller.cpp-220-226 (1)
220-226: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
pitch_gravity_feedforward()在保持模式下恒返回 0。第 221 行在
ctrl_hold_active_为真时提前返回0.0。第 278 行只在保持模式下调用该函数,此时ctrl_hold_active_一定为真。因此保持模式的重力前馈永远是 0。第 92 行的调用只发生在!ctrl_hold_active_分支,该早退分支也永远不会命中。保持模式需要重力补偿来维持目标角度。请移除该早退,或明确说明保持模式不使用前馈并删除第 278 行的加项。
🐛 建议修复
auto pitch_gravity_feedforward() const -> double { - if (ctrl_hold_active_)- return 0.0; if (!input_.pitch_angle.ready() || !std::isfinite(*input_.pitch_angle)) return 0.0; return pitch_gravity_ff_gain_ * std::sin(*input_.pitch_angle - pitch_gravity_ff_phase_); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/gimbal/deformable_infantry_gimbal_controller.cpp` around lines 220 - 226, 移除 pitch_gravity_feedforward() 中基于 ctrl_hold_active_ 的提前返回,使保持模式调用该函数时仍能计算重力补偿;保留角度输入有效性检查及现有前馈计算逻辑。rmcs_ws/src/rmcs_core/src/hardware/device/dr16.hpp-49-59 (1)
49-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
valid_和last_remote_control_received_at_存在跨线程数据竞争。
store_status()由板卡的串口接收线程调用,它写入last_remote_control_received_at_(第 50 行)和valid_(第 51 行)。update_status()由执行器线程调用,它读取这两个成员,并在refresh_validity()中写入valid_ = false(第 199 行)。这两个成员不是原子的。
TimePoint是 8 字节以上的对象,非原子读写属于未定义行为。相比之下,data_part1_、data_part2_、data_part3_都使用了std::atomic,说明这里确实是双线程访问。请把这两个字段改为原子类型。
🛡️ 建议修改
- TimePoint last_remote_control_received_at_ = TimePoint::min();- bool valid_ = false;+ std::atomic<TimePoint::rep> last_remote_control_received_at_{TimePoint::min()+ .time_since_epoch()+ .count()};+ std::atomic<bool> valid_{false}; bool timeout_enabled_ = true;对应地,
store_status()使用store(..., std::memory_order::relaxed),refresh_validity()使用load(..., std::memory_order::relaxed)并把rep还原为TimePoint。Also applies to: 194-200, 281-283
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/device/dr16.hpp` around lines 49 - 59, 将 Device 中的 valid_ 和 last_remote_control_received_at_ 改为可安全跨线程访问的原子类型,并更新 store_status()、update_status()、refresh_validity() 及相关读取逻辑以使用 relaxed load/store;对原子时间值读取后还原为 TimePoint,保持现有有效性判断和状态更新行为不变。rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp-183-189 (1)
183-189: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
detect_friction_faulty()现在恒返回false,卡弹检测被完全禁用。循环命中故障条件后返回
false,函数末尾也返回false。因此update_friction_status()永远不会进入故障分支,/gimbal/friction_jammed永远为false,摩擦轮堵转时不会自动停机。如果目的是临时关闭该检测,请直接让函数返回
false并加注释说明;如果目的是保留检测,请恢复return true。🐛 恢复故障检测的修改
bool detect_friction_faulty() { for (size_t i = 0; i < friction_count_; i++) { if (abs(*friction_velocities_[i]) < abs(*friction_control_velocities_[i] * 0.5)) - return false;+ return true; } return false; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpp` around lines 183 - 189, 修复 detect_friction_faulty() 的返回逻辑:当任一摩擦轮满足速度低于控制速度一半的故障条件时返回 true,循环结束仍返回 false。确保 update_friction_status() 能进入故障分支并正确报告摩擦轮堵转。rmcs_ws/src/rmcs_core/src/hardware/device/dji_motor.hpp-142-171 (1)
142-171: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win当
id_为 0 时,recv_id()会返回发送帧的 ID。
recv_id(Type::kM3508, 0)返回0x200,recv_id(Type::kGM6020, 0)返回0x204。0x200是 DJI 电调的指令帧 ID,不是反馈帧 ID。如果某个未配置 ID 的电机调用match_then_store_status(),它会把回环的指令帧当作反馈数据存入,导致角度、速度和力矩全部错误。请让 ID 为 0 时返回一个不可能匹配的值,并让
match_then_store_status()直接拒绝。🛡️ 建议修改
static constexpr auto recv_id(Type type, std::uint8_t index) -> std::uint32_t { + if (index == 0)+ return 0; switch (type) { case Type::kGM6020: case Type::kGM6020Voltage: return 0x204 + index; case Type::kM3508: case Type::kM2006: return 0x200 + index; } return 0; }bool match_then_store_status(std::uint32_t can_id, std::span<const std::byte> can_data) { - if (can_id != recv_id())+ const auto expected = recv_id();+ if (expected == 0 || can_id != expected) return false; store_status(can_data); return true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/device/dji_motor.hpp` around lines 142 - 171, 更新静态 recv_id(Type, index) 与 match_then_store_status:当 index/id_ 为 0 时返回不可能匹配的接收 ID,并让 match_then_store_status 在 id_ 为 0 时立即返回 false,不调用 store_status;其他已配置 ID 的匹配与存储行为保持不变。rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp-150-154 (1)
150-154: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win底盘轮方向配置需要同步更新。
chassis_wheel_motors_当前未配置set_reversed(),但DjiMotor的速度和力矩换算方向由reversed控制,DeformableOmniWheelController仍按之前的底盘坐标约定使用轮速度输入/力矩输出。如果硬件或本体方向改为默认正号,底盘控制方向会整体反转;需要补齐方向配置,或在deformable_omni_wheel_controller与硬件接口中同步符号约定。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp` around lines 150 - 154, 同步底盘轮方向配置:在 chassis_wheel_motors_ 的 DjiMotor::Config 链中补充明确的 set_reversed() 设置,使 DjiMotor 的速度和力矩符号继续匹配 DeformableOmniWheelController 既有底盘坐标约定;根据实际硬件安装方向使用正确的反向值,不要依赖默认方向。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: aed7a725-73ba-4206-afed-f49cbbd357be
📒 Files selected for processing (96)
.github/workflows/update-image.yml.gitmodules.script/autoaim-debug.script/complete/_local-context.script/complete/_remote-context.script/host/rmcs.script/identification/fit_friction_velocity_pid.py.script/identification/fit_gravity_torque.py.script/identification/fit_sweep_graybox.py.script/local-context.script/remote-context.script/remote-status.script/rmcs-cli.script/scan-remoteDockerfilermcs_ws/src/rmcs_auto_aim_v2rmcs_ws/src/rmcs_bringup/config/auto_aim_test.yamlrmcs_ws/src/rmcs_bringup/config/deformable-infantry-omni-b.yamlrmcs_ws/src/rmcs_bringup/config/deformable-infantry-omni.yamlrmcs_ws/src/rmcs_bringup/config/flight.yamlrmcs_ws/src/rmcs_bringup/config/navigation_test.yamlrmcs_ws/src/rmcs_bringup/config/sentry.yamlrmcs_ws/src/rmcs_bringup/config/steering-hero-little-six-friction.yamlrmcs_ws/src/rmcs_bringup/config/steering-hero-little.yamlrmcs_ws/src/rmcs_bringup/config/steering-hero.yamlrmcs_ws/src/rmcs_bringup/config/steering-infantry.yamlrmcs_ws/src/rmcs_core/CMakeLists.txtrmcs_ws/src/rmcs_core/package.xmlrmcs_ws/src/rmcs_core/plugins.xmlrmcs_ws/src/rmcs_core/src/controller/chassis/chassis_climber_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/climber/co_schduler.hpprmcs_ws/src/rmcs_core/src/controller/chassis/climber/stick_group.hpprmcs_ws/src/rmcs_core/src/controller/chassis/climber/track_group.hpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_chassis.cpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_layer.hpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_mode.hpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_omni_wheel_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_suspension.cpprmcs_ws/src/rmcs_core/src/controller/chassis/deformable_wheel_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/hero_chassis_controller.cpprmcs_ws/src/rmcs_core/src/controller/chassis/sentry_climber.cpprmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_controller.cpprmcs_ws/src/rmcs_core/src/controller/flight/px4_vision_bridge.cpprmcs_ws/src/rmcs_core/src/controller/gimbal/deformable_infantry_gimbal_controller.cpprmcs_ws/src/rmcs_core/src/controller/gimbal/dual_yaw_controller.cpprmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw.cpprmcs_ws/src/rmcs_core/src/controller/gimbal/eccentric_dual_yaw_solver.hpprmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpprmcs_ws/src/rmcs_core/src/controller/gimbal/player_viewer.cpprmcs_ws/src/rmcs_core/src/controller/gimbal/two_axis_gimbal_solver.hpprmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_17mm.cpprmcs_ws/src/rmcs_core/src/controller/shooting/friction_wheel_controller.cpprmcs_ws/src/rmcs_core/src/controller/shooting/hero_friction_wheel_controller.cpprmcs_ws/src/rmcs_core/src/controller/shooting/hero_heat_controller.cpprmcs_ws/src/rmcs_core/src/controller/shooting/putter_controller.cpprmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpprmcs_ws/src/rmcs_core/src/debug/value_collector.cpprmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni-b.cpprmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpprmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-steering.cpprmcs_ws/src/rmcs_core/src/hardware/device/dji_motor.hpprmcs_ws/src/rmcs_core/src/hardware/device/dr16.hpprmcs_ws/src/rmcs_core/src/hardware/device/lk_motor.hpprmcs_ws/src/rmcs_core/src/hardware/device/remote_control.hpprmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpprmcs_ws/src/rmcs_core/src/hardware/flight.cpprmcs_ws/src/rmcs_core/src/hardware/omni_infantry.cpprmcs_ws/src/rmcs_core/src/hardware/sentry.cpprmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpprmcs_ws/src/rmcs_core/src/hardware/steering-hero.cpprmcs_ws/src/rmcs_core/src/hardware/steering-infantry.cpprmcs_ws/src/rmcs_core/src/hardware/util/status_monitor.hpprmcs_ws/src/rmcs_core/src/identification/static_torque_test_controller.cpprmcs_ws/src/rmcs_core/src/identification/swept_frequency_controller.cpprmcs_ws/src/rmcs_core/src/referee/app/ui/auto_aim.cpprmcs_ws/src/rmcs_core/src/referee/app/ui/deformable_infantry_ui.cpprmcs_ws/src/rmcs_core/src/referee/app/ui/hero.cpprmcs_ws/src/rmcs_core/src/referee/app/ui/infantry.cpprmcs_ws/src/rmcs_core/src/referee/app/ui/shape/shape.hpprmcs_ws/src/rmcs_core/src/referee/app/ui/widget/animated_toggle.hpprmcs_ws/src/rmcs_core/src/referee/app/ui/widget/crosshair_circle.hpprmcs_ws/src/rmcs_core/src/referee/app/ui/widget/deformable_chassis_top_view.hpprmcs_ws/src/rmcs_core/src/referee/app/ui/widget/status_ring.hpprmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpprmcs_ws/src/rmcs_core/src/referee/status.cpprmcs_ws/src/rmcs_core/src/referee/status/field.hpprmcs_ws/src/rmcs_msgs/include/rmcs_msgs/chassis_mode.hpprmcs_ws/src/rmcs_msgs/include/rmcs_msgs/rmcs_msgs.hpprmcs_ws/src/rmcs_msgs/include/rmcs_msgs/robot_id.hpprmcs_ws/src/rmcs_msgs/include/rmcs_msgs/sentry_event.hpprmcs_ws/src/rmcs_utility/include/rmcs_utility/csv_writer.hpprmcs_ws/src/rmcs_utility/include/rmcs_utility/rclcpp/node_mixin.hpprmcs_ws/src/rmcs_utility/include/rmcs_utility/ring_buffer.hpp
💤 Files with no reviewable changes (7)
- rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_joint_layer.hpp
- rmcs_ws/src/rmcs_bringup/config/steering-infantry.yaml
- rmcs_ws/src/rmcs_bringup/config/steering-hero-little.yaml
- rmcs_ws/src/rmcs_bringup/config/steering-hero.yaml
- rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-steering.cpp
- rmcs_ws/src/rmcs_core/src/controller/chassis/deformable_wheel_controller.cpp
- rmcs_ws/src/rmcs_core/src/referee/app/ui/shape/shape.hpp
| subprocess.run([ | ||
| "tmux", "send-keys", "-t", _pane_control, | ||
| f"cd {RMCS_PATH} && RMCS_CLI=1 {__file__}", "Enter" | ||
| ]) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
__file__ 可能是相对路径,cd 之后重启会失败。
用户以 .script/rmcs-cli 这类相对路径调用脚本时,__file__ 为相对路径。发送到窗格的命令先执行 cd {RMCS_PATH},再执行 {__file__}。若当前工作目录不是 RMCS_PATH,控制面板不会启动,窗格只显示 “command not found”。
请使用绝对路径。
🐛 建议的修复
+ script_path = os.path.abspath(__file__)
subprocess.run([
"tmux", "send-keys", "-t", _pane_control,
- f"cd {RMCS_PATH} && RMCS_CLI=1 {__file__}", "Enter"+ f"cd {RMCS_PATH} && RMCS_CLI=1 {script_path}", "Enter"
])📝 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.
| subprocess.run([ | |
| "tmux", "send-keys", "-t", _pane_control, | |
| f"cd {RMCS_PATH} && RMCS_CLI=1 {__file__}", "Enter" | |
| ]) | |
| script_path = os.path.abspath(__file__) | |
| subprocess.run([ | |
| "tmux", "send-keys", "-t", _pane_control, | |
| f"cd {RMCS_PATH} && RMCS_CLI=1 {script_path}", "Enter" | |
| ]) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/rmcs-cli around lines 137 - 140, Update the tmux restart command
around __file__ to resolve the script path to an absolute path before
constructing the command sent by subprocess.run. Preserve the existing RMCS_PATH
change-directory and environment-variable behavior while ensuring restarting
works when the script was invoked via a relative path such as .script/rmcs-cli.
| @staticmethod | ||
| def read_key(): | ||
| if not RawTerminal.key_ready(0.02): | ||
| return None | ||
| raw = sys.stdin.buffer.raw.read(1) | ||
| if raw == b"\x03": | ||
| raise KeyboardInterrupt | ||
| if raw in (b"\r", b"\n"): | ||
| return "enter" | ||
| if raw == b"\t": | ||
| return "next" | ||
| if raw in (b"w", b"W"): | ||
| return "prev" | ||
| if raw in (b"s", b"S"): | ||
| return "next" | ||
| if raw == b"\x0e": | ||
| return "next" | ||
| if raw == b"\x10": | ||
| return "prev" | ||
| if raw in (b"\x7f", b"\x08"): | ||
| return "backspace" | ||
| if raw == b"\x1b": | ||
| if not RawTerminal.key_ready(0.02): | ||
| return "quit" | ||
| nxt = sys.stdin.buffer.raw.read(1) | ||
| if nxt != b"[": | ||
| return "escape" | ||
| if not RawTerminal.key_ready(0.02): | ||
| return "escape" | ||
| direction = sys.stdin.buffer.raw.read(1) | ||
| if direction == b"A": | ||
| return "prev" | ||
| if direction == b"B": | ||
| return "next" | ||
| if direction == b"Z": | ||
| return "prev" | ||
| return "escape" | ||
| if raw in (b"q", b"Q"): | ||
| return "quit" | ||
| os._exit(130) | ||
| if raw in RawTerminal._SIMPLE_KEYS: | ||
| return RawTerminal._SIMPLE_KEYS[raw] | ||
| if raw == b"\x1b": | ||
| return RawTerminal._read_escape() | ||
| if len(raw) == 1 and 32 <= raw[0] <= 126: | ||
| return raw.decode() | ||
| return None | ||
| @staticmethod | ||
| def _read_escape(): | ||
| if not RawTerminal.key_ready(0.02): | ||
| os._exit(130) | ||
| nxt = sys.stdin.buffer.raw.read(1) | ||
| if nxt != b"[" or not RawTerminal.key_ready(0.02): | ||
| os._exit(130) | ||
| direction = sys.stdin.buffer.raw.read(1) | ||
| if direction in RawTerminal._ESCAPE_DIRS: | ||
| return RawTerminal._ESCAPE_DIRS[direction] | ||
| os._exit(130) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
os._exit(130) 会跳过终端恢复,退出后用户终端保持 raw 模式。
RawTerminal.__enter__ 关闭了 ECHO 和 ICANON(第 43 行)。os._exit 立即终止进程,不执行 __exit__、finally 或 atexit。因此第 66、78、81、85 行的每一条退出路径都会让调用者的终端停留在无回显、无行编辑的状态。用户必须手动执行 stty sane 才能恢复。
第二个问题在 _read_escape。任何以 ESC 开头但不是 ESC [ A/B/Z 的序列都会触发退出,包括 Alt+字母、Home、End、F1~F4 以及 ESC [ C/D(左右方向键)。用户按方向键左右时程序会直接终止。
建议改为抛出自定义异常,由 RawTerminal 上下文管理器恢复终端后再 sys.exit(130);未识别的转义序列应返回 None 而不是退出。
🐛 建议的修复
+class UserAbort(Exception):+ pass++
class RawTerminal: `@staticmethod`
def read_key():
if not RawTerminal.key_ready(0.02):
return None
raw = sys.stdin.buffer.raw.read(1)
if raw in (b"q", b"Q"):
- os._exit(130)+ raise UserAbort
if raw in RawTerminal._SIMPLE_KEYS:
return RawTerminal._SIMPLE_KEYS[raw]
if raw == b"\x1b":
return RawTerminal._read_escape()
if len(raw) == 1 and 32 <= raw[0] <= 126:
return raw.decode()
return None
`@staticmethod`
def _read_escape():
if not RawTerminal.key_ready(0.02):
- os._exit(130)+ raise UserAbort
nxt = sys.stdin.buffer.raw.read(1)
- if nxt != b"[" or not RawTerminal.key_ready(0.02):- os._exit(130)+ if nxt != b"[":+ return None+ if not RawTerminal.key_ready(0.02):+ return None
direction = sys.stdin.buffer.raw.read(1)
- if direction in RawTerminal._ESCAPE_DIRS:- return RawTerminal._ESCAPE_DIRS[direction]- os._exit(130)+ return RawTerminal._ESCAPE_DIRS.get(direction)在 main 的顶层捕获 UserAbort 并 sys.exit(130)。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/scan-remote around lines 60 - 85, Replace every os._exit(130) path
in RawTerminal.read_key and RawTerminal._read_escape with the custom UserAbort
exception so the RawTerminal context manager can restore terminal settings;
catch UserAbort at main’s top level and exit with status 130. Make _read_escape
return None for unrecognized or incomplete escape sequences, including Alt-key,
Home/End, function-key, and left/right-arrow sequences, while preserving
recognized escape-key mappings.
| def expanded_networks(interface): | ||
| ip = interface.ip | ||
| if ip.packed[0] == 192 and ip.packed[1] == 168: | ||
| return [ | ||
| ipaddress.ip_network(f"192.168.{segment}.0/24") | ||
| for segment in DEFAULT_192_168_SEGMENTS | ||
| ] | ||
| return [ipaddress.ip_network(f"{ip}/24", strict=False)] | ||
| prefix = ".".join(str(part) for part in ip.packed[:2]) | ||
| return [ | ||
| ipaddress.ip_network(f"{prefix}.{segment}.0/24") | ||
| for segment in DEFAULT_SCAN_SEGMENTS | ||
| ] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
expanded_networks 不再包含接口自身所在的网段。
新实现只取 IP 的前两个字节,然后固定生成 x.y.1.0/24 到 x.y.5.0/24。接口自身的第三段被丢弃。
举例:接口地址为 10.20.30.40/24 时,返回 10.20.1.0/24 … 10.20.5.0/24,其中不包含 10.20.30.0/24。同网段的远端主机因此扫不到。地址为 192.168.100.5 时同样失效。
请始终把接口所在网络加入结果。
🐛 建议的修复
def expanded_networks(interface):
ip = interface.ip
prefix = ".".join(str(part) for part in ip.packed[:2])
- return [- ipaddress.ip_network(f"{prefix}.{segment}.0/24")- for segment in DEFAULT_SCAN_SEGMENTS- ]+ networks = [ipaddress.ip_network(f"{ip}/24", strict=False)]+ networks.extend(+ ipaddress.ip_network(f"{prefix}.{segment}.0/24")+ for segment in DEFAULT_SCAN_SEGMENTS+ )+ return networks📝 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.
| def expanded_networks(interface): | |
| ip = interface.ip | |
| if ip.packed[0] == 192 and ip.packed[1] == 168: | |
| return [ | |
| ipaddress.ip_network(f"192.168.{segment}.0/24") | |
| for segment in DEFAULT_192_168_SEGMENTS | |
| ] | |
| return [ipaddress.ip_network(f"{ip}/24", strict=False)] | |
| prefix = ".".join(str(part) for part in ip.packed[:2]) | |
| return [ | |
| ipaddress.ip_network(f"{prefix}.{segment}.0/24") | |
| for segment in DEFAULT_SCAN_SEGMENTS | |
| ] | |
| def expanded_networks(interface): | |
| ip = interface.ip | |
| prefix = ".".join(str(part) for part in ip.packed[:2]) | |
| networks = [ipaddress.ip_network(f"{ip}/24", strict=False)] | |
| networks.extend( | |
| ipaddress.ip_network(f"{prefix}.{segment}.0/24") | |
| for segment in DEFAULT_SCAN_SEGMENTS | |
| ) | |
| return networks |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.script/scan-remote around lines 184 - 190, 更新 expanded_networks,使返回结果始终包含
interface.ip 所在的实际网络,并保留 DEFAULT_SCAN_SEGMENTS 生成的扫描网段;使用接口地址的网络前缀和 prefixlen
计算该网段,而不是仅根据 IP 前两段构造结果。
| if (tick_++ % write_interval_ != 0) | ||
| return; | ||
| csv_file_ << sample_count_++; | ||
| for (const auto& unit : units_) | ||
| csv_file_ << "," << *unit->value; | ||
| csv_file_ << "\n"; | ||
| if (sample_count_ % flush_interval_ == 0) | ||
| csv_file_.flush(); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
write_interval_ 或 flush_interval_ 为 0 时会触发除零。
这两个值来自参数,没有做校验。整数取模运算中除数为 0 是未定义行为,在 x86 上会产生 SIGFPE 并导致进程崩溃。负值也会让取模结果为负,从而永远不等于 0,采样与刷新都会停止。
另外 tick_ 和 sample_count_ 是 int。以 1 kHz 运行约 25 天后会发生有符号整数溢出,属于未定义行为。请改为 std::int64_t。
🛡️ 建议修改
node::param("write_interval", write_interval_);
node::param("flush_interval", flush_interval_);
++ if (write_interval_ < 1) {+ node::error("write_interval must be >= 1, got {}; fallback to 1", write_interval_);+ write_interval_ = 1;+ }+ if (flush_interval_ < 1) {+ node::error("flush_interval must be >= 1, got {}; fallback to 1", flush_interval_);+ flush_interval_ = 1;+ }- int tick_ = 0;- int sample_count_ = 0;+ std::int64_t tick_ = 0;+ std::int64_t sample_count_ = 0;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/debug/value_collector.cpp` around lines 87 - 96, 在
value collector 的参数初始化或构造流程中校验 write_interval_ 和 flush_interval_
必须为正值,避免后续取模除零及负值导致采样、刷新停止;同时将 tick_ 和 sample_count_ 的类型改为
std::int64_t,并确保相关声明和使用保持一致。
| DeformableInfantryOmni& status_; | ||
| Component& command_; | ||
| std::unique_ptr<librmcs::board::RmcsBoardLite> board_; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
两个 BottomBoard 都把 board_ 声明在设备成员之前,销毁顺序不安全。 共同根因是成员声明顺序:C++ 按声明的逆序销毁成员,因此 board_ 会在 dr16_、supercap_、各电机和 monitor_ 之后销毁。librmcs::board::RmcsBoardLite 在内部线程上分发接收回调,在 board_ 析构停止该线程之前,这些成员已被销毁,回调会访问悬垂对象。两个文件中的 TopBoard 都把 board_ 声明为最后一个成员,顺序是正确的。
rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp#L373-L373: 把std::unique_ptr<librmcs::board::RmcsBoardLite> board_;从此处移到BottomBoard的最后一个成员位置,即StatusMonitor monitor_{};之后。rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni-b.cpp#L563-L563: 做同样的移动,把board_放到StatusMonitor monitor_{};之后。
📍 Affects 2 files
rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp#L373-L373(this comment)rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni-b.cpp#L563-L563
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/deformable-infantry-omni.cpp` at line 373,
在 deformable-infantry-omni.cpp 的 BottomBoard 中,将 board_ 移到 StatusMonitor
monitor_{} 之后;在 deformable-infantry-omni-b.cpp 的 BottomBoard
中执行相同调整(分别位于指定行范围)。保持其他成员顺序不变,使 board_ 成为最后声明的成员。
| enum class ModeSwitch : uint8_t { | ||
| kUnknown = 0, | ||
| kCine = 1, | ||
| kNormal = 2, | ||
| kSport = 3, | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
mode_switch + 1 会产生越界的枚举值。
data.mode_switch 是 2 位位域,取值范围是 0..3。第 203 行加 1 后得到 1..4。ModeSwitch 只定义到 3(kSport)。当原始值为 3 时,mode_switch_ 变成 4。
RemoteControl::select_control_source() 的 switch 对值 4 不匹配任何 case,会走到 break,然后回退到 DR16。结果是遥控源仲裁在该输入下静默失效,且没有任何告警。
请确认协议中 mode_switch 原始值到模式的映射,并对未知值做显式处理。
🐛 显式映射并处理未知值
- mode_switch_ = static_cast<ModeSwitch>(data.mode_switch + 1);+ switch (data.mode_switch) {+ case 0: mode_switch_ = ModeSwitch::kCine; break;+ case 1: mode_switch_ = ModeSwitch::kNormal; break;+ case 2: mode_switch_ = ModeSwitch::kSport; break;+ default: mode_switch_ = ModeSwitch::kUnknown; break;+ }请按实际协议调整 case 的对应关系。
Also applies to: 202-203
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/device/vt13.hpp` around lines 27 - 32, 修正
RemoteControl 中 data.mode_switch 到 ModeSwitch 的映射,避免对 2 位原始值直接执行 +1 导致值
4;依据实际协议为 0..3 显式映射到对应枚举,并在未知或未定义值时进行显式处理,确保 select_control_source() 不会静默回退到
DR16。
| void can_receive_callback(const Spec::Can& can, const View::Can& data) override { | ||
| if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]] | ||
| return; | ||
| auto can_id = data.can_id; | ||
| // can2_receive_rate_counter_.record(can_id); | ||
| if (can_id == 0x143) { | ||
| gimbal_player_viewer_motor_.store_status(data.can_data); | ||
| if (can == Spec::kCans.kCan0) { | ||
| // can0_receive_rate_counter_.record(can_id); | ||
| can0_detect[can_id - 0x141] = 1; | ||
| if (can_id == 0x141) { | ||
| gimbal_top_yaw_motor_.store_status(data.can_data); | ||
| } else if (can_id == 0x143) { | ||
| gimbal_pitch_motor_.store_status(data.can_data); | ||
| } else if (can_id == 0x142) { | ||
| gimbal_bullet_feeder_.store_status(data.can_data); | ||
| } | ||
| } else if (can == Spec::kCans.kCan1) { | ||
| // can1_receive_rate_counter_.record(can_id); | ||
| friciton_detect[can_id - 0x201] = 1; | ||
| if (can_id == 0x201) { | ||
| gimbal_friction_wheels_[0].store_status(data.can_data); | ||
| } else if (can_id == 0x202) { | ||
| gimbal_friction_wheels_[1].store_status(data.can_data); | ||
| } else if (can_id == 0x203) { | ||
| gimbal_friction_wheels_[2].store_status(data.can_data); | ||
| } else if (can_id == 0x204) { | ||
| gimbal_friction_wheels_[3].store_status(data.can_data); | ||
| } | ||
| } else if (can == Spec::kCans.kCan2) { | ||
| // can2_receive_rate_counter_.record(can_id); | ||
| if (can_id == 0x203) { | ||
| putter_motor_.store_status(data.can_data); | ||
| } else if (can_id == 0x201) { | ||
| gimbal_friction_wheels_[4].store_status(data.can_data); | ||
| friciton_detect[4] = 1; | ||
| } else if (can_id == 0x202) { | ||
| gimbal_friction_wheels_[5].store_status(data.can_data); | ||
| friciton_detect[5] = 1; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
friciton_detect 与 can0_detect 未初始化,并且索引计算会越界写入。
这里有两个独立的内存安全缺陷。
第一,第 547-548 行声明 int friciton_detect[6]; 与 int can0_detect[3];。TopBoard 的构造函数初始化列表没有初始化它们,声明处也没有初始化器。第 360 行与第 366 行读取这些元素,读到的是不确定值。对比第 910 行的 int check[10] = {0};,那里做了初始化。
第二,第 461 行与第 471 行在检查 can_id 之前就用它计算索引:
can0_detect[can_id - 0x141]:数组只有 3 个元素,只有can_id为 0x141/0x142/0x143 时索引才合法。Can0 上任何其他 ID 都会越界写。例如can_id为 0x201 时索引是 192。friciton_detect[can_id - 0x201]:数组只有 6 个元素。Can1 上收到 0x1FF 或 0x300 时索引分别是 -2 和 255。
同样的模式出现在第 835 行与第 848 行的 check[can_id - 0x201]。虽然 check 有 10 个元素且已初始化,越界范围小一些,但缺陷性质相同。
请初始化数组,并在写入前做范围检查。
🐛 初始化数组并增加范围检查
int count_ = 0;
- int friciton_detect[6];- int can0_detect[3];+ int friction_detect[6] = {0};+ int can0_detect[3] = {0}; if (can == Spec::kCans.kCan0) {
- can0_detect[can_id - 0x141] = 1;+ if (can_id >= 0x141 && can_id <= 0x143)+ can0_detect[can_id - 0x141] = 1;
if (can_id == 0x141) { } else if (can == Spec::kCans.kCan1) {
- friciton_detect[can_id - 0x201] = 1;+ if (can_id >= 0x201 && can_id <= 0x206)+ friction_detect[can_id - 0x201] = 1;
if (can_id == 0x201) {BottomBoard 的第 835 行与第 848 行请做同样的范围检查。同时请修正 friciton_detect 的拼写为 friction_detect,并同步第 360、364、487、490 行的引用。
Also applies to: 546-548
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/hardware/steering-hero-little-six-friction.cpp`
around lines 455 - 493, Initialize TopBoard’s friciton_detect and can0_detect
arrays to zero at declaration or in the constructor, then rename friciton_detect
to friction_detect and update all references including the cited status checks
and callbacks. In can_receive_callback, validate each CAN ID before computing an
array index: restrict can0_detect to 0x141–0x143 and friction_detect to
0x201–0x206, while preserving motor status handling. Apply equivalent bounds
checks before check[can_id - 0x201] writes in BottomBoard.
| } else { | ||
| to_erase.push_back(event); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
verify_feedback 的 else 分支会擦除本帧未被下发的请求。
consume_one_event() 每帧只下发一个最高优先级请求(第 193 行 break)。随后 verify_feedback() 遍历整个requests_ 集合,第 229-231 行把所有 EXCHANGE_AMMO_SUPPLY_POINT、EXCHANGE_AMMO_REMOTE、EXCHANGE_HP_REMOTE 无条件加入擦除列表。
结果:如果同一帧内 requests_ 中存在多个此类事件,只有优先级最高的那个被写入 sentry_decision_field_,其余的在从未下发的情况下被丢弃。
此外,即便只有一个请求,它也只在单帧内出现于输出字段。裁判系统交互命令有发送频率上限,下游发送模块很可能采样不到这个单帧脉冲。
请只擦除本帧实际被消费的那个事件,并保持请求存在直到确认已发送。
🐛 建议修复方向
- auto update() -> void override {+ auto update() -> void override {
...
- consume_one_event();+ last_consumed_event_ = consume_one_event();
verify_feedback();
} } else {
- to_erase.push_back(event);+ // 只擦除本帧实际下发的一次性请求。+ if (last_consumed_event_ && *last_consumed_event_ == event)+ to_erase.push_back(event);
}consume_one_event() 需要返回 std::optional<SentryEvent>。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rmcs_ws/src/rmcs_core/src/referee/command/interaction/sentry_decision.cpp`
around lines 229 - 231, 修改 consume_one_event() 使其返回
std::optional<SentryEvent>,仅在实际选中并写入 sentry_decision_field_ 的事件被消费时返回该事件;更新
verify_feedback() 只将这个已消费事件加入 to_erase,并保留其他 requests_ 请求,直到后续确认发送。
变更摘要
DeformableInfantryOmniB、Sentry、SteeringHeroLittle等硬件组件。ChassisMode、SentryEvent、裁判系统状态字段和哨兵指令协议。librmcsSDK,新增mavlink、nav_msgs和ament_index_cpp依赖。rmcs-cli、autoaim-debug、上下文管理、远程扫描和容器 Agent 启动工具。.script/template/内容变更时触发构建。CsvWriter、NodeMixin、协程调度器和环形缓冲区查看接口等基础设施。