Skip to content

wip: develop initial framework#3

Closed
creeper5820 wants to merge 27 commits intomainfrom
wip/framework
Closed

wip: develop initial framework#3
creeper5820 wants to merge 27 commits intomainfrom
wip/framework

Conversation

@creeper5820
Copy link
Contributor

@creeper5820 creeper5820 commented Mar 15, 2026

RMCS 导航系统初始框架开发

项目构建与包元信息

  • 增加顶层 CMake 构建(CMake ≥3.30),导出 compile_commands,强制 C++23,默认 Release,添加常用警告选项(-Wall -Wextra -Wpedantic)。
  • 新增 package.xml 与一系列 CMake 导出/版本脚本(cmake/version.cmake、cmake/export.cmake)、配置模板(rmcs-navigationConfig.cmake.in、rmcs-navigationConfigVersion.cmake.in)。
  • 导出共享库目标 rmcs-navigation,并通过 pluginlib 导出插件描述(plugins.xml)。

核心 C++ 组件

  • 新增 Navigation 组件(src/component/component.cc):作为 rmcs_executor::Component 与 rclcpp::Node 混合实现,订阅 /cmd_vel_smoothed,维护命令超时(100ms)与输出 command_velocity;通过 PLUGINLIB_EXPORT_CLASS 导出。
  • 规划/规则子系统:
    • PlanBox(src/component/brain/plan.hh/.cc):Pimpl 实现,维持位置信息、血量/弹药,支持简易策略(to_the_home / cruise_mode)。
    • Rule(src/component/brain/rule.hh):解析 YAML 的 Limit、Battlefield、Rule 结构(血量/弹药阈值、home 与巡航点)。
  • 工具库:
    • FSM 模板(src/component/util/fsm.hh):通用枚举驱动有限状态机,支持注册/运行语义。
    • PImpl 宏(src/component/util/pimpl.hh)。
    • RclcppNode 模块(src/component/util/rclcpp/node.ccm):提供 rclcpp::Node 封装、QoS 预设、格式化日志助手与自动初始化保护。
    • 状态序列化(src/component/util/status.hh):GameStage/RobotStatus -> 字符串映射及 YAML 序列化的 Status 结构。

ROS2 启动与 Nav2 集成

  • launch/nav2.launch.py:构建 Nav2 启动描述,包含 controller_server、planner_server、behavior_server、bt_navigator、waypoint_follower、velocity_smoother、lifecycle_manager 等节点的参数重写与 remapping。
  • launch/custom.launch.py:基于运行模式(online / bag / static)动态组合 nav2 启动、map_server(全局/本地 mock)、传感器驱动(Livox / Point-LIO)、rosbag 播放与初始位姿发布。
  • launch/follow_waypoints.launch.py:声明式启动 follow_waypoints_runner 节点,接受 YAML 路径与容差参数。

Python 决策与运行时节点

  • goal_topic_bridge.py:监听 /move_base_simple/goal 与 /goal_pose,将接收目标转发为 Nav2 NavigateToPose 行为(带取消旧目标逻辑)。
  • follow_waypoints_runner.py:从 YAML 加载路径点,发布 Path,使用 Nav2 NavigateToPose action client 串行导航并基于里程计容差判定到达。
  • decision.py:决策模块骨架(dataclass、py_trees 黑板写入、行为占位实现)。

配置与资源

  • Nav2 配置(config/nav2.yaml):包含 controller、planner、行为、代价地图、速度平滑器、waypoint_follower、lifecycle 管理等大量参数(含 MPPI 控制器配置)。
  • 其他配置文件:
    • config/rule.yaml、config/custom.yaml、config/follow_waypoints.yaml、config/plan.yaml
    • 地图资源及配置:maps/rmul.yaml、maps/local_mock.yaml
  • README.md:包含安装特定 CMake 版本的示例脚本。
  • .clangd:移除若干模块/私有 gcc 专用编译标志以改善 clangd 索引(-fmodules-ts 等)。

测试与示例

  • 测试构建(test/CMakeLists.txt)与若干测试桩:
    • test/fsm.cc:有限状态机行为测试(示例实现)。
    • test/map.cc:地图生成测试占位。

文档与开发规范

  • AGENTS.md:添加开发与调试 SOP、C++ 风格、传感器适配与验证流程、常见故障处理等操作指南。

公共接口/导出变更(要点)

  • 新增公开 CMake 目标:rmcs-navigation(库与安装规则、导出配置)。
  • 新增并导出插件类:rmcs::Brain(plugins.xml)以及通过 PLUGINLIB_EXPORT_CLASS 的 rmcs::Navigation 组件。
  • 新增公开类型/节点:FollowWaypointsRunner、GoalTopicBridge(Python 节点)、多个 C++ 公共工具/类(PlanBox、Fsm、RclcppNode 等)。

@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

该PR引入了完整的ROS 2 RMCS导航组件框架,包括CMake构建配置、C++组件实现、Python决策节点、导航栈配置、启动文件、工具库和测试基础设施,建立完整的导航子系统。

Changes

Cohort / File(s) Summary
Build System Configuration
CMakeLists.txt, cmake/version.cmake, cmake/export.cmake, package.xml, plugins.xml, rmcs-navigationConfig.cmake.in, rmcs-navigationConfigVersion.cmake.in
CMake构建脚本、版本定义、目标导出、ROS 2包清单和pluginlib插件注册。
Test Framework
test/CMakeLists.txt, test/fsm.cc, test/map.cc
测试套件配置和FSM/地图生成器测试源文件。
Launch Files
launch/custom.launch.py, launch/nav2.launch.py, launch/follow_waypoints.launch.py
ROS 2启动脚本,支持多种模式(在线/离线包/静态)、导航栈配置、导航生命周期管理和路径点跟踪。
Navigation Configuration
config/nav2.yaml, config/custom.yaml, config/follow_waypoints.yaml
Nav2导航栈参数配置(规划器、控制器、成本地图)、调试设置和路点定义。
Map Assets
maps/rmul.yaml, maps/empty.yaml
占栅地图元数据配置文件。
C++ Component Core
src/component/component.cc
rmcs_navigation导航组件ROS节点实现,处理速度命令订阅、命令解析、参考状态服务和超时检测。
C++ Brain/Planning
src/component/brain/plan.hh, src/component/brain/plan.cc, src/component/brain/rule.hh
规划决策逻辑(PlanBox)、规则配置结构(Limit、Battlefield、Rule)和Pimpl实现。
C++ Utilities
src/component/util/fsm.hh, src/component/util/pimpl.hh, src/component/util/command.hh, src/component/util/status.hh, src/component/util/rclcpp/node.ccm
有限状态机模板、Pimpl宏、命令和状态结构、RclcppNode C++20模块及日志辅助方法。
Python Decision Nodes
src/decision/decision.py, src/decision/follow_waypoints_runner.py, src/decision/goal_topic_bridge.py
决策系统(数据模型、黑板)、路点跟踪动作客户端、目标话题网桥(PoseStamped→NavigateToPose转换)。
Documentation & Configuration
AGENTS.md, README.md, .clangd
开发指南、CMake安装说明、clangd配置调整。

Sequence Diagram

sequenceDiagram
    participant User
    participant LaunchSystem as Launch System
    participant NavComponent as rmcs_navigation Component
    participant Nav2Stack as Nav2 Stack
    participant Planner as Planner Server
    participant Controller as Controller Server
    participant ROS2Topics as ROS 2 Topics/Actions

    User->>LaunchSystem: Execute custom.launch.py
    LaunchSystem->>NavComponent: Start Navigation Component
    LaunchSystem->>Nav2Stack: Start Nav2 (bt_navigator, controller, planner)
    
    NavComponent->>ROS2Topics: Subscribe /cmd_vel_smoothed
    NavComponent->>ROS2Topics: Subscribe /{node_name}/command
    NavComponent->>ROS2Topics: Expose /referee_status Service
    
    User->>ROS2Topics: Publish /move_base_simple/goal (PoseStamped)
    ROS2Topics->>NavComponent: Goal received
    NavComponent->>Nav2Stack: Send NavigateToPose action goal
    
    Nav2Stack->>Planner: Request path plan
    Planner->>Nav2Stack: Return planned path
    Nav2Stack->>Controller: Execute trajectory
    Controller->>ROS2Topics: Publish /cmd_vel_smoothed
    
    ROS2Topics->>NavComponent: Update cmd_vel_smoothed
    NavComponent->>NavComponent: Monitor timeout (100ms)
    NavComponent->>NavComponent: Update chassis/gimbal velocity
    
    Controller->>Nav2Stack: Motion complete
    Nav2Stack->>ROS2Topics: Emit action result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wip: develop fsm #2: 两个PR均修改/新增项目的FSM实现和测试(引入rmcs::Fsm模板和test/fsm.cc),在代码级别直接相关。

Poem

🐰 Navigation pathways now take flight,
FSM states guide every sight,
Launch files dance with modes so bright,
ROS 2 nodes coordinate the might—
A rabbit's joy: the system's light!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 标题 'wip: develop initial framework' 宽泛且缺乏具体性,未能清晰总结此大规模初始框架提交的主要变更内容。 建议使用更具体的标题,如 'Init: rmcs-navigation ROS 2 package with Nav2 integration and component framework' 或类似的描述,以明确包含的主要组件和功能。
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wip/framework

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

@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: 7

Note

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

🟠 Major comments (14)
test/map.cc-1-4 (1)

1-4: ⚠️ Potential issue | 🟠 Major

缺少头文件 core/map/generator.hh 将导致编译失败。

该头文件在仓库中不存在,编译时会报错。建议:

  1. 添加缺失的头文件实现 src/core/map/generator.hh
  2. 或移除此 include 直到实现完成

测试用例当前为空,建议添加 // TODO: implement test 注释以明确后续工作。

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

In `@test/map.cc` around lines 1 - 4, The test includes a non-existent header
"core/map/generator.hh" which breaks build; either add the missing header
implementation (create src/core/map/generator.hh with the
declarations/definitions needed) or remove the include from test/map.cc until
the generator is implemented; also mark the empty test TEST(map, generate) with
a comment like "// TODO: implement test" so intent is clear for future work.
src/brain/plan.hh-19-23 (1)

19-23: ⚠️ Potential issue | 🟠 Major

潜在的数据竞争:update_information 缺少线程同步。

根据 src/brain/plan.cc 的上下文,information_() 返回对共享 Information 成员的非 const 引用,而 do_plan_() 读取相同的字段。若 update_information() 从多线程调用(例如多线程执行器),会产生数据竞争。

如果此组件保证在单线程执行器中运行,建议添加注释说明此约束。否则,考虑添加互斥锁保护:

🔒 示例:添加互斥锁保护

Impl 中添加 std::mutex,并在 update_information 中加锁:

// In Impl struct
std::mutex mtx;

