Files
recovery_core/docs/PLUGIN_GUIDE.md
2026-08-03 22:32:40 +07:00

6.0 KiB
Raw Blame History

Viết một recovery behavior mới

1. Chọn họ output

Quyết định đầu tiên và không đổi được về sau: outputKind().

Họ Khi nào Cổng bắt buộc trong context
kNone Behavior không lái robot (đợi, xoá costmap, gọi thiết bị ngoài)
kVelocity Behavior tự lái từng cycle PoseProvider + CollisionChecker
kPath Behavior sinh ra đường đi mới PlanProvider

Base kiểm cổng theo họ ngay ở configure(), nên một behavior họ velocity thiếu collision checker sẽ không nạp được, thay vì phát hiện lúc đang lái.

2. Khung plugin

#include <recovery_core/recovery_behavior.h>
#include <recovery_core/recovery_math.h>

#include <boost/dll/alias.hpp>
#include <robot/robot.h>

namespace recovery_plugins
{
namespace
{
constexpr double kDefaultLimit = 1.0;   // [m] đơn vị ghi ngay tại chỗ khai báo
}

class MyRecovery final : public recovery_core::RecoveryBehavior
{
public:
  MyRecovery() = default;

  static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
  {
    return std::make_shared<MyRecovery>();
  }

  recovery_core::RecoveryOutputType outputKind() const override
  {
    return recovery_core::RecoveryOutputType::kVelocity;
  }

protected:
  // nh ĐÃ được caller scope vào namespace param của instance này — đọc khoá phẳng.
  bool onConfigure(robot::NodeHandle& nh) override
  {
    nh.param("limit", limit_, kDefaultLimit);
    if (!std::isfinite(limit_) || limit_ <= 0.0)
    {
      robot::log_warning("[recovery_core] '%s': limit=%.3f không hợp lệ; dùng %.3f.",
                         name().c_str(), limit_, kDefaultLimit);
      limit_ = kDefaultLimit;
    }
    return true;   // false = không chạy được; registry bỏ behavior này và log đích danh
  }

  // Chốt mục tiêu lượt này + kiểm điều kiện an toàn để khởi động. KHÔNG sinh tick ở đây.
  bool onStart(const recovery_core::RecoveryGoal& goal) override
  {
    if (!ctx().pose->getRobotPose(start_pose_))
      return false;                        // không biết robot ở đâu -> từ chối khởi động

    target_ = goal.distance.value_or(limit_);
    return true;
  }

  // dt là thời gian THẬT tính từ tick trước; tick đầu ngay sau start() có dt = 0.
  recovery_core::RecoveryResult onUpdate(const robot::Time& now, double dt) override
  {
    robot_geometry_msgs::PoseStamped pose;
    if (!ctx().pose->getRobotPose(pose))
      return stopResult(recovery_core::RecoveryStatus::kFailed).withMessage("mất pose robot");

    const double done = -recovery_core::projectOntoHeading(pose, start_pose_, start_yaw_);
    if (done >= target_)
      return stopResult(recovery_core::RecoveryStatus::kSucceeded).withProgress(1.0, 0.0);

    robot_geometry_msgs::Twist cmd;
    // ... tính cmd, nhớ ramp theo acc_lim và kiểm collision ở pose dự đoán ...
    return recovery_core::RecoveryResult::Velocity(cmd, recovery_core::RecoveryStatus::kRunning)
        .withProgress(done / target_, target_ - done);
  }

  // Tuỳ chọn: cơ hội giảm tốc thay vì nhảy thẳng về 0.
  recovery_core::RecoveryResult onCancel() override
  {
    return stopResult(recovery_core::RecoveryStatus::kCancelled).withMessage("cancelled");
  }

private:
  double limit_ = kDefaultLimit;                    ///< [m]
  double target_ = kDefaultLimit;                   ///< [m]
  double start_yaw_ = 0.0;                          ///< [rad]
  robot_geometry_msgs::PoseStamped start_pose_;
};

}  // namespace recovery_plugins

BOOST_DLL_ALIAS(recovery_plugins::MyRecovery::create, MyRecovery)

Những gì không phải viết: guard configured_/started_, kiểm cancel, đo elapsed, ép timeout, đặt output_type cho stop output, kiểm NaN của lệnh vận tốc. Base làm hết — và giữ được vì toàn bộ state của nó là private.

3. Đăng ký build

add_recovery_core_plugin(
  recovery_core_my_recovery
  plugins/my_recovery.cpp
)

Thêm tên target vào catkin_package(LIBRARIES ...) để consumer khác dùng lại được.

4. Khai trong YAML

recovery:
  behaviors:
    - {name: my_instance, type: MyRecovery}   # thứ tự trong danh sách CHÍNH LÀ thứ tự thử
  my_instance:
    limit: 0.5                                # [m]
    timeout: 10.0                             # [s] base đọc; 0 = không giới hạn

MyRecovery:
  library_path: librecovery_core_my_recovery  # THIẾU KHOÁ NÀY LÀ LỖI PHỔ BIẾN NHẤT

Tên alias trong BOOST_DLL_ALIAS phải khớp type. Namespace param của instance là <ns>/<name> — registry dựng NodeHandle đó và truyền vào configure(); plugin không tự đi tìm config trên disk. Đó là lý do test chỉ cần trỏ vào cây config của mình là chạy được.

Một plugin có thể có nhiều instance với tham số khác nhau — bộ mặc định dùng ClearCostmapRecovery hai lần (conservative_resetaggressive_reset).

5. Test

Đặt trong test/, thêm tên vào danh sách RECOVERY_CORE_TESTS của CMakeLists.txt (dùng catkin_add_gtest, nên ctest -R recovery_core bắt được).

Dùng recovery_test::VelocityRig (costmap giả + pose giả + collision checker giả + đồng hồ giả) và nạp plugin qua RecoveryRegistry để đi đúng đường Boost.DLL mà runtime dùng — không link thẳng .so vào test.

Tối thiểu phải phủ:

  • từ chối khởi động khi điều kiện an toàn không thoả;
  • mất pose giữa chừng → kFailed + lệnh dừng;
  • loop chạy chậm (dt gấp 520 lần nhịp thường) vẫn dừng đúng chỗ;
  • cancel → lệnh dừng;
  • param ngoài dải → dùng default, không nhận giá trị sai.

Cuối cùng, kiểm rằng test fail được: gỡ .so khỏi devel/lib rồi chạy lại. Phải đỏ. Bộ test cũ của gói này in [PASS] khi không nạp được plugin nào — đó là thứ phải tránh.