22 KiB
Kế Hoạch Xây Dựng Package recovery_core (Interface Thuần, Không Chạy ROS)
recovery_corelà package ĐỊNH NGHĨA INTERFACE cho các hành vi recovery — đóng đúng vai trò nhưrobot_nav_corecung cấpRecoveryBehavior, nhưng không chạy ROS và biểu đạt hợp đồng quarobot_geometry_msgs/robot_nav_msgs(header-only).Bản thân package không chứa hành vi cụ thể (backup, spin, clear costmap, regen path). Các hành vi đó là plugin implement interface — làm ở Phase 3 hoặc ở package khác.
1. Bối Cảnh & Quyết Định Thiết Kế
1.1. recovery_core là gì
- Là thư viện interface (giống
robot_nav_core::RecoveryBehavior), nhưng ROS-free:- Không include
robot/robot.h, khôngNodeHandle, khôngCostmap2DROBOT, khôngtf3::BufferCore. - Chỉ phụ thuộc kiểu dữ liệu header-only:
robot_geometry_msgs(Twist, Pose2D, PoseStamped) vàrobot_nav_msgs(Path). Các kiểu này không kéo runtime ROS.
- Không include
- Cung cấp một base class thuần ảo đủ tổng quát để bao 3 họ recovery (mục 1.2), cùng các kiểu phụ trợ (status, result, config, context).
So với robot_nav_core::RecoveryBehavior (blocking runBehavior(), cần costmap + tf):
recovery_core giữ tinh thần interface nhưng thay hạ tầng ROS bằng abstraction ROS-free và
mở rộng để hành vi có thể trả về output (không chỉ chạy rồi thôi).
1.2. Ba họ recovery interface phải bao được
| Họ | Ví dụ | Output đặc trưng |
|---|---|---|
| A. Trả v<><76> path | tạo lại một đoạn đường thoát/né | robot_nav_msgs::Path |
| B. Không output | clear costmap, reset state | chỉ trạng thái (SUCCEEDED/FAILED) |
| C. Trả về vận tốc | rotation tại chỗ, backup (không ROS) | robot_geometry_msgs::Twist theo cycle |
Interface phải diễn đạt cả 3 mà không ép một hành vi phải điền output nó không dùng.
→ Dùng một RecoveryResult hợp nhất mang cờ loại output + các trường optional
(status luôn có; twist, path chỉ hợp lệ khi cờ tương ứng bật).
1.3. Ranh giới trách nhiệm
recovery_core CHỈ cung cấp:
- Base class
RecoveryBehavior(thuần ảo) + vòng đời chuẩn. - Kiểu hợp đồng:
RecoveryStatus,RecoveryOutputType,RecoveryResult,RecoveryConfig,RecoveryContext(abstraction ROS-free thay cho tf/costmap/publisher). - Không thuật toán hành vi cụ thể, không collision-check, không I/O, không vòng lặp thời gian.
Caller / plugin chịu trách nhiệm: cấp pose (qua RecoveryContext), tiêu thụ output
(Twist → cmd_vel, Path → planner), đảm bảo an toàn.
1.4. Vị trí & chuẩn
- Đặt tại
.../Navigations/Libraries/recovery_core(cùng cấprobot_clear_costmap_recovery). - C++17, namespace
recovery_core, guardRECOVERY_CORE_<FILE>_H_. - Identifiers/comments tiếng Anh; tài liệu
.mdtiếng Việt. - Build catkin + standalone (theo pattern
robot_clear_costmap_recovery). - Phần lớn là header-only (interface) —
.cppchỉ cho helper/validate không inline.
2. Thiết Kế Interface (bản chốt ở Phase 2, nháp ở đây)
2.1. Kiểu hợp đồng
namespace recovery_core {
// Trạng thái tiến trình 1 lượt recovery.
enum class RecoveryStatus { kIdle, kRunning, kSucceeded, kFailed };
// Loại output mà hành vi sinh ra ở cycle hiện tại.
enum class RecoveryOutputType { kNone, kVelocity, kPath };
// Kết quả hợp nhất cho cả 3 họ. Chỉ đọc trường khớp với output_type.
struct RecoveryResult {
RecoveryStatus status = RecoveryStatus::kRunning;
RecoveryOutputType output_type = RecoveryOutputType::kNone;
robot_geometry_msgs::Twist command; // hợp lệ khi output_type == kVelocity
robot_nav_msgs::Path path; // hợp lệ khi output_type == kPath
// Tiện ích khởi tạo nhanh (định nghĩa trong .cpp hoặc inline):
static RecoveryResult Running();
static RecoveryResult Succeeded();
static RecoveryResult Failed();
static RecoveryResult Velocity(const robot_geometry_msgs::Twist&, RecoveryStatus);
static RecoveryResult PathOut(const robot_nav_msgs::Path&, RecoveryStatus);
};
2.2. Context ROS-free (thay tf/costmap/publisher)
RecoveryContext là abstraction để plugin lấy trạng thái thế giới mà không biết ROS.
recovery_core chỉ khai báo interface; caller cấp implementation cụ thể (đọc từ đâu là việc
của caller).
// Interface tối thiểu; mở rộng dần khi có nhu cầu thực.
class RecoveryContext {
public:
virtual ~RecoveryContext() = default;
// Pose robot hiện tại trong frame quy ước (rad, m). Trả false nếu không có.
virtual bool getRobotPose(robot_geometry_msgs::Pose2D* out) const = 0;
// (tuỳ chọn, cho họ cần) truy vấn cost tại điểm; mặc định không hỗ trợ.
// virtual bool getCost(double x, double y, unsigned char* cost) const { return false; }
};
Ghi chú: interface base không ép hành vi phải dùng context (họ C rotation thuần có thể chỉ cần pose truyền vào computeCommand). Context là kênh mở rộng cho họ A/B cần hỏi thế giới.
2.3. Base class interface
class RecoveryBehavior {
public:
// shared_ptr để KHỚP cơ chế nạp Boost.DLL của workspace:
// boost::dll::import_alias<recovery_core::RecoveryBehavior::Ptr()>(...)
// (xem move_base.cpp loadRecoveryBehaviors + robot_nav_core::RecoveryBehavior::Ptr).
using Ptr = std::shared_ptr<RecoveryBehavior>;
virtual ~RecoveryBehavior() = default;
// 1) Nạp + validate config. Không throw; trả false + set error nếu sai.
virtual bool configure(const RecoveryConfig& config, std::string* error) = 0;
// 2) Bắt đầu 1 lượt: chốt state khởi đầu, reset progress về kRunning.
virtual void start(const robot_geometry_msgs::Pose2D& current_pose) = 0;
// 3a) Kiểu PER-CYCLE (họ C, và họ A nếu sinh path dần): gọi mỗi control cycle.
// dt (s) > 0. Guard chưa configure/start -> Failed + output kNone.
virtual RecoveryResult computeCommand(const robot_geometry_msgs::Pose2D& current_pose,
double dt) = 0;
// 3b) Kiểu ONE-SHOT/BLOCKING (họ B clear costmap, họ A regen path 1 lần):
// chạy trọn hành vi qua context, trả kết quả cuối. Mặc định: lặp computeCommand.
virtual RecoveryResult runBehavior(RecoveryContext* ctx); // có default impl
virtual void reset() = 0;
virtual RecoveryStatus status() const = 0;
virtual const char* name() const = 0;
protected:
RecoveryBehavior() = default;
};
Vì sao có cả computeCommand lẫn runBehavior:
- Họ C (rotation/backup) tự nhiên là per-cycle → override
computeCommand. - Họ B (clear costmap) tự nhiên là one-shot → override
runBehavior(dùngctx),computeCommandchỉ trả trạng thái. - Họ A (regen path) có thể one-shot (
runBehaviortrảPath) hoặc per-cycle tuỳ plugin. runBehaviorcó default implementation trongrecovery_core(lặpcomputeCommandtới khi kháckRunning) để plugin per-cycle không phải viết lại; plugin one-shot thì override.
2.4. Config
struct RecoveryConfig {
std::string type; // "back_up" | "spin" | "clear_costmap" | "regen_path" ...
double control_frequency = 20.0; // Hz, dùng cho default runBehavior loop (dt = 1/f)
double timeout = 0.0; // s, 0 = không timeout
std::map<std::string, double> params; // tham số riêng của từng plugin
// validate(): control_frequency > 0, timeout >= 0, hữu hạn. Trả false + message.
bool validate(std::string* error) const;
};
Package interface không biết tham số riêng của backup/spin — để plugin tự đọc từ
params.recovery_corechỉ validate phần chung.
3. PHASE 1 — Dựng Khung Package (KHÔNG code triển khai)
Mục tiêu: tạo cây thư mục + file stub (header có guard/namespace/Doxygen, .cpp thân
rỗng compile được), package.xml, CMakeLists.txt build ra lib rỗng. Package phải configure
CMake thành công.
3.1. Cây thư mục mục tiêu
recovery_core/
├── PLAN.md # tài liệu này
├── README.md # phạm vi: INTERFACE, không ROS, 3 họ recovery
├── package.xml # depend: robot_geometry_msgs, robot_nav_msgs
├── CMakeLists.txt # catkin + standalone; lib chủ yếu header, .cpp cho helper
├── include/
│ └── recovery_core/
│ ├── recovery_types.h # enum Status, OutputType; struct RecoveryResult
│ ├── recovery_config.h # struct RecoveryConfig + validate()
│ ├── recovery_context.h # interface RecoveryContext (ROS-free)
│ └── recovery_behavior.h # base class RecoveryBehavior (interface chính)
├── src/
│ ├── recovery_types.cpp # (stub) factory helper Running()/Failed()/...
│ ├── recovery_config.cpp # (stub) validate()
│ └── recovery_behavior.cpp # (stub) default runBehavior() loop
├── test/
│ ├── CMakeLists.txt # khai báo test target (tuỳ chọn)
│ ├── interface_contract_test.cpp # (stub) dùng 1 MockBehavior kiểm vòng đời
│ └── mock_behavior.h # (stub) implement tối thiểu để test interface
├── examples/
│ └── minimal_plugin.cpp # (stub) ví dụ 1 plugin per-cycle bé xíu, không ROS
└── docs/
├── ARCHITECTURE.md # sơ đồ interface + luồng runBehavior/computeCommand
├── PLUGIN_GUIDE.md # hướng dẫn viết plugin cho từng họ A/B/C
└── SAFETY.md # cảnh báo: không collision-check trong core
3.2. Quy ước nội dung stub (Phase 1)
- Header: license/author ngắn, guard
RECOVERY_CORE_*_H_, include tối thiểu (<robot_geometry_msgs/Twist.h>,<robot_geometry_msgs/Pose2D.h>,<robot_nav_msgs/Path.h>khi cần), khai báo đầy đủ chữ ký + Doxygen public API, thân hàm non-inline để trong.cpp. - Source: thân tối thiểu
// TODO(phase-2): implement+return {}để compile. package.xml: format 2,<depend>robot_geometry_msgs</depend>,<depend>robot_nav_msgs</depend>, buildtool catkin. Không depend costmap/tf/robot_cpp.CMakeLists.txt: khung catkin+standalone củarobot_clear_costmap_recoverynhưng chỉ giữ 2 msgs depend; tạo libraryrecovery_coretừ các.cpp(kể cả khi phần lớn header-only, vẫn build 1 lib nhỏ cho validate/helper). Install header + export target 2 chế độ.
3.3. Checklist Phase 1
- Tạo đủ cây thư mục 3.1.
- 4 header có guard, namespace
recovery_core, Doxygen; khai báo khớp mục 2. - 3
.cppcompile với thân stub. package.xmlchỉ dependrobot_geometry_msgs+robot_nav_msgs.CMakeLists.txtconfigure OK cả catkin lẫn standalone; build ralibrecovery_corerỗng.README.mdnêu rõ: đây là INTERFACE, không ROS, 3 họ recovery, không hành vi cụ thể.- Kiểm chứng:
cmake+makethành công.
4. PHASE 2 — Triển Khai Interface (điền logic phần thuộc về core)
Core là interface nên "triển khai" ở đây = hoàn thiện phần chung mà base cung cấp, KHÔNG phải viết hành vi cụ thể. Cụ thể:
4.1. Nội dung triển khai
recovery_types: định nghĩa các factoryRecoveryResult::Running/Succeeded/Failed/Velocity/PathOut, đảm bảo set đúngoutput_type+status. Bất biến: khioutput_type==kNonethìcommand/pathđể mặc định (không rác).recovery_config::validate: kiểmcontrol_frequency > 0,timeout >= 0, hữu hạn;typekhông rỗng. Trảfalse+ message rõ. Không throw.recovery_behavior.cpp— defaultrunBehavior(ctx):- Guard
ctx == nullptr→Failed. - Lấy pose qua
ctx->getRobotPose; nếu fail →Failed. dt = 1 / control_frequency; lặpcomputeCommand(pose, dt)tới khi status kháckRunninghoặc vượttimeout(nếu > 0) → khi timeout trảFailed.- Mỗi vòng cập nhật pose qua
ctx(mô phỏng caller thật). Trả result cuối. - Không cấp phát trong vòng lặp; không log spam (chỉ log khi đổi status).
- Guard
- Bất biến & guard chung (document + test):
- Chưa
configurethành công → mọi call trảFailed. dt <= 0hoặc pose NaN/Inf →Failed, outputkNone.
- Chưa
4.2. Kiểm thử interface (không cần plugin thật)
test/mock_behavior.h:MockBehaviorper-cycle đơn giản (đếm N cycle rồikSucceeded, trảkVelocity) +MockContextcấp pose cố định.interface_contract_test.cpp:configuresai →false+ message.start→status()==kRunning.computeCommandtiến trình đúng, đạt đích →kSucceeded.- default
runBehaviorchạy hết vòng đời quaMockContext, tôn trọngtimeout. - guard: chưa configure, dt<=0, ctx null, pose NaN.
4.3. Checklist Phase 2
RecoveryResultfactories set đúng cờ; có test bất biến output.RecoveryConfig::validatetừ chối mọi input sai với message rõ.- default
runBehaviorđúng: loop, timeout, guard ctx/pose; không alloc trong loop. MockBehavior/MockContext+ contract test pass.docs/PLUGIN_GUIDE.mdmô tả cách 1 plugin họ A/B/C override method nào.- Kiểm chứng: build + test pass.
5. PHASE 3 — Tạo Recovery Plugin (implement interface)
Mục tiêu: viết các hành vi cụ thể implement recovery_core::RecoveryBehavior, mỗi
họ ít nhất một ví dụ, + cơ chế nạp theo string.
5.1. Plugin mẫu cho từng họ
- Họ C —
SpinRecovery/BackUpRecovery(per-cycle, ROS-free):- Override
computeCommand: sinhTwisttheo pose + dt (xoay tới góc / lùi tới khoảng cách), đạt đích →kSucceeded. Đọc tham số từRecoveryConfig::params(spin_target_angle,spin_speed,backup_distance,backup_speed...). - Dùng default
runBehavior(không cần override).
- Override
- Họ B —
ClearCostmapRecovery-like (one-shot, cần context):- Override
runBehavior(ctx): gọi hành động clear qua một context mở rộng (RecoveryContextthêm hook clear — hoặc để ở adapter). TrảkSucceeded/kFailed, outputkNone.computeCommandchỉ trả trạng thái. - Lưu ý: clear costmap thật cần costmap → phần đó nằm ở adapter/caller, không trong
recovery_core. Plugin ở đây minh hoạ contract, thao tác thật uỷ thác qua context.
- Override
- Họ A —
RegenPathRecovery(trả path):- Override để trả
RecoveryResultvớioutput_type == kPath, điềnrobot_nav_msgs::Path(một đoạn đường thoát hình học đơn giản, ví dụ cung lùi). one-shot hoặc per-cycle.
- Override để trả
Các plugin này có thể đặt trong
recovery_core/plugins/hoặc package riêng — cần chốt (mục 6). Dù đặt đâu, chúng chỉ được include header củarecovery_core, không thêm dependency ROS vào package interface.
5.2. Cơ chế nạp — Boost.DLL (chốt, theo đúng convention workspace)
Workspace nạp planner/recovery plugin bằng Boost.DLL import_alias, KHÔNG dùng pluginlib/ class_loader. Bằng chứng trong repo:
- Plugin export:
robot_clear_costmap_recovery/src/clear_costmap_recovery.cpp:37,330-337. - Loader import:
move_base/src/move_base.cpp:22, 2016-2026(loadRecoveryBehaviors).
recovery_core (interface) không include Boost.DLL. Chỉ plugin export symbol và
loader/adapter import. Interface chỉ cần Ptr = std::shared_ptr<RecoveryBehavior> để khớp
chữ ký import_alias<recovery_core::RecoveryBehavior::Ptr()>.
5.2.1. Phía PLUGIN — export factory bằng BOOST_DLL_ALIAS
Mỗi plugin cung cấp một factory không tham số trả Ptr (shared_ptr), rồi alias ra tên
symbol dùng làm type trong YAML:
// spin_recovery.cpp
#include <recovery_core/recovery_behavior.h>
#include <boost/dll/alias.hpp>
namespace recovery_plugins {
class SpinRecovery : public recovery_core::RecoveryBehavior { /* override ... */
public:
static recovery_core::RecoveryBehavior::Ptr create() { // factory không tham số
return std::make_shared<SpinRecovery>();
}
};
} // namespace recovery_plugins
// Tên alias thứ 2 ("spin_recovery") chính là `type` trong YAML recovery_behaviors.
BOOST_DLL_ALIAS(recovery_plugins::SpinRecovery::create, spin_recovery)
Quy ước (giống ClearCostmapRecovery → alias ClearCostmapRecovery):
- Factory là
static Ptr create()— không tham số (Boost.DLL alias yêu cầu signaturePtr()), cấu hình đi quaconfigure()sau khi tạo, không qua constructor. - Trả
std::shared_ptr(KHÔNGunique_ptr) để khớp loader. - Tên alias = tên
typemà YAML/loader sẽ dùng để tìm library + symbol.
5.2.2. Phía LOADER — import_alias (mẫu, thường nằm ở adapter/caller)
#include <boost/dll/import.hpp>
// path .so lấy qua PluginLoaderHelper::findLibraryPath(type) như move_base đang làm.
auto loader = boost::dll::import_alias<recovery_core::RecoveryBehavior::Ptr()>(
path_so, /*symbol=*/type, boost::dll::load_mode::append_decorations);
recovery_core::RecoveryBehavior::Ptr behavior = loader(); // tạo instance
std::string err;
behavior->configure(config, &err); // rồi mới cấu hình
recovery_corekhông viết loader này (nó thuộc caller/move_base/adapter). Nhưngdocs/PLUGIN_GUIDE.mdsẽ ghi mẫu để người dùng plugin biết cách nạp.
5.2.3. CMake cho PLUGIN (không phải cho core)
find_package(Boost REQUIRED COMPONENTS system filesystem)(Boost.DLL cầnfilesystem;systemtheo pattern package cũ). Link${Boost_LIBRARIES}và${CMAKE_DL_LIBS}(dl).set_target_properties(<plugin> PROPERTIES POSITION_INDEPENDENT_CODE ON)— bắt buộc cho .so.- Build mỗi plugin thành shared library riêng; tên library + symbol khớp
typeYAML. - Install
.sovào nơifindLibraryPathtìm được (theo conventionPluginLoaderHelper). - Không cần
plugins.xml(workspace này dùng Boost.DLL thuần, không pluginlib).
5.3. Adapter ROS (tuỳ chọn, package RIÊNG)
Nếu cần chạy trong move_base thật: package adapter bọc plugin recovery_core, implement
robot_nav_core::RecoveryBehavior (có costmap/tf), cấp RecoveryContext đọc TF/costmap,
tiêu thụ Twist/Path. Giữ recovery_core sạch, không đổi.
5.4. Checklist Phase 3
- Chốt: plugin đặt trong
recovery_core/plugins/hay package riêng (đề xuất: package riêng cho plugin cần costmap/tf; plugin thuần spin/backup có thể ởrecovery_core/plugins/). - Mỗi họ A/B/C có ≥ 1 plugin mẫu implement interface, build ra shared library riêng.
- Mỗi plugin có
static Ptr create()+BOOST_DLL_ALIAS(...::create, <type>); tên alias =typeYAML; trảstd::shared_ptr. - CMake plugin:
find_package(Boost COMPONENTS system filesystem), link${CMAKE_DL_LIBS},POSITION_INDEPENDENT_CODE ON, install.sođúng nơi loader tìm. - Test end-to-end (không ROS):
import_alias<recovery_core::RecoveryBehavior::Ptr()>nạp.somẫu →configure→runBehavior/computeCommandvới MockContext → kiểm output đúng họ (Twist / none / Path). - Cập nhật
docs/ARCHITECTURE.md,docs/PLUGIN_GUIDE.md(kèm mẫu export + import). - Kiểm chứng: build plugin
.so+ test nạp Boost.DLL pass.
6. Rủi Ro & Điểm Cần Xác Nhận
- Kiểu output hợp nhất vs đa interface: đề xuất một
RecoveryResultmang cờoutput_type(đơn giản, 1 base class). Phương án khác: template/đa base theo họ — phức tạp hơn. Cần chốt. RecoveryContextrộng tới đâu: tối thiểu chỉgetRobotPose. Họ B (clear costmap) và họ A (regen path cần costmap) sẽ cần hook thêm — nên để ở context mở rộng của adapter, giữ context lõi nhỏ. Cần chốt mức tối thiểu.- Vị trí plugin: trong
recovery_core/plugins/hay package riêng. Đề xuất: package riêng cho plugin cần costmap/tf; plugin thuần (spin/backup) có thể ởrecovery_core/plugins/. - Cơ chế nạp = Boost.DLL (đã chốt): theo đúng convention workspace (
import_alias, không pluginlib). Ràng buộc kéo theo:Ptr = std::shared_ptr; factorystatic Ptr create()không tham số +BOOST_DLL_ALIAS; cấu hình quaconfigure()sau khi tạo. Core interface KHÔNG phụ thuộc Boost.DLL — chỉ plugin và loader. robot_nav_msgsbuild dep: xác nhận có CMake config/target đểfind_package(catkin) hoặc include-only (standalone). Là header msgs nên nhiều khả năng include-only.- An toàn: core không collision-check → ghi rõ
docs/SAFETY.md; caller/adapter chịu trách nhiệm an toàn khi thực thi backup/spin.
7. Tóm Tắt 3 Phase
| Phase | Kết quả | Kiểm chứng |
|---|---|---|
| 1 | Khung package interface: 4 header + stub, package.xml, CMake build lib rỗng | cmake + make OK |
| 2 | Hoàn thiện phần chung của interface: types/config/validate + default runBehavior, contract test qua Mock |
build + test pass |
| 3 | Plugin mẫu cho 3 họ (Twist/none/Path), export BOOST_DLL_ALIAS, nạp qua import_alias, doc |
build .so + test nạp Boost.DLL pass |