optimal & fix file cmake
This commit is contained in:
@@ -1,46 +1,106 @@
|
||||
# Kiến Trúc recovery_core
|
||||
# Kiến trúc recovery_core
|
||||
|
||||
## Vị trí trong stack
|
||||
## Vị trí
|
||||
|
||||
`recovery_core` nằm ở tầng `Navigations/Libraries`, cạnh `robot_clear_costmap_recovery`. Nó
|
||||
giữ vai trò interface tương tự `robot_nav_core::RecoveryBehavior` cho các recovery cần **trả
|
||||
về output** (path/vận tốc), đồng thời vẫn dùng global path/costmap/tf như bản gốc.
|
||||
Gói nằm ở `Test/recovery_core`. Nó định nghĩa interface recovery **duy nhất** của workspace
|
||||
(`CLAUDE.md`); bản legacy `robot_nav_core::RecoveryBehavior` đã dừng phát triển và không được port
|
||||
sang.
|
||||
|
||||
## Các thành phần
|
||||
Consumer đầu tiên là `RecoveryRunner` của `move_base2` — nó include thẳng
|
||||
`recovery_core/recovery_behavior.h`, nên contract được compiler kiểm đầy đủ ở một nơi.
|
||||
|
||||
- `RecoveryBehavior` (interface, template-method): API non-virtual `configure`/`start`/`update`/
|
||||
`cancel`; plugin chỉ triển khai hook `onConfigure`/`onStart(goal)`/`onUpdate()`.
|
||||
- `RecoveryContext`: gói con trỏ ngữ cảnh (tf/costmap/global_path) truyền một lần qua `configure`.
|
||||
- `RecoveryGoal`: mục tiêu RUNTIME mỗi lượt (angle/distance/target_pose/params).
|
||||
- `RecoveryResult` / `RecoveryStatus` / `RecoveryOutputType`: hợp đồng output hợp nhất 3 họ +
|
||||
rich feedback (`progress`/`remaining`/`elapsed`/`message`), thêm trạng thái `kCancelled`.
|
||||
- Plugin mẫu:
|
||||
- `ClearCostmapRecovery`: clear layer costmap theo tên, one-shot no-output.
|
||||
- `RotateRecovery`: quay tới `goal.angle` (rad); sinh `Twist.angular.z` mỗi cycle.
|
||||
- `BackUpRecovery`: lùi tới `goal.distance` (m); sinh `Twist.linear.x < 0` mỗi cycle.
|
||||
- `RegenPathRecovery`: trả lại `robot_nav_msgs::Path` từ `global_path` hiện tại.
|
||||
## Vì sao tick-based
|
||||
|
||||
Recovery thế hệ trước chạy blocking bên trong một lời gọi và không trả gì. Hệ quả: không cancel
|
||||
được giữa chừng, không báo tiến độ, và **không phát được vận tốc** — nên mọi behavior cần robot cử
|
||||
động phải tự quay vòng lặp bên trong, tranh quyền phát `cmd_vel` với controller.
|
||||
|
||||
Bản này trả kết quả từng cycle. Ràng buộc kéo theo: recovery phải được tick từ **đúng thread đang
|
||||
sở hữu cmd_vel**, không được có thread riêng.
|
||||
|
||||
## Thành phần
|
||||
|
||||
| Thành phần | Vai trò |
|
||||
|---|---|
|
||||
| `RecoveryBehavior` | Base template-method. Giữ toàn bộ bất biến; plugin chỉ triển khai hook |
|
||||
| `RecoveryContext` | Các **cổng** môi trường: `PoseProvider`, `CollisionChecker`, `PlanProvider`, con trỏ costmap |
|
||||
| `RecoveryGoal` | Mục tiêu runtime mỗi lượt: `trigger`, `angle`, `distance`, `target_pose`, `params` |
|
||||
| `RecoveryResult` | Kết quả một tick: status, output theo họ, progress/remaining/elapsed/message |
|
||||
| `RecoveryRegistry` | Danh sách behavior **có thứ tự**, nạp từ YAML qua Boost.DLL |
|
||||
| `adapters/` | `CostmapPoseProvider`, `CostmapCollisionChecker` — nối cổng vào costmap thật |
|
||||
| `plugins/` | Bộ mặc định: wait, clear costmap, rotate, back up |
|
||||
|
||||
## Ai giữ bất biến gì
|
||||
|
||||
Đây là điểm khác quan trọng nhất so với bản trước, nơi state là `protected` và plugin ghi thẳng
|
||||
được vào `status_`.
|
||||
|
||||
**Base giữ:**
|
||||
|
||||
- guard vòng đời: `start` sau `configure`, `update` sau `start`, `configure` chỉ một lần;
|
||||
- cổng bắt buộc theo `outputKind()` — họ velocity không có `PoseProvider`/`CollisionChecker` thì
|
||||
`configure()` trả `false` ngay, chứ không để plugin phát hiện lúc đang lái;
|
||||
- validate goal (NaN/Inf, `distance > 0`) trước khi plugin nhìn thấy;
|
||||
- đo `dt` và `elapsed` bằng đồng hồ **thật**, ép `timeout`;
|
||||
- cưỡng chế `output_type ∈ {outputKind(), kNone}`;
|
||||
- chặn NaN/Inf trong lệnh vận tốc;
|
||||
- sinh **stop output đúng họ** ở mọi nhánh guard/cancel/timeout.
|
||||
|
||||
**Plugin giữ:** đọc param riêng, chốt mục tiêu lượt này, và sinh một tick an toàn. Toàn bộ state của
|
||||
base là `private`; plugin truy cập qua accessor `const`.
|
||||
|
||||
## Luồng runtime
|
||||
|
||||
```
|
||||
configure(name, ctx) // 1 lần: cache ctx, đọc config chung, onConfigure()
|
||||
configure(name, ctx, nh) # 1 lần. Kiểm cổng bắt buộc, đọc timeout, gọi onConfigure(nh).
|
||||
│
|
||||
start(goal) // mỗi lượt: chốt mục tiêu runtime, onStart()
|
||||
start(goal, now) # mỗi lượt. Validate goal, mốc thời gian, gọi onStart(goal) -> bool.
|
||||
│
|
||||
├── one-shot (họ A/B): update() 1 lần ───────────► RecoveryResult{status, path|none, msg}
|
||||
├── kNone : update() -> làm việc / đếm giờ ─────► {status, elapsed, message}
|
||||
├── kVelocity : loop { update(now) } ───────────────► {status, Twist, progress, remaining}
|
||||
└── kPath : update() -> sinh path ──────────────► {status, Path}
|
||||
│
|
||||
└── per-cycle (họ C): loop { update() } ────────► RecoveryResult{status, velocity,
|
||||
(tới khi status != kRunning) progress, remaining}
|
||||
cancel() # tick kế tiếp gọi onCancel() -> stop output + kCancelled.
|
||||
```
|
||||
|
||||
## Ghi chú thiết kế
|
||||
## Quyết định thiết kế
|
||||
|
||||
- Template-method: base xử lý guard vòng đời (configure→start→update) và `cancel` một chỗ;
|
||||
plugin không lặp lại các guard này.
|
||||
- Mục tiêu là RUNTIME qua `RecoveryGoal` (không cố định trong config): cùng plugin phục vụ nhiều
|
||||
yêu cầu góc/khoảng khác nhau. Field = 0 → dùng default plugin đọc ở `onConfigure()`.
|
||||
- `onUpdate()` lấy pose robot từ costmap/tf bên trong; không truyền pose qua tham số.
|
||||
- Sau `cancel()`, base tự trả stop output (Twist 0) + `kCancelled`.
|
||||
- Param RIÊNG của plugin đọc trong `onConfigure()`.
|
||||
- Interface KHÔNG include Boost.DLL; export/import là việc của plugin/loader.
|
||||
- Plugin mẫu có dùng Boost.DLL alias, nhưng core contract vẫn không biết loader/adaptor.
|
||||
**Cổng, không phải dữ liệu, nằm trong context.** `PlanProvider` là accessor chứ không phải con trỏ
|
||||
tới `std::vector` vì runtime dùng triple-buffer plan và **xoay con trỏ** giữa ba bộ đệm — một con
|
||||
trỏ cache lúc `configure()` sẽ trỏ vào bộ đệm scratch sau vài chu kỳ planner. Cùng lý do đó, con trỏ
|
||||
costmap phải được caller làm mới trước mỗi lượt và plugin không được cache `Costmap2D*` bên trong.
|
||||
|
||||
**`std::optional` thay cho sentinel 0.** `goal.angle = 0` là yêu cầu hợp lệ ("đừng quay") và phải
|
||||
phân biệt được với "caller không đặt". Quy ước cũ "0 nghĩa là dùng default" khiến một góc tính từ
|
||||
hình học ra ~0 bị âm thầm thay bằng π/2.
|
||||
|
||||
**Họ output nằm trong kiểu.** `outputKind()` khai một lần và base cưỡng chế. Trước kia mỗi kết quả
|
||||
tự mang `output_type` nên nó không dùng để route được: một behavior họ path báo `kVelocity` ở tick
|
||||
đầu, còn base thì sinh `Velocity(zero)` cho mọi họ ở nhánh terminal.
|
||||
|
||||
**Param do caller cấp namespace.** `configure()` nhận `robot::NodeHandle&` đã scope sẵn; plugin
|
||||
không tự dựng `NodeHandle("~/" + name)` và tự nạp YAML từ disk. Nhờ vậy test chỉ cần trỏ vào cây
|
||||
config của mình, không phải dựng cây config production.
|
||||
|
||||
**Không có `RecoveryDirective`.** Từng cân nhắc thêm kênh "xin lập plan lại", nhưng state machine
|
||||
của `move_base2` đã chuyển `RECOVERING → PLANNING` với `start_planner = true` trên **cả**
|
||||
`kSucceeded` lẫn `kFailed` — replan sau recovery đã là hành vi mặc định, thêm kênh riêng là thêm thứ
|
||||
không ai đọc.
|
||||
|
||||
**`kCancelled` không qua ranh giới port.** `move_base2` không tick recovery sau khi cancel (state
|
||||
chuyển sang `CANCELLING`, nơi mọi nguồn vận tốc bị khoá). Đường `onCancel()` vẫn phải đúng vì nó là
|
||||
hàng rào cho host khác, nhưng `RecoveryTick` của `move_base2` không cần giá trị tương ứng.
|
||||
|
||||
## Điều kiện đóng lại phần legacy
|
||||
|
||||
`robot_nav_core::RecoveryBehavior` và `robot_clear_costmap_recovery` chỉ còn được tham chiếu ở hai
|
||||
nơi, và cả hai biến mất cùng lúc khi `move_base2` thay xong `move_base` cũ:
|
||||
|
||||
| Nơi | Xử lý |
|
||||
|---|---|
|
||||
| `move_base/src/move_base.cpp` (loader gen-1) | Bị `move_base2` thay, không sửa |
|
||||
| `robot_clear_costmap_recovery` | Bị `ClearCostmapRecovery` gen-2 thay, không port |
|
||||
|
||||
Khoá `recovery_behaviors:` trong `move_base_common_params.yaml` đã được gỡ: các entry cũ trỏ tên
|
||||
alias vào `.so` gen-2 trong khi loader ở đó import theo chữ ký gen-1, và Boost.DLL không kiểm kiểu
|
||||
qua ranh giới `.so`.
|
||||
|
||||
@@ -1,110 +1,160 @@
|
||||
# Hướng Dẫn Viết Plugin recovery_core
|
||||
# Viết một recovery behavior mới
|
||||
|
||||
Package hiện có 4 plugin mẫu dưới `plugins/`:
|
||||
- `clear_costmap_recovery` — nhóm B, one-shot, no output.
|
||||
- `rotate_recovery` — nhóm C, per-cycle velocity.
|
||||
- `back_up_recovery` — nhóm C, per-cycle velocity.
|
||||
- `regen_path_recovery` — nhóm A, path output.
|
||||
## 1. Chọn họ output
|
||||
|
||||
## Bước chung
|
||||
Quyết định đầu tiên và không đổi được về sau: `outputKind()`.
|
||||
|
||||
1. Kế thừa `recovery_core::RecoveryBehavior`.
|
||||
2. Override hook `onConfigure()` (tuỳ chọn) — đọc param riêng qua `robot::NodeHandle("~/" + name)`;
|
||||
ngữ cảnh tf/global_path/costmap lấy qua `ctx()`.
|
||||
3. Override `onStart(goal)` + `onUpdate()` theo họ (xem dưới). KHÔNG override
|
||||
`configure/start/update/cancel` — base đã lo guard vòng đời/cancel.
|
||||
4. Thêm factory `static RecoveryBehaviorPtr create()` **không tham số** + `BOOST_DLL_ALIAS(...)`.
|
||||
| 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` |
|
||||
|
||||
## Vòng đời (goal-driven)
|
||||
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.
|
||||
|
||||
```
|
||||
configure(name, ctx) // 1 lần: cache ctx, đọc config chung, gọi onConfigure()
|
||||
│
|
||||
start(goal) // mỗi lượt: chốt mục tiêu RUNTIME (angle/distance/pose), gọi onStart()
|
||||
│
|
||||
loop update() // mỗi cycle tới khi status != kRunning; base guard vòng đời/cancel
|
||||
│
|
||||
[cancel()] // update() kế tiếp -> stop output + kCancelled
|
||||
```
|
||||
|
||||
`RecoveryGoal` là điểm mấu chốt: cùng plugin, mỗi lượt caller đặt `goal.angle` (rad) hay
|
||||
`goal.distance` (m) khác nhau; field = 0 nghĩa là dùng default đã cấu hình. Override thêm truyền
|
||||
qua `goal.params` (vd `goal.params["angular_speed"] = 0.8`).
|
||||
|
||||
## Override theo họ
|
||||
|
||||
| Họ | Override | Trả về |
|
||||
|----|----------|--------|
|
||||
| A. path | `onUpdate()` (one-shot) | `RecoveryResult::PathOut(path, kSucceeded)` |
|
||||
| B. none | `onUpdate()` (one-shot) | `RecoveryResult::Succeeded()` / `Failed()` |
|
||||
| C. velocity | `onStart()` chốt goal + `onUpdate()` mỗi cycle | `RecoveryResult::Velocity(twist, kRunning\|kSucceeded)` |
|
||||
|
||||
Mọi kết quả nên gắn feedback qua `.withProgress(progress, remaining)` và `.withMessage(...)` để
|
||||
caller giám sát tiến độ (progress ∈ [0,1], remaining theo rad/m).
|
||||
|
||||
## Export bằng Boost.DLL (bắt buộc cho plugin)
|
||||
## 2. Khung plugin
|
||||
|
||||
```cpp
|
||||
#include <recovery_core/recovery_behavior.h>
|
||||
#include <boost/dll/alias.hpp>
|
||||
#include <recovery_core/recovery_math.h>
|
||||
|
||||
namespace recovery_plugins {
|
||||
class SpinRecovery : public recovery_core::RecoveryBehavior {
|
||||
public:
|
||||
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create() {
|
||||
return std::make_shared<SpinRecovery>();
|
||||
#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>();
|
||||
}
|
||||
protected:
|
||||
// override onConfigure()/onStart(goal)/onUpdate()...
|
||||
|
||||
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
|
||||
|
||||
// alias = `type` dùng trong YAML recovery_behaviors.
|
||||
BOOST_DLL_ALIAS(recovery_plugins::SpinRecovery::create, spin_recovery)
|
||||
BOOST_DLL_ALIAS(recovery_plugins::MyRecovery::create, MyRecovery)
|
||||
```
|
||||
|
||||
## Nạp phía loader (adapter/caller — không nằm trong recovery_core)
|
||||
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`.
|
||||
|
||||
```cpp
|
||||
#include <boost/dll/import.hpp>
|
||||
auto loader = boost::dll::import_alias<recovery_core::RecoveryBehavior::RecoveryBehaviorPtr()>(
|
||||
path_so, /*symbol=*/type, boost::dll::load_mode::append_decorations);
|
||||
recovery_core::RecoveryBehavior::RecoveryBehaviorPtr behavior = loader();
|
||||
## 3. Đăng ký build
|
||||
|
||||
recovery_core::RecoveryContext ctx;
|
||||
ctx.tf = tf; ctx.global_path = global_path;
|
||||
ctx.global_costmap = global_costmap; ctx.local_costmap = local_costmap;
|
||||
behavior->configure(name, ctx);
|
||||
|
||||
recovery_core::RecoveryGoal goal;
|
||||
goal.angle = 1.57; // "quay 90 độ ngay lượt này"
|
||||
recovery_core::RecoveryResult r = behavior->start(goal);
|
||||
while (r.status == recovery_core::RecoveryStatus::kRunning) {
|
||||
r = behavior->update(); // publish r.command; đọc r.progress/r.remaining/r.message
|
||||
}
|
||||
```cmake
|
||||
add_recovery_core_plugin(
|
||||
recovery_core_my_recovery
|
||||
plugins/my_recovery.cpp
|
||||
)
|
||||
```
|
||||
|
||||
Lưu ý: adapter/test phải giữ handle `.so` sống lâu hơn object plugin. Nếu library bị unload trong
|
||||
khi object plugin còn tồn tại, virtual call qua vtable của plugin có thể crash.
|
||||
Thêm tên target vào `catkin_package(LIBRARIES ...)` để consumer khác dùng lại được.
|
||||
|
||||
## CMake cho plugin
|
||||
## 4. Khai trong YAML
|
||||
|
||||
- `find_package(Boost REQUIRED COMPONENTS system filesystem)`
|
||||
- link `${Boost_LIBRARIES}`, `${CMAKE_DL_LIBS}`, `recovery_core`
|
||||
- `set_target_properties(<plugin> PROPERTIES POSITION_INDEPENDENT_CODE ON)`
|
||||
- build shared library, tên library + symbol khớp `type`; install `.so` nơi loader tìm.
|
||||
```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
|
||||
|
||||
## Test plugin
|
||||
|
||||
```bash
|
||||
catkin_make --pkg recovery_core
|
||||
./devel/lib/recovery_core/recovery_core_plugin_loader_test
|
||||
MyRecovery:
|
||||
library_path: librecovery_core_my_recovery # THIẾU KHOÁ NÀY LÀ LỖI PHỔ BIẾN NHẤT
|
||||
```
|
||||
|
||||
Standalone:
|
||||
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.
|
||||
|
||||
```bash
|
||||
cmake -S src/AMR_T800/pnkx_nav_core/src/Navigations/Libraries/recovery_core -B /tmp/recovery_core_phase3_build
|
||||
make -C /tmp/recovery_core_phase3_build -j4
|
||||
/tmp/recovery_core_phase3_build/test/recovery_core_plugin_loader_test
|
||||
```
|
||||
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` và `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 5–20 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.
|
||||
|
||||
@@ -1,30 +1,64 @@
|
||||
# An Toàn — recovery_core
|
||||
# An toàn — recovery_core
|
||||
|
||||
> Cảnh báo an toàn khi dùng interface này. Đọc trước khi triển khai plugin họ vận tốc.
|
||||
> Đọc trước khi viết plugin họ vận tốc. Recovery chạy đúng lúc robot đã ở tình huống xấu, nên
|
||||
> default ở đây phải fail-safe chứ không fail-open.
|
||||
|
||||
## recovery_core KHÔNG đảm bảo
|
||||
## Base bảo đảm
|
||||
|
||||
- **Không collision-check tự động.** Interface không kiểm tra va chạm khi sinh vận tốc
|
||||
(backup/spin) hay khi tạo path. Việc tránh va chạm là trách nhiệm của **plugin** (dùng
|
||||
costmap được cấp qua `initialize`) hoặc của **caller**.
|
||||
- **Không quản lý vòng lặp thời gian thực.** Caller chịu trách nhiệm gọi `update()` đúng nhịp
|
||||
và publish command.
|
||||
- **Không đảm bảo frame/đơn vị.** Pose lấy từ costmap/tf phải đúng frame; đơn vị phải nhất quán
|
||||
(m, rad, s, m/s, rad/s).
|
||||
- **Guard vòng đời ở base.** `start()` trước `configure()`, hay `update()` trước `start()`, đều trả
|
||||
`RecoveryResult::Failed()`. Sau `cancel()`, base trả stop output (Twist 0) + `kCancelled`.
|
||||
Những điều dưới đây đúng kể cả khi plugin viết ẩu — base cưỡng chế, không phụ thuộc plugin nhớ.
|
||||
|
||||
## Nguyên tắc cho plugin
|
||||
| Bất biến | Cơ chế |
|
||||
|---|---|
|
||||
| Họ velocity không chạy được nếu thiếu pose hoặc collision checker | `configure()` trả `false` khi `ctx.pose`/`ctx.collision` null |
|
||||
| `dt` là thời gian **thật**, không phải chu kỳ cấu hình | `update(now)` tính `now - lần trước`; đồng hồ đi lùi → `dt = 0` |
|
||||
| `elapsed` luôn có mặt trên mọi kết quả | Base ghi, plugin không phải đặt |
|
||||
| Recovery không treo vô hạn | Param `timeout` [s]; quá hạn → `kFailed` + stop output |
|
||||
| `output_type` luôn thuộc `{outputKind(), kNone}` | Sai họ → hạ về `kNone`, xoá dữ liệu, log lỗi |
|
||||
| NaN/Inf không ra được `cmd_vel` | Lệnh không hữu hạn → Twist 0 + `kFailed` |
|
||||
| Guard vòng đời không cần plugin tự làm | `start` trước `configure`, `update` trước `start` → `kFailed` + stop output |
|
||||
| Stop output đúng họ | Họ velocity nhận **Twist 0 tường minh**; họ khác nhận `kNone` |
|
||||
|
||||
- Guard costmap/tf null (lấy qua `ctx()`) trước khi thao tác; fail an toàn -> `RecoveryResult::Failed()`.
|
||||
- Không cần tự guard vòng đời/cancel — base đã lo; plugin tập trung logic recovery.
|
||||
- Với họ vận tốc: khi không chắc an toàn, trả **stop command** (Twist 0), không trả vận tốc mù.
|
||||
- Kiểm tra NaN/Inf của pose/vận tốc trước khi xuất command.
|
||||
- Tôn trọng giới hạn vận tốc/gia tốc của robot (đọc qua param).
|
||||
- `BackUpRecovery` có param `require_costmap` để bắt buộc có local costmap trước khi xuất
|
||||
vận tốc lùi. Adapter production vẫn nên có safety gate/collision check riêng trước publish.
|
||||
## Base KHÔNG bảo đảm
|
||||
|
||||
## Trách nhiệm caller/adapter
|
||||
- **Không thay caller quyết định có nên recovery hay không.** Ngữ cảnh cho phép recovery (đã dừng
|
||||
planner, không đang ở chế độ thủ công, không đang estop) là việc của caller.
|
||||
- **Không quản lý nhịp gọi.** Caller phải tick đúng chu kỳ control và publish lệnh.
|
||||
- **Không là hàng rào vận tốc cuối cùng.** Plugin clamp theo giới hạn của mình; `VelocityArbiter`
|
||||
phía ngoài vẫn phải clamp lần cuối. Hai tầng, không chồng nhau: plugin lập **kế hoạch** dừng/ramp,
|
||||
arbiter **chặn** giá trị vượt ngưỡng.
|
||||
|
||||
- Đảm bảo ngữ cảnh cho phép recovery (vd: đã dừng planner, vùng xung quanh đủ an toàn).
|
||||
- Áp timeout/giám sát ngoài để tránh recovery chạy vô hạn.
|
||||
## Quy tắc cho plugin họ vận tốc
|
||||
|
||||
1. **Không tích phân vận tốc lệnh để đo tiến độ.** Dùng `PoseProvider` và hình chiếu delta pose
|
||||
(`recovery_core::projectOntoHeading`). Tích phân lệnh thì bánh trượt hay robot bị chặn vẫn báo đi
|
||||
đủ quãng — và đó là lúc nguy hiểm nhất để nói dối.
|
||||
2. **Kiểm va chạm trên pose *dự đoán*, trước khi phát lệnh.** Không phải sau. `BackUpRecovery` dò
|
||||
pose ở cuối chu kỳ tới; `RotateRecovery` quét toàn bộ cung ngay tại `onStart()` và refuse khởi
|
||||
động nếu có bất kỳ góc nào bị chặn.
|
||||
3. **Mất pose là dừng.** `getRobotPose()` trả `false` → trả `kFailed` + Twist 0. Không dùng pose cũ.
|
||||
4. **Ramp theo `acc_lim_*`.** Tick đầu không được nhảy thẳng lên tốc độ tối đa; `onCancel()` cũng
|
||||
nên giảm tốc thay vì nhảy bậc.
|
||||
5. **Clamp mọi param có dải.** `angle` ∈ [-2π, 2π], `distance` ∈ (0, `*_max`], tốc độ ≤ trần. Param
|
||||
sai thì log cảnh báo **kèm giá trị** rồi dùng default — không im lặng.
|
||||
6. **Ghi rõ dấu.** `linear.x < 0` là lùi; `angular.z > 0` là ngược chiều kim đồng hồ. Param cấu hình
|
||||
là **độ lớn**, dấu do plugin đặt theo ngữ cảnh.
|
||||
|
||||
## Quy tắc chung cho mọi plugin
|
||||
|
||||
- `onUpdate()` chạy trên thread phát `cmd_vel`. **Không block quá một phần nhỏ chu kỳ control**:
|
||||
không parse file, không cấp phát lớn, không gọi việc nặng của costmap (`ClearCostmapRecovery` cố
|
||||
ý không gọi `updateMap()` vì lý do này).
|
||||
- **Không cache `Costmap2D*`** qua các tick. Con trỏ có thể bị thay giữa hai cycle; lấy lại mỗi lần
|
||||
dùng.
|
||||
- Kiểm mã trả về của `CollisionChecker` bằng `< 0`, **không** so với một giá trị âm cụ thể — các
|
||||
hiện thực trong workspace không thống nhất mã lỗi.
|
||||
- `configure()` trả `false` khi cấu hình không chạy được, thay vì log warning rồi chạy tiếp bằng
|
||||
default. Registry sẽ bỏ behavior đó và log đích danh.
|
||||
|
||||
## Trách nhiệm của caller
|
||||
|
||||
- Làm mới con trỏ costmap trong `RecoveryContext` trước mỗi `start()`/`update()`.
|
||||
- Tick recovery từ **đúng một thread** — thread sở hữu `cmd_vel`. Thread riêng cho recovery nghĩa là
|
||||
hai bộ điều khiển cùng phát vận tốc.
|
||||
- Chỉ gọi `update()` sau khi `start()` trả `true`.
|
||||
- Sau `cancel()`, hoặc tick tiếp để nhận stop output, hoặc tự khoá nguồn vận tốc về 0.
|
||||
|
||||
Reference in New Issue
Block a user