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

161 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```cpp
#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
```cmake
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
```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_reset``aggressive_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.