// In update_information
template <std::invocable<Information&> F>
auto update_information(F&& function) noexcept -> void {
    std::lock_guard lock(pimpl->mtx);  // 需要通过 pimpl 访问
    std::forward<F>(function)(information_());
    do_plan_();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain/plan.hh` around lines 19 - 23, update_information currently mutates
information_() then calls do_plan_() without synchronization which can cause
races; either document that this component is single-thread-only or add mutex
protection: add a std::mutex mtx to Impl (accessible via pimpl) and acquire a
lock_guard in update_information before invoking the passed function and calling
do_plan_; keep the lock scope spanning both the mutation (information_()) and
the subsequent do_plan_() to prevent concurrent access.
src/util/pimpl.hh-11-14 (1)

11-14: ⚠️ Potential issue | 🟠 Major

noexcept 构造函数与可能抛出异常的 Impl 不匹配。

宏将 CLASS()~CLASS() 声明为 noexcept,但根据 src/brain/plan.cc:24-26Impl::Impl()noexcept 构造函数内部执行 std::unordered_map 插入操作,这可能因哈希计算或内存分配而抛出异常。若 std::make_unique<Impl>() 抛出异常,将触发 std::terminate

建议移除宏中构造函数的 noexcept 限定,或确保所有 Impl 构造函数真正不抛出异常。

🔧 建议修复
 `#define` RMCS_PIMPL_DEFINITION(CLASS)                                                               \
 public:                                                                                            \
-    explicit CLASS() noexcept;                                                                     \
+    explicit CLASS();                                                                              \
     ~CLASS() noexcept;                                                                             \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/pimpl.hh` around lines 11 - 14, The macro RMCS_PIMPL_DEFINITION
currently marks the generated constructor and destructor as noexcept but
Impl::Impl() (see Impl::Impl and its use via std::make_unique<Impl>()) can throw
(e.g., unordered_map insert/alloc), so remove the noexcept from the CLASS()
declaration in RMCS_PIMPL_DEFINITION (or alternatively ensure every Impl's
constructor is noexcept and handles allocations safely); update the macro to
declare explicit CLASS() without noexcept so std::make_unique<Impl>() exceptions
propagate instead of causing std::terminate.
src/util/fsm.hh-90-94 (1)

90-94: ⚠️ Potential issue | 🟠 Major

spin_once() 在状态未注册时会崩溃。

如果 current_state 对应的状态未通过 use() 注册,states_map.at(...) 返回的 unique_ptr 为空,get() 返回 nullptr,随后调用 current_event->on_begin() 将导致空指针解引用。

建议添加运行时检查或断言:

🛡️ 建议修复:添加空指针检查
     auto spin_once() -> bool {
         if (current_event == nullptr) {
-            current_event = states_map.at(std::to_underlying(current_state)).get();
+            auto& state_ptr = states_map.at(std::to_underlying(current_state));
+            assert(state_ptr && "State handler not registered");
+            current_event = state_ptr.get();
             current_event->on_begin();
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/fsm.hh` around lines 90 - 94, spin_once() currently dereferences a
nullptr when current_state wasn't registered via use(); modify spin_once() to
fetch the pointer from states_map (using
states_map.at(std::to_underlying(current_state))) and check that the returned
unique_ptr.get() is non-null before assigning to current_event and calling
current_event->on_begin(); if the pointer is null, either assert/throw a clear
runtime_error indicating the unregistered state or return false/handle
gracefully so no null dereference occurs (refer to symbols: spin_once,
current_event, states_map, current_state, use, on_begin).
src/brain/rule.hh-11-14 (1)

11-14: ⚠️ Potential issue | 🟠 Major

YAML 解析缺少错误处理,可能导致节点崩溃。

config["health"].as<std::uint16_t>() 在键缺失或类型不匹配时会抛出 YAML::BadConversion 异常。由于调用链(brain.ccPlanBox::set_ruleRule 构造函数)中没有 try-catch,任何 YAML 配置错误都会导致节点意外终止。

建议添加错误处理或使用带默认值的 as<T>(default) 方法:

🛡️ 建议修复:添加带默认值的安全解析
 explicit Limit(const YAML::Node& config) {
-    health = config["health"].as<std::uint16_t>();
-    bullet = config["bullet"].as<std::uint16_t>();
+    if (config["health"]) {
+        health = config["health"].as<std::uint16_t>();
+    }
+    if (config["bullet"]) {
+        bullet = config["bullet"].as<std::uint16_t>();
+    }
 }

或使用带默认值的版本:

health = config["health"].as<std::uint16_t>(0);
bullet = config["bullet"].as<std::uint16_t>(0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain/rule.hh` around lines 11 - 14, The Limit constructor currently
calls config["health"].as<std::uint16_t>() and
config["bullet"].as<std::uint16_t>() without handling YAML::BadConversion or
missing keys; update the Limit(const YAML::Node& config) constructor to safely
parse these values by either using the as<T>(default) overload (e.g.,
as<std::uint16_t>(0)) for health and bullet or wrapping the parses in a
try-catch that handles YAML::BadConversion and provides sane defaults or
rethrows a clearer exception; ensure you modify the Limit constructor (and
adjust any callers like Rule construction if you choose to propagate errors) so
missing or wrong-typed YAML nodes no longer crash the process.
src/app/test.ccm-13-18 (1)

13-18: ⚠️ Potential issue | 🟠 Major

run() 目前为空,Test 实际不执行任何状态机逻辑

当前实现既没有注册状态处理器,也没有驱动 fsm 进入事件循环;这个导出的类在运行时等同于空操作。建议在构造阶段完成 use<...> 注册,并在 run() 中循环 spin_once()END

💡 可参考的最小修复方向
 export class Test final {
 private:
     enum class Status { TEST, END };

     rmcs::Fsm<Status> fsm{Status::TEST};
+    auto setup() -> void {
+        fsm.use<Status::TEST>(
+            [] {},
+            [] { return Status::END; });
+        fsm.use<Status::END>(
+            [] {},
+            [] { return Status::END; });
+    }

 public:
+    Test() { setup(); }
     auto run() -> void {
-        //
+        while (!fsm.spin_once()) {
+        }
     }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/test.ccm` around lines 13 - 18, The Test export currently does
nothing because no state handlers are registered and run() is empty; in the
class constructor register handlers using fsm.use<...>(...) for each state
(e.g., TEST and others) and any transition handlers you need, then implement
run() to repeatedly call fsm.spin_once() (or the Fsm method that advances one
step) in a loop until the state becomes Status::END (or !fsm.running()); ensure
you reference rmcs::Fsm<Status> fsm, the use<T>() registrations, and
spin_once()/Status::END so the state machine is driven to completion.
test/fsm.cc-5-45 (1)

5-45: ⚠️ Potential issue | 🟠 Major

使用 ament_add_gtest 注册但采用自定义 main() 而非 TEST 用例的结构

test/fsm.cc 采用独立可执行程序的结构(自定义 main() 函数),但在 CMakeLists.txt 中使用 ament_add_gtest(fsm_test fsm.cc) 注册。ament_add_gtest 期望文件包含 TEST()TEST_F() 宏,这导致框架无法正确识别和执行测试,容易出现断言数为 0 的情况。建议改写为标准 Google Test 用例并移除自定义 main() 函数。

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

In `@test/fsm.cc` around lines 5 - 45, The current test defines a standalone
main() which conflicts with using ament_add_gtest; remove the custom main() and
convert the code into a Google Test case (e.g., TEST(FsmTest, RunsToEnd) or a
TEST_F fixture) that constructs rmcs::Fsm{Status::A}, registers the states via
fsm.use<Status::A/B/C>(), checks fsm.fully_registered()/registered_count() with
EXPECT/ASSERT, then runs the event loop using the existing
b_event_count/b_event_limit and fsm.spin_once() inside the test body and assert
the expected outcome (e.g., final state reached or spin loop exited). Ensure no
standalone main() remains so ament_add_gtest can discover and run the test.
src/util/rclcpp/node.ccm-3-5 (1)

3-5: ⚠️ Potential issue | 🟠 Major

添加缺失的标准库头文件以确保编译可移植性

代码使用了 std::string_view(第17行)、std::ranges::all_ofstd::ranges::empty(第18、24行)、std::string(第45行)、std::formatstd::format_string(第50、55、60行)、std::forward(第51、56、61行),但未直接包含对应的标准库头文件。这些特性的声明可能通过 rclcpp 头文件的传递包含隐式引入,但依赖这种间接包含会影响代码可移植性。需要补齐直接依赖。

📦 建议补充头文件
 module;

 `#include` <rclcpp/executors.hpp>
 `#include` <rclcpp/node.hpp>
+#include <format>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <utility>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/rclcpp/node.ccm` around lines 3 - 5, The file uses std::string_view,
std::ranges::all_of, std::ranges::empty, std::string, std::format,
std::format_string and std::forward but does not directly include their standard
headers; add the appropriate headers (e.g., <string_view>, <string>, <utility>,
<format>, and <ranges> or <algorithm> as needed) at the top of node.ccm so these
symbols are declared explicitly rather than relying on transitive includes from
rclcpp; ensure the includes appear before any code that references
std::format/std::format_string, std::forward, std::string_view, std::string, and
std::ranges utilities.
src/brain/plan.cc-16-27 (1)

16-27: ⚠️ Potential issue | 🟠 Major

plan_mapdo_plan() 返回值未被使用

plan_map 中注册的 lambdas 从未被调用,do_plan() 返回的计划名称被 do_plan_() 丢弃(返回类型为 void)。这导致规划逻辑无实际效果。

🐛 建议执行计划动作
-auto PlanBox::do_plan_() noexcept -> void { pimpl->do_plan(); }
+auto PlanBox::do_plan_() noexcept -> void {
+    auto plan_name = pimpl->do_plan();
+    if (auto it = pimpl->plan_map.find(plan_name); it != pimpl->plan_map.end()) {
+        it->second();
+    }
+}

Also applies to: 62-62

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

In `@src/brain/plan.cc` around lines 16 - 27, The registered lambdas in
Impl::plan_map (keys to_the_home and cruise_mode) are never invoked and
do_plan()'s result is discarded by do_plan_() which is void; update the flow so
selecting a plan executes its corresponding action: modify do_plan_() (or
do_plan()) to return the chosen plan name (or directly execute) and then lookup
plan_map[plan] and invoke the stored std::function; ensure Impl::plan_map
entries (to_the_home, cruise_mode) contain real handlers and that callers of
do_plan_ use its return value or let do_plan_ call plan_map.at(...)() to perform
the action.
src/main.cc-10-21 (1)

10-21: ⚠️ Potential issue | 🟠 Major

修复拼写错误并处理 ROS 节点自旋

第 17 行存在拼写错误:"Uncathed" 应为 "Uncaught"。

此外,app_node 被创建但从未调用 spin(),这意味着如果应用中添加了 ROS 订阅或服务,它们将无法处理回调。目前 app::Test::run() 为空实现,但后续功能需要确保节点能处理 ROS 消息。

拼写错误修复
     auto always_run = [&]<class App> {
         try {
             auto app = App{};
             app.run();
         } catch (const std::exception& e) {
-            app_node.error("Uncathed exception while app running, shutdown now");
+            app_node.error("Uncaught exception while app running, shutdown now");
             app_node.error("  {}", e.what());
             app_node.shutdown();
         }
     };

需要添加 executor 来处理 ROS 消息,可以在 app.run() 内部实现自旋逻辑或在 main() 中为节点创建 executor。

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

In `@src/main.cc` around lines 10 - 21, Fix the typo in the error message and
ensure the ROS node spins so callbacks run: change the string passed to
app_node.error from "Uncathed exception while app running, shutdown now" to
"Uncaught exception while app running, shutdown now", and add executor/spin
logic so the created app_node actually processes ROS callbacks — either
integrate spinning into App::run (e.g., accept or create a rclcpp::Executor and
call spin/poll inside run) or create a rclcpp::executors::Executor in main
alongside app_node and call spin_some()/spin() while the app is running; update
the catch block to shutdown the executor/node as needed (references: app_node,
always_run, App, app.run, App::run/app::Test::run).
scripts/follow_waypoints_runner.py-27-28 (1)

27-28: ⚠️ Potential issue | 🟠 Major

这里的 yaw 反算在有 roll/pitch 时会失真。

_quaternion_to_yaw() 只使用了 z/w,把 x/y 分量直接丢掉了。默认里程计话题 /aft_mapped_to_init 这类 3D 姿态一旦带有滚转或俯仰,yaw_error 就会算错,进而提前或延后触发 waypoint 到达判定。建议改成完整四元数公式。

🧮 建议修改
-def _quaternion_to_yaw(z: float, w: float) -> float:
-    return math.atan2(2.0 * w * z, 1.0 - 2.0 * z * z)
+def _quaternion_to_yaw(x: float, y: float, z: float, w: float) -> float:
+    siny_cosp = 2.0 * (w * z + x * y)
+    cosy_cosp = 1.0 - 2.0 * (y * y + z * z)
+    return math.atan2(siny_cosp, cosy_cosp)
-        current_yaw = _quaternion_to_yaw(
-            self.current_pose.orientation.z,
-            self.current_pose.orientation.w,
-        )
+        current_yaw = _quaternion_to_yaw(
+            self.current_pose.orientation.x,
+            self.current_pose.orientation.y,
+            self.current_pose.orientation.z,
+            self.current_pose.orientation.w,
+        )

Also applies to: 209-212

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

In `@scripts/follow_waypoints_runner.py` around lines 27 - 28, _quaternion_to_yaw
currently computes yaw using only z and w, which breaks when roll/pitch are
nonzero; replace it with the full quaternion-to-yaw formula that uses all
components (x, y, z, w): compute yaw as atan2(2*(w*z + x*y), 1 - 2*(y*y + z*z)).
Update the function signature and callers so they pass x and y as well (and
adjust any code computing yaw_error), and apply the same fix to the other
occurrence referenced (the block around the other quaternion-to-yaw usage at
lines 209-212).
scripts/follow_waypoints_runner.py-170-175 (1)

170-175: ⚠️ Potential issue | 🟠 Major

失败分支当前会让 runner 永久空转。

goal 被拒绝或结果非成功时,这里只清掉 goal_in_flight 并记录日志,没有 retry、skip,也没有 finished 终止。而 _ensure_started() 只会在 current_index < 0 时再次启动流程,所以第一个失败 waypoint 之后节点就不会再前进了。这里至少要明确一种失败策略。

Also applies to: 194-197

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

In `@scripts/follow_waypoints_runner.py` around lines 170 - 175, The failure
branch leaves the runner stuck because it only clears goal_in_flight and logs,
so after a rejected/failed waypoint the loop never advances; update the handler
for rejected goals (the block checking goal_handle.accepted) and the analogous
result-non-success block to implement a clear failure policy: either mark the
run finished (set finished = True) or advance to the next waypoint by
incrementing current_index and invoking the routine that starts/dispatches the
next goal (use _ensure_started() or the existing dispatch/advance helper used
elsewhere), and optionally reset/initialize a per-waypoint retry counter if you
want retries; ensure goal_in_flight is False, update current_index or finished
consistently, and call the same restart/advance logic in both failure locations
so the runner does not permanently stall.
scripts/follow_waypoints_runner.py-104-107 (1)

104-107: ⚠️ Potential issue | 🟠 Major

不要把缺失坐标的 waypoint 静默补成 0.0

dict 形式的 waypoint 一旦漏了 xy,这里会直接把目标改到 (0, 0) 附近继续运行。配置里只要有个键名拼写错误,就会把机器人发到错误位置。x/y 应该是必填项,只有 yaw 适合保留默认值。

🧩 建议修改
             elif isinstance(point, dict):
-                x = float(point.get("x", 0.0))
-                y = float(point.get("y", 0.0))
+                if "x" not in point or "y" not in point:
+                    raise RuntimeError(f"Waypoint is missing x/y: {point}")
+                x = float(point["x"])
+                y = float(point["y"])
                 yaw = float(point.get("yaw", 0.0))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/follow_waypoints_runner.py` around lines 104 - 107, The current
branch that handles dict waypoints silently substitutes missing x/y to 0.0;
change it to validate x and y as required fields instead of defaulting them:
when isinstance(point, dict) check for presence of "x" and "y" (and that they
are convertible to float), raise a clear error (or log and abort) naming the
offending waypoint (use the loop index or the point content) if either is
missing or invalid, while still allowing "yaw" to default to 0.0; update the
code paths around the variables x, y, yaw so x and y are only set from
point.get(...) after presence is confirmed.
launch/custom.launch.py-188-200 (1)

188-200: ⚠️ Potential issue | 🟠 Major

在模式分支内部延迟解析可选依赖的 launch 路径。

generate_launch_description() 在第188-189行立即解析 livox_ros_driver2point_lio 的share目录。但根据 MODE_MAPstatic模式根本不需要这些包(use_livox_driver=False, use_point_lio=False),而 bag 模式也不需要 livox_ros_driver2use_livox_driver=False)。实际使用这些路径的地方是在 _build_mode_actions() 函数内的条件分支中(第102行和第110行)。

当前的设计会导致:用户运行 mode=staticmode=bag 时,如果机器上没有安装对应的可选包,launch 会在第188-189行直接失败,完全无法启动,而不是按需延迟解析这些依赖。建议将这些路径解析移到 _build_mode_actions() 函数内,改为在实际需要使用时再解析。

此外,第220行中硬编码的bag默认路径 /workspaces/data/bag/battlefield_1 是开发者本地路径,降低了可移植性,建议使用相对路径或从配置文件加载。

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

In `@launch/custom.launch.py` around lines 188 - 200,
generate_launch_description() eagerly calls get_package_share_directory for
livox_ros_driver2 and point_lio (livox_share/point_lio_share and
livox_launch/point_lio_launch) even when MODE_MAP indicates
use_livox_driver/use_point_lio are false; move the package/share resolution and
os.path.join creation for livox_launch and point_lio_launch into the conditional
branches inside _build_mode_actions() so those packages are only resolved when
use_livox_driver or use_point_lio is true, and clean up the now-unused top-level
livox_share/point_lio_share variables; also replace the hardcoded bag default
path (currently a literal like "/workspaces/data/bag/battlefield_1") with a
relative-path default or load it from a configuration/launch argument so the
default is portable.
🟡 Minor comments (5)
src/core/environment/voxel-map.ccm-5-9 (1)

5-9: ⚠️ Potential issue | 🟡 Minor

C++20 模块编译配置已禁用

CMakeLists.txt 中的整个 C++20 模块编译配置(第 31-61 行)当前被注释掉,包括模块文件的文件集定义。这导致 src/core/environment/voxel-map.ccm 及其他 .ccm 模块文件无法被编译。当前项目仅通过 ${PROJECT_NAME}_brain 库目标使用传统的 .cc 源文件进行构建。

若要启用此模块及其他 C++20 模块的编译,需取消 CMakeLists.txt 第 31-61 行的注释。

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

In `@src/core/environment/voxel-map.ccm` around lines 5 - 9, CMake currently has
the C++20 module build configuration commented out, so module files like
voxel-map.ccm (which defines rmcs::VoxelMap) are never compiled; to fix this,
re-enable the C++20 module build by uncommenting the module-related block in
CMakeLists (the block that defines the file set for .ccm files and the module
handling), ensure the project sets C++20 modules support (e.g., enable the
experimental module support or target_compile_features/target_compile_options
for cxx_std_20 as used by your build) and add voxel-map.ccm (and other .ccm
files) to the target_sources or file set so rmcs::VoxelMap in voxel-map.ccm is
built into the ${PROJECT_NAME}_brain target.
config/custom.yaml-6-8 (1)

6-8: ⚠️ Potential issue | 🟡 Minor

硬编码的绝对路径影响可移植性。

bag.path 使用了硬编码的绝对路径 /workspaces/data/bag/520双创飙车事件/,这会导致:

  1. 在其他开发环境或用户系统上无法直接使用
  2. 路径中的中文字符可能在某些系统上引发编码问题

建议使用相对路径、环境变量或在 .gitignore 中忽略此文件并提供 custom.yaml.example 模板。

💡 建议的配置模板方案

创建 config/custom.yaml.example:

debug_goal_bridge:
  ros__parameters:
    use_sim_time: false
    goal_frame: base_link

bag:
  path: /path/to/your/bag/directory/
  use_clock: true

并在 .gitignore 中添加:

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

In `@config/custom.yaml` around lines 6 - 8, The hard-coded absolute path in
config/custom.yaml (bag.path) reduces portability and risks encoding issues;
replace it by documenting a placeholder or environment-driven value: remove the
literal "/workspaces/..." and set bag.path to a relative or templated
placeholder (e.g., ./bags/ or ${BAG_PATH}) in config/custom.yaml.example, add
config/custom.yaml to .gitignore, and update any code that reads bag.path to
fall back to an environment variable (BAG_PATH) or the relative default if not
set; ensure the unique key "bag.path" is updated in the example and the repo
ignore so developers populate their own local config.
rmcs-navigationConfig.cmake.in-1-13 (1)

1-13: ⚠️ Potential issue | 🟡 Minor

rmcs-navigationConfig.cmake.in 模板文件未被使用。

该模板文件存在但未被处理。确认的问题:

  1. @CONF_INCLUDE_DIRS@ 占位符从未定义,整个代码库中没有 configure_file() 调用来处理此模板
  2. 模板引用的 export_rmcs-navigation.cmake 文件生成过程不明确
  3. 在此 ament_cmake 项目中,配置文件生成应通过 ament_package() 自动处理,但该 .in 模板并未被正确集成

建议移除这些未使用的 .in 文件(rmcs-navigationConfig.cmake.in 和 rmcs-navigationConfigVersion.cmake.in),或在 CMakeLists.txt 中补充适当的配置(如设置 CONF_INCLUDE_DIRS 变量并调用 configure_file())。

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

In `@rmcs-navigationConfig.cmake.in` around lines 1 - 13, The
rmcs-navigationConfig.cmake.in template is never processed because
`@CONF_INCLUDE_DIRS`@ is undefined and no configure_file() is invoked; either
remove the unused templates (rmcs-navigationConfig.cmake.in and
rmcs-navigationConfigVersion.cmake.in) or integrate them into CMake: define
CONF_INCLUDE_DIRS, call configure_file() to generate rmcs-navigationConfig.cmake
from rmcs-navigationConfig.cmake.in, and ensure the export_rmcs-navigation.cmake
target generation is wired into the install/export steps (or rely on
ament_package() to produce the proper config files); update CMakeLists.txt
accordingly to call configure_file(), set the CONF_INCLUDE_DIRS value, and
produce/export export_rmcs-navigation.cmake so the IMPORTED targets referenced
in the template are valid.
scripts/debug_goal_bridge.py-130-142 (1)

130-142: ⚠️ Potential issue | 🟡 Minor

finally 块中的 return 会抑制异常

根据静态分析提示(Ruff B012),finally 块中的 return(第 141 行)会抑制 run_goal(next_goal) 可能抛出的任何异常。

🐛 建议重构以避免在 finally 中 return
 def _run_goal_thread(self, msg: PoseStamped) -> None:
+    current_msg = msg
+    while True:
         try:
-            self.run_goal(msg)
-        finally:
-            with self._lock:
-                self._current_compute_handle = None
-                self._current_follow_handle = None
-                next_goal = self._pending_goal
-                self._pending_goal = None
-                if next_goal is None:
-                    self._busy = False
-                    return
-        self.run_goal(next_goal)
+            self.run_goal(current_msg)
+        finally:
+            with self._lock:
+                self._current_compute_handle = None
+                self._current_follow_handle = None
+                next_goal = self._pending_goal
+                self._pending_goal = None
+                if next_goal is None:
+                    self._busy = False
+                    break
+                current_msg = next_goal
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug_goal_bridge.py` around lines 130 - 142, The finally block in
_run_goal_thread currently contains a return which can suppress exceptions;
refactor so the finally only does state mutation under self._lock (clearing
_current_compute_handle, _current_follow_handle and capturing next_goal into a
local var while setting _pending_goal to None) and does not return; after the
try/finally, check the captured next_goal: if None set self._busy = False inside
the lock or via a safe path, otherwise call self.run_goal(next_goal) outside the
finally/lock; use the existing symbols _run_goal_thread, run_goal, _lock,
_current_compute_handle, _current_follow_handle, _pending_goal, and _busy to
locate and implement the change.
launch/custom.launch.py-218-221 (1)

218-221: ⚠️ Potential issue | 🟡 Minor

避免把开发机本地路径当成 bag 默认值。

/workspaces/data/bag/battlefield_1 是环境相关的绝对路径。mode=bag 时如果用户没显式覆盖参数,默认值大概率就是不存在的路径,体验上等同于开箱即坏。更稳妥的做法是默认留空,并在 bag 模式下显式校验。

🧩 建议修改
-    bag_path_default = str(
-        bag_config.get("path", "/workspaces/data/bag/battlefield_1")
-    )
+    bag_path_default = str(bag_config.get("path", ""))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/custom.launch.py` around lines 218 - 221, The code currently sets
bag_path_default to a hardcoded dev-specific absolute path via
bag_config.get("path", "/workspaces/data/bag/battlefield_1"); change this to use
an empty default (e.g., "" or None) instead of the absolute path, and add
explicit validation when running in bag mode (check the runtime mode, e.g., mode
== "bag") to raise a clear error or prompt if bag_path is empty or the path does
not exist; update references to bag_config and bag_path_default and ensure the
validation occurs before any code that assumes the bag path exists.
🧹 Nitpick comments (15)
src/core/environment/voxel-map.ccm (1)

1-3: 不必要的全局模块片段声明

module; 用于声明全局模块片段(Global Module Fragment),其目的是在模块声明之前放置预处理指令(如 #include)。当前代码中 module;export module 之间没有任何内容,可以直接移除。

♻️ 建议的修改
-module;
-
 export module core.env.voxel_map;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/environment/voxel-map.ccm` around lines 1 - 3,
当前文件在顶端保留了不必要的全局模块片段标记 "module;" 而随后直接声明了导出模块 "export module
core.env.voxel_map;"; 请删除孤立的 "module;" 行,保留并使用 "export module
core.env.voxel_map;" 作为模块声明以清理代码并避免冗余全局模块片段声明。
AGENTS.md (1)

38-41: 代码块缺少语言标识符。

该代码块展示的是 C++ 示例代码,应指定语言类型以启用语法高亮。

📝 建议修复
-```
+```cpp
 # 使用大括号构造和类型后置
 auto var = T { };
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @AGENTS.md around lines 38 - 41, The fenced code block showing the C++
example ("auto var = T { };") lacks a language identifier; update the opening
fence from tocpp so the block becomes cpp followed by the line "auto var = T { };" and the closing to enable C++ syntax highlighting in the
AGENTS.md document.


</details>

</blockquote></details>
<details>
<summary>src/brain/plan.hh (1)</summary><blockquote>

`1-3`: **缺少 `<tuple>` 和 `<cstdint>` 头文件。**

`goal_position()` 返回 `std::tuple<double, double>`,`Information` 使用 `std::uint16_t`,但未显式包含 `<tuple>` 和 `<cstdint>` 头文件。虽然这些可能通过 `<yaml-cpp/yaml.h>` 间接包含,但依赖传递包含是脆弱的。


<details>
<summary>📦 建议添加必要的头文件</summary>

```diff
 `#pragma` once
+#include <cstdint>
+#include <tuple>
 `#include` "util/pimpl.hh"
 `#include` <yaml-cpp/yaml.h>
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/brain/plan.hh` around lines 1 - 3, Add explicit includes for <tuple> and
<cstdint> at the top of the header: goal_position() returns
std::tuple<double,double> and the Information type uses std::uint16_t, so
include those headers to avoid relying on transitive includes; update the
include block that currently contains util/pimpl.hh and <yaml-cpp/yaml.h> to
also `#include` <tuple> and `#include` <cstdint>.
```

</details>

</blockquote></details>
<details>
<summary>src/brain/rule.hh (1)</summary><blockquote>

`1-3`: **缺少 `<tuple>` 和 `<vector>` 头文件。**

`Battlefield` 使用 `std::tuple` 和 `std::vector`,但未显式包含相应头文件。


<details>
<summary>📦 建议添加必要的头文件</summary>

```diff
 `#pragma` once
 `#include` <cstdint>
+#include <tuple>
+#include <vector>
 `#include` <yaml-cpp/yaml.h>
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/brain/rule.hh` around lines 1 - 3, Header file is missing includes for
types used by Battlefield: add explicit includes for <tuple> and <vector> so
std::tuple and std::vector are available; update the top of rule.hh (where YAML
and cstdint are included) to also include <tuple> and <vector> to resolve
missing type declarations used by Battlefield.
```

</details>

</blockquote></details>
<details>
<summary>config/nav2.yaml (1)</summary><blockquote>

`3-3`: **`use_sim_time` 硬编码为 `true`,可能影响实机部署。**

整个配置文件中 `use_sim_time: true` 被硬编码在多个节点配置中。在实机部署时需要设置为 `false`。建议通过 launch 文件参数化此配置,或创建单独的实机配置文件。

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@config/nav2.yaml` at line 3, The config currently hardcodes use_sim_time:
true which breaks real-robot runs; change it to be driven by a launch arg or
separate real-world params instead of a hardcoded value. Update your launch file
to declare and expose a boolean launch argument (e.g., use_sim_time) and pass
that into the Nav2 parameter override when composing nodes, or create an
alternate parameters file for real hardware and switch files based on a launch
flag; ensure all occurrences of use_sim_time in the Nav2 params are replaced
with the launch-provided value or the alternate file so the real-robot
deployment can set it to false.
```

</details>

</blockquote></details>
<details>
<summary>src/util/pimpl.hh (1)</summary><blockquote>

`4-9`: **抑制未使用 include 警告的方式不够优雅。**

此辅助函数仅为消除未使用 `<memory>` 的警告而存在。由于宏 `RMCS_PIMPL_DEFINITION` 展开时会使用 `std::unique_ptr`,包含该头文件的用户代码自然会使用 `<memory>`,此 workaround 应无必要。若确实需要,考虑使用 `[[maybe_unused]]` 属性或在文档中说明头文件的必要依赖。

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/util/pimpl.hh` around lines 4 - 9, The helper function
pimpl::internal::use_memory_header that assigns to std::ignore to silence an
unused <memory> include should be removed; instead either rely on the fact
RMCS_PIMPL_DEFINITION uses std::unique_ptr (so <memory> is a real dependency)
or, if you must keep a no-op symbol, mark it with [[maybe_unused]] to avoid
side-effecty code. Update/remove use_memory_header and add a short comment in
the header stating that RMCS_PIMPL_DEFINITION requires <memory> (or annotate the
no-op with [[maybe_unused]] if you prefer to keep it).
```

</details>

</blockquote></details>
<details>
<summary>src/util/fsm.hh (1)</summary><blockquote>

`28-28`: **枚举值必须从 0 开始且连续,否则数组大小计算错误。**

`std::to_underlying(StateKey::END)` 假设枚举值从 0 开始且连续递增。如果用户定义的枚举有间隙或显式值(例如 `A = 0, B = 10, END = 11`),`states_map` 大小将不正确,可能导致越界访问。

建议在文档或注释中明确此约束,或添加编译时检查。


<details>
<summary>📝 建议:添加使用约束注释</summary>

```diff
 template <typename state_type>
 class Fsm {
     static_assert(std::is_enum_v<state_type>, "state_type must be an enum");
     static_assert(requires { state_type::END; }, "state_type::END is required");
+    // Note: state_type enum values must be sequential starting from 0,
+    // with END as the last value. Example:
+    // enum class State { A, B, C, END };  // OK: A=0, B=1, C=2, END=3
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/util/fsm.hh` at line 28, The code assumes StateKey values are 0-based and
contiguous for states_map sizing; add a compile-time check and a short comment
to enforce this. Implement a constexpr validator (e.g.,
validate_contiguous_enum<StateKey>()) and a static_assert that
std::to_underlying(StateKey::END) equals the number of contiguous values
starting at 0 (and/or that the first valid enum value is 0), then keep the
existing std::array<StateUnique, std::to_underlying(StateKey::END)> states_map;
and add a brief comment above mentioning the 0-based contiguous enum constraint;
reference the StateKey enum and the states_map declaration when adding the
check.
```

</details>

</blockquote></details>
<details>
<summary>launch/follow_waypoints.launch.py (1)</summary><blockquote>

`38-62`: **应改用 `launch_ros.actions.Node` 替代 `ExecuteProcess` 启动此 ROS 节点**

`follow_waypoints_runner.py` 是一个正规的 ROS 节点(继承自 `rclpy.node.Node`),使用参数声明机制。当前方式虽可运行,但需手动拼接命令行参数。改为 `Node` 可直接通过 `parameters` 属性传递,参数管理、日志处理和生命周期行为更标准,同时与项目其他启动文件(如 `nav2.launch.py`)保持一致。

示例参考:
```python
from launch_ros.actions import Node

Node(
    package="rmcs-navigation",
    executable="follow_waypoints_runner.py",
    parameters=[
        {"config_file": LaunchConfiguration("follow_waypoints_file")},
        {"odom_topic": LaunchConfiguration("odom_topic")},
        {"distance_tolerance": LaunchConfiguration("distance_tolerance")},
        {"yaw_tolerance": LaunchConfiguration("yaw_tolerance")},
    ],
    output="screen",
)
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@launch/follow_waypoints.launch.py` around lines 38 - 62, 将当前用 ExecuteProcess
启动 follow_waypoints_runner.py 的代码替换为使用 launch_ros.actions.Node:不要手动拼接 cmd 列表,改用
Node(package="rmcs-navigation", executable="follow_waypoints_runner.py") 并通过
parameters 传入参数字典,使用 LaunchConfiguration("follow_waypoints_file") 映射到
config_file,LaunchConfiguration("odom_topic") 映射到
odom_topic,LaunchConfiguration("distance_tolerance") 映射到
distance_tolerance,LaunchConfiguration("yaw_tolerance") 映射到 yaw_tolerance,保留
output="screen";移除原先对 ExecuteProcess 的 cmd 构建以便使用 ROS 参数声明机制和标准生命周期/日志行为。
```

</details>

</blockquote></details>
<details>
<summary>scripts/goal_topic_bridge.py (2)</summary><blockquote>

`41-49`: **`wait_for_server()` 无超时可能导致无限阻塞**

`wait_for_server()` 没有设置超时参数,如果 action server 不可用,会导致线程无限阻塞。此外,取消目标时未等待取消操作完成就发送新目标,可能产生竞态条件。


<details>
<summary>♻️ 建议添加超时并等待取消完成</summary>

```diff
 def _send_goal(self, msg: PoseStamped) -> None:
-    self._client.wait_for_server()
+    if not self._client.wait_for_server(timeout_sec=5.0):
+        self.get_logger().warning("navigate_to_pose action server not available")
+        return

     with self._lock:
         current_goal = self._current_goal

     if current_goal is not None:
         cancel_future = current_goal.cancel_goal_async()
-        cancel_future.add_done_callback(lambda _: None)
+        rclpy.spin_until_future_complete(self, cancel_future, timeout_sec=2.0)
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@scripts/goal_topic_bridge.py` around lines 41 - 49, In _send_goal, avoid
indefinite blocking and race conditions by calling wait_for_server with a
sensible timeout (e.g., pass a timeout/duration) and handling the timeout case
(log/error/return) instead of blocking forever, and when cancelling a prior goal
use the returned cancel_future from cancel_goal_async to wait for completion
(e.g., await or call result() with timeout or attach a done-wait) before
proceeding to send the new goal; update references in this method (_send_goal,
wait_for_server, cancel_goal_async, cancel_future) accordingly to handle
timeout/failure paths and ensure the cancel completes before sending the next
goal.
```

</details>

---

`60-68`: **未处理 `future.result()` 可能抛出的异常**

如果 future 由于异常而完成,`future.result()` 会抛出该异常。建议添加 try-except 处理。


<details>
<summary>🛡️ 建议添加异常处理</summary>

```diff
 def _handle_goal_response(self, future) -> None:
-    goal_handle = future.result()
+    try:
+        goal_handle = future.result()
+    except Exception as e:
+        self.get_logger().error(f"Failed to send goal: {e}")
+        return
     if goal_handle is None or not goal_handle.accepted:
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@scripts/goal_topic_bridge.py` around lines 60 - 68, The handler
_handle_goal_response should catch exceptions raised by future.result(): wrap
the call to future.result() in a try/except, log the exception (using
self.get_logger().error or warning with the exception details) and return early
if an exception occurs, then continue with the existing acceptance check and the
critical-section update of self._current_goal and last_goal inside the
self._lock. Ensure you reference future.result(), goal_handle,
self.get_logger(), self._lock, self._current_goal and self._last_goal when
making the change.
```

</details>

</blockquote></details>
<details>
<summary>CMakeLists.txt (1)</summary><blockquote>

`20-23`: **Eigen3 和 PCL 已声明但未链接**

`find_package(Eigen3 REQUIRED)` 和 `find_package(PCL REQUIRED)` 已声明,但 `${PROJECT_NAME}_brain` 目标未链接这些库。根据之前的 learnings,如果这是为未来使用预留的,建议添加注释说明。


<details>
<summary>💡 建议添加说明注释</summary>

```diff
+# TODO: Eigen3 is reserved for future matrix operations in planning
 find_package(Eigen3 REQUIRED)
 find_package(yaml-cpp REQUIRED)
 find_package(rclcpp REQUIRED)
+# TODO: PCL is reserved for future point cloud processing
 find_package(PCL REQUIRED)
```
</details>

Based on learnings: "In CMakeLists.txt files, when you intentionally include a dependency for future use, add a brief comment explaining the rationale."

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 20 - 23, The CMakeLists declares
find_package(Eigen3 REQUIRED) and find_package(PCL REQUIRED) but never links
them to the target `${PROJECT_NAME}_brain`; either link the required libraries
(use target_link_libraries(${PROJECT_NAME}_brain PUBLIC Eigen3::Eigen PCL::PCL
or the correct imported targets for your packages) or, if they are intentionally
reserved for future use, add a brief comment next to the find_package lines
explaining that they are declared now for future features and therefore not
linked to `${PROJECT_NAME}_brain` yet.
```

</details>

</blockquote></details>
<details>
<summary>src/brain.cc (1)</summary><blockquote>

`71-100`: **FSM 状态转换逻辑为空壳实现**

每个状态的 `spin` 函数体只有注释 `// ...`,且立即无条件返回下一个状态。这意味着 FSM 会在一次 `spin_once()` 调用中快速遍历所有状态直到 `END`。

如果这是 WIP 代码,建议添加 TODO 注释标明待实现的逻辑;如果是预期行为,请确认这符合设计意图。


需要我帮助实现基于 `game_stage` 输入的状态转换逻辑吗?

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/brain.cc` around lines 71 - 100, The FSM in generate_brain_fsm currently
has empty spin lambdas that immediately return the next state, causing the FSM
to fast-forward to END; replace these placeholders with real transition logic
(or TODO markers) that inspect the node's runtime input (e.g. a member like
game_stage or a sensor/event flag) and only return the next BrainStage when the
appropriate condition is met; update the lambdas for BrainStage::WaitForBegin,
::Prepare, ::Running and ::GameOver to capture any needed members (keep [this]
where state handlers need access) and evaluate the real conditions before
returning the next enum value, or add explicit TODO comments in each lambda
documenting the intended condition and a short TODO tag so reviewers know it's
WIP, ensuring brain_fsm.fully_registered() remains checked afterward.
```

</details>

</blockquote></details>
<details>
<summary>scripts/debug_goal_bridge.py (1)</summary><blockquote>

`72-80`: **重复创建 service client 效率较低**

`get_lifecycle_state()` 和 `change_lifecycle_state()` 每次调用都创建新的 service client。建议在 `__init__` 中缓存这些 client 或复用实例。




Also applies to: 82-94

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug_goal_bridge.py` around lines 72 - 80, get_lifecycle_state and
change_lifecycle_state currently call create_client each time which is
inefficient; modify the class to create and cache needed service clients (e.g.,
GetState and ChangeState clients) during __init__ and reuse them instead of
recreating per call—either store as attributes like self._get_state_client and
self._change_state_client or keep a dict keyed by f"/{node_name}/get_state" /
f"/{node_name}/change_state" and lookup/reuse clients in get_lifecycle_state and
change_lifecycle_state (refer to create_client, get_lifecycle_state,
change_lifecycle_state to locate spots).
```

</details>

</blockquote></details>
<details>
<summary>launch/nav2.launch.py (1)</summary><blockquote>

`106-106`: **建议使用列表解包替代连接操作**

静态分析建议使用 `[*remappings, ("cmd_vel", "cmd_vel_nav")]` 替代列表连接,这是更符合 Python 习惯的写法。


<details>
<summary>♻️ 建议的修改</summary>

```diff
         Node(
             package="nav2_controller",
             executable="controller_server",
             name="controller_server",
             output="screen",
             parameters=[controller_params, {"use_sim_time": use_sim_time}],
-            remappings=remappings + [("cmd_vel", "cmd_vel_nav")],
+            remappings=[*remappings, ("cmd_vel", "cmd_vel_nav")],
         ),
```

```diff
         Node(
             package="nav2_behaviors",
             executable="behavior_server",
             name="behavior_server",
             output="screen",
             parameters=[planner_params, {"use_sim_time": use_sim_time}],
-            remappings=remappings + [("cmd_vel", "cmd_vel_nav")],
+            remappings=[*remappings, ("cmd_vel", "cmd_vel_nav")],
         ),
```

```diff
         Node(
             package="nav2_velocity_smoother",
             executable="velocity_smoother",
             name="velocity_smoother",
             output="screen",
             parameters=[planner_params, {"use_sim_time": use_sim_time}],
-            remappings=remappings + [("cmd_vel", "cmd_vel_nav")],
+            remappings=[*remappings, ("cmd_vel", "cmd_vel_nav")],
         ),
```
</details>


Also applies to: 122-122, 146-146

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@launch/nav2.launch.py` at line 106, Replace list concatenation expressions
that append a single tuple to remappings (e.g., the statement remappings =
remappings + [("cmd_vel", "cmd_vel_nav")]) with list-unpacking syntax:
remappings = [*remappings, ("cmd_vel", "cmd_vel_nav")]. Update the same pattern
wherever remappings is extended (the other occurrences following the same form)
so each uses the unpacking form to follow Python idioms and avoid creating an
intermediate list via +.
```

</details>

</blockquote></details>
<details>
<summary>scripts/static_grid_publisher.py (1)</summary><blockquote>

`65-84`: **考虑缓存栅格数据以减少内存分配**

`publish_grid()` 每次调用都创建新的 `[0] * (width * height)` 列表,对于默认的 505x505 栅格,每 0.5 秒分配约 255K 整数。对于 "empty" 模式,可以预先创建并复用数据。


<details>
<summary>♻️ 建议优化</summary>

```diff
 def __init__(self) -> None:
     # ... existing code ...
     self.timer = self.create_timer(0.5, self.publish_grid)
+    self._empty_data = [0] * (self.width * self.height)

 def publish_grid(self) -> None:
     msg = OccupancyGrid()
     # ... header setup ...
-    msg.data = [0] * (self.width * self.height)
+    if self.map_mode == "demo":
+        msg.data = [0] * (self.width * self.height)
+        self.mark_rect(msg.data, -0.4, 0.4, 0.8, 3.2)
+        # ... other mark_rect calls ...
+    else:
+        msg.data = self._empty_data.copy()

-    if self.map_mode == "demo":
-        self.mark_rect(msg.data, -0.4, 0.4, 0.8, 3.2)
-        # ...
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@scripts/static_grid_publisher.py` around lines 65 - 84, publish_grid
currently allocates a new list [0] * (self.width * self.height) on every call;
create and reuse a cached base grid (e.g., self._base_grid) sized once in the
class __init__ (or lazily on first publish_grid call) to avoid repeated
allocations, then in publish_grid use that cached list directly for
self.map_mode == "empty" and for other modes (when calling mark_rect) make a
cheap copy (e.g., working = list(self._base_grid)) before mutating so mark_rect
updates do not change the cached base; update references in publish_grid,
mark_rect, and class init to use self._base_grid.
```

</details>

</blockquote></details>

</blockquote></details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `a594c362-6d6b-4137-91cd-feab0ab36c78`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between dde4d5bd38f8be3e0e0782fc00a43228bfb0d235 and e87c52b6c99f95513a742432299a34a585b7fc55.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `test/.cache/clangd/index/fsm.cc.B7DEAC8886980F9C.idx` is excluded by `!**/.cache/**`

</details>

<details>
<summary>📒 Files selected for processing (38)</summary>

* `.clangd`
* `AGENTS.md`
* `CMakeLists.txt`
* `README.md`
* `cmake/export.cmake`
* `cmake/version.cmake`
* `config/custom.yaml`
* `config/follow_waypoints.yaml`
* `config/nav2.yaml`
* `config/rule.yaml`
* `launch/custom.launch.py`
* `launch/follow_waypoints.launch.py`
* `launch/nav2.launch.py`
* `package.xml`
* `plugins.xml`
* `rmcs-navigationConfig.cmake.in`
* `rmcs-navigationConfigVersion.cmake.in`
* `scripts/debug_goal_bridge.py`
* `scripts/follow_waypoints_runner.py`
* `scripts/goal_topic_bridge.py`
* `scripts/static_grid_publisher.py`
* `src/app/common.cc`
* `src/app/common.hh`
* `src/app/rmuc.ccm`
* `src/app/rmul.ccm`
* `src/app/test.ccm`
* `src/brain.cc`
* `src/brain/plan.cc`
* `src/brain/plan.hh`
* `src/brain/rule.hh`
* `src/core/environment/voxel-map.ccm`
* `src/main.cc`
* `src/util/fsm.hh`
* `src/util/pimpl.hh`
* `src/util/rclcpp/node.ccm`
* `test/CMakeLists.txt`
* `test/fsm.cc`
* `test/map.cc`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +1 to +4
install(
TARGETS ${PROJECT_NAME}_brain
DESTINATION lib/${PROJECT_NAME}
)
Copy link

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
# 对比插件声明路径与安装路径是否一致
rg -n -C2 '<library path=' plugins.xml
rg -n -C2 'install\(|TARGETS\s+\$\{PROJECT_NAME\}_brain|DESTINATION\s+lib/\$\{PROJECT_NAME\}|LIBRARY DESTINATION|ARCHIVE DESTINATION|RUNTIME DESTINATION' cmake/export.cmake

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 586


🏁 Script executed:

# Find plugin-related code and loading mechanism
fd -t f '\.cpp$|\.h$|\.py$' | head -20
rg -l 'plugin|Plugin|load.*library|dlopen' --type cpp --type h | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 296


🏁 Script executed:

# Check for any CMake configuration related to library search paths or RPATH
rg -n 'RPATH|CMAKE_PREFIX_PATH|LD_LIBRARY_PATH|lib_DIRS|find_package' cmake/ --type cmake | head -30

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 134


🏁 Script executed:

# Look for other plugin installations to compare
rg -n 'install.*TARGETS' cmake/ -A 3 | head -40

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 60


🏁 Script executed:

# Check the main CMakeLists.txt for any configuration
cat CMakeLists.txt 2>/dev/null | head -100

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 2294


🏁 Script executed:

# Search for rmcs_executor related code and how it loads plugins
rg -n 'rmcs_executor|rmcs::.*Component' src/ --type cpp

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 265


🏁 Script executed:

# Check for any test or CI configuration that might reveal issues
fd -t f '\.yml$|\.yaml$|CMakeLists\.txt' | xargs grep -l 'test\|CI\|plugin' 2>/dev/null | head -10

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 129


🏁 Script executed:

# Look for any documentation or README about plugin setup
fd -t f 'README|INSTALL|SETUP|docs' | head -10

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 87


🏁 Script executed:

# Check if there's any ament-related CMake configuration for library installation
cat cmake/export.cmake 2>/dev/null

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 594


🏁 Script executed:

# Check README for plugin setup information
cat README.md

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 501


🏁 Script executed:

# Check test configuration
cat test/CMakeLists.txt

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 753


🏁 Script executed:

# Search for any plugin resolution or library path configuration in rmcs_executor
# First, let's see if rmcs_executor is available and check its installation pattern
rg -n 'install.*lib' --type cmake

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 60


🏁 Script executed:

# Look for any usage examples or documentation about plugin paths
fd -t f '\.md$|\.txt$' | xargs grep -l 'plugin\|library.*path' 2>/dev/null

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 92


🏁 Script executed:

# Check for package.xml to understand ROS 2 package structure
fd -t f 'package.xml'

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 89


🏁 Script executed:

# Look for any installation or build verification scripts
fd -t f 'install\.sh|setup\.sh|build\.sh|test\.sh'

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 60


🏁 Script executed:

# Check git history or issues that might reference plugin loading problems
git log --oneline --all 2>/dev/null | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 174


🏁 Script executed:

# Search for how other components/plugins are installed in rmcs ecosystem
rg -n 'DESTINATION.*lib' --type cmake

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 134


修正插件库安装目录以解决运行时加载失败问题

plugins.xml 声明的库名为 rmcs-navigation_brain,但当前安装到 lib/${PROJECT_NAME} 子目录会导致 pluginlib 无法找到该库。ROS 2 的 ament_cmake 和 pluginlib 系统期望库直接安装在 lib/ 目录中。这将在运行时导致 rmcs_executor 无法加载该插件组件。

推荐修复
 install(
     TARGETS ${PROJECT_NAME}_brain
-    DESTINATION lib/${PROJECT_NAME}
+    LIBRARY DESTINATION lib
+    ARCHIVE DESTINATION lib
+    RUNTIME DESTINATION bin
 )
📝 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
install(
TARGETS ${PROJECT_NAME}_brain
DESTINATION lib/${PROJECT_NAME}
)
install(
TARGETS ${PROJECT_NAME}_brain
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/export.cmake` around lines 1 - 4, The plugin library is being installed
under lib/${PROJECT_NAME}, causing pluginlib to miss rmcs-navigation_brain;
change the install destination so the target ${PROJECT_NAME}_brain is installed
directly into lib by updating the install() call that references TARGETS
${PROJECT_NAME}_brain and DESTINATION lib/${PROJECT_NAME} to use DESTINATION lib
so the built library name (rmcs-navigation_brain) is placed where ROS2/pluginlib
expects it.

Comment on lines +177 to +178
result_future = self.goal_handle.get_result_async()
result_future.add_done_callback(self._on_goal_result)
Copy link

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:

find . -type f -name "follow_waypoints_runner.py"

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 114


🏁 Script executed:

wc -l ./scripts/follow_waypoints_runner.py

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 118


🏁 Script executed:

cat -n ./scripts/follow_waypoints_runner.py

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 9651


🏁 Script executed:

rg "add_done_callback|cancel_goal_async|get_result_async" --type py -B 2 -A 2

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 3833


先取消当前 goal 再立刻发下一个,会导致状态污染和日志错位。

cancel_goal_async() 是异步的,这里未等待取消完成就直接调用 _send_next_goal()(第 228 行),导致旧 goal 的 result callback 仍会在随后进入 _on_goal_result()。此时 self.current_index 已指向下一跳,导致:

  • 日志中输出的 waypoint 序号错误(第 189、196 行)
  • 若旧 goal 状态为 4(成功),会再次调用 _send_next_goal()(第 191 行),多发一个 goal
  • goal_handle / goal_in_flight 状态被覆盖

同样的问题也存在于 177-178 行的 result callback 注册处。建议在 cancel 完成后再发下一跳,或为 callback 绑定 goal ID 并忽略过期结果。

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

In `@scripts/follow_waypoints_runner.py` around lines 177 - 178, The result
callback is registered on result_future and cancel_goal_async() is invoked
without awaiting its completion, which allows stale goal results to reach
_on_goal_result() after self.current_index has advanced; to fix, either await
the cancellation future before calling _send_next_goal() (i.e., await the
cancel_goal_async() completion and only then call _send_next_goal()) or bind the
active goal identifier to the result callback (capture goal_handle.get_goal_id()
when sending a goal and in _on_goal_result() ignore results whose goal id
differs from the captured id), and ensure updates to goal_in_flight and
goal_handle only occur for the matching goal id to avoid state/log pollution.

src/brain.cc Outdated
Comment on lines +135 to +141
auto update() -> void override {
auto update = [this](PlanBox::Information& info) {
std::ignore = this;
info.health = 0.0;
info.health = 0.0;
};
plan_box.update_information(update);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

info.health 被重复赋值,info.bullet 未设置

update() lambda 中,info.health 被赋值两次(第 138 和 139 行),而 info.bullet 从未被设置。这会导致 do_plan() 中的 bullet 判断逻辑使用未初始化的值。

🐛 建议修复
     auto update = [this](PlanBox::Information& info) {
         std::ignore = this;
         info.health = 0.0;
-        info.health = 0.0;
+        info.bullet = 0.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
auto update() -> void override {
auto update = [this](PlanBox::Information& info) {
std::ignore = this;
info.health = 0.0;
info.health = 0.0;
};
plan_box.update_information(update);
auto update() -> void override {
auto update = [this](PlanBox::Information& info) {
std::ignore = this;
info.health = 0.0;
info.bullet = 0.0;
};
plan_box.update_information(update);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain.cc` around lines 135 - 141, The lambda passed to
plan_box.update_information in update() mistakenly assigns info.health twice and
never sets info.bullet, leaving PlanBox::Information::bullet uninitialized for
do_plan(); modify the lambda (inside update()) to remove the duplicate health
assignment and instead set info.bullet to an appropriate initial value (and keep
the single intended info.health assignment), ensuring PlanBox::Information
members are correctly initialized before plan_box.update_information returns.

Comment on lines +29 to +41
auto do_plan() const noexcept {
auto health = information.health;
if (health < rule->limit.health) {
return to_the_home;
}

auto bullet = information.bullet;
if (bullet < rule->limit.bullet) {
return to_the_home;
}

return cruise_mode;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

rule 可能为空指针导致崩溃

do_plan() 直接解引用 rule->limit,但 rulestd::unique_ptr<brain::Rule>,在 set_rule() 被调用之前为空。如果在规则设置前调用 do_plan(),会导致空指针解引用。

🐛 建议添加空指针检查
 auto do_plan() const noexcept {
+    if (!rule) {
+        return cruise_mode;  // 或返回安全默认值
+    }
     auto health = information.health;
     if (health < rule->limit.health) {
         return to_the_home;
     }
📝 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
auto do_plan() const noexcept {
auto health = information.health;
if (health < rule->limit.health) {
return to_the_home;
}
auto bullet = information.bullet;
if (bullet < rule->limit.bullet) {
return to_the_home;
}
return cruise_mode;
}
auto do_plan() const noexcept {
if (!rule) {
return cruise_mode; // 或返回安全默认值
}
auto health = information.health;
if (health < rule->limit.health) {
return to_the_home;
}
auto bullet = information.bullet;
if (bullet < rule->limit.bullet) {
return to_the_home;
}
return cruise_mode;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain/plan.cc` around lines 29 - 41, do_plan() dereferences rule->limit
without checking rule, causing a potential null dereference; update do_plan() to
guard against rule being null by checking rule (the
std::unique_ptr<brain::Rule>) before accessing rule->limit and return a safe
default (e.g., to_the_home or cruise_mode) when rule is not set; reference the
do_plan() method, the rule unique_ptr, set_rule(), rule->limit,
information.health/bullet, and the return values to_the_home/cruise_mode when
adding the null check and early-return logic.

Copy link

@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: 3

♻️ Duplicate comments (1)
src/brain.cc (1)

137-140: ⚠️ Potential issue | 🟠 Major

这里把 health 连续写了两次,bullet 没有刷新。

规划逻辑会读取 bullet;第二次仍写 health 会让 bullet 保持默认值或上一次的值,后续决策会基于错误数据。

🐛 建议修改
         auto update = [this](PlanBox::Information& info) {
             std::ignore = this;
             info.health = 0.0;
-            info.health = 0.0;
+            info.bullet = 0;
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain.cc` around lines 137 - 140, The lambda update (auto update =
[this](PlanBox::Information& info)) erroneously writes info.health twice and
never refreshes info.bullet; change the second assignment to update/reset
info.bullet (or explicitly clear it to the correct default) so
PlanBox::Information.bullet is refreshed before planning reads it, ensuring you
still set info.health once and then assign info.bullet appropriately inside
update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/brain.cc`:
- Around line 129-133: 当调用
ament_index_cpp::get_package_share_directory(kAppLabel.data()),
YAML::LoadFile(config_file) 或 plan_box.set_rule(config_yaml)
失败时,当前异常缺少包名和配置路径定位信息;请在这段代码周围添加捕获(try/catch)逻辑,捕获所有异常后在抛出或记录前将
kAppLabel、config_file(或 config_path +
"config/rule.yaml")和原始异常信息一并附加到错误消息中,确保重新抛出带有上下文的异常或使用组件日志记录(包含
get_package_share_directory、YAML::LoadFile、plan_box.set_rule 的调用点信息)以便上线后快速定位问题。
- Around line 115-122: The subscription callback for
Node::create_subscription<Twist> uses an unsupported signature (const
std::unique_ptr<Twist>&) which will fail to compile; change the lambda parameter
to a supported type such as Twist::ConstSharedPtr (or Twist::UniquePtr if you
want ownership) and update the lambda body to dereference the shared/unique
pointer when assigning to command_velocity->x()/y()/z(), and keep setting
command_received_timestamp and has_warning_timeout as before; locate the
subscription_twist creation and replace the callback signature accordingly.

In `@src/brain/plan.hh`:
- Around line 20-24: 模板约束和调用方式不匹配:在 update_information 模板中,改为约束函数类型为
std::invocable<F, Information&> 并使用 std::invoke(std::forward<F>(function),
information_()) 来调用回调(以支持成员函数指针等可调用对象),同时移除或替换无条件 noexcept 为条件
noexcept(noexcept(std::invoke(std::declval<F>(),
std::declval<Information&>()))),并保持在调用后调用 do_plan_().

---

Duplicate comments:
In `@src/brain.cc`:
- Around line 137-140: The lambda update (auto update =
[this](PlanBox::Information& info)) erroneously writes info.health twice and
never refreshes info.bullet; change the second assignment to update/reset
info.bullet (or explicitly clear it to the correct default) so
PlanBox::Information.bullet is refreshed before planning reads it, ensuring you
still set info.health once and then assign info.bullet appropriately inside
update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e24fc76-7ace-400d-9680-c04fd77cd3b2

📥 Commits

Reviewing files that changed from the base of the PR and between e87c52b and 8c91257.

📒 Files selected for processing (3)
  • config/plan.yaml
  • src/brain.cc
  • src/brain/plan.hh
✅ Files skipped from review due to trivial changes (1)
  • config/plan.yaml

src/brain.cc Outdated
Comment on lines +129 to +133
const auto config_path = ament_index_cpp::get_package_share_directory(kAppLabel.data());
const auto config_file = std::filesystem::path{config_path} / "config" / "rule.yaml";
const auto config_yaml = YAML::LoadFile(config_file);

plan_box.set_rule(config_yaml);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

规则配置初始化失败时缺少足够的定位信息。

get_package_share_directory()LoadFile()set_rule() 任一处抛异常,都会让组件构造直接失败;但当前异常里没有包名或配置路径,上线后很难快速定位是部署问题还是 YAML 内容问题。

🛡️ 建议修改
-        const auto config_path = ament_index_cpp::get_package_share_directory(kAppLabel.data());
-        const auto config_file = std::filesystem::path{config_path} / "config" / "rule.yaml";
-        const auto config_yaml = YAML::LoadFile(config_file);
-
-        plan_box.set_rule(config_yaml);
+        try {
+            const auto config_path = ament_index_cpp::get_package_share_directory(kAppLabel.data());
+            const auto config_file = std::filesystem::path{config_path} / "config" / "rule.yaml";
+            const auto config_yaml = YAML::LoadFile(config_file.string());
+            plan_box.set_rule(config_yaml);
+        } catch (const std::exception& e) {
+            throw std::runtime_error{
+                std::format("Failed to initialize planning rules for {}: {}", kAppLabel, e.what())
+            };
+        }
📝 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
const auto config_path = ament_index_cpp::get_package_share_directory(kAppLabel.data());
const auto config_file = std::filesystem::path{config_path} / "config" / "rule.yaml";
const auto config_yaml = YAML::LoadFile(config_file);
plan_box.set_rule(config_yaml);
try {
const auto config_path = ament_index_cpp::get_package_share_directory(kAppLabel.data());
const auto config_file = std::filesystem::path{config_path} / "config" / "rule.yaml";
const auto config_yaml = YAML::LoadFile(config_file.string());
plan_box.set_rule(config_yaml);
} catch (const std::exception& e) {
throw std::runtime_error{
std::format("Failed to initialize planning rules for {}: {}", kAppLabel, e.what())
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain.cc` around lines 129 - 133, 当调用
ament_index_cpp::get_package_share_directory(kAppLabel.data()),
YAML::LoadFile(config_file) 或 plan_box.set_rule(config_yaml)
失败时,当前异常缺少包名和配置路径定位信息;请在这段代码周围添加捕获(try/catch)逻辑,捕获所有异常后在抛出或记录前将
kAppLabel、config_file(或 config_path +
"config/rule.yaml")和原始异常信息一并附加到错误消息中,确保重新抛出带有上下文的异常或使用组件日志记录(包含
get_package_share_directory、YAML::LoadFile、plan_box.set_rule 的调用点信息)以便上线后快速定位问题。

Comment on lines +20 to +24
template <std::invocable<Information&> F>
auto update_information(F&& function) noexcept -> void {
std::forward<F>(function)(information_());
do_plan_();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

C++20 std::invocable的判定是否基于std::invoke?如果模板约束是 std::invocable<Information&>,实现里直接写 function(arg)而不是std::invoke(function, arg),会不会遗漏成员函数指针等合法可调用对象?

💡 Result:

是的,C++20 std::invocable 的判定基于 std::invoke。 std::invocable<F, Args...> 的定义是检查 std::invoke(std::forward(f), std::forward<Args...>(args)...) 是否有效,其中 f 是 F&& 类型,args 是 Args&& 类型。 如果模板约束是 std::invocable<Information&, Arg>(假设 function 是 Information& 类型,arg 是 Arg 类型),则保证 std::invoke(function, arg) 有效,但不保证 function(arg) 有效。 对于成员函数指针(如 &Class::method,类型 Ret(Class::)(Arg)),std::invocable<&Class::method, Class&, Arg> 为 true,因为 std::invoke(&Class::method, obj, arg) 等价于 obj.(&Class::method)(arg) 或 obj.method(arg) 有效。但直接 function(arg) 编译失败,因为成员函数指针没有 operator,也不是普通函数指针。 因此,在实现中使用 function(arg) 而非 std::invoke(function, arg) 会遗漏成员函数指针等合法可调用对象,会导致编译错误。 要正确调用所有满足约束的可调用对象,必须使用 std::invoke(function, arg)。

Citations:


std::invocable 约束与实际调用方式不一致。

模板约束 std::invocable<Information&> 基于 std::invoke 语义,保证 std::invoke(function, information_()) 有效,但不保证直接调用 function(information_()) 有效。成员函数指针等合法的可调用对象会因为直接调用语法而编译失败。另外,无条件 noexcept 会导致回调抛异常时直接 std::terminate

修改建议
 `#include` "util/pimpl.hh"
+#include <functional>
 `#include` <yaml-cpp/yaml.h>
@@
     template <std::invocable<Information&> F>
-    auto update_information(F&& function) noexcept -> void {
-        std::forward<F>(function)(information_());
+    auto update_information(F&& function)
+        noexcept(noexcept(std::invoke(std::forward<F>(function), information_())))
+        -> void {
+        std::invoke(std::forward<F>(function), information_());
         do_plan_();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/brain/plan.hh` around lines 20 - 24, 模板约束和调用方式不匹配:在 update_information
模板中,改为约束函数类型为 std::invocable<F, Information&> 并使用
std::invoke(std::forward<F>(function), information_())
来调用回调(以支持成员函数指针等可调用对象),同时移除或替换无条件 noexcept 为条件
noexcept(noexcept(std::invoke(std::declval<F>(),
std::declval<Information&>()))),并保持在调用后调用 do_plan_().

Copy link

@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: 12

♻️ Duplicate comments (3)
src/component/brain/plan.hh (1)

20-23: ⚠️ Potential issue | 🟠 Major

模板约束和实际调用方式仍然不一致。

std::invocablestd::invoke 语义判定,但这里直接 function(...),成员函数指针等合法 callable 仍会编译失败;无条件 noexcept 也会把回调异常升级成 std::terminate

C++20 `std::invocable` 的判定是否基于 `std::invoke`?如果模板约束是 `std::invocable<F, Information&>`,实现里直接写 `function(information_())` 会不会漏掉成员函数指针等合法 callable?无条件 `noexcept` 是否会把回调异常变成 `std::terminate`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/component/brain/plan.hh` around lines 20 - 23, 模板约束和调用需要统一:在
update_information 模板上改用 std::invocable<F, Information&> 作为约束,且不要直接用
function(...) 调用;改为通过 std::invoke(std::forward<F>(function), information_())
来支持成员函数指针等所有可调用对象,并将 noexcept 改为条件 noexcept(std::is_nothrow_invocable_v<F,
Information&>)(或移除 noexcept 如果不确定),保持调用后仍执行 do_plan_().
src/component/component.cc (1)

65-72: ⚠️ Potential issue | 🔴 Critical

订阅回调签名需要改成 rclcpp 支持的类型。

const std::unique_ptr<Twist>& 不在 create_subscription() 支持的回调签名里,这里会直接编译失败。改成 Twist::ConstSharedPtrTwist::UniquePtr

可直接修改成这样
         subscription_twist = Node::create_subscription<Twist>(
-            "/cmd_vel_smoothed", 10, [&, this](const std::unique_ptr<Twist>& msg) {
+            "/cmd_vel_smoothed", 10, [this](Twist::ConstSharedPtr msg) {
                 command_velocity->x() = msg->linear.x;
                 command_velocity->y() = msg->linear.y;
                 command_velocity->z() = msg->angular.z;
                 command_received_timestamp = std::chrono::steady_clock::now();
                 has_warning_timeout = false;
             });
ROS 2 rclcpp::Node::create_subscription 支持哪些回调签名?`const std::unique_ptr<geometry_msgs::msg::Twist>&` 是否合法,还是应改用 `geometry_msgs::msg::Twist::ConstSharedPtr` / `UniquePtr`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/component/component.cc` around lines 65 - 72, The subscription callback
for subscription_twist uses an unsupported signature (const
std::unique_ptr<Twist>&) for Node::create_subscription; change the lambda
parameter to a supported type such as Twist::ConstSharedPtr (or
Twist::UniquePtr) and update the body to access fields via the pointer
(msg->linear.x, msg->linear.y, msg->angular.z), leaving the rest of the logic
(setting command_velocity->x/y/z, command_received_timestamp,
has_warning_timeout) intact; locate this inside the subscription_twist creation
call that invokes Node::create_subscription and replace the parameter type only.
src/decision/follow_waypoints_runner.py (1)

177-178: ⚠️ Potential issue | 🔴 Critical

取消当前 goal 后不要立刻推进到下一跳。

cancel_goal_async() 是异步的;旧 goal 的 result callback 之后仍会进入 _on_goal_result()。那时 current_index 已经前移,日志会错位,成功结果还会再次 _send_next_goal(),导致多发 waypoint。这里要么等 cancel 完成后再推进,要么给 callback 绑定 goal id/sequence 并忽略过期结果。

Also applies to: 180-191, 220-228

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

In `@src/decision/follow_waypoints_runner.py` around lines 177 - 178, When
cancelling a goal you must avoid advancing current_index or sending the next
waypoint until the old goal's cancel has completed or its result is verified as
not stale; modify the cancel flow around cancel_goal_async() and the result
callback handling so that either (a) you await the cancel future (from
cancel_goal_async) before calling _send_next_goal(), or (b) bind the goal's
unique id/sequence into the future callback you register on result_future (from
get_result_async()/result_future.add_done_callback) and in _on_goal_result
ignore results whose goal id/sequence does not match the active
goal_id/current_index. Update the code paths that call cancel_goal_async(),
_on_goal_result, get_result_async(), result_future.add_done_callback, and
_send_next_goal to implement one of these approaches consistently.
🧹 Nitpick comments (6)
src/component/util/rclcpp/node.ccm (2)

9-14: qos 命名空间未导出

prefix 命名空间相同,qos 命名空间中的预设也未导出,模块外部无法访问。

建议修复
 namespace rmcs::util {
-namespace qos {
+export namespace qos {

 inline const auto debug = rclcpp::QoS{10}.reliable().keep_last(10);
 inline const auto real_time = rclcpp::QoS{5}.best_effort().keep_last(5);

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

In `@src/component/util/rclcpp/node.ccm` around lines 9 - 14, The qos presets are
not exported from the C++ module, so make them available outside the module by
exporting them: change the declaration to export the qos namespace or export the
individual symbols so other translation units can access debug and real_time
(refer to the namespace qos and the inline const auto debug and real_time
definitions using rclcpp::QoS{...}). Ensure the export applies to the namespace
or to each variable (e.g., export namespace qos { ... } or export inline const
auto debug/... ) so the presets are visible outside the module.

15-29: 命名验证工具未导出

check_namingnaming_standard 定义在模块内但未使用 export 关键字,模块外部无法访问这些工具。如果这些工具仅供模块内部使用,建议移至匿名命名空间;如果需要外部使用,需要添加 export

如需导出,建议修复
-namespace prefix {
+export namespace prefix {

-constexpr auto check_naming(std::string_view name) noexcept -> bool {
+constexpr auto check_naming(std::string_view name) noexcept -> bool {
     ...
 }

-constexpr auto naming_standard = "Names must match pattern: ^[a-z0-9_/]+$";
+constexpr auto naming_standard = "Names must match pattern: ^[a-z0-9_/]+$";

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

In `@src/component/util/rclcpp/node.ccm` around lines 15 - 29, The helper
functions check_naming and naming_standard are defined inside namespace prefix
but not exported from the module; either export them for external use or move
them into an internal (unnamed) namespace if they should be internal. To export,
add the module export keyword (e.g., export constexpr auto check_naming(...)
noexcept -> bool and export constexpr auto naming_standard = "...";) inside
namespace prefix so callers can access them; otherwise wrap their definitions in
an anonymous namespace (or remove namespace prefix and place them in unnamed
namespace) to mark them internal. Ensure both check_naming and naming_standard
are handled the same way.
CMakeLists.txt (2)

56-59: 建议将 find_package(pluginlib) 与其他依赖声明放在一起

为保持一致性和可读性,建议将 find_package(pluginlib REQUIRED) 移动到其他 find_package 语句附近(第 20-29 行区域)。

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

In `@CMakeLists.txt` around lines 56 - 59, Move the find_package(pluginlib
REQUIRED) call to be grouped with the other find_package declarations near the
top of the CMakeLists (so all dependency find_package calls are together); keep
pluginlib_export_plugin_description_file(rmcs_executor plugins.xml) where it is,
just relocate the find_package line so dependency declarations are consistent
and readable.

20-23: PCL 依赖声明后未使用

find_package(PCL REQUIRED) 声明了 PCL 依赖,但未在 target_include_directoriestarget_link_libraries 中引用。如果是为将来使用而保留,建议添加注释说明(类似 Eigen3 的情况)。

💡 建议添加注释或移除未使用的依赖
+# TODO: PCL will be used for point cloud processing in future iterations
 find_package(PCL REQUIRED)

Based on learnings: "In CMakeLists.txt files, when you intentionally include a dependency for future use... add a brief comment explaining the rationale."

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

In `@CMakeLists.txt` around lines 20 - 23, The CMakeLists currently calls
find_package(PCL REQUIRED) but never uses it in target_include_directories or
target_link_libraries; either remove the unused find_package(PCL REQUIRED) or
explicitly consume it: add PCL include/link usage to the appropriate target
(e.g., reference PCL include/libs via
target_include_directories/target_link_libraries or use the PCL::PCL target) or,
if intentional for future work, add a brief comment next to find_package(PCL
REQUIRED) explaining why it’s declared now (similar to the Eigen3 comment
pattern).
launch/custom.launch.py (2)

165-175: 硬编码的 world->base_link 变换坐标

变换坐标 "1.2", "6.3", "0" 为硬编码值,这可能是特定场地或机器人的初始位置。建议将这些值提取到配置文件或作为 launch argument 暴露,以提高灵活性。

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

In `@launch/custom.launch.py` around lines 165 - 175, 当前在 launch 中以静态硬编码方式在
Node(package="tf2_ros", executable="static_transform_publisher",
name="world_to_base_link_tf") 的 arguments 列表里使用了 "1.2", "6.3", "0" 作为
world->base_link 初始平移,需将这些值暴露为可配置项;修改方案:为 launch 文件添加 launch arguments(例如
world_x, world_y, world_z 或从 config 参数读取),在创建 Node 时用传入的
LaunchConfiguration/DeclareLaunchArgument 值替换硬编码字符串,并确保为每个参数设置合理默认值和描述以保持向后兼容性。

248-250: 建议添加配置文件异常处理

_load_custom_config 在文件不存在或 YAML 格式错误时会抛出未捕获的异常。建议添加适当的错误处理以提供更友好的错误信息。

💡 建议添加异常处理
 def _load_custom_config(config_file):
-    with open(config_file, "r", encoding="utf-8") as stream:
-        return yaml.safe_load(stream) or {}
+    try:
+        with open(config_file, "r", encoding="utf-8") as stream:
+            return yaml.safe_load(stream) or {}
+    except FileNotFoundError:
+        print(f"Warning: Config file not found: {config_file}, using defaults")
+        return {}
+    except yaml.YAMLError as e:
+        raise RuntimeError(f"Failed to parse config file {config_file}: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/custom.launch.py` around lines 248 - 250, The helper
_load_custom_config currently opens and parses YAML without handling errors;
wrap the file open and yaml.safe_load calls in a try/except that catches
FileNotFoundError/PermissionError and yaml.YAMLError, and handle each case by
either returning an empty dict for missing/unreadable config or raising a
clearer exception/logging a user-friendly message that includes the config_file
path and the underlying error; make sure the function name _load_custom_config
is updated to use the new error handling and preserve returning {} when
appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@launch/custom.launch.py`:
- Around line 152-155: The LaunchConfiguration object bag_path is being used
directly when building bag_cmd, which embeds the object reference instead of the
actual string; convert LaunchConfiguration values to strings before building the
command by calling bag_path.perform(context) (and similarly call
bag_use_clock.perform(context) before doing .lower()), or alternatively
construct the ExecuteProcess cmd using launch.substitutions (i.e., pass
LaunchConfiguration/substitution objects instead of raw Python strings) so that
ExecuteProcess receives proper substitutions; update the code around bag_cmd,
bag_use_clock, and the ExecuteProcess invocation (where result_actions is
appended) to use one of these two approaches.

In `@src/component/brain/plan.cc`:
- Around line 43-46: The goal_position() implementation is a placeholder
returning NaN via goal_x/goal_y; replace the stub with real logic that reads the
plan's authoritative goal state (e.g., the member variables or methods declared
in plan.hh such as the goal fields or any set_goal/update_goal functions),
compute and assign goal_x and goal_y from that state (or from the current
path/goal index if the class stores path_ or goal_index), validate they are
finite (fallback to a safe default or std::optional/empty result if not), and
return the computed tuple; ensure you update/initialize the underlying goal
storage wherever the class exposes mutators so goal_position() no longer returns
NaN.
- Line 62: PlanBox::do_plan_() currently calls pimpl->do_plan() and discards its
return value; Impl::do_plan() actually returns a plan name (e.g. "to_the_home"
or "cruise_mode") that should be used to run the corresponding callable stored
in plan_map. Change do_plan_() to capture the returned plan key from
Impl::do_plan(), look it up in PlanBox::plan_map (or pimpl->plan_map if stored
in Impl), and invoke the callable if found; add a safe fallback (log or no-op)
when the key is missing or callable is empty and preserve noexcept behavior of
do_plan_().

In `@src/component/brain/plan.hh`:
- Around line 20-23: update_information calls do_plan_ unconditionally which
lets Impl::do_plan access rule->limit.* while PlanBox's rule is null by default;
to fix, add a guard in update_information (or in do_plan_) to check that rule is
set before calling do_plan_ (e.g., return early if rule == nullptr), or make
rule a required constructor parameter so it cannot be null; reference symbols:
update_information, do_plan_(), Impl::do_plan, PlanBox, rule, set_rule().

In `@src/component/util/fsm.hh`:
- Around line 40-75: The code currently allows registering/starting the sentinel
END state and later indexing states_map out of range; forbid END both at
compile-time and runtime: add a compile-time check (e.g., static_assert or
std::enable_if) in the template overloads use<state>() and start_on<state>() to
ensure the template parameter is not state_type::END, and add a runtime guard in
the non-template use(state_type state, ...) and start_on(state_type) functions
that throws or asserts if state == state_type::END before touching states_map
(and include a clear error message referencing END and states_map). Ensure you
update all overloads mentioned (use(state_type,...), use<state>(),
use<state>(on_event), and start_on variants) so END cannot be registered or
started.
- Around line 90-101: spin_once() can dereference a null handler when
states_map.at(std::to_underlying(current_state)).get() returns nullptr; before
calling current_event->on_begin() or ->on_event() check that the handler exists
(and that the map lookup succeeded) and handle the missing-handler case by
logging/throwing a clear error or transitioning to a safe state instead of
dereferencing. Locate spin_once, current_event, states_map, current_state and
add an existence check after lookup (or use find + nullptr check) and
return/handle appropriately; ensure the function still returns whether next ==
StateKey::END only after a valid next is obtained or after handling the error.

In `@src/component/util/rclcpp/node.ccm`:
- Around line 1-6: 该模块文件 src/component/util/rclcpp/node.ccm 未在 CMakeLists.txt
中通过 FILE_SET CXX_MODULES 注册,导致模块不会被编译或导出;打开项目的 CMakeLists.txt,找到定义该库的
add_library(...)(当前只包含 src/component/component.cc),并将 node.ccm 加入到该库的 FILE_SET
CXX_MODULES 列表中(或创建新的 file_set 并在 target_link_libraries/target_sources
中引用),确保模块名 util.rclcpp_node 的编译单元被包含并导出以便链接。

In `@src/component/util/status.hh`:
- Around line 19-39: Both free functions to_string(rmcs_msgs::GameStage) and
to_string(RobotStatus) are defined in a header and must be marked inline to
avoid ODR violations when included in multiple translation units; update the
declarations/definitions of to_string(rmcs_msgs::GameStage) and
to_string(RobotStatus) in this header to be inline (or alternatively move their
definitions into a .cc file), keeping the same logic and noexcept specifier, and
consider returning std::string_view or const char* later as an optional
optimization.

In `@src/decision/decision.py`:
- Around line 43-45: The entrypoint() function currently spins with while True:
continue, causing a 100% CPU busy loop; replace that busy-loop with a blocking
behavior or an explicit unimplemented signal: either raise
NotImplementedError("entrypoint not implemented") to fail fast until real logic
is added, or block without CPU use (e.g., use time.sleep(1) in the loop or
threading.Event().wait() to wait indefinitely) and add the necessary import
(time or threading) and a short comment indicating this is a deliberate
placeholder; update the body of entrypoint() accordingly.
- Around line 17-19: The CruiseMethod class has an invalid type annotation and a
mutable default for waypoints; change the annotation of waypoints to a sequence
of coordinate pairs (e.g. list[tuple[float, float]] or Sequence[Tuple[float,
float]]) and replace the shared empty list default with a dataclasses-safe
default_factory (use field(default_factory=list) from dataclasses); update
imports to include dataclasses.field (and typing.Tuple/Sequence if you choose
those) and keep method_name as-is.

In `@src/decision/follow_waypoints_runner.py`:
- Around line 27-28: _quaternion_to_yaw currently computes yaw using only z and
w components which is only valid when x and y are zero; replace the
implementation to use the full quaternion-to-yaw formula that includes x and y
(yaw = atan2(2*(w*z + x*y), 1 - 2*(y*y + z*z))) and update any other occurrences
of the simplified formula (the other location referenced around lines 209-212)
to use the same full formula; locate and modify the function named
_quaternion_to_yaw and the other identical snippet to accept x, y, z, w (or read
x and y where available) and compute yaw with the full expression so yaw_error
is correct when x/y are nonzero.

In `@src/decision/goal_topic_bridge.py`:
- Around line 31-55: Replace the per-message thread spawn in _on_goal/_send_goal
with a single sequential worker: stop creating a new Thread for every incoming
PoseStamped and instead push msg into a FIFO queue (e.g., collections.deque or
queue.Queue) and have one dedicated worker thread/loop that processes entries
one-by-one; inside that worker perform the entire cancel → wait_for_server →
send_goal_async → handle acceptance lifecycle while holding/using self._lock to
update self._current_goal and self._last_goal so they cannot be interleaved by
concurrent sends, and ensure wait_for_server is called once or from the worker
(not blocking per-message threads) and that _handle_goal_response is invoked
from the serialized worker context or safely updates shared state under the same
lock.

---

Duplicate comments:
In `@src/component/brain/plan.hh`:
- Around line 20-23: 模板约束和调用需要统一:在 update_information 模板上改用 std::invocable<F,
Information&> 作为约束,且不要直接用 function(...) 调用;改为通过
std::invoke(std::forward<F>(function), information_()) 来支持成员函数指针等所有可调用对象,并将
noexcept 改为条件 noexcept(std::is_nothrow_invocable_v<F, Information&>)(或移除
noexcept 如果不确定),保持调用后仍执行 do_plan_().

In `@src/component/component.cc`:
- Around line 65-72: The subscription callback for subscription_twist uses an
unsupported signature (const std::unique_ptr<Twist>&) for
Node::create_subscription; change the lambda parameter to a supported type such
as Twist::ConstSharedPtr (or Twist::UniquePtr) and update the body to access
fields via the pointer (msg->linear.x, msg->linear.y, msg->angular.z), leaving
the rest of the logic (setting command_velocity->x/y/z,
command_received_timestamp, has_warning_timeout) intact; locate this inside the
subscription_twist creation call that invokes Node::create_subscription and
replace the parameter type only.

In `@src/decision/follow_waypoints_runner.py`:
- Around line 177-178: When cancelling a goal you must avoid advancing
current_index or sending the next waypoint until the old goal's cancel has
completed or its result is verified as not stale; modify the cancel flow around
cancel_goal_async() and the result callback handling so that either (a) you
await the cancel future (from cancel_goal_async) before calling
_send_next_goal(), or (b) bind the goal's unique id/sequence into the future
callback you register on result_future (from
get_result_async()/result_future.add_done_callback) and in _on_goal_result
ignore results whose goal id/sequence does not match the active
goal_id/current_index. Update the code paths that call cancel_goal_async(),
_on_goal_result, get_result_async(), result_future.add_done_callback, and
_send_next_goal to implement one of these approaches consistently.

---

Nitpick comments:
In `@CMakeLists.txt`:
- Around line 56-59: Move the find_package(pluginlib REQUIRED) call to be
grouped with the other find_package declarations near the top of the CMakeLists
(so all dependency find_package calls are together); keep
pluginlib_export_plugin_description_file(rmcs_executor plugins.xml) where it is,
just relocate the find_package line so dependency declarations are consistent
and readable.
- Around line 20-23: The CMakeLists currently calls find_package(PCL REQUIRED)
but never uses it in target_include_directories or target_link_libraries; either
remove the unused find_package(PCL REQUIRED) or explicitly consume it: add PCL
include/link usage to the appropriate target (e.g., reference PCL include/libs
via target_include_directories/target_link_libraries or use the PCL::PCL target)
or, if intentional for future work, add a brief comment next to find_package(PCL
REQUIRED) explaining why it’s declared now (similar to the Eigen3 comment
pattern).

In `@launch/custom.launch.py`:
- Around line 165-175: 当前在 launch 中以静态硬编码方式在 Node(package="tf2_ros",
executable="static_transform_publisher", name="world_to_base_link_tf") 的
arguments 列表里使用了 "1.2", "6.3", "0" 作为 world->base_link 初始平移,需将这些值暴露为可配置项;修改方案:为
launch 文件添加 launch arguments(例如 world_x, world_y, world_z 或从 config 参数读取),在创建
Node 时用传入的 LaunchConfiguration/DeclareLaunchArgument
值替换硬编码字符串,并确保为每个参数设置合理默认值和描述以保持向后兼容性。
- Around line 248-250: The helper _load_custom_config currently opens and parses
YAML without handling errors; wrap the file open and yaml.safe_load calls in a
try/except that catches FileNotFoundError/PermissionError and yaml.YAMLError,
and handle each case by either returning an empty dict for missing/unreadable
config or raising a clearer exception/logging a user-friendly message that
includes the config_file path and the underlying error; make sure the function
name _load_custom_config is updated to use the new error handling and preserve
returning {} when appropriate.

In `@src/component/util/rclcpp/node.ccm`:
- Around line 9-14: The qos presets are not exported from the C++ module, so
make them available outside the module by exporting them: change the declaration
to export the qos namespace or export the individual symbols so other
translation units can access debug and real_time (refer to the namespace qos and
the inline const auto debug and real_time definitions using rclcpp::QoS{...}).
Ensure the export applies to the namespace or to each variable (e.g., export
namespace qos { ... } or export inline const auto debug/... ) so the presets are
visible outside the module.
- Around line 15-29: The helper functions check_naming and naming_standard are
defined inside namespace prefix but not exported from the module; either export
them for external use or move them into an internal (unnamed) namespace if they
should be internal. To export, add the module export keyword (e.g., export
constexpr auto check_naming(...) noexcept -> bool and export constexpr auto
naming_standard = "...";) inside namespace prefix so callers can access them;
otherwise wrap their definitions in an anonymous namespace (or remove namespace
prefix and place them in unnamed namespace) to mark them internal. Ensure both
check_naming and naming_standard are handled the same way.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d68694d6-7c4c-4805-b82c-70a200d1b2c9

📥 Commits

Reviewing files that changed from the base of the PR and between 8c91257 and 11afb3e.

⛔ Files ignored due to path filters (2)
  • maps/local_mock.png is excluded by !**/*.png
  • maps/rmul.png is excluded by !**/*.png
📒 Files selected for processing (20)
  • CMakeLists.txt
  • cmake/export.cmake
  • config/nav2.yaml
  • config/plan.yaml
  • launch/custom.launch.py
  • launch/follow_waypoints.launch.py
  • maps/local_mock.yaml
  • maps/rmul.yaml
  • package.xml
  • src/component/brain/plan.cc
  • src/component/brain/plan.hh
  • src/component/brain/rule.hh
  • src/component/component.cc
  • src/component/util/fsm.hh
  • src/component/util/pimpl.hh
  • src/component/util/rclcpp/node.ccm
  • src/component/util/status.hh
  • src/decision/decision.py
  • src/decision/follow_waypoints_runner.py
  • src/decision/goal_topic_bridge.py
✅ Files skipped from review due to trivial changes (4)
  • cmake/export.cmake
  • maps/local_mock.yaml
  • maps/rmul.yaml
  • package.xml
🚧 Files skipped from review as they are similar to previous changes (3)
  • config/plan.yaml
  • launch/follow_waypoints.launch.py
  • config/nav2.yaml

Comment on lines +152 to +155
bag_cmd = ["ros2", "bag", "play", bag_path]
if bag_use_clock.lower() in {"1", "true", "yes", "on"}:
bag_cmd.append("--clock")
result_actions.append(ExecuteProcess(cmd=bag_cmd, output="screen"))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

bag_path 未正确转换为字符串

bag_pathLaunchConfiguration 对象,在第 57 行未调用 .perform(context),但在第 152 行直接用于构建命令列表。这会导致命令中包含对象引用而非实际路径字符串。

🐛 建议修复
-    bag_path = LaunchConfiguration("bag_path")
+    bag_path = LaunchConfiguration("bag_path").perform(context)

或者使用 launch.substitutions 的方式处理:

     if config["use_bag"]:
-        bag_cmd = ["ros2", "bag", "play", bag_path]
+        bag_cmd = ["ros2", "bag", "play", LaunchConfiguration("bag_path")]
         if bag_use_clock.lower() in {"1", "true", "yes", "on"}:
             bag_cmd.append("--clock")
-        result_actions.append(ExecuteProcess(cmd=bag_cmd, output="screen"))
+        result_actions.append(ExecuteProcess(
+            cmd=bag_cmd,
+            output="screen",
+            shell=False,
+        ))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/custom.launch.py` around lines 152 - 155, The LaunchConfiguration
object bag_path is being used directly when building bag_cmd, which embeds the
object reference instead of the actual string; convert LaunchConfiguration
values to strings before building the command by calling
bag_path.perform(context) (and similarly call bag_use_clock.perform(context)
before doing .lower()), or alternatively construct the ExecuteProcess cmd using
launch.substitutions (i.e., pass LaunchConfiguration/substitution objects
instead of raw Python strings) so that ExecuteProcess receives proper
substitutions; update the code around bag_cmd, bag_use_clock, and the
ExecuteProcess invocation (where result_actions is appended) to use one of these
two approaches.

Comment on lines +43 to +46
auto goal_position() noexcept {
// ......
return std::tuple{goal_x, goal_y};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

goal_position() 实现不完整

方法体包含占位符注释 // ......,且 goal_x/goal_y 始终返回 NaN。根据 plan.hh 中的公共接口,没有任何方法会更新这些坐标值,导致 goal_position() 永远返回无效数据。

需要我帮助实现目标位置更新逻辑吗?

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

In `@src/component/brain/plan.cc` around lines 43 - 46, The goal_position()
implementation is a placeholder returning NaN via goal_x/goal_y; replace the
stub with real logic that reads the plan's authoritative goal state (e.g., the
member variables or methods declared in plan.hh such as the goal fields or any
set_goal/update_goal functions), compute and assign goal_x and goal_y from that
state (or from the current path/goal index if the class stores path_ or
goal_index), validate they are finite (fallback to a safe default or
std::optional/empty result if not), and return the computed tuple; ensure you
update/initialize the underlying goal storage wherever the class exposes
mutators so goal_position() no longer returns NaN.

return pimpl->goal_position();
}

auto PlanBox::do_plan_() noexcept -> void { pimpl->do_plan(); }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

do_plan() 返回值被丢弃

Impl::do_plan() 返回计划名称 (to_the_homecruise_mode),但 do_plan_() 调用时未使用该返回值。考虑到 plan_map 中存储了对应的 callable,返回值可能应该用于执行相应的计划操作。

💡 建议使用返回值执行计划
-auto PlanBox::do_plan_() noexcept -> void { pimpl->do_plan(); }
+auto PlanBox::do_plan_() noexcept -> void {
+    auto plan_name = pimpl->do_plan();
+    if (auto it = pimpl->plan_map.find(plan_name); it != pimpl->plan_map.end()) {
+        it->second();
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/component/brain/plan.cc` at line 62, PlanBox::do_plan_() currently calls
pimpl->do_plan() and discards its return value; Impl::do_plan() actually returns
a plan name (e.g. "to_the_home" or "cruise_mode") that should be used to run the
corresponding callable stored in plan_map. Change do_plan_() to capture the
returned plan key from Impl::do_plan(), look it up in PlanBox::plan_map (or
pimpl->plan_map if stored in Impl), and invoke the callable if found; add a safe
fallback (log or no-op) when the key is missing or callable is empty and
preserve noexcept behavior of do_plan_().

Comment on lines +20 to +23
template <std::invocable<Information&> F>
auto update_information(F&& function) noexcept -> void {
std::forward<F>(function)(information_());
do_plan_();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

set_rule() 之前调用 update_information() 会直接撞空指针。

src/component/brain/plan.cc 里的 Impl::do_plan() 直接读取 rule->limit.*,但 PlanBox 默认构造时 rule 还是空的;这里每次更新信息都无条件 do_plan_(),首次调用若早于 set_rule() 就会崩。请在规划前做 guard,或者把 rule 变成构造必需项。

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

In `@src/component/brain/plan.hh` around lines 20 - 23, update_information calls
do_plan_ unconditionally which lets Impl::do_plan access rule->limit.* while
PlanBox's rule is null by default; to fix, add a guard in update_information (or
in do_plan_) to check that rule is set before calling do_plan_ (e.g., return
early if rule == nullptr), or make rule a required constructor parameter so it
cannot be null; reference symbols: update_information, do_plan_(),
Impl::do_plan, PlanBox, rule, set_rule().

Comment on lines +40 to +75
auto use(state_type state, on_begin_type&& on_begin, on_event_type&& on_event) {
static_assert(
std::is_invocable_r_v<void, on_begin_type>, "on_begin must be callable returning void");
static_assert(
std::is_invocable_r_v<state_type, on_event_type>,
"on_event must be callable returning state_type");

struct State final : IState {
[[no_unique_address]] std::decay_t<on_begin_type> impl_on_begin;
[[no_unique_address]] std::decay_t<on_event_type> impl_on_event;

explicit State(on_begin_type&& on_begin, on_event_type&& on_event)
: impl_on_begin{std::forward<on_begin_type>(on_begin)}
, impl_on_event{std::forward<on_event_type>(on_event)} {}

~State() override = default;

auto on_begin() -> void override { impl_on_begin(); }
auto on_event() -> state_type override { return impl_on_event(); }
};
states_map.at(std::to_underlying(state)) = std::make_unique<State>(
std::forward<on_begin_type>(on_begin), std::forward<on_event_type>(on_event));
}

template <state_type state>
auto use(auto&& on_begin, auto&& on_event) {
using _1 = decltype(on_begin);
using _2 = decltype(on_event);
use(state, std::forward<_1>(on_begin), std::forward<_2>(on_event));
}

template <state_type state>
auto use(auto&& on_event) {
using _1 = decltype(on_event);
use(state, [] {}, std::forward<_1>(on_event));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

请显式禁止将 END 当作可注册/可启动状态。

当前 use(...)start_on(...) 没有拦截 END,会在后续索引 states_map 时触发越界访问路径(例如 Line 60/Line 92)。建议在运行时和编译期都加保护,避免把 sentinel 误当正常状态使用。

🔧 建议修复
+#include <stdexcept>
+
 template <typename on_begin_type, typename on_event_type>
 auto use(state_type state, on_begin_type&& on_begin, on_event_type&& on_event) {
+    if (state == StateKey::END) {
+        throw std::invalid_argument("END is a sentinel and cannot be registered");
+    }
     static_assert(
         std::is_invocable_r_v<void, on_begin_type>, "on_begin must be callable returning void");
@@
 template <state_type state>
 auto use(auto&& on_begin, auto&& on_event) {
+    static_assert(state != StateKey::END, "END is a sentinel and cannot be registered");
     using _1 = decltype(on_begin);
     using _2 = decltype(on_event);
     use(state, std::forward<_1>(on_begin), std::forward<_2>(on_event));
 }
@@
 auto start_on(state_type state) {
+    if (state == StateKey::END) {
+        throw std::invalid_argument("cannot start on END");
+    }
     current_state = state;
     current_event = nullptr;
 }

Based on learnings: 在 rmcs-navigation 项目的 FSM 实现中,END 是 sentinel 终止标记,不是需要注册处理器的正常状态。

Also applies to: 84-87

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

In `@src/component/util/fsm.hh` around lines 40 - 75, The code currently allows
registering/starting the sentinel END state and later indexing states_map out of
range; forbid END both at compile-time and runtime: add a compile-time check
(e.g., static_assert or std::enable_if) in the template overloads use<state>()
and start_on<state>() to ensure the template parameter is not state_type::END,
and add a runtime guard in the non-template use(state_type state, ...) and
start_on(state_type) functions that throws or asserts if state ==
state_type::END before touching states_map (and include a clear error message
referencing END and states_map). Ensure you update all overloads mentioned
(use(state_type,...), use<state>(), use<state>(on_event), and start_on variants)
so END cannot be registered or started.

Comment on lines +19 to +39
auto to_string(rmcs_msgs::GameStage stage) noexcept -> std::string {
using rmcs_msgs::GameStage;
switch (stage) {
case GameStage::NOT_START: return "NotStart";
case GameStage::PREPARATION: return "Preparation";
case GameStage::REFEREE_CHECK: return "RefereeCheck";
case GameStage::COUNTDOWN: return "Countdown";
case GameStage::STARTED: return "Started";
case GameStage::SETTLING: return "Settling";
case GameStage::UNKNOWN: return "Unknown";
}
return "Unknown";
}
auto to_string(RobotStatus status) noexcept -> std::string {
switch (status) {
case RobotStatus::Unknown: return "Unknown";
case RobotStatus::Normal: return "Normal";
case RobotStatus::Invincible: return "Invincible";
case RobotStatus::Deaded: return "Deaded";
}
return "Unknown";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

C++ 头文件中定义非 inline的自由函数会不会导致 multiple definition?返回std::string的函数标记为noexcept是否安全,还是应改为返回字面量类型(如const char */std::string_view)?

💡 Result:

是的,在C++头文件中定义非inline的自由函数会导致multiple definition错误。这违反了One Definition Rule (ODR),因为头文件被多个翻译单元包含时,非inline函数会有多个定义,而链接器无法处理重复符号。只有标记为inline的函数、类内定义的成员函数、模板等才允许在头文件中定义并在多个TU中出现,前提是所有定义相同。 对于返回std::string的函数标记为noexcept是安全的。std::string的构造函数、赋值、析构等操作在标准库实现中不抛异常(除了可能的前置条件违反,如length_error,但noexcept主要用于优化和传播不抛异常信息)。cppreference显示std::string的许多操作是noexcept的,返回std::string(通过RVO或move)也不会引入异常。标记noexcept可以启用更好的优化,如在noexcept函数中使用。 如果函数总是返回相同的内容(如常量字符串),使用std::string_view或const char*更高效,避免构造std::string的开销。但对于一般情况,返回std::string并标记noexcept是标准且安全的做法。

Citations:


这两个 to_string() 函数需要在头文件中标记为 inline

它们现在是头文件里的自由函数定义,多个翻译单元同时包含时会产生重复定义,违反单一定义规则(ODR)。需要加上 inline 关键字,或者将定义移到 .cc 文件中。

关于 noexcept 和返回 std::string:标准库中 std::string 的构造、赋值和移动操作都是安全的,不会在正常情况下抛异常,因此返回 std::string 并标记 noexcept 是安全且符合 C++ 最佳实践的。

如果函数总是返回相同内容,可以考虑改为返回 std::string_viewconst char* 以避免构造开销,但这是可选的性能优化,不是正确性问题。

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

In `@src/component/util/status.hh` around lines 19 - 39, Both free functions
to_string(rmcs_msgs::GameStage) and to_string(RobotStatus) are defined in a
header and must be marked inline to avoid ODR violations when included in
multiple translation units; update the declarations/definitions of
to_string(rmcs_msgs::GameStage) and to_string(RobotStatus) in this header to be
inline (or alternatively move their definitions into a .cc file), keeping the
same logic and noexcept specifier, and consider returning std::string_view or
const char* later as an optional optimization.

Comment on lines +17 to +19
class CruiseMethod:
method_name: str = ""
waypoints: list[float, float] = []
Copy link

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

🌐 Web query:

Python 内置泛型 list 是否只接受一个类型参数?list[float, float]在运行时会怎样?dataclass 字段为什么要用field(default_factory=list)而不是=[]

💡 Result:

是,Python内置泛型list只接受一个类型参数。list[float, float]在运行时会引发TypeError: Too many parameters for typing.List; actual 2, expected 1(尽管是内置泛型,但泛型别名行为类似),因为list是单参数泛型(PEP 585和typing规范)。 dataclass字段使用field(default_factory=list)而不是=[],是因为可变默认值问题:=[]创建单个共享列表实例,所有对象修改同一列表(Python类属性默认行为)。default_factory=list为每个实例调用list创建新列表,避免共享状态bug(dataclasses文档明确禁止直接可变默认,并强制使用default_factory)。

Citations:


🏁 Script executed:

cat -n src/decision/decision.py

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 1201


修复 CruiseMethod.waypoints 的类型注解和默认值。

list[float, float] 不是合法的泛型写法——list 只接受一个类型参数,代码将在导入时抛出 TypeError。同时 = [] 会导致所有实例共享同一个列表对象。请改为正确的类型注解(如 list[tuple[float, float]]),并使用 field(default_factory=list)

🧰 Tools
🪛 Ruff (0.15.6)

[warning] 19-19: Do not use mutable default values for dataclass attributes

(RUF008)

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

In `@src/decision/decision.py` around lines 17 - 19, The CruiseMethod class has an
invalid type annotation and a mutable default for waypoints; change the
annotation of waypoints to a sequence of coordinate pairs (e.g.
list[tuple[float, float]] or Sequence[Tuple[float, float]]) and replace the
shared empty list default with a dataclasses-safe default_factory (use
field(default_factory=list) from dataclasses); update imports to include
dataclasses.field (and typing.Tuple/Sequence if you choose those) and keep
method_name as-is.

Comment on lines +27 to +28
def _quaternion_to_yaw(z: float, w: float) -> float:
return math.atan2(2.0 * w * z, 1.0 - 2.0 * z * z)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Yaw 计算少了四元数的 x/y 分量。

当前公式只在 x == 0 && y == 0 的平面四元数上成立;odometry 一旦带有非零 x/yyaw_error 就会被算错,导致 waypoint 提前或延后判定到达。这里应使用完整四元数公式计算 yaw。

Also applies to: 209-212

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

In `@src/decision/follow_waypoints_runner.py` around lines 27 - 28,
_quaternion_to_yaw currently computes yaw using only z and w components which is
only valid when x and y are zero; replace the implementation to use the full
quaternion-to-yaw formula that includes x and y (yaw = atan2(2*(w*z + x*y), 1 -
2*(y*y + z*z))) and update any other occurrences of the simplified formula (the
other location referenced around lines 209-212) to use the same full formula;
locate and modify the function named _quaternion_to_yaw and the other identical
snippet to accept x, y, z, w (or read x and y where available) and compute yaw
with the full expression so yaw_error is correct when x/y are nonzero.

Comment on lines +31 to +55
def _on_goal(self, msg: PoseStamped) -> None:
threading.Thread(
target=self._send_goal,
args=(msg,),
daemon=True,
).start()

def _send_goal(self, msg: PoseStamped) -> None:
self._client.wait_for_server()

with self._lock:
current_goal = self._current_goal

if current_goal is not None:
cancel_future = current_goal.cancel_goal_async()
cancel_future.add_done_callback(lambda _: None)

goal = NavigateToPose.Goal()
goal.pose = msg
goal.behavior_tree = ""
with self._lock:
self._last_goal = msg

send_future = self._client.send_goal_async(goal)
send_future.add_done_callback(self._handle_goal_response)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

把 goal 发送串行化,别为每条消息新建一个线程。

两个目标几乎同时到达时,会并发执行 _send_goal();哪个 response 最后回来,就会把 _current_goal 回写成哪个 handle,_last_goal 的日志也会对错目标,后续 cancel 很容易打到旧 goal。更糟的是 action server 未起来时,wait_for_server() 会让这些线程一直堆积。这里更适合单个 worker/队列,或者把整个 cancel → send → accept 生命周期串行化。

Also applies to: 57-72

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

In `@src/decision/goal_topic_bridge.py` around lines 31 - 55, Replace the
per-message thread spawn in _on_goal/_send_goal with a single sequential worker:
stop creating a new Thread for every incoming PoseStamped and instead push msg
into a FIFO queue (e.g., collections.deque or queue.Queue) and have one
dedicated worker thread/loop that processes entries one-by-one; inside that
worker perform the entire cancel → wait_for_server → send_goal_async → handle
acceptance lifecycle while holding/using self._lock to update self._current_goal
and self._last_goal so they cannot be interleaved by concurrent sends, and
ensure wait_for_server is called once or from the worker (not blocking
per-message threads) and that _handle_goal_response is invoked from the
serialized worker context or safely updates shared state under the same lock.

Copy link

@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: 5

♻️ Duplicate comments (3)
src/decision/decision.py (2)

17-21: ⚠️ Potential issue | 🔴 Critical

修复 waypoints 的类型注解与默认值;当前写法会导致导入期异常和实例间共享状态。

Line 20 的 list[float, float] 不是合法写法(list 只接受一个类型参数),且 =[] 是可变默认值,会在实例间共享。

建议修改
-from dataclasses import dataclass
+from dataclasses import dataclass, field

 `@dataclass`
 class CruiseMethod:
     method_name: str = ""
-    waypoints: list[float, float] = []
+    waypoints: list[tuple[float, float]] = field(default_factory=list)
#!/bin/bash
# 验证当前代码是否命中问题模式,并验证 Python 对该泛型写法的行为
rg -n 'waypoints:\s*list\[float,\s*float\]\s*=\s*\[\]' src/decision/decision.py
python - <<'PY'
try:
    eval("list[float, float]")
    print("Unexpected: accepted")
except TypeError as e:
    print("Expected TypeError:", e)
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/decision/decision.py` around lines 17 - 21, The CruiseMethod dataclass
has an invalid type annotation and a mutable default for waypoints: replace the
annotation list[float, float] with a correct type (e.g., list[tuple[float,
float]] or list[Tuple[float, float]]/Sequence[Tuple[float, float]]) and stop
using a shared mutable default; set the default to None or use
dataclasses.field(default_factory=list) instead. Update imports accordingly
(from dataclasses import field and from typing import Tuple or Sequence if using
those) and change the waypoints declaration in CruiseMethod to use the new type
and safe default.

44-46: ⚠️ Potential issue | 🟠 Major

entrypoint() 的 busy loop 会持续占满 CPU。

Line 45-46 当前实现会空转。建议改为阻塞等待或直接 raise NotImplementedError(...)

建议修改(fail-fast 占位)
 def entrypoint() -> None:
-    while True:
-        continue
+    raise NotImplementedError("entrypoint is not implemented yet")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/decision/decision.py` around lines 44 - 46, entrypoint() currently
contains a busy loop that spins CPU indefinitely; replace the while True:
continue with a non-spin placeholder such as a blocking wait or a fail-fast stub
by raising NotImplementedError("entrypoint not implemented") (or use an
appropriate blocking primitive for your runtime) so the function no longer
consumes CPU while unimplemented; locate the entrypoint function in decision.py
and make this change.
src/component/component.cc (1)

70-77: ⚠️ Potential issue | 🔴 Critical

订阅回调签名仍然不符合 rclcpp 的支持列表。

这和之前指出的问题一致。AnySubscriptionCallbackPossibleTypes 支持的是 const MessageT&std::shared_ptr<const MessageT> / const std::shared_ptr<const MessageT>&、以及按值接收的 UniquePtr(及带 MessageInfo 的变体);const std::unique_ptr<Twist>& 不在列表里,所以这里还是会在模板实例化时失败。 (docs.ros.org)

建议修改
         subscription_twist = Node::create_subscription<Twist>(
-            "/cmd_vel_smoothed", 10, [&, this](const std::unique_ptr<Twist>& msg) {
+            "/cmd_vel_smoothed", 10, [this](Twist::ConstSharedPtr msg) {
                 command_velocity->x() = msg->linear.x;
                 command_velocity->y() = msg->linear.y;
                 command_velocity->z() = msg->angular.z;
                 command_received_timestamp = std::chrono::steady_clock::now();
                 has_warning_timeout = false;
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/component/component.cc` around lines 70 - 77, The subscription callback
signature is invalid for rclcpp: replace the current parameter type "const
std::unique_ptr<Twist>&" in the Node::create_subscription call
(subscription_twist) with a supported type such as Twist::UniquePtr (or
std::unique_ptr<Twist> by value) or std::shared_ptr<const Twist> or const
Twist&; update the lambda parameter and its uses accordingly (e.g., use
msg->linear.x when keeping a pointer type, or msg.linear.x if switching to const
Twist&) so the callback matches rclcpp's AnySubscriptionCallbackPossibleTypes.
🧹 Nitpick comments (3)
src/decision/decision.py (2)

23-24: 占位函数建议显式失败(NotImplementedError),避免“静默成功”。

pass 会让调用方误以为功能正常执行,调试成本更高。

建议修改
 def setup_cruise_methods():
-    pass
+    raise NotImplementedError("setup_cruise_methods is not implemented yet")
@@
 def update_blackboard(combat_status: str):
-    pass
+    raise NotImplementedError("update_blackboard is not implemented yet")

Also applies to: 32-33

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

In `@src/decision/decision.py` around lines 23 - 24, The placeholder function
setup_cruise_methods (and any other stubbed functions around lines 32-33) should
raise NotImplementedError instead of using pass so callers fail fast; locate the
setup_cruise_methods definition in decision.py (and the other empty stubs
nearby), replace the body with a raised NotImplementedError including a short
message like "setup_cruise_methods not implemented" to make missing
implementation explicit and aid debugging.

27-29: 模块导入即写入 Blackboard 会产生全局副作用,建议改为显式初始化。

当前导入该模块就会修改全局状态,不利于测试隔离与复用。

建议修改方向
-Blackboard.set("health", 0)
-Blackboard.set("bullet", 0)
-Blackboard.set("cruise_method", "occupation")
+def initialize_blackboard_defaults() -> None:
+    Blackboard.set("health", 0)
+    Blackboard.set("bullet", 0)
+    Blackboard.set("cruise_method", "occupation")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/decision/decision.py` around lines 27 - 29, The module currently performs
global writes to Blackboard at import time (Blackboard.set("health", 0),
Blackboard.set("bullet", 0), Blackboard.set("cruise_method", "occupation")),
causing side effects; refactor by removing those top-level Blackboard.set calls
and add an explicit initializer function (e.g., initialize_blackboard_defaults
or setup_blackboard_defaults) that performs those three Blackboard.set
operations, then ensure callers (application entrypoint or relevant class
constructor) invoke that initializer where appropriate; keep the same keys
("health", "bullet", "cruise_method") and values so behavior is unchanged but no
writes happen on import.
launch/nav2.launch.py (1)

109-109: 优化列表拼接写法

根据静态分析工具建议,使用解包语法更符合 Python 惯例。

♻️ 建议修改
-            remappings=remappings + [("cmd_vel", "cmd_vel_nav")],
+            remappings=[*remappings, ("cmd_vel", "cmd_vel_nav")],

同样适用于第 125 行和第 149 行。

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

In `@launch/nav2.launch.py` at line 109, 将使用列表加法拼接 remappings 的写法(出现于
remappings=remappings + [("cmd_vel", "cmd_vel_nav")]
以及类似第125行和第149行的表达式)替换为解包语法,将 remappings = remappings + [...] 改为 remappings =
[*remappings, ("cmd_vel", "cmd_vel_nav")](并对第125行和第149行对应的元组做同样修改),以遵循 Python
列表解包惯例并提高可读性。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@launch/custom.launch.py`:
- Around line 261-263: The _load_custom_config function currently opens
config_file without handling missing files; catch FileNotFoundError around the
open/read in _load_custom_config and either return an empty dict as a safe
default or re-raise a clearer exception using the config_file path in the
message so launch failures show a helpful error; update references inside
_load_custom_config (the open(...) call and the yaml.safe_load(...) return)
accordingly.

In `@launch/nav2.launch.py`:
- Around line 27-41: The RewrittenYaml param_rewrites use incorrect keys so the
nested map_topic entries in nav2.yaml aren't being matched; update the
controller_params and planner_params RewrittenYaml blocks to rewrite the full
dot-separated nested parameter paths instead of "map_topic" (e.g. use
"local_costmap.local_costmap.ros__parameters.static_layer.map_topic" for
controller_params and
"global_costmap.global_costmap.ros__parameters.static_layer.map_topic" for
planner_params), keeping convert_types=True and mapping each full path to the
corresponding launch-time topic variable (local_map_topic and global_map_topic).

In `@src/component/component.cc`:
- Around line 60-63: The output registration currently seeds command_velocity
with NaNs (using kNan) which causes downstream consumers to briefly observe NaN
before the first /cmd_vel_smoothed arrives; change the initial registered value
passed to Component::register_output for name "/{}/command_velocity" (and the
other occurrences at the noted locations) from Eigen::Vector3d{kNan, kNan, kNan}
to Eigen::Vector3d::Zero(), and keep a separate boolean/state (e.g., a
"has_command" flag tied to command_received_timestamp or a new member) to
represent "no command received yet", leaving update() logic to emit zeros until
a real command arrives or timeout occurs.
- Around line 1-15: 把原来带发行版路径的头改为可移植形式并补上缺失的标准头:在 component.cc 中把 `#include`
<eigen3/Eigen/Geometry> 改为 `#include` <Eigen/Geometry>,并显式添加所需的标准库头 `#include`
<format>, `#include` <chrono>, `#include` <atomic>, `#include` <string_view>, `#include`
<limits>(这些符号由 std::format/
std::format_string、std::chrono::steady_clock::time_point、std::atomic<bool>、std::string_view、std::numeric_limits
提供,kNan 使用了 numeric_limits)。同时在 CMake 列表中为该目标在 target_link_libraries 中加入
Eigen3::Eigen 以传播 Eigen 的 include 目录。确保修改后能编译且不依赖发行版特定 include 路径。

In `@src/decision/decision.py`:
- Around line 36-41: The MoveTo behaviour currently doesn't follow the
py_trees.Behaviour contract: update() must return a py_trees.common.Status and
__init__ must call the base constructor to set the node name. Fix __init__ in
class MoveTo by calling super().__init__(name="MoveTo") and initialize any
needed instance state there; implement update(self) to return one of
py_trees.common.Status values (e.g., Status.RUNNING, Status.SUCCESS or
Status.FAILURE) based on the node's logic (use py_trees.common.Status to
reference the enum) rather than returning None, and ensure any state transitions
(e.g., setting succeeded/failed conditions) are reflected by the returned
Status.

---

Duplicate comments:
In `@src/component/component.cc`:
- Around line 70-77: The subscription callback signature is invalid for rclcpp:
replace the current parameter type "const std::unique_ptr<Twist>&" in the
Node::create_subscription call (subscription_twist) with a supported type such
as Twist::UniquePtr (or std::unique_ptr<Twist> by value) or
std::shared_ptr<const Twist> or const Twist&; update the lambda parameter and
its uses accordingly (e.g., use msg->linear.x when keeping a pointer type, or
msg.linear.x if switching to const Twist&) so the callback matches rclcpp's
AnySubscriptionCallbackPossibleTypes.

In `@src/decision/decision.py`:
- Around line 17-21: The CruiseMethod dataclass has an invalid type annotation
and a mutable default for waypoints: replace the annotation list[float, float]
with a correct type (e.g., list[tuple[float, float]] or list[Tuple[float,
float]]/Sequence[Tuple[float, float]]) and stop using a shared mutable default;
set the default to None or use dataclasses.field(default_factory=list) instead.
Update imports accordingly (from dataclasses import field and from typing import
Tuple or Sequence if using those) and change the waypoints declaration in
CruiseMethod to use the new type and safe default.
- Around line 44-46: entrypoint() currently contains a busy loop that spins CPU
indefinitely; replace the while True: continue with a non-spin placeholder such
as a blocking wait or a fail-fast stub by raising
NotImplementedError("entrypoint not implemented") (or use an appropriate
blocking primitive for your runtime) so the function no longer consumes CPU
while unimplemented; locate the entrypoint function in decision.py and make this
change.

---

Nitpick comments:
In `@launch/nav2.launch.py`:
- Line 109: 将使用列表加法拼接 remappings 的写法(出现于 remappings=remappings + [("cmd_vel",
"cmd_vel_nav")] 以及类似第125行和第149行的表达式)替换为解包语法,将 remappings = remappings + [...] 改为
remappings = [*remappings, ("cmd_vel",
"cmd_vel_nav")](并对第125行和第149行对应的元组做同样修改),以遵循 Python 列表解包惯例并提高可读性。

In `@src/decision/decision.py`:
- Around line 23-24: The placeholder function setup_cruise_methods (and any
other stubbed functions around lines 32-33) should raise NotImplementedError
instead of using pass so callers fail fast; locate the setup_cruise_methods
definition in decision.py (and the other empty stubs nearby), replace the body
with a raised NotImplementedError including a short message like
"setup_cruise_methods not implemented" to make missing implementation explicit
and aid debugging.
- Around line 27-29: The module currently performs global writes to Blackboard
at import time (Blackboard.set("health", 0), Blackboard.set("bullet", 0),
Blackboard.set("cruise_method", "occupation")), causing side effects; refactor
by removing those top-level Blackboard.set calls and add an explicit initializer
function (e.g., initialize_blackboard_defaults or setup_blackboard_defaults)
that performs those three Blackboard.set operations, then ensure callers
(application entrypoint or relevant class constructor) invoke that initializer
where appropriate; keep the same keys ("health", "bullet", "cruise_method") and
values so behavior is unchanged but no writes happen on import.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40982dcd-e85f-468d-a067-79df4e614402

📥 Commits

Reviewing files that changed from the base of the PR and between 11afb3e and f18a03e.

📒 Files selected for processing (5)
  • config/nav2.yaml
  • launch/custom.launch.py
  • launch/nav2.launch.py
  • src/component/component.cc
  • src/decision/decision.py
✅ Files skipped from review due to trivial changes (1)
  • config/nav2.yaml

Comment on lines +261 to +263
def _load_custom_config(config_file):
with open(config_file, "r", encoding="utf-8") as stream:
return yaml.safe_load(stream) or {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

缺少文件不存在的错误处理

当配置文件不存在时,open() 会抛出 FileNotFoundError,导致 launch 过程失败且错误信息不够清晰。建议添加错误处理或更明确的错误提示。

🛡️ 建议修复
 def _load_custom_config(config_file):
+    if not os.path.exists(config_file):
+        raise FileNotFoundError(
+            f"Configuration file not found: {config_file}"
+        )
     with open(config_file, "r", encoding="utf-8") as stream:
         return yaml.safe_load(stream) or {}

或者返回默认空配置:

 def _load_custom_config(config_file):
+    if not os.path.exists(config_file):
+        return {}
     with open(config_file, "r", encoding="utf-8") as stream:
         return yaml.safe_load(stream) or {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/custom.launch.py` around lines 261 - 263, The _load_custom_config
function currently opens config_file without handling missing files; catch
FileNotFoundError around the open/read in _load_custom_config and either return
an empty dict as a safe default or re-raise a clearer exception using the
config_file path in the message so launch failures show a helpful error; update
references inside _load_custom_config (the open(...) call and the
yaml.safe_load(...) return) accordingly.

Comment on lines +27 to +41
controller_params = RewrittenYaml(
source_file=params_file,
param_rewrites={
"map_topic": local_map_topic,
},
convert_types=True,
)

planner_params = RewrittenYaml(
source_file=params_file,
param_rewrites={
"map_topic": global_map_topic,
},
convert_types=True,
)
Copy link

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

🌐 Web query:

Nav2 RewrittenYaml param_rewrites nested parameters

💡 Result:

In Nav2, RewrittenYaml from nav2_common.launch is a launch Substitution that modifies a YAML parameter file by overriding specific leaf values based on param_rewrites dict during launch. For nested parameters, use dot-separated full paths in param_rewrites keys matching the YAML structure. Key mechanism from source code: It loads the YAML, collects all leaf keys using getYamlLeafKeys (recursive traversal yielding dot-separated paths like 'local_costmap.local_costmap.obstacle_layer.scan.topic'), and in substitute_params/add_params, if a path matches a param_rewrites key, updates the leaf value. Example usage in launch file: from nav2_common.launch import RewrittenYaml params_file = 'path/to/nav2_params.yaml' rewritten_params = RewrittenYaml( source_file=params_file, param_rewrites={ 'use_sim_time': use_sim_time, # top-level 'local_costmap.local_costmap.obstacle_layer.scan.topic': 'robot_name/scan', # nested 'controller_server.rosparameters.controller_frequency': '20.0' }, convert_types=True ) Then pass rewritten_params to Node(parameters=[rewritten_params]). Common issues (from GitHub #4308): Top-level params like robot_base_frame work, but deeply nested ones fail if paths incorrect or root_key mis-set (root_key scopes substitution). Without root_key='', it searches entire YAML. For per-node params under 'node_name.rosparameters', use full path. This matches Nav2 bringup launches overriding use_sim_time etc. across nodes. Source confirms leaf-only replacement via paths.

Citations:


🏁 Script executed:

# Find the nav2.yaml file
find . -name "nav2.yaml" -o -name "nav2_params.yaml" | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 96


🏁 Script executed:

# Also search for the launch file mentioned
find . -name "nav2.launch.py" | head -10

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 101


🏁 Script executed:

# Search for RewrittenYaml usage patterns in launch files
rg "RewrittenYaml" --type py -B 5 -A 10 | head -100

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 2406


🏁 Script executed:

# Examine the nav2.yaml file structure
cat ./config/nav2.yaml | head -200

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 5673


🏁 Script executed:

# Search specifically for map_topic in the yaml file
rg "map_topic" ./config/nav2.yaml -B 3 -A 1

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 581


修复 RewrittenYaml 中的嵌套参数路径错误

RewrittenYamlparam_rewrites 支持嵌套参数,但需要使用完整的点分隔路径。当前使用 "map_topic" 作为 key 无法匹配 nav2.yaml 中的实际参数位置,因为 map_topic 位于深层嵌套结构中:

  • local_costmap.local_costmap.ros__parameters.static_layer.map_topic
  • global_costmap.global_costmap.ros__parameters.static_layer.map_topic

当前配置会静默失败,导致 Nav2 节点的 costmap 订阅错误的地图话题。

🐛 修复方案
 controller_params = RewrittenYaml(
     source_file=params_file,
     param_rewrites={
-        "map_topic": local_map_topic,
+        "local_costmap.local_costmap.ros__parameters.static_layer.map_topic": local_map_topic,
     },
     convert_types=True,
 )

 planner_params = RewrittenYaml(
     source_file=params_file,
     param_rewrites={
-        "map_topic": global_map_topic,
+        "global_costmap.global_costmap.ros__parameters.static_layer.map_topic": global_map_topic,
     },
     convert_types=True,
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/nav2.launch.py` around lines 27 - 41, The RewrittenYaml param_rewrites
use incorrect keys so the nested map_topic entries in nav2.yaml aren't being
matched; update the controller_params and planner_params RewrittenYaml blocks to
rewrite the full dot-separated nested parameter paths instead of "map_topic"
(e.g. use "local_costmap.local_costmap.ros__parameters.static_layer.map_topic"
for controller_params and
"global_costmap.global_costmap.ros__parameters.static_layer.map_topic" for
planner_params), keeping convert_types=True and mapping each full path to the
corresponding launch-time topic variable (local_map_topic and global_map_topic).

Comment on lines +1 to +15
#include <eigen3/Eigen/Geometry>
#include <geometry_msgs/msg/twist.hpp>
#include <rclcpp/node.hpp>
#include <rclcpp/subscription.hpp>
#include <std_msgs/msg/string.hpp>

#include <rmcs_executor/component.hpp>
#include <rmcs_msgs/game_stage.hpp>

#include <yaml-cpp/yaml.h>

namespace rmcs {

constexpr auto kRosLabel = std::string_view{"rmcs_navigation"};
constexpr auto kNan = std::numeric_limits<double>::quiet_NaN();
Copy link

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:

# First, let's explore the repository structure and find the relevant files
git ls-files | grep -E "(CMakeLists|component\.cc)" | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 139


🏁 Script executed:

# Let's examine the actual src/component/component.cc file to see all includes and what's actually used
find . -name "component.cc" -type f

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 106


🏁 Script executed:

# Check if there are any other source files to understand the project structure
find . -name "*.cc" -o -name "*.cpp" | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 164


🏁 Script executed:

# Look for CMakeLists.txt to understand Eigen configuration
find . -name "CMakeLists.txt" -type f

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 116


🏁 Script executed:

# Read the component.cc file to see the includes and usage
wc -l ./src/component/component.cc

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 109


🏁 Script executed:

# Read the full component.cc file
cat -n ./src/component/component.cc | head -150

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 4436


🏁 Script executed:

# Read the CMakeLists.txt to see how Eigen is configured
cat -n ./CMakeLists.txt

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 2016


🏁 Script executed:

# Verify if Eigen3 should be linked - check if it's header-only
# and check what the proper Eigen3 include convention is
rg "Eigen3::Eigen|eigen3" --type cmake

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 60


🏁 Script executed:

# Check if there are any other files using Eigen to understand the convention
rg "#include.*[Ee]igen" --type cc --type hpp

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 104


🏁 Script executed:

# Check other Eigen usage in the codebase
rg "#include.*[Ee]igen"

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 137


🏁 Script executed:

# Check the actual Eigen include convention in project
rg "Eigen"

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 439


🏁 Script executed:

# Let's verify the Eigen documentation convention by checking what modern CMake Eigen3 packages typically provide
# First, let's see if there are any build artifacts or configuration files
ls -la | grep -E "build|cmake"

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 299


🏁 Script executed:

# Check what include path Eigen3 modern CMake packages typically provide
# by looking at how other ROS2 projects might use it
web_search "Eigen3 CMake find_package include path convention"

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 128


🏁 Script executed:

# Let's also verify if Eigen3 needs to be linked explicitly
# Check if there's a linking issue
rg "target_link_libraries" CMakeLists.txt -A 10

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 365


🌐 Web query:

Eigen3 CMake find_package include path convention header-only

💡 Result:

The standard convention for using Eigen3 with CMake's find_package, particularly for its header-only nature, is: 1. Use find_package(Eigen3 REQUIRED NO_MODULE) (CMake 3.5+ required). 2. Link your target to the imported INTERFACE target Eigen3::Eigen via target_link_libraries(your_target Eigen3::Eigen). 3. Do NOT manually add include directories. The Eigen3::Eigen target has INTERFACE_INCLUDE_DIRECTORIES set to the Eigen headers directory (typically the parent of the 'Eigen/' subdirectory, e.g., /usr/include/eigen3), so linking propagates the include path automatically. This allows #include <Eigen/Dense> in your code. Eigen must be built/installed first to generate Eigen3Config.cmake (despite being header-only), usually placing headers in /include/eigen3/Eigen and config in /share/eigen3/cmake. If not installed, manual include_directories or FetchContent with manual INTERFACE target setup is an alternative, but find_package is the official convention per Eigen docs.

Citations:


把头文件依赖改成可移植且自洽的形式。

#include <eigen3/Eigen/Geometry> 把发行版目录结构写进了源码,当前静态分析也已经在这里报了找不到头文件;Eigen 文档给出的标准做法是使用 CMake 的 find_package(Eigen3 REQUIRED) 后,在代码中用 #include <Eigen/Geometry> 而不是带 eigen3/ 前缀的路径。另外,这个 TU 直接用了 std::formatstd::format_stringstd::chrono::steady_clock::time_pointstd::atomic<bool>std::string_viewstd::numeric_limits,它们分别在标准库的 <format><chrono><atomic><string_view><limits> 头里声明,当前文件没有显式包含。建议一起修掉,避免构建结果依赖特定安装布局和传递包含。

同时注意 CMakeLists.txt 第 47-55 行的 target_link_libraries 没有链接 Eigen3::Eigen,即使 Eigen 是纯头文件库,也需要通过链接目标来传播 CMake 的 include 目录配置。 (eigen.tuxfamily.org)

🧰 Tools
🪛 Clang (14.0.6)

[error] 1-1: 'eigen3/Eigen/Geometry' file not found

(clang-diagnostic-error)

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

In `@src/component/component.cc` around lines 1 - 15,
把原来带发行版路径的头改为可移植形式并补上缺失的标准头:在 component.cc 中把 `#include` <eigen3/Eigen/Geometry>
改为 `#include` <Eigen/Geometry>,并显式添加所需的标准库头 `#include` <format>, `#include` <chrono>,
`#include` <atomic>, `#include` <string_view>, `#include` <limits>(这些符号由 std::format/
std::format_string、std::chrono::steady_clock::time_point、std::atomic<bool>、std::string_view、std::numeric_limits
提供,kNan 使用了 numeric_limits)。同时在 CMake 列表中为该目标在 target_link_libraries 中加入
Eigen3::Eigen 以传播 Eigen 的 include 目录。确保修改后能编译且不依赖发行版特定 include 路径。

Comment on lines +60 to +63
const auto name = std::format("/{}/command_velocity", kRosLabel);
const auto vec_nan = Eigen::Vector3d{kNan, kNan, kNan};
Component::register_output(name, command_velocity, vec_nan);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

启动阶段会短暂向下游暴露 NaN 速度。

command_velocity 在注册时被初始化成 NaN,但 command_received_timestamp 又在构造函数里被设为 now()。因此在首条 /cmd_vel_smoothed 到来前的前 100ms 内,update() 不会进入超时分支,下游读到的不是安全的 0 速度而是 NaN。建议把初始输出设为 Eigen::Vector3d::Zero(),并单独用状态位区分“尚未收到命令”。

Also applies to: 79-80, 82-92

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

In `@src/component/component.cc` around lines 60 - 63, The output registration
currently seeds command_velocity with NaNs (using kNan) which causes downstream
consumers to briefly observe NaN before the first /cmd_vel_smoothed arrives;
change the initial registered value passed to Component::register_output for
name "/{}/command_velocity" (and the other occurrences at the noted locations)
from Eigen::Vector3d{kNan, kNan, kNan} to Eigen::Vector3d::Zero(), and keep a
separate boolean/state (e.g., a "has_command" flag tied to
command_received_timestamp or a new member) to represent "no command received
yet", leaving update() logic to emit zeros until a real command arrives or
timeout occurs.

Comment on lines +36 to +41
class MoveTo(Behaviour):
def __init__(self) -> None:
pass

def update(self) -> None:
pass
Copy link

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

🌐 Web query:

在 py_trees 中,继承 py_trees.behaviour.Behaviour 时,__init__ 需要如何调用父类?update() 的返回值类型和可用状态枚举是什么?

💡 Result:

在 py_trees 中,继承 py_trees.behaviour.Behaviour 时,init 方法需要调用 super.init(name),其中 name 是字符串参数,用于设置行为名称。这是官方文档和示例中标准做法。 update 方法的返回值类型是 py_trees.common.Status,必须返回 RUNNING、SUCCESS 或 FAILURE(INVALID 是初始默认状态,不应由 update 返回)。 可用状态枚举 py_trees.common.Status 包括: - SUCCESS: 行为检查通过或动作成功完成。 - FAILURE: 行为检查失败或动作失败。 - RUNNING: 行为正在执行中,结果待定。 - INVALID: 未初始化或非活动状态。

Citations:


🏁 Script executed:

cat -n src/decision/decision.py | head -50

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 1209


修复 MoveTo 类违反行为树基类契约的问题

MoveTo 类未正确实现 py_trees.behaviour.Behaviour 接口:

  • __init__() 未调用 super().__init__(name="MoveTo"),行为节点名称无法初始化
  • update() 返回类型为 None,但必须返回 py_trees.common.Status(SUCCESS、FAILURE 或 RUNNING)
修改建议
+from py_trees.common import Status
@@
 class MoveTo(Behaviour):
     def __init__(self) -> None:
-        pass
+        super().__init__(name="MoveTo")
 
-    def update(self) -> None:
-        pass
+    def update(self) -> Status:
+        return Status.RUNNING
📝 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
class MoveTo(Behaviour):
def __init__(self) -> None:
pass
def update(self) -> None:
pass
from py_trees.common import Status
class MoveTo(Behaviour):
def __init__(self) -> None:
super().__init__(name="MoveTo")
def update(self) -> Status:
return Status.RUNNING
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/decision/decision.py` around lines 36 - 41, The MoveTo behaviour
currently doesn't follow the py_trees.Behaviour contract: update() must return a
py_trees.common.Status and __init__ must call the base constructor to set the
node name. Fix __init__ in class MoveTo by calling
super().__init__(name="MoveTo") and initialize any needed instance state there;
implement update(self) to return one of py_trees.common.Status values (e.g.,
Status.RUNNING, Status.SUCCESS or Status.FAILURE) based on the node's logic (use
py_trees.common.Status to reference the enum) rather than returning None, and
ensure any state transitions (e.g., setting succeeded/failed conditions) are
reflected by the returned Status.

Copy link

@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: 3

🧹 Nitpick comments (5)
CMakeLists.txt (3)

20-23: 建议为预留依赖添加用途说明。

Eigen3PCL 已声明但在当前组件代码中未见使用。根据项目的过往实践,若这些依赖是为未来功能预留,应添加注释说明其预期用途。

💡 建议的修改
+# TODO: Eigen3 will be used for pose transformations and matrix operations
 find_package(Eigen3 REQUIRED)
 find_package(yaml-cpp REQUIRED)
 find_package(rclcpp REQUIRED)
+# TODO: PCL will be used for point cloud processing in obstacle detection
 find_package(PCL REQUIRED)

基于 learnings:在 CMakeLists.txt 文件中,当有意包含供将来使用的依赖项时,应添加简短注释说明理由或用 TODO 记录其预期用途。

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

In `@CMakeLists.txt` around lines 20 - 23, The CMakeLists.txt declares
find_package(Eigen3 REQUIRED) and find_package(PCL REQUIRED) but the code
currently doesn't use them; add a short inline comment next to these
find_package lines (or a TODO above them) explaining their intended future use
(e.g., "reserved for future linear algebra ops / sensor point-cloud processing")
so reviewers know they are intentionally predeclared; update the lines
referencing Eigen3 and PCL in CMakeLists.txt accordingly to include that
explanatory comment.

59-62: 建议将 find_package(pluginlib) 与其他依赖声明放在一起。

find_package(pluginlib REQUIRED) 位于库目标定义之后(第 59 行),而其他所有 find_package 调用都集中在第 20-30 行。虽然功能上没有问题,但将所有依赖查找语句放在一起可以提高可读性和可维护性。

♻️ 建议的修改

将第 59 行移动到第 30 行之后:

 find_package(rmcs_executor REQUIRED)
+find_package(pluginlib REQUIRED)
 
 # RMCS Component
 add_library(${PROJECT_NAME} SHARED)

并移除原来的位置:

 )
-find_package(pluginlib REQUIRED)
 pluginlib_export_plugin_description_file(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 59 - 62, Move the dependency discovery call
find_package(pluginlib REQUIRED) to join the other find_package calls (grouped
around the existing block at lines ~20-30) so all dependency declarations are
together; keep the pluginlib_export_plugin_description_file(rmcs_executor
plugins.xml) call where it is or immediately after, and remove the original
find_package(pluginlib REQUIRED) from its current location so there is only one
declaration. Ensure you reference the same package name (pluginlib) and keep
rmcs_executor and plugins.xml unchanged.

7-8: 建议为 CMake 策略设置添加注释说明。

CMP0167CMP0144 策略设置缺少解释性注释。这些策略会影响 FindBoost 行为和 find_package() 大小写敏感性,添加注释可以帮助后续维护者理解为什么需要这些特定设置。

💡 建议的修改
-set(CMAKE_POLICY_DEFAULT_CMP0167 OLD)
-set(CMAKE_POLICY_DEFAULT_CMP0144 NEW)
+# CMP0167: Use legacy FindBoost module behavior for compatibility
+set(CMAKE_POLICY_DEFAULT_CMP0167 OLD)
+# CMP0144: find_package() uses upper-case <PACKAGENAME>_ROOT variables
+set(CMAKE_POLICY_DEFAULT_CMP0144 NEW)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 7 - 8, The two CMake policy settings
set(CMAKE_POLICY_DEFAULT_CMP0167 OLD) and set(CMAKE_POLICY_DEFAULT_CMP0144 NEW)
lack explanatory comments; update the CMakeLists to add brief comments next to
these set(...) lines explaining what each policy controls (e.g., CMP0167 affects
FindBoost target semantics/namespace behavior and why OLD is chosen, CMP0144
controls case-sensitivity of find_package() and why NEW is required) and include
the rationale or link to the CMake policy documentation so future maintainers
understand why these specific values were forced.
launch/custom.launch.py (1)

180-197: 考虑将初始位姿参数化

world->base_link 的初始位姿硬编码为 (1.2, 6.3, 0),这可能仅适用于特定环境。建议将其提取为 launch 参数,便于不同场景下复用。

♻️ 建议修改

generate_launch_description() 中添加参数:

DeclareLaunchArgument("initial_x", default_value="1.2"),
DeclareLaunchArgument("initial_y", default_value="6.3"),

_build_mode_actions 中使用:

+    initial_x = LaunchConfiguration("initial_x").perform(context)
+    initial_y = LaunchConfiguration("initial_y").perform(context)
     ...
             arguments=[
-                "1.2", "6.3", "0",
+                initial_x, initial_y, "0",
                 "0", "0", "0",
                 "world", "base_link"
             ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/custom.launch.py` around lines 180 - 197, Extract the hardcoded
world->base_link pose into launch arguments: add DeclareLaunchArgument entries
(e.g., "initial_x" and "initial_y", with defaults "1.2" and "6.3") in
generate_launch_description(), then read them where _build_mode_actions
constructs initial_base_link and replace the literal "1.2" and "6.3" with the
launch substitutions (use LaunchConfiguration for "initial_x" and "initial_y");
keep the TimerAction/initial_base_link node creation and arguments order
unchanged and ensure types remain strings for the static_transform_publisher
arguments.
launch/nav2.launch.py (1)

115-115: 考虑使用列表解包语法替代列表拼接

静态分析工具建议使用 [*remappings, ("cmd_vel", "cmd_vel_nav")] 替代 remappings + [...],这种写法更符合 Python 惯用风格且性能略优。同样适用于第 131 行和第 155 行。

♻️ 建议修改
-            remappings=remappings + [("cmd_vel", "cmd_vel_nav")],
+            remappings=[*remappings, ("cmd_vel", "cmd_vel_nav")],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/nav2.launch.py` at line 115, Replace the list concatenations that
build remappings (e.g., occurrences of remappings + [("cmd_vel", "cmd_vel_nav")]
and the similar patterns used later) with list-unpacking style to follow Python
idioms and improve performance; locate the expressions that append tuples to the
existing remappings list (references: the variable remappings and the
concatenation expressions) and change them to use [*remappings, ("cmd_vel",
"cmd_vel_nav")] (and analogous replacements for the other two concatenations).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@launch/custom.launch.py`:
- Around line 78-91: The launch sets enable_local_map_node to the same value as
use_local_mock, which starts the live local_map node when a static local mock is
intended; update the nav2_actions launch_arguments so enable_local_map_node is
the boolean inverse of use_local_mock (i.e., when use_local_mock is "true" set
enable_local_map_node to "false" and vice‑versa), ensuring you convert the
inverted value back to the expected string (e.g., "true"/"false") before
inserting it into the dict for IncludeLaunchDescription; adjust the code around
the symbols use_local_mock and enable_local_map_node inside nav2_actions
accordingly.
- Around line 245-247: The current assignment of bag_path_default uses
str(bag_config.get("path")) which turns None into the literal "None"; change it
to safely handle missing values by reading the raw value (via
bag_config.get("path", None) or checking if bag_config.get("path") is None) and
only convert to string when a non-None path exists (e.g., set bag_path_default
to "" or None when missing), updating related logic that consumes
bag_path_default to treat empty/None as "no bag file" instead of a filename;
refer to bag_config, bag_path_default, and bag_use_clock_default to locate and
adjust the code paths that expect a valid bag path.

In `@src/component/util/command.hh`:
- Around line 22-24: The Command::gimbal_mode member is not initialized in
explicit Command(const YAML::Node& yaml), causing undefined behavior when later
read and passed to set_gimbal_mode; fix by initializing gimbal_mode inside the
Command(const YAML::Node&) constructor (parse the YAML node to a valid
GimbalMode or set a safe default) and ensure any YAML parsing errors fallback to
that default or throw, so set_gimbal_mode always receives a well-defined enum
value.

---

Nitpick comments:
In `@CMakeLists.txt`:
- Around line 20-23: The CMakeLists.txt declares find_package(Eigen3 REQUIRED)
and find_package(PCL REQUIRED) but the code currently doesn't use them; add a
short inline comment next to these find_package lines (or a TODO above them)
explaining their intended future use (e.g., "reserved for future linear algebra
ops / sensor point-cloud processing") so reviewers know they are intentionally
predeclared; update the lines referencing Eigen3 and PCL in CMakeLists.txt
accordingly to include that explanatory comment.
- Around line 59-62: Move the dependency discovery call find_package(pluginlib
REQUIRED) to join the other find_package calls (grouped around the existing
block at lines ~20-30) so all dependency declarations are together; keep the
pluginlib_export_plugin_description_file(rmcs_executor plugins.xml) call where
it is or immediately after, and remove the original find_package(pluginlib
REQUIRED) from its current location so there is only one declaration. Ensure you
reference the same package name (pluginlib) and keep rmcs_executor and
plugins.xml unchanged.
- Around line 7-8: The two CMake policy settings
set(CMAKE_POLICY_DEFAULT_CMP0167 OLD) and set(CMAKE_POLICY_DEFAULT_CMP0144 NEW)
lack explanatory comments; update the CMakeLists to add brief comments next to
these set(...) lines explaining what each policy controls (e.g., CMP0167 affects
FindBoost target semantics/namespace behavior and why OLD is chosen, CMP0144
controls case-sensitivity of find_package() and why NEW is required) and include
the rationale or link to the CMake policy documentation so future maintainers
understand why these specific values were forced.

In `@launch/custom.launch.py`:
- Around line 180-197: Extract the hardcoded world->base_link pose into launch
arguments: add DeclareLaunchArgument entries (e.g., "initial_x" and "initial_y",
with defaults "1.2" and "6.3") in generate_launch_description(), then read them
where _build_mode_actions constructs initial_base_link and replace the literal
"1.2" and "6.3" with the launch substitutions (use LaunchConfiguration for
"initial_x" and "initial_y"); keep the TimerAction/initial_base_link node
creation and arguments order unchanged and ensure types remain strings for the
static_transform_publisher arguments.

In `@launch/nav2.launch.py`:
- Line 115: Replace the list concatenations that build remappings (e.g.,
occurrences of remappings + [("cmd_vel", "cmd_vel_nav")] and the similar
patterns used later) with list-unpacking style to follow Python idioms and
improve performance; locate the expressions that append tuples to the existing
remappings list (references: the variable remappings and the concatenation
expressions) and change them to use [*remappings, ("cmd_vel", "cmd_vel_nav")]
(and analogous replacements for the other two concatenations).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6256561-2ac2-4f8c-8b3c-a394015c04aa

📥 Commits

Reviewing files that changed from the base of the PR and between f18a03e and f6d5cf4.

⛔ Files ignored due to path filters (1)
  • maps/empty.png is excluded by !**/*.png
📒 Files selected for processing (8)
  • CMakeLists.txt
  • config/nav2.yaml
  • launch/custom.launch.py
  • launch/nav2.launch.py
  • maps/empty.yaml
  • plugins.xml
  • src/component/component.cc
  • src/component/util/command.hh
✅ Files skipped from review due to trivial changes (3)
  • plugins.xml
  • maps/empty.yaml
  • config/nav2.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/component/component.cc

Comment on lines +245 to +247
bag_config = custom_config.get("bag", {})
bag_path_default = str(bag_config.get("path"))
bag_use_clock_default = str(bag_config.get("use_clock", True)).lower()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

bag_path_default 可能变为 "None" 字符串

custom.yaml 中没有配置 bag.path 时,bag_config.get("path") 返回 Nonestr(None) 会变成字符串 "None",导致 bag 模式下尝试播放名为 "None" 的文件。

🛡️ 建议修复
     bag_config = custom_config.get("bag", {})
-    bag_path_default = str(bag_config.get("path"))
+    bag_path_default = bag_config.get("path", "")
+    if bag_path_default is None:
+        bag_path_default = ""
     bag_use_clock_default = str(bag_config.get("use_clock", True)).lower()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@launch/custom.launch.py` around lines 245 - 247, The current assignment of
bag_path_default uses str(bag_config.get("path")) which turns None into the
literal "None"; change it to safely handle missing values by reading the raw
value (via bag_config.get("path", None) or checking if bag_config.get("path") is
None) and only convert to string when a non-None path exists (e.g., set
bag_path_default to "" or None when missing), updating related logic that
consumes bag_path_default to treat empty/None as "no bag file" instead of a
filename; refer to bag_config, bag_path_default, and bag_use_clock_default to
locate and adjust the code paths that expect a valid bag path.

Comment on lines +22 to +24
GimbalMode gimbal_mode;

explicit Command(const YAML::Node& yaml) {}
Copy link

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:

find . -type f -name "command.hh" | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 109


🏁 Script executed:

find . -type f -name "component.cc" | head -20

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 106


🏁 Script executed:

wc -l ./src/component/util/command.hh ./src/component/component.cc

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 159


🏁 Script executed:

sed -n '15,35p' ./src/component/util/command.hh

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 327


🏁 Script executed:

sed -n '115,135p' ./src/component/component.cc

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 589


🏁 Script executed:

rg "from_string" ./src/component/util/command.hh -A 3 -B 3

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 269


🏁 Script executed:

sed -n '1,30p' ./src/component/util/command.hh

Repository: Alliance-Algorithm/rmcs-navigation

Length of output: 614


gimbal_mode 未初始化即被读取,存在未定义行为。

Command(const YAML::Node&) 构造函数为空,gimbal_mode 成员没有初始化;而 src/component/component.cc 第 123 行立即读取该字段并传给 set_gimbal_mode。这会在运行时读取未初始化的枚举值,导致未定义行为。

建议修复
 struct Command {
-    GimbalMode gimbal_mode;
+    GimbalMode gimbal_mode = GimbalMode::Unknown;
 
-    explicit Command(const YAML::Node& yaml) {}
+    explicit Command(const YAML::Node& yaml) {
+        if (const auto mode = yaml["gimbal_mode"]; mode && mode.IsScalar()) {
+            from_string(mode.as<std::string>(), gimbal_mode);
+        }
+    }
 };
📝 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
GimbalMode gimbal_mode;
explicit Command(const YAML::Node& yaml) {}
GimbalMode gimbal_mode = GimbalMode::Unknown;
explicit Command(const YAML::Node& yaml) {
if (const auto mode = yaml["gimbal_mode"]; mode && mode.IsScalar()) {
from_string(mode.as<std::string>(), gimbal_mode);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/component/util/command.hh` around lines 22 - 24, The Command::gimbal_mode
member is not initialized in explicit Command(const YAML::Node& yaml), causing
undefined behavior when later read and passed to set_gimbal_mode; fix by
initializing gimbal_mode inside the Command(const YAML::Node&) constructor
(parse the YAML node to a valid GimbalMode or set a safe default) and ensure any
YAML parsing errors fallback to that default or throw, so set_gimbal_mode always
receives a well-defined enum value.

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.

1 participant