temporary storage 7/9/2026 19:41
This commit is contained in:
@@ -195,7 +195,6 @@ endif()
|
|||||||
# ========================================================
|
# ========================================================
|
||||||
add_library(recovery_core SHARED
|
add_library(recovery_core SHARED
|
||||||
src/recovery_types.cpp
|
src/recovery_types.cpp
|
||||||
src/recovery_config.cpp
|
|
||||||
src/recovery_behavior.cpp
|
src/recovery_behavior.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
21
PLAN.md
21
PLAN.md
@@ -25,11 +25,10 @@ plugin recovery có thể trả trạng thái, velocity command hoặc path.
|
|||||||
- `RecoveryStatus`
|
- `RecoveryStatus`
|
||||||
- `RecoveryOutputType`
|
- `RecoveryOutputType`
|
||||||
- `RecoveryResult`
|
- `RecoveryResult`
|
||||||
- Cung cấp helper config chung:
|
- Cung cấp ngữ cảnh + mục tiêu runtime:
|
||||||
- `RecoveryConfig::control_frequency`
|
- `RecoveryContext` (tf/costmap/global_path)
|
||||||
- `RecoveryConfig::timeout`
|
- `RecoveryGoal` (angle/distance/target_pose/params)
|
||||||
- `validate()`
|
- Param chung `timeout` do base đọc trong `configure()` (param riêng plugin đọc `onConfigure()`).
|
||||||
- `fromNodeHandle()`
|
|
||||||
- Cung cấp docs/test stub để plugin sau này implement đúng contract.
|
- Cung cấp docs/test stub để plugin sau này implement đúng contract.
|
||||||
|
|
||||||
### 2.2. `recovery_core` không chịu trách nhiệm
|
### 2.2. `recovery_core` không chịu trách nhiệm
|
||||||
@@ -157,18 +156,18 @@ recovery_core/
|
|||||||
├── PLAN.md
|
├── PLAN.md
|
||||||
├── include/recovery_core/
|
├── include/recovery_core/
|
||||||
│ ├── recovery_behavior.h
|
│ ├── recovery_behavior.h
|
||||||
│ ├── recovery_config.h
|
|
||||||
│ └── recovery_types.h
|
│ └── recovery_types.h
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── recovery_behavior.cpp
|
│ ├── recovery_behavior.cpp
|
||||||
│ ├── recovery_config.cpp
|
|
||||||
│ └── recovery_types.cpp
|
│ └── recovery_types.cpp
|
||||||
|
├── plugins/
|
||||||
|
│ ├── clear_costmap_recovery.cpp
|
||||||
|
│ ├── rotate_recovery.cpp
|
||||||
|
│ ├── back_up_recovery.cpp
|
||||||
|
│ └── regen_path_recovery.cpp
|
||||||
├── test/
|
├── test/
|
||||||
│ ├── CMakeLists.txt
|
│ ├── CMakeLists.txt
|
||||||
│ ├── interface_contract_test.cpp
|
│ └── plugin_loader_contract_test.cpp
|
||||||
│ └── mock_behavior.h
|
|
||||||
├── examples/
|
|
||||||
│ └── minimal_recovery.cpp
|
|
||||||
└── docs/
|
└── docs/
|
||||||
├── ARCHITECTURE.md
|
├── ARCHITECTURE.md
|
||||||
├── PLUGIN_GUIDE.md
|
├── PLUGIN_GUIDE.md
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -13,21 +13,35 @@ Interface (base class) cho các hành vi **recovery** của navigation stack ROS
|
|||||||
- Core interface không publish trực tiếp. Package hiện có thêm các plugin mẫu build thành
|
- Core interface không publish trực tiếp. Package hiện có thêm các plugin mẫu build thành
|
||||||
`.so` riêng và nạp qua **Boost.DLL** để kiểm chứng contract end-to-end.
|
`.so` riêng và nạp qua **Boost.DLL** để kiểm chứng contract end-to-end.
|
||||||
|
|
||||||
|
## Vòng đời hướng-goal
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
behavior->configure(name, ctx); // 1 lần: ctx = {tf, global_path, global/local costmap}
|
||||||
|
RecoveryGoal goal; goal.angle = 1.57; // mục tiêu RUNTIME: "quay 90 độ ngay lượt này"
|
||||||
|
RecoveryResult r = behavior->start(goal);
|
||||||
|
while (r.status == RecoveryStatus::kRunning)
|
||||||
|
r = behavior->update(dt); // publish r.command; đọc r.progress / r.remaining / r.message
|
||||||
|
```
|
||||||
|
|
||||||
|
API công khai `configure/start/update/cancel` là non-virtual (base lo guard/timeout/elapsed/cancel);
|
||||||
|
plugin chỉ override hook `onConfigure()/onStart(goal)/onUpdate(dt)`.
|
||||||
|
|
||||||
## Ba họ recovery
|
## Ba họ recovery
|
||||||
|
|
||||||
| Họ | Ví dụ | Output | Method chính |
|
| Họ | Ví dụ | Output | Hook chính |
|
||||||
|----|-------|--------|--------------|
|
|----|-------|--------|-----------|
|
||||||
| A | regen path (đường thoát) | `robot_nav_msgs::Path` | `runBehavior()` |
|
| A | regen path (đường thoát) | `robot_nav_msgs::Path` | `onUpdate()` one-shot |
|
||||||
| B | clear costmap | không có (chỉ status) | `runBehavior()` |
|
| B | clear costmap | không có (chỉ status) | `onUpdate()` one-shot |
|
||||||
| C | rotation / backup | `robot_geometry_msgs::Twist` mỗi cycle | `computeCommand(dt)` |
|
| C | rotation / backup | `robot_geometry_msgs::Twist` mỗi cycle | `onStart(goal)` + `onUpdate(dt)` |
|
||||||
|
|
||||||
Cả 3 chia sẻ một `RecoveryResult` hợp nhất mang cờ `output_type`.
|
Cả 3 chia sẻ một `RecoveryResult` hợp nhất mang cờ `output_type` + rich feedback
|
||||||
|
(`progress`/`remaining`/`elapsed`/`message`).
|
||||||
|
|
||||||
## Cấu trúc
|
## Cấu trúc
|
||||||
|
|
||||||
```
|
```
|
||||||
include/recovery_core/ recovery_types.h, recovery_config.h, recovery_behavior.h
|
include/recovery_core/ recovery_types.h, recovery_behavior.h
|
||||||
src/ phần chung của contract (types/config/default behavior)
|
src/ phần chung của contract (types + base lifecycle)
|
||||||
plugins/ clear_costmap, rotate, backup, regen_path plugin mẫu
|
plugins/ clear_costmap, rotate, backup, regen_path plugin mẫu
|
||||||
test/ contract test + Boost.DLL loader test
|
test/ contract test + Boost.DLL loader test
|
||||||
examples/ minimal_recovery.cpp
|
examples/ minimal_recovery.cpp
|
||||||
|
|||||||
@@ -8,34 +8,41 @@ về output** (path/vận tốc), đồng thời vẫn dùng global path/costmap
|
|||||||
|
|
||||||
## Các thành phần
|
## Các thành phần
|
||||||
|
|
||||||
- `RecoveryBehavior` (interface): `initialize` (mirror nav_core) + `runBehavior` (one-shot) +
|
- `RecoveryBehavior` (interface, template-method): API non-virtual `configure`/`start`/`update`/
|
||||||
`computeCommand` (per-cycle) + `status`.
|
`cancel`; plugin chỉ triển khai hook `onConfigure`/`onStart(goal)`/`onUpdate(dt)`.
|
||||||
- `RecoveryResult` / `RecoveryStatus` / `RecoveryOutputType`: hợp đồng output hợp nhất 3 họ.
|
- `RecoveryContext`: gói con trỏ ngữ cảnh (tf/costmap/global_path) truyền một lần qua `configure`.
|
||||||
- `RecoveryConfig`: param chung (control_frequency, timeout) + validate + đọc từ NodeHandle.
|
- `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:
|
- Plugin mẫu:
|
||||||
- `ClearCostmapRecovery`: clear layer costmap theo tên, trả no-output status.
|
- `ClearCostmapRecovery`: clear layer costmap theo tên, one-shot no-output.
|
||||||
- `RotateRecovery`: sinh `Twist.angular.z` theo chu kỳ tới khi đủ góc.
|
- `RotateRecovery`: quay tới `goal.angle` (rad); sinh `Twist.angular.z` mỗi cycle.
|
||||||
- `BackUpRecovery`: sinh `Twist.linear.x < 0` theo chu kỳ tới khi đủ khoảng lùi.
|
- `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.
|
- `RegenPathRecovery`: trả lại `robot_nav_msgs::Path` từ `global_path` hiện tại.
|
||||||
|
|
||||||
## Luồng runtime
|
## Luồng runtime
|
||||||
|
|
||||||
```
|
```
|
||||||
initialize(name, tf, global_path, global, local) // 1 lần, đọc param qua NodeHandle
|
configure(name, ctx) // 1 lần: cache ctx, đọc config chung, onConfigure()
|
||||||
│
|
│
|
||||||
├── one-shot (họ A/B): runBehavior() ──────────────► RecoveryResult{status, path|none}
|
start(goal) // mỗi lượt: chốt mục tiêu runtime, onStart()
|
||||||
│
|
│
|
||||||
└── per-cycle (họ C): loop { computeCommand(dt) } ─► RecoveryResult{status, velocity}
|
├── one-shot (họ A/B): update(dt) 1 lần ─────────► RecoveryResult{status, path|none, msg}
|
||||||
(caller publish command mỗi cycle tới khi status != kRunning)
|
│
|
||||||
|
└── per-cycle (họ C): loop { update(dt) } ──────► RecoveryResult{status, velocity,
|
||||||
|
(tới khi status != kRunning) progress, remaining}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ghi chú thiết kế
|
## Ghi chú thiết kế
|
||||||
|
|
||||||
- `computeCommand(dt)` lấy pose robot từ costmap/tf bên trong (nhất quán mirror nav_core),
|
- Template-method: base xử lý guard vòng đời (configure→start→update), `dt<=0`, `timeout`, đo
|
||||||
không truyền pose qua tham số.
|
`elapsed`, và `cancel` một chỗ; plugin không lặp lại các guard này.
|
||||||
- Default `RecoveryBehavior::computeCommand(dt)` trả `RecoveryResult::Failed()` để họ A/B không
|
- Mục tiêu là RUNTIME qua `RecoveryGoal` (không cố định trong config): cùng plugin phục vụ nhiều
|
||||||
vô tình sinh command mù.
|
yêu cầu góc/khoảng khác nhau. Field = 0 → dùng default plugin đọc ở `onConfigure()`.
|
||||||
- `RecoveryConfig::validate()` từ chối `NaN/Inf`, `control_frequency <= 0`, `timeout < 0`.
|
- `onUpdate(dt)` lấy pose robot từ costmap/tf bên trong; không truyền pose qua tham số.
|
||||||
- `RecoveryConfig::fromNodeHandle()` đọc param chung và thay giá trị invalid bằng default an toàn.
|
- Sau `cancel()`, base tự trả stop output (Twist 0) + `kCancelled`.
|
||||||
|
- Param chung duy nhất còn lại là `timeout` (s, 0 = không giới hạn), base đọc trong `configure()`
|
||||||
|
từ `~/<name>/timeout`; giá trị `NaN/Inf/<0` bị thay bằng 0. Param RIÊNG của plugin đọc trong
|
||||||
|
`onConfigure()`.
|
||||||
- Interface KHÔNG include Boost.DLL; export/import là việc của plugin/loader.
|
- 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.
|
- Plugin mẫu có dùng Boost.DLL alias, nhưng core contract vẫn không biết loader/adaptor.
|
||||||
|
|||||||
@@ -9,18 +9,38 @@ Package hiện có 4 plugin mẫu dưới `plugins/`:
|
|||||||
## Bước chung
|
## Bước chung
|
||||||
|
|
||||||
1. Kế thừa `recovery_core::RecoveryBehavior`.
|
1. Kế thừa `recovery_core::RecoveryBehavior`.
|
||||||
2. Override `initialize()` — đọc param riêng qua `robot::NodeHandle("~/" + name)`, cache
|
2. Override hook `onConfigure()` (tuỳ chọn) — đọc param riêng qua `robot::NodeHandle("~/" + name)`;
|
||||||
tf/global_path/costmap.
|
ngữ cảnh tf/global_path/costmap lấy qua `ctx()`.
|
||||||
3. Override method theo họ (xem dưới) + `status()`.
|
3. Override `onStart(goal)` + `onUpdate(dt)` theo họ (xem dưới). KHÔNG override
|
||||||
4. Thêm factory `static Ptr create()` **không tham số** + `BOOST_DLL_ALIAS(...)`.
|
`configure/start/update/cancel` — base đã lo guard vòng đời/timeout/cancel/elapsed.
|
||||||
|
4. Thêm factory `static RecoveryBehaviorPtr create()` **không tham số** + `BOOST_DLL_ALIAS(...)`.
|
||||||
|
|
||||||
|
## Vòng đời (goal-driven)
|
||||||
|
|
||||||
|
```
|
||||||
|
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(dt) // mỗi cycle tới khi status != kRunning; base tick elapsed + guard
|
||||||
|
│
|
||||||
|
[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ọ
|
## Override theo họ
|
||||||
|
|
||||||
| Họ | Override | Trả về |
|
| Họ | Override | Trả về |
|
||||||
|----|----------|--------|
|
|----|----------|--------|
|
||||||
| A. path | `runBehavior()` | `RecoveryResult::PathOut(path, kSucceeded)` |
|
| A. path | `onUpdate()` (one-shot) | `RecoveryResult::PathOut(path, kSucceeded)` |
|
||||||
| B. none | `runBehavior()` | `RecoveryResult::Succeeded()` / `Failed()` |
|
| B. none | `onUpdate()` (one-shot) | `RecoveryResult::Succeeded()` / `Failed()` |
|
||||||
| C. velocity | `computeCommand(dt)` | `RecoveryResult::Velocity(twist, kRunning|kSucceeded)` |
|
| C. velocity | `onStart()` chốt goal + `onUpdate(dt)` 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)
|
## Export bằng Boost.DLL (bắt buộc cho plugin)
|
||||||
|
|
||||||
@@ -31,10 +51,11 @@ Package hiện có 4 plugin mẫu dưới `plugins/`:
|
|||||||
namespace recovery_plugins {
|
namespace recovery_plugins {
|
||||||
class SpinRecovery : public recovery_core::RecoveryBehavior {
|
class SpinRecovery : public recovery_core::RecoveryBehavior {
|
||||||
public:
|
public:
|
||||||
static recovery_core::RecoveryBehavior::Ptr create() {
|
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create() {
|
||||||
return std::make_shared<SpinRecovery>();
|
return std::make_shared<SpinRecovery>();
|
||||||
}
|
}
|
||||||
// override initialize()/computeCommand()/runBehavior()/status()...
|
protected:
|
||||||
|
// override onConfigure()/onStart(goal)/onUpdate(dt)...
|
||||||
};
|
};
|
||||||
} // namespace recovery_plugins
|
} // namespace recovery_plugins
|
||||||
|
|
||||||
@@ -46,10 +67,21 @@ BOOST_DLL_ALIAS(recovery_plugins::SpinRecovery::create, spin_recovery)
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <boost/dll/import.hpp>
|
#include <boost/dll/import.hpp>
|
||||||
auto loader = boost::dll::import_alias<recovery_core::RecoveryBehavior::Ptr()>(
|
auto loader = boost::dll::import_alias<recovery_core::RecoveryBehavior::RecoveryBehaviorPtr()>(
|
||||||
path_so, /*symbol=*/type, boost::dll::load_mode::append_decorations);
|
path_so, /*symbol=*/type, boost::dll::load_mode::append_decorations);
|
||||||
recovery_core::RecoveryBehavior::Ptr behavior = loader();
|
recovery_core::RecoveryBehavior::RecoveryBehaviorPtr behavior = loader();
|
||||||
behavior->initialize(name, tf, global_path, global_costmap, local_costmap);
|
|
||||||
|
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(dt); // publish r.command; đọc r.progress/r.remaining/r.message
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Lưu ý: adapter/test phải giữ handle `.so` sống lâu hơn object plugin. Nếu library bị unload trong
|
Lưu ý: adapter/test phải giữ handle `.so` sống lâu hơn object plugin. Nếu library bị unload trong
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
đúng nhịp và publish command.
|
đú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
|
- **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).
|
(m, rad, s, m/s, rad/s).
|
||||||
- **Default per-cycle fail an toàn.** Behavior không override `computeCommand(dt)` sẽ nhận
|
- **Guard vòng đời ở base.** `start()` trước `configure()`, hay `update()` trước `start()`, đều trả
|
||||||
`RecoveryResult::Failed()` thay vì velocity mặc định.
|
`RecoveryResult::Failed()`. `dt <= 0`, `NaN`, `Inf`, và `timeout` do base chặn trước khi gọi
|
||||||
|
`onUpdate()`. Sau `cancel()`, base trả stop output (Twist 0) + `kCancelled`.
|
||||||
|
|
||||||
## Nguyên tắc cho plugin
|
## Nguyên tắc cho plugin
|
||||||
|
|
||||||
- Guard `initialized_` và costmap/tf null trước khi thao tác; fail an toàn -> `RecoveryResult::Failed()`.
|
- Guard costmap/tf null (lấy qua `ctx()`) trước khi thao tác; fail an toàn -> `RecoveryResult::Failed()`.
|
||||||
- Guard `dt <= 0`, `NaN`, `Inf` trước khi tính velocity.
|
- Không cần tự guard `dt <= 0`/`NaN`/`Inf`/`timeout` — base đã lo; chỉ 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ù.
|
- 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.
|
- 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).
|
- Tôn trọng giới hạn vận tốc/gia tốc của robot (đọc qua param).
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
*
|
*
|
||||||
* recovery_core — interface (base class) cho recovery behaviors.
|
* recovery_core — interface (base class) cho recovery behaviors.
|
||||||
*
|
*
|
||||||
* Mô phỏng robot_nav_core::RecoveryBehavior (giữ chữ ký initialize với tf + costmap),
|
* Vòng đời hợp nhất, hướng-goal cho cả 3 họ recovery (path / none / velocity):
|
||||||
* nhưng tổng quát hoá output để bao 3 họ recovery (path / none / velocity).
|
* configure(name, ctx) -> start(goal) -> lặp update(dt) -> [cancel()]
|
||||||
|
* Base lo phần chung (guard init/dt/timeout/cancel, đo elapsed, đẩy feedback); plugin chỉ
|
||||||
|
* override các hook nhỏ onConfigure()/onStart()/onUpdate().
|
||||||
*
|
*
|
||||||
* Author: DuongTD
|
* Author: DuongTD
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
@@ -27,72 +29,113 @@ namespace recovery_core
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @class RecoveryBehavior
|
* @class RecoveryBehavior
|
||||||
* @brief Interface cho mọi hành vi recovery không chạy roscpp/ROS master thật
|
* @brief Interface hướng-goal cho mọi hành vi recovery (không chạy roscpp/ROS master thật).
|
||||||
* (dùng lớp ROS-like robot_*).
|
|
||||||
*
|
*
|
||||||
* Ba họ hành vi và method chính tương ứng:
|
* Thiết kế theo template-method: API công khai (configure/start/update/cancel) là NON-VIRTUAL
|
||||||
* - Họ A (trả path) : override runBehavior() -> RecoveryResult::PathOut(...)
|
* và do base xử lý phần lặp lại; plugin chỉ triển khai các hook protected:
|
||||||
* - Họ B (không output) : override runBehavior() -> RecoveryResult::Succeeded()/Failed()
|
* - onConfigure() : đọc param riêng từ ctx()/NodeHandle (tuỳ chọn).
|
||||||
* - Họ C (trả vận tốc) : override computeCommand() -> RecoveryResult::Velocity(...)
|
* - onStart(goal) : chốt mục tiêu lượt này (rad/m/pose), reset trạng thái tiến độ.
|
||||||
|
* - onUpdate(dt) : một "tick"; họ one-shot (path/clear) hoàn tất ngay lần đầu.
|
||||||
*
|
*
|
||||||
* Vòng đời: initialize() một lần -> runBehavior() (one-shot) hoặc lặp computeCommand()
|
* Ba họ hành vi:
|
||||||
* (per-cycle) -> status(). Guard initialized_/costmap trước khi thao tác.
|
* - Họ A (trả path) : onUpdate() trả RecoveryResult::PathOut(...), kSucceeded ngay.
|
||||||
|
* - Họ B (không output) : onUpdate() làm việc rồi trả Succeeded()/Failed() ngay.
|
||||||
|
* - Họ C (trả vận tốc) : onUpdate() sinh Twist mỗi cycle tới khi đạt goal -> kSucceeded.
|
||||||
|
*
|
||||||
|
* Bất biến: start() chỉ hợp lệ sau configure(); update() chỉ chạy sau start(). Vi phạm ->
|
||||||
|
* RecoveryResult::Failed(). Base tự trả stop output + kCancelled sau khi cancel().
|
||||||
*/
|
*/
|
||||||
class RecoveryBehavior
|
class RecoveryBehavior
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// shared_ptr để khớp cơ chế nạp Boost.DLL của workspace
|
/// shared_ptr để khớp cơ chế nạp Boost.DLL của workspace
|
||||||
/// (boost::dll::import_alias<recovery_core::RecoveryBehavior::Ptr()>(...)).
|
/// (boost::dll::import_alias<recovery_core::RecoveryBehavior::RecoveryBehaviorPtr()>(...)).
|
||||||
using RecoveryBehaviorPtr = std::shared_ptr<RecoveryBehavior>;
|
using RecoveryBehaviorPtr = std::shared_ptr<RecoveryBehavior>;
|
||||||
|
|
||||||
virtual ~RecoveryBehavior() = default;
|
virtual ~RecoveryBehavior() = default;
|
||||||
|
|
||||||
/**
|
// ------------------------------------------------------------------
|
||||||
* @brief Khởi tạo — mở rộng chữ ký robot_nav_core::RecoveryBehavior::initialize (thêm
|
// API công khai — NON-VIRTUAL, base xử lý phần chung.
|
||||||
* global_path). Chỉ chạy một lần; đọc param qua robot::NodeHandle("~/" + name);
|
// ------------------------------------------------------------------
|
||||||
* cache tf/costmap/global_path (không sở hữu).
|
|
||||||
* @param name Tên instance (dùng cho namespace param + log).
|
|
||||||
* @param tf Transform buffer (không sở hữu).
|
|
||||||
* @param global_path Đường đi toàn cục hiện tại (không sở hữu) — họ A regen path tham
|
|
||||||
* chiếu để tạo lại/né; có thể null nếu chưa có plan.
|
|
||||||
* @param global_costmap Costmap toàn cục (không sở hữu).
|
|
||||||
* @param local_costmap Costmap cục bộ (không sở hữu).
|
|
||||||
*/
|
|
||||||
virtual void initialize(std::string name, tf3::BufferCore* tf,
|
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap) = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Chạy hành vi kiểu ONE-SHOT (họ A regen path, họ B clear costmap).
|
* @brief Cấu hình một lần: cache ngữ cảnh (không sở hữu), đọc config chung, gọi onConfigure().
|
||||||
* Trả kết quả cuối kèm output (path hoặc none). Guard chưa initialize/costmap null
|
* @param name Tên instance (namespace param + log).
|
||||||
* -> RecoveryResult::Failed().
|
* @param ctx Ngữ cảnh môi trường (tf/costmap/global_path). Gọi lại lần 2 bị bỏ qua.
|
||||||
*/
|
*/
|
||||||
virtual RecoveryResult runBehavior() = 0;
|
void configure(const std::string& name, const RecoveryContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sinh command kiểu PER-CYCLE (họ C rotation/backup). Lấy pose robot từ costmap/tf
|
* @brief Bắt đầu một lượt recovery với mục tiêu RUNTIME.
|
||||||
* bên trong; caller lặp gọi mỗi control cycle và publish command.
|
* @param goal Mục tiêu lượt này (góc/khoảng lùi/pose + override). Field 0 = dùng default plugin.
|
||||||
|
* @return Kết quả tick đầu (thường kRunning; kFailed nếu chưa configure hoặc goal không hợp lệ).
|
||||||
|
*/
|
||||||
|
RecoveryResult start(const RecoveryGoal& goal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Một control cycle. Guard chưa start/dt<=0/timeout/cancel trước khi gọi onUpdate().
|
||||||
* @param dt Khoảng thời gian control cycle (s), > 0.
|
* @param dt Khoảng thời gian control cycle (s), > 0.
|
||||||
* @return RecoveryResult (thường output_type == kVelocity).
|
|
||||||
*
|
|
||||||
* Mặc định: coi như không hỗ trợ per-cycle và trả Failed() — họ A/B không cần override.
|
|
||||||
*/
|
*/
|
||||||
virtual RecoveryResult computeCommand(double dt);
|
RecoveryResult update(double dt);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Trạng thái hiện tại của lượt recovery.
|
* @brief Yêu cầu huỷ: update() kế tiếp trả stop output + kCancelled.
|
||||||
*/
|
*/
|
||||||
virtual RecoveryStatus status() const = 0;
|
void cancel();
|
||||||
|
|
||||||
virtual std::string getNameRecoveryBehavior() const
|
/// @brief Trạng thái hiện tại của lượt recovery.
|
||||||
|
RecoveryStatus status() const
|
||||||
|
{
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Thời gian đã trôi từ start() (s).
|
||||||
|
double elapsed() const
|
||||||
|
{
|
||||||
|
return elapsed_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& name() const
|
||||||
|
{
|
||||||
|
return name_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Giữ tên cũ cho tương thích call-site loader.
|
||||||
|
std::string getNameRecoveryBehavior() const
|
||||||
{
|
{
|
||||||
return name_;
|
return name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RecoveryBehavior() = default;
|
RecoveryBehavior() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Hook cho plugin.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// @brief Đọc param riêng (qua ctx()/NodeHandle) sau khi base cache ngữ cảnh. Tuỳ chọn.
|
||||||
|
virtual void onConfigure() {}
|
||||||
|
|
||||||
|
/// @brief Chốt mục tiêu lượt này; reset bộ đếm tiến độ nội bộ. Trả tick khởi đầu.
|
||||||
|
virtual RecoveryResult onStart(const RecoveryGoal& goal) = 0;
|
||||||
|
|
||||||
|
/// @brief Một tick. Họ one-shot trả kSucceeded/kFailed ngay; họ velocity trả kRunning tới goal.
|
||||||
|
virtual RecoveryResult onUpdate(double dt) = 0;
|
||||||
|
|
||||||
|
// Truy cập cho plugin (chỉ đọc ngữ cảnh/goal).
|
||||||
|
const RecoveryContext& ctx() const { return ctx_; }
|
||||||
|
const RecoveryGoal& goal() const { return goal_; }
|
||||||
|
bool cancelRequested() const { return cancel_requested_; }
|
||||||
|
|
||||||
|
RecoveryContext ctx_;
|
||||||
|
RecoveryGoal goal_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
double timeout_ = 0.0; ///< s — 0 nghĩa là không timeout; đọc param "~/<name>/timeout".
|
||||||
|
double elapsed_ = 0.0;
|
||||||
|
bool configured_ = false;
|
||||||
|
bool started_ = false;
|
||||||
|
bool cancel_requested_ = false;
|
||||||
|
RecoveryStatus status_ = RecoveryStatus::kIdle;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace recovery_core
|
} // namespace recovery_core
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
/*********************************************************************
|
|
||||||
*
|
|
||||||
* Software License Agreement (BSD License)
|
|
||||||
*
|
|
||||||
* recovery_core — config chung cho recovery behaviors.
|
|
||||||
*
|
|
||||||
* Author: DuongTD
|
|
||||||
*********************************************************************/
|
|
||||||
#ifndef RECOVERY_CORE_RECOVERY_CONFIG_H_
|
|
||||||
#define RECOVERY_CORE_RECOVERY_CONFIG_H_
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Forward declare để không kéo <robot/robot.h> vào header interface.
|
|
||||||
namespace robot { class NodeHandle; }
|
|
||||||
|
|
||||||
namespace recovery_core
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @struct RecoveryConfig
|
|
||||||
* @brief Gói các tham số CHUNG cho vòng đời recovery.
|
|
||||||
*
|
|
||||||
* Tham số RIÊNG của từng hành vi (vd: spin_target_angle, backup_distance) do plugin tự đọc
|
|
||||||
* qua robot::NodeHandle trong initialize(); struct này chỉ giữ phần chung.
|
|
||||||
*
|
|
||||||
* Đơn vị:
|
|
||||||
* - control_frequency : Hz (tần số gọi computeCommand khi chạy per-cycle)
|
|
||||||
* - timeout : s (0 = không giới hạn thời gian)
|
|
||||||
*/
|
|
||||||
struct RecoveryConfig
|
|
||||||
{
|
|
||||||
double control_frequency = 20.0; ///< Hz, > 0.
|
|
||||||
double timeout = 0.0; ///< s, >= 0; 0 nghĩa là không timeout.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Kiểm tra hợp lệ các tham số.
|
|
||||||
* @param error [out] Nếu != nullptr và invalid, ghi thông điệp lỗi.
|
|
||||||
* @return true nếu hợp lệ.
|
|
||||||
*/
|
|
||||||
bool validate(std::string* error) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Đọc config từ NodeHandle (có default, có validate + log cảnh báo nếu sai).
|
|
||||||
* @param nh NodeHandle đã trỏ tới namespace của behavior.
|
|
||||||
* @return RecoveryConfig đã điền (giá trị sai được thay bằng default).
|
|
||||||
*/
|
|
||||||
static RecoveryConfig fromNodeHandle(robot::NodeHandle& nh);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace recovery_core
|
|
||||||
|
|
||||||
#endif // RECOVERY_CORE_RECOVERY_CONFIG_H_
|
|
||||||
@@ -9,9 +9,18 @@
|
|||||||
#ifndef RECOVERY_CORE_RECOVERY_TYPES_H_
|
#ifndef RECOVERY_CORE_RECOVERY_TYPES_H_
|
||||||
#define RECOVERY_CORE_RECOVERY_TYPES_H_
|
#define RECOVERY_CORE_RECOVERY_TYPES_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <robot_geometry_msgs/PoseStamped.h>
|
||||||
#include <robot_geometry_msgs/Twist.h>
|
#include <robot_geometry_msgs/Twist.h>
|
||||||
#include <robot_nav_msgs/Path.h>
|
#include <robot_nav_msgs/Path.h>
|
||||||
|
|
||||||
|
// Forward declare để không kéo header nặng vào contract type.
|
||||||
|
namespace tf3 { class BufferCore; }
|
||||||
|
namespace robot_costmap_2d { class Costmap2DROBOT; }
|
||||||
|
|
||||||
namespace recovery_core
|
namespace recovery_core
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -21,10 +30,11 @@ namespace recovery_core
|
|||||||
*/
|
*/
|
||||||
enum class RecoveryStatus
|
enum class RecoveryStatus
|
||||||
{
|
{
|
||||||
kIdle, ///< Chưa bắt đầu (sau initialize/reset).
|
kIdle, ///< Chưa bắt đầu (sau configure/reset).
|
||||||
kRunning, ///< Đang thực thi, cần tiếp tục gọi.
|
kRunning, ///< Đang thực thi, cần tiếp tục gọi update().
|
||||||
kSucceeded, ///< Hoàn thành thành công.
|
kSucceeded, ///< Hoàn thành thành công (đạt goal).
|
||||||
kFailed ///< Lỗi/không thể thực thi an toàn (trả stop output).
|
kFailed, ///< Lỗi/không thể thực thi an toàn (trả stop output).
|
||||||
|
kCancelled ///< Bị caller huỷ giữa chừng (trả stop output).
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,13 +50,59 @@ enum class RecoveryOutputType
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct RecoveryResult
|
* @struct RecoveryContext
|
||||||
* @brief Kết quả hợp nhất cho cả 3 họ recovery.
|
* @brief Ngữ cảnh môi trường cấp cho behavior một lần qua configure().
|
||||||
*
|
*
|
||||||
* Bất biến: chỉ đọc trường khớp với @ref output_type.
|
* Gói các con trỏ KHÔNG sở hữu (tf/costmap/global_path) thay cho danh sách tham số dài của
|
||||||
|
* initialize() cũ. Thêm field mới ở đây không phá vỡ chữ ký configure() của mọi plugin.
|
||||||
|
*/
|
||||||
|
struct RecoveryContext
|
||||||
|
{
|
||||||
|
tf3::BufferCore* tf = nullptr; ///< Transform buffer.
|
||||||
|
std::vector<robot_geometry_msgs::PoseStamped>* global_path = nullptr; ///< Plan hiện tại.
|
||||||
|
robot_costmap_2d::Costmap2DROBOT* global_costmap = nullptr; ///< Costmap toàn cục.
|
||||||
|
robot_costmap_2d::Costmap2DROBOT* local_costmap = nullptr; ///< Costmap cục bộ.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct RecoveryGoal
|
||||||
|
* @brief Mục tiêu RUNTIME cho một lượt recovery — caller truyền vào start(goal).
|
||||||
|
*
|
||||||
|
* Đây là điểm cốt lõi giúp behavior "thông minh" hơn: cùng một plugin, mỗi lượt caller có thể
|
||||||
|
* yêu cầu góc quay / khoảng lùi khác nhau, thay vì cố định trong config lúc configure().
|
||||||
|
*
|
||||||
|
* Quy ước dùng default: trường mang giá trị 0 (hoặc has_target_pose == false) nghĩa là "dùng
|
||||||
|
* default đã cấu hình của plugin". Đơn vị: angle [rad], distance [m].
|
||||||
|
*/
|
||||||
|
struct RecoveryGoal
|
||||||
|
{
|
||||||
|
double angle = 0.0; ///< rad — góc quay đích (họ rotate). 0 = dùng default plugin.
|
||||||
|
double distance = 0.0; ///< m — khoảng lùi đích (họ backup). 0 = dùng default plugin.
|
||||||
|
|
||||||
|
robot_geometry_msgs::PoseStamped target_pose; ///< Pose đích (họ path/detour), tuỳ chọn.
|
||||||
|
bool has_target_pose = false; ///< true nếu target_pose hợp lệ.
|
||||||
|
|
||||||
|
std::map<std::string, double> params; ///< Override mở rộng theo từng plugin (vd tốc độ).
|
||||||
|
|
||||||
|
/// @brief Đọc override double trong params, trả default nếu không có.
|
||||||
|
double param(const std::string& key, double fallback) const
|
||||||
|
{
|
||||||
|
const auto it = params.find(key);
|
||||||
|
return it == params.end() ? fallback : it->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct RecoveryResult
|
||||||
|
* @brief Kết quả hợp nhất + rich feedback cho cả 3 họ recovery.
|
||||||
|
*
|
||||||
|
* Bất biến output: chỉ đọc trường khớp với @ref output_type.
|
||||||
* - kNone : bỏ qua command/path.
|
* - kNone : bỏ qua command/path.
|
||||||
* - kVelocity : dùng command; path để mặc định.
|
* - kVelocity : dùng command; path để mặc định.
|
||||||
* - kPath : dùng path; command để mặc định.
|
* - kPath : dùng path; command để mặc định.
|
||||||
|
*
|
||||||
|
* Feedback (progress/remaining/elapsed/message) luôn hợp lệ để caller giám sát/log, độc lập với
|
||||||
|
* output_type. progress trong [0,1]; remaining theo đơn vị của goal (rad hoặc m).
|
||||||
*/
|
*/
|
||||||
struct RecoveryResult
|
struct RecoveryResult
|
||||||
{
|
{
|
||||||
@@ -56,18 +112,30 @@ struct RecoveryResult
|
|||||||
robot_geometry_msgs::Twist command; ///< Hợp lệ khi output_type == kVelocity.
|
robot_geometry_msgs::Twist command; ///< Hợp lệ khi output_type == kVelocity.
|
||||||
robot_nav_msgs::Path path; ///< Hợp lệ khi output_type == kPath.
|
robot_nav_msgs::Path path; ///< Hợp lệ khi output_type == kPath.
|
||||||
|
|
||||||
|
double progress = 0.0; ///< [0,1] tiến độ tới goal.
|
||||||
|
double remaining = 0.0; ///< Phần còn lại tới goal (rad hoặc m). >= 0.
|
||||||
|
double elapsed = 0.0; ///< s — thời gian trôi từ start().
|
||||||
|
std::string message; ///< Mô tả người-đọc-được (vd "rotated 1.20/1.57 rad").
|
||||||
|
|
||||||
/// @brief Đang chạy, không output.
|
/// @brief Đang chạy, không output.
|
||||||
static RecoveryResult Running();
|
static RecoveryResult Running();
|
||||||
/// @brief Thành công, không output.
|
/// @brief Thành công, không output.
|
||||||
static RecoveryResult Succeeded();
|
static RecoveryResult Succeeded();
|
||||||
/// @brief Thất bại, không output (caller nên dừng an toàn).
|
/// @brief Thất bại, không output (caller nên dừng an toàn).
|
||||||
static RecoveryResult Failed();
|
static RecoveryResult Failed();
|
||||||
/// @brief Output vận tốc kèm status (kRunning hoặc kSucceeded).
|
/// @brief Bị huỷ, không output (caller nên dừng an toàn).
|
||||||
|
static RecoveryResult Cancelled();
|
||||||
|
/// @brief Output vận tốc kèm status (kRunning/kSucceeded/kFailed/kCancelled).
|
||||||
static RecoveryResult Velocity(const robot_geometry_msgs::Twist& command,
|
static RecoveryResult Velocity(const robot_geometry_msgs::Twist& command,
|
||||||
RecoveryStatus status);
|
RecoveryStatus status);
|
||||||
/// @brief Output path kèm status.
|
/// @brief Output path kèm status.
|
||||||
static RecoveryResult PathOut(const robot_nav_msgs::Path& path,
|
static RecoveryResult PathOut(const robot_nav_msgs::Path& path,
|
||||||
RecoveryStatus status);
|
RecoveryStatus status);
|
||||||
|
|
||||||
|
/// @brief Gắn thêm feedback (fluent) — trả về chính nó để chain.
|
||||||
|
RecoveryResult& withProgress(double progress_value, double remaining_value);
|
||||||
|
/// @brief Gắn message (fluent).
|
||||||
|
RecoveryResult& withMessage(std::string text);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace recovery_core
|
} // namespace recovery_core
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
*
|
*
|
||||||
* Software License Agreement (BSD License)
|
* Software License Agreement (BSD License)
|
||||||
*
|
*
|
||||||
* recovery_core — per-cycle backup recovery plugin.
|
* recovery_core — per-cycle backup recovery plugin (goal-driven).
|
||||||
*
|
*
|
||||||
* Author: DuongTD
|
* Author: DuongTD
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
|
|
||||||
#include <recovery_core/recovery_behavior.h>
|
#include <recovery_core/recovery_behavior.h>
|
||||||
#include <recovery_core/recovery_config.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/dll/alias.hpp>
|
#include <boost/dll/alias.hpp>
|
||||||
#include <robot/robot.h>
|
#include <robot/robot.h>
|
||||||
@@ -22,129 +20,114 @@ namespace recovery_plugins
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
robot_geometry_msgs::Twist zeroTwist()
|
constexpr double kDefaultBackupDistance = 0.5; // m.
|
||||||
{
|
constexpr double kDefaultLinearSpeed = 0.1; // m/s.
|
||||||
return robot_geometry_msgs::Twist();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool validCycle(double dt)
|
|
||||||
{
|
|
||||||
return std::isfinite(dt) && dt > 0.0;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class BackUpRecovery
|
||||||
|
* @brief Lùi thẳng tới KHOẢNG ĐÍCH do caller yêu cầu ở start(goal).
|
||||||
|
*
|
||||||
|
* goal.distance (m, > 0) là khoảng lùi lượt này; 0 nghĩa là dùng default configured. Tốc độ
|
||||||
|
* tuyến tính mặc định đọc từ param, có thể override qua goal.params["linear_speed"]. Mỗi
|
||||||
|
* update() trả Twist.linear.x < 0 kèm progress/remaining tới khi đủ khoảng -> kSucceeded.
|
||||||
|
*/
|
||||||
class BackUpRecovery final : public recovery_core::RecoveryBehavior
|
class BackUpRecovery final : public recovery_core::RecoveryBehavior
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BackUpRecovery() = default;
|
BackUpRecovery() = default;
|
||||||
|
|
||||||
void initialize(std::string name, tf3::BufferCore* tf,
|
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap) override
|
|
||||||
{
|
|
||||||
if (initialized_)
|
|
||||||
{
|
|
||||||
robot::log_error("[recovery_core] BackUpRecovery '%s' initialized twice; ignoring.",
|
|
||||||
name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name_ = std::move(name);
|
|
||||||
tf_ = tf;
|
|
||||||
global_path_ = global_path;
|
|
||||||
global_costmap_ = global_costmap;
|
|
||||||
local_costmap_ = local_costmap;
|
|
||||||
|
|
||||||
robot::NodeHandle private_nh("~/" + name_);
|
|
||||||
config_ = recovery_core::RecoveryConfig::fromNodeHandle(private_nh);
|
|
||||||
private_nh.param("backup_distance", backup_distance_, 0.5);
|
|
||||||
private_nh.param("linear_speed", linear_speed_, 0.1);
|
|
||||||
private_nh.param("require_costmap", require_costmap_, false);
|
|
||||||
|
|
||||||
if (!std::isfinite(backup_distance_) || backup_distance_ <= 0.0)
|
|
||||||
{
|
|
||||||
robot::log_warning("[recovery_core] Invalid backup_distance for '%s'; using 0.5 m.",
|
|
||||||
name_.c_str());
|
|
||||||
backup_distance_ = 0.5;
|
|
||||||
}
|
|
||||||
if (!std::isfinite(linear_speed_) || linear_speed_ <= 0.0)
|
|
||||||
{
|
|
||||||
robot::log_warning("[recovery_core] Invalid linear_speed for '%s'; using 0.1 m/s.",
|
|
||||||
name_.c_str());
|
|
||||||
linear_speed_ = 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
status_ = recovery_core::RecoveryStatus::kIdle;
|
|
||||||
}
|
|
||||||
|
|
||||||
recovery_core::RecoveryResult runBehavior() override
|
|
||||||
{
|
|
||||||
status_ = initialized_ ? recovery_core::RecoveryStatus::kRunning :
|
|
||||||
recovery_core::RecoveryStatus::kFailed;
|
|
||||||
return initialized_ ? recovery_core::RecoveryResult::Running() :
|
|
||||||
recovery_core::RecoveryResult::Failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
recovery_core::RecoveryResult computeCommand(double dt) override
|
|
||||||
{
|
|
||||||
if (!initialized_ || !validCycle(dt) || (require_costmap_ && local_costmap_ == nullptr))
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kFailed;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), );
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed_ += dt;
|
|
||||||
if (config_.timeout > 0.0 && elapsed_ > config_.timeout)
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kFailed;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (traveled_distance_ >= backup_distance_)
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kSucceeded;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
robot_geometry_msgs::Twist command;
|
|
||||||
command.linear.x = -std::abs(linear_speed_);
|
|
||||||
traveled_distance_ = std::min(backup_distance_, traveled_distance_ + std::abs(command.linear.x) * dt);
|
|
||||||
|
|
||||||
if (traveled_distance_ >= backup_distance_)
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kSucceeded;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
status_ = recovery_core::RecoveryStatus::kRunning;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(command, status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
recovery_core::RecoveryStatus status() const override
|
|
||||||
{
|
|
||||||
return status_;
|
|
||||||
}
|
|
||||||
|
|
||||||
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
||||||
{
|
{
|
||||||
return std::make_shared<BackUpRecovery>();
|
return std::make_shared<BackUpRecovery>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
tf3::BufferCore* tf_ = nullptr;
|
void onConfigure() override
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path_ = nullptr;
|
{
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap_ = nullptr;
|
robot::NodeHandle private_nh("~/" + name_);
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap_ = nullptr;
|
private_nh.param("backup_distance", default_backup_distance_, kDefaultBackupDistance);
|
||||||
recovery_core::RecoveryConfig config_;
|
private_nh.param("linear_speed", default_linear_speed_, kDefaultLinearSpeed);
|
||||||
bool initialized_ = false;
|
private_nh.param("require_costmap", require_costmap_, false);
|
||||||
recovery_core::RecoveryStatus status_ = recovery_core::RecoveryStatus::kIdle;
|
|
||||||
|
|
||||||
double backup_distance_ = 0.5;
|
if (!std::isfinite(default_backup_distance_) || default_backup_distance_ <= 0.0)
|
||||||
double linear_speed_ = 0.1;
|
{
|
||||||
|
robot::log_warning("[recovery_core] Invalid backup_distance for '%s'; using 0.5 m.",
|
||||||
|
name_.c_str());
|
||||||
|
default_backup_distance_ = kDefaultBackupDistance;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(default_linear_speed_) || default_linear_speed_ <= 0.0)
|
||||||
|
{
|
||||||
|
robot::log_warning("[recovery_core] Invalid linear_speed for '%s'; using 0.1 m/s.",
|
||||||
|
name_.c_str());
|
||||||
|
default_linear_speed_ = kDefaultLinearSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recovery_core::RecoveryResult onStart(const recovery_core::RecoveryGoal& goal) override
|
||||||
|
{
|
||||||
|
// Costmap là bắt buộc? fail sớm trước khi xuất vận tốc lùi.
|
||||||
|
if (require_costmap_ && ctx().local_costmap == nullptr)
|
||||||
|
{
|
||||||
|
return recovery_core::RecoveryResult::Failed().withMessage("backup requires local costmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_distance_ = (std::isfinite(goal.distance) && goal.distance > 0.0)
|
||||||
|
? goal.distance
|
||||||
|
: default_backup_distance_;
|
||||||
|
|
||||||
|
linear_speed_ = std::abs(goal.param("linear_speed", default_linear_speed_));
|
||||||
|
if (!std::isfinite(linear_speed_) || linear_speed_ <= 0.0)
|
||||||
|
{
|
||||||
|
linear_speed_ = default_linear_speed_;
|
||||||
|
}
|
||||||
|
|
||||||
|
traveled_distance_ = 0.0;
|
||||||
|
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||||
|
recovery_core::RecoveryStatus::kRunning)
|
||||||
|
.withProgress(0.0, backup_distance_)
|
||||||
|
.withMessage("backup start");
|
||||||
|
}
|
||||||
|
|
||||||
|
recovery_core::RecoveryResult onUpdate(double dt) override
|
||||||
|
{
|
||||||
|
if (traveled_distance_ >= backup_distance_)
|
||||||
|
{
|
||||||
|
return succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
robot_geometry_msgs::Twist command;
|
||||||
|
command.linear.x = -std::abs(linear_speed_);
|
||||||
|
traveled_distance_ =
|
||||||
|
std::min(backup_distance_, traveled_distance_ + std::abs(command.linear.x) * dt);
|
||||||
|
|
||||||
|
if (traveled_distance_ >= backup_distance_)
|
||||||
|
{
|
||||||
|
return succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
return recovery_core::RecoveryResult::Velocity(command,
|
||||||
|
recovery_core::RecoveryStatus::kRunning)
|
||||||
|
.withProgress(traveled_distance_ / backup_distance_,
|
||||||
|
backup_distance_ - traveled_distance_)
|
||||||
|
.withMessage("backing up");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
recovery_core::RecoveryResult succeeded()
|
||||||
|
{
|
||||||
|
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||||
|
recovery_core::RecoveryStatus::kSucceeded)
|
||||||
|
.withProgress(1.0, 0.0)
|
||||||
|
.withMessage("backup complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
double default_backup_distance_ = kDefaultBackupDistance;
|
||||||
|
double default_linear_speed_ = kDefaultLinearSpeed;
|
||||||
bool require_costmap_ = false;
|
bool require_costmap_ = false;
|
||||||
double elapsed_ = 0.0;
|
|
||||||
|
double backup_distance_ = kDefaultBackupDistance;
|
||||||
|
double linear_speed_ = kDefaultLinearSpeed;
|
||||||
double traveled_distance_ = 0.0;
|
double traveled_distance_ = 0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,24 +46,14 @@ class ClearCostmapRecovery final : public recovery_core::RecoveryBehavior
|
|||||||
public:
|
public:
|
||||||
ClearCostmapRecovery() = default;
|
ClearCostmapRecovery() = default;
|
||||||
|
|
||||||
void initialize(std::string name, tf3::BufferCore* tf,
|
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap) override
|
|
||||||
{
|
{
|
||||||
if (initialized_)
|
return std::make_shared<ClearCostmapRecovery>();
|
||||||
{
|
}
|
||||||
robot::log_error("[recovery_core] ClearCostmapRecovery '%s' initialized twice; ignoring.",
|
|
||||||
name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name_ = std::move(name);
|
|
||||||
tf_ = tf;
|
|
||||||
global_path_ = global_path;
|
|
||||||
global_costmap_ = global_costmap;
|
|
||||||
local_costmap_ = local_costmap;
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onConfigure() override
|
||||||
|
{
|
||||||
robot::NodeHandle private_nh("~/" + name_);
|
robot::NodeHandle private_nh("~/" + name_);
|
||||||
private_nh.param("reset_distance", reset_distance_, 3.0);
|
private_nh.param("reset_distance", reset_distance_, 3.0);
|
||||||
private_nh.param("invert_area_to_clear", invert_area_to_clear_, false);
|
private_nh.param("invert_area_to_clear", invert_area_to_clear_, false);
|
||||||
@@ -89,52 +79,37 @@ public:
|
|||||||
std::vector<std::string> clearable_layers;
|
std::vector<std::string> clearable_layers;
|
||||||
private_nh.param("layer_names", clearable_layers, clearable_layers_default);
|
private_nh.param("layer_names", clearable_layers, clearable_layers_default);
|
||||||
clearable_layers_.insert(clearable_layers.begin(), clearable_layers.end());
|
clearable_layers_.insert(clearable_layers.begin(), clearable_layers.end());
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
status_ = recovery_core::RecoveryStatus::kIdle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recovery_core::RecoveryResult runBehavior() override
|
recovery_core::RecoveryResult onStart(const recovery_core::RecoveryGoal& /*goal*/) override
|
||||||
{
|
{
|
||||||
if (!initialized_)
|
// One-shot: công việc thực hiện ở onUpdate() lần đầu.
|
||||||
{
|
return recovery_core::RecoveryResult::Running().withMessage("clear costmap start");
|
||||||
status_ = recovery_core::RecoveryStatus::kFailed;
|
}
|
||||||
return recovery_core::RecoveryResult::Failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
recovery_core::RecoveryResult onUpdate(double /*dt*/) override
|
||||||
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
if (affected_maps_ == "global" || affected_maps_ == "both")
|
if (affected_maps_ == "global" || affected_maps_ == "both")
|
||||||
{
|
{
|
||||||
ok = clear(global_costmap_) && ok;
|
ok = clear(ctx().global_costmap) && ok;
|
||||||
if (ok && force_updating_ && global_costmap_ != nullptr)
|
if (ok && force_updating_ && ctx().global_costmap != nullptr)
|
||||||
{
|
{
|
||||||
global_costmap_->updateMap();
|
ctx().global_costmap->updateMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (affected_maps_ == "local" || affected_maps_ == "both")
|
if (affected_maps_ == "local" || affected_maps_ == "both")
|
||||||
{
|
{
|
||||||
ok = clear(local_costmap_) && ok;
|
ok = clear(ctx().local_costmap) && ok;
|
||||||
if (ok && force_updating_ && local_costmap_ != nullptr)
|
if (ok && force_updating_ && ctx().local_costmap != nullptr)
|
||||||
{
|
{
|
||||||
local_costmap_->updateMap();
|
ctx().local_costmap->updateMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status_ = ok ? recovery_core::RecoveryStatus::kSucceeded :
|
return ok ? recovery_core::RecoveryResult::Succeeded().withMessage("clear costmap complete")
|
||||||
recovery_core::RecoveryStatus::kFailed;
|
: recovery_core::RecoveryResult::Failed().withMessage("clear costmap failed");
|
||||||
return ok ? recovery_core::RecoveryResult::Succeeded() :
|
|
||||||
recovery_core::RecoveryResult::Failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
recovery_core::RecoveryStatus status() const override
|
|
||||||
{
|
|
||||||
return status_;
|
|
||||||
}
|
|
||||||
|
|
||||||
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
|
||||||
{
|
|
||||||
return std::make_shared<ClearCostmapRecovery>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -216,13 +191,6 @@ private:
|
|||||||
costmap->getOriginY() + costmap->getSizeInMetersY());
|
costmap->getOriginY() + costmap->getSizeInMetersY());
|
||||||
}
|
}
|
||||||
|
|
||||||
tf3::BufferCore* tf_ = nullptr;
|
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path_ = nullptr;
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap_ = nullptr;
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap_ = nullptr;
|
|
||||||
bool initialized_ = false;
|
|
||||||
recovery_core::RecoveryStatus status_ = recovery_core::RecoveryStatus::kIdle;
|
|
||||||
|
|
||||||
bool force_updating_ = false;
|
bool force_updating_ = false;
|
||||||
double reset_distance_ = 3.0;
|
double reset_distance_ = 3.0;
|
||||||
bool invert_area_to_clear_ = false;
|
bool invert_area_to_clear_ = false;
|
||||||
|
|||||||
@@ -2,81 +2,59 @@
|
|||||||
*
|
*
|
||||||
* Software License Agreement (BSD License)
|
* Software License Agreement (BSD License)
|
||||||
*
|
*
|
||||||
* recovery_core — path output recovery plugin.
|
* recovery_core — path output recovery plugin (goal-driven, one-shot).
|
||||||
*
|
*
|
||||||
* Author: DuongTD
|
* Author: DuongTD
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
|
|
||||||
#include <recovery_core/recovery_behavior.h>
|
#include <recovery_core/recovery_behavior.h>
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/dll/alias.hpp>
|
#include <boost/dll/alias.hpp>
|
||||||
#include <robot/robot.h>
|
#include <robot/robot.h>
|
||||||
|
|
||||||
namespace recovery_plugins
|
namespace recovery_plugins
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class RegenPathRecovery
|
||||||
|
* @brief Họ A (path output), one-shot: trả lại robot_nav_msgs::Path từ global_path hiện tại.
|
||||||
|
*
|
||||||
|
* Hoàn tất ngay ở lần update() đầu. Guard chưa configure/global_path null hoặc rỗng -> Failed().
|
||||||
|
*/
|
||||||
class RegenPathRecovery final : public recovery_core::RecoveryBehavior
|
class RegenPathRecovery final : public recovery_core::RecoveryBehavior
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RegenPathRecovery() = default;
|
RegenPathRecovery() = default;
|
||||||
|
|
||||||
void initialize(std::string name, tf3::BufferCore* tf,
|
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap) override
|
|
||||||
{
|
|
||||||
if (initialized_)
|
|
||||||
{
|
|
||||||
robot::log_error("[recovery_core] RegenPathRecovery '%s' initialized twice; ignoring.",
|
|
||||||
name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name_ = std::move(name);
|
|
||||||
tf_ = tf;
|
|
||||||
global_path_ = global_path;
|
|
||||||
global_costmap_ = global_costmap;
|
|
||||||
local_costmap_ = local_costmap;
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
status_ = recovery_core::RecoveryStatus::kIdle;
|
|
||||||
}
|
|
||||||
|
|
||||||
recovery_core::RecoveryResult runBehavior() override
|
|
||||||
{
|
|
||||||
if (!initialized_ || global_path_ == nullptr || global_path_->empty())
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kFailed;
|
|
||||||
return recovery_core::RecoveryResult::Failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
robot_nav_msgs::Path path;
|
|
||||||
path.poses = *global_path_;
|
|
||||||
|
|
||||||
status_ = recovery_core::RecoveryStatus::kSucceeded;
|
|
||||||
return recovery_core::RecoveryResult::PathOut(path, status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
recovery_core::RecoveryStatus status() const override
|
|
||||||
{
|
|
||||||
return status_;
|
|
||||||
}
|
|
||||||
|
|
||||||
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
||||||
{
|
{
|
||||||
return std::make_shared<RegenPathRecovery>();
|
return std::make_shared<RegenPathRecovery>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
tf3::BufferCore* tf_ = nullptr;
|
recovery_core::RecoveryResult onStart(const recovery_core::RecoveryGoal& /*goal*/) override
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path_ = nullptr;
|
{
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap_ = nullptr;
|
// One-shot: công việc thực hiện ở onUpdate() lần đầu, giữ start() gọn.
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap_ = nullptr;
|
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||||
bool initialized_ = false;
|
recovery_core::RecoveryStatus::kRunning)
|
||||||
recovery_core::RecoveryStatus status_ = recovery_core::RecoveryStatus::kIdle;
|
.withMessage("regen path start");
|
||||||
|
}
|
||||||
|
|
||||||
|
recovery_core::RecoveryResult onUpdate(double /*dt*/) override
|
||||||
|
{
|
||||||
|
const auto* global_path = ctx().global_path;
|
||||||
|
if (global_path == nullptr || global_path->empty())
|
||||||
|
{
|
||||||
|
return recovery_core::RecoveryResult::Failed().withMessage("no global path to regenerate");
|
||||||
|
}
|
||||||
|
|
||||||
|
robot_nav_msgs::Path path;
|
||||||
|
path.poses = *global_path;
|
||||||
|
|
||||||
|
return recovery_core::RecoveryResult::PathOut(path, recovery_core::RecoveryStatus::kSucceeded)
|
||||||
|
.withProgress(1.0, 0.0)
|
||||||
|
.withMessage("regen path complete");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace recovery_plugins
|
} // namespace recovery_plugins
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
*
|
*
|
||||||
* Software License Agreement (BSD License)
|
* Software License Agreement (BSD License)
|
||||||
*
|
*
|
||||||
* recovery_core — per-cycle rotate recovery plugin.
|
* recovery_core — per-cycle rotate recovery plugin (goal-driven).
|
||||||
*
|
*
|
||||||
* Author: DuongTD
|
* Author: DuongTD
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
|
|
||||||
#include <recovery_core/recovery_behavior.h>
|
#include <recovery_core/recovery_behavior.h>
|
||||||
#include <recovery_core/recovery_config.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/dll/alias.hpp>
|
#include <boost/dll/alias.hpp>
|
||||||
#include <robot/robot.h>
|
#include <robot/robot.h>
|
||||||
@@ -22,90 +20,76 @@ namespace recovery_plugins
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
robot_geometry_msgs::Twist zeroTwist()
|
constexpr double kDefaultTargetAngle = 1.57079632679; // pi/2 rad.
|
||||||
{
|
constexpr double kDefaultAngularSpeed = 0.4; // rad/s.
|
||||||
return robot_geometry_msgs::Twist();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool validCycle(double dt)
|
|
||||||
{
|
|
||||||
return std::isfinite(dt) && dt > 0.0;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class RotateRecovery
|
||||||
|
* @brief Quay tại chỗ tới GÓC ĐÍCH do caller yêu cầu ở start(goal).
|
||||||
|
*
|
||||||
|
* goal.angle (rad, có dấu) là góc quay lượt này; 0 nghĩa là dùng default configured. Tốc độ
|
||||||
|
* góc mặc định đọc từ param, có thể override qua goal.params["angular_speed"]. Mỗi update() trả
|
||||||
|
* Twist.angular.z kèm progress/remaining tới khi đủ góc -> kSucceeded (zero command).
|
||||||
|
*/
|
||||||
class RotateRecovery final : public recovery_core::RecoveryBehavior
|
class RotateRecovery final : public recovery_core::RecoveryBehavior
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RotateRecovery() = default;
|
RotateRecovery() = default;
|
||||||
|
|
||||||
void initialize(std::string name, tf3::BufferCore* tf,
|
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap,
|
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap) override
|
|
||||||
{
|
{
|
||||||
if (initialized_)
|
return std::make_shared<RotateRecovery>();
|
||||||
{
|
}
|
||||||
robot::log_error("[recovery_core] RotateRecovery '%s' initialized twice; ignoring.",
|
|
||||||
name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name_ = std::move(name);
|
|
||||||
tf_ = tf;
|
|
||||||
global_path_ = global_path;
|
|
||||||
global_costmap_ = global_costmap;
|
|
||||||
local_costmap_ = local_costmap;
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onConfigure() override
|
||||||
|
{
|
||||||
robot::NodeHandle private_nh("~/" + name_);
|
robot::NodeHandle private_nh("~/" + name_);
|
||||||
config_ = recovery_core::RecoveryConfig::fromNodeHandle(private_nh);
|
private_nh.param("target_angle", default_target_angle_, kDefaultTargetAngle);
|
||||||
private_nh.param("target_angle", target_angle_, 1.57079632679);
|
private_nh.param("angular_speed", default_angular_speed_, kDefaultAngularSpeed);
|
||||||
private_nh.param("angular_speed", angular_speed_, 0.4);
|
|
||||||
|
|
||||||
if (!std::isfinite(target_angle_) || std::abs(target_angle_) <= 0.0)
|
if (!std::isfinite(default_target_angle_) || std::abs(default_target_angle_) <= 0.0)
|
||||||
{
|
{
|
||||||
robot::log_warning("[recovery_core] Invalid target_angle for '%s'; using pi/2.",
|
robot::log_warning("[recovery_core] Invalid target_angle for '%s'; using pi/2.",
|
||||||
name_.c_str());
|
name_.c_str());
|
||||||
target_angle_ = 1.57079632679;
|
default_target_angle_ = kDefaultTargetAngle;
|
||||||
}
|
}
|
||||||
if (!std::isfinite(angular_speed_) || angular_speed_ <= 0.0)
|
if (!std::isfinite(default_angular_speed_) || default_angular_speed_ <= 0.0)
|
||||||
{
|
{
|
||||||
robot::log_warning("[recovery_core] Invalid angular_speed for '%s'; using 0.4 rad/s.",
|
robot::log_warning("[recovery_core] Invalid angular_speed for '%s'; using 0.4 rad/s.",
|
||||||
name_.c_str());
|
name_.c_str());
|
||||||
angular_speed_ = 0.4;
|
default_angular_speed_ = kDefaultAngularSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
status_ = recovery_core::RecoveryStatus::kIdle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recovery_core::RecoveryResult runBehavior() override
|
recovery_core::RecoveryResult onStart(const recovery_core::RecoveryGoal& goal) override
|
||||||
{
|
{
|
||||||
status_ = initialized_ ? recovery_core::RecoveryStatus::kRunning :
|
// Góc đích: goal.angle nếu hợp lệ, ngược lại default configured.
|
||||||
recovery_core::RecoveryStatus::kFailed;
|
target_angle_ = (std::isfinite(goal.angle) && std::abs(goal.angle) > 0.0)
|
||||||
return initialized_ ? recovery_core::RecoveryResult::Running() :
|
? goal.angle
|
||||||
recovery_core::RecoveryResult::Failed();
|
: default_target_angle_;
|
||||||
|
|
||||||
|
angular_speed_ = std::abs(goal.param("angular_speed", default_angular_speed_));
|
||||||
|
if (!std::isfinite(angular_speed_) || angular_speed_ <= 0.0)
|
||||||
|
{
|
||||||
|
angular_speed_ = default_angular_speed_;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotated_angle_ = 0.0;
|
||||||
|
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||||
|
recovery_core::RecoveryStatus::kRunning)
|
||||||
|
.withProgress(0.0, std::abs(target_angle_))
|
||||||
|
.withMessage("rotate start");
|
||||||
}
|
}
|
||||||
|
|
||||||
recovery_core::RecoveryResult computeCommand(double dt) override
|
recovery_core::RecoveryResult onUpdate(double dt) override
|
||||||
{
|
{
|
||||||
if (!initialized_ || !validCycle(dt))
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kFailed;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed_ += dt;
|
|
||||||
if (config_.timeout > 0.0 && elapsed_ > config_.timeout)
|
|
||||||
{
|
|
||||||
status_ = recovery_core::RecoveryStatus::kFailed;
|
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
|
||||||
|
|
||||||
const double target = std::abs(target_angle_);
|
const double target = std::abs(target_angle_);
|
||||||
|
|
||||||
if (rotated_angle_ >= target)
|
if (rotated_angle_ >= target)
|
||||||
{
|
{
|
||||||
status_ = recovery_core::RecoveryStatus::kSucceeded;
|
return succeeded(target);
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
robot_geometry_msgs::Twist command;
|
robot_geometry_msgs::Twist command;
|
||||||
@@ -114,36 +98,29 @@ public:
|
|||||||
|
|
||||||
if (rotated_angle_ >= target)
|
if (rotated_angle_ >= target)
|
||||||
{
|
{
|
||||||
status_ = recovery_core::RecoveryStatus::kSucceeded;
|
return succeeded(target);
|
||||||
return recovery_core::RecoveryResult::Velocity(zeroTwist(), status_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
status_ = recovery_core::RecoveryStatus::kRunning;
|
return recovery_core::RecoveryResult::Velocity(command,
|
||||||
return recovery_core::RecoveryResult::Velocity(command, status_);
|
recovery_core::RecoveryStatus::kRunning)
|
||||||
}
|
.withProgress(rotated_angle_ / target, target - rotated_angle_)
|
||||||
|
.withMessage("rotating");
|
||||||
recovery_core::RecoveryStatus status() const override
|
|
||||||
{
|
|
||||||
return status_;
|
|
||||||
}
|
|
||||||
|
|
||||||
static recovery_core::RecoveryBehavior::RecoveryBehaviorPtr create()
|
|
||||||
{
|
|
||||||
return std::make_shared<RotateRecovery>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
tf3::BufferCore* tf_ = nullptr;
|
recovery_core::RecoveryResult succeeded(double target)
|
||||||
std::vector<robot_geometry_msgs::PoseStamped>* global_path_ = nullptr;
|
{
|
||||||
robot_costmap_2d::Costmap2DROBOT* global_costmap_ = nullptr;
|
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||||
robot_costmap_2d::Costmap2DROBOT* local_costmap_ = nullptr;
|
recovery_core::RecoveryStatus::kSucceeded)
|
||||||
recovery_core::RecoveryConfig config_;
|
.withProgress(1.0, 0.0)
|
||||||
bool initialized_ = false;
|
.withMessage("rotate complete");
|
||||||
recovery_core::RecoveryStatus status_ = recovery_core::RecoveryStatus::kIdle;
|
}
|
||||||
|
|
||||||
double target_angle_ = 1.57079632679;
|
double default_target_angle_ = kDefaultTargetAngle;
|
||||||
double angular_speed_ = 0.4;
|
double default_angular_speed_ = kDefaultAngularSpeed;
|
||||||
double elapsed_ = 0.0;
|
|
||||||
|
double target_angle_ = kDefaultTargetAngle;
|
||||||
|
double angular_speed_ = kDefaultAngularSpeed;
|
||||||
double rotated_angle_ = 0.0;
|
double rotated_angle_ = 0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,118 @@
|
|||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* recovery_core — default impl cho RecoveryBehavior.
|
* recovery_core — phần chung (template-method) của RecoveryBehavior.
|
||||||
*
|
*
|
||||||
* Chỉ chứa default computeCommand() (họ A/B không override). initialize()/runBehavior()/
|
* configure()/start()/update()/cancel() là NON-VIRTUAL: base lo guard vòng đời, timeout, đo
|
||||||
* status() là thuần ảo, do plugin cụ thể triển khai.
|
* elapsed, và xử lý cancel; plugin chỉ triển khai onConfigure()/onStart()/onUpdate().
|
||||||
*
|
*
|
||||||
* Author: DuongTD
|
* Author: DuongTD
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
#include <recovery_core/recovery_behavior.h>
|
#include <recovery_core/recovery_behavior.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <robot/robot.h>
|
||||||
|
|
||||||
namespace recovery_core
|
namespace recovery_core
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
RecoveryResult RecoveryBehavior::computeCommand(double /*dt*/)
|
|
||||||
{
|
{
|
||||||
return RecoveryResult::Failed();
|
robot_geometry_msgs::Twist zeroTwist()
|
||||||
|
{
|
||||||
|
return robot_geometry_msgs::Twist();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validCycle(double dt)
|
||||||
|
{
|
||||||
|
return std::isfinite(dt) && dt > 0.0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void RecoveryBehavior::configure(const std::string& name, const RecoveryContext& ctx)
|
||||||
|
{
|
||||||
|
if (configured_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
name_ = name;
|
||||||
|
ctx_ = ctx;
|
||||||
|
status_ = RecoveryStatus::kIdle;
|
||||||
|
|
||||||
|
// Param chung duy nhất còn lại: timeout (s, 0 = không giới hạn).
|
||||||
|
robot::NodeHandle private_nh("~/" + name_);
|
||||||
|
private_nh.param("timeout", timeout_, 0.0);
|
||||||
|
if (!std::isfinite(timeout_) || timeout_ < 0.0)
|
||||||
|
{
|
||||||
|
robot::log_warning("[recovery_core] Invalid timeout for '%s'; using 0 (no timeout).",
|
||||||
|
name_.c_str());
|
||||||
|
timeout_ = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfigure();
|
||||||
|
|
||||||
|
configured_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecoveryResult RecoveryBehavior::start(const RecoveryGoal& goal)
|
||||||
|
{
|
||||||
|
if (!configured_)
|
||||||
|
{
|
||||||
|
status_ = RecoveryStatus::kFailed;
|
||||||
|
return RecoveryResult::Failed().withMessage("start() before configure()");
|
||||||
|
}
|
||||||
|
|
||||||
|
goal_ = goal;
|
||||||
|
elapsed_ = 0.0;
|
||||||
|
cancel_requested_ = false;
|
||||||
|
started_ = true;
|
||||||
|
status_ = RecoveryStatus::kRunning;
|
||||||
|
|
||||||
|
return onStart(goal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecoveryResult RecoveryBehavior::update(double dt)
|
||||||
|
{
|
||||||
|
if (!configured_ || !started_)
|
||||||
|
{
|
||||||
|
status_ = RecoveryStatus::kFailed;
|
||||||
|
return RecoveryResult::Failed().withMessage("update() before start()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Đã kết thúc: giữ nguyên trạng thái, không tick thêm.
|
||||||
|
if (status_ != RecoveryStatus::kRunning)
|
||||||
|
{
|
||||||
|
return RecoveryResult::Velocity(zeroTwist(), status_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel_requested_)
|
||||||
|
{
|
||||||
|
status_ = RecoveryStatus::kCancelled;
|
||||||
|
return RecoveryResult::Velocity(zeroTwist(), status_)
|
||||||
|
.withMessage("cancelled by caller");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validCycle(dt))
|
||||||
|
{
|
||||||
|
status_ = RecoveryStatus::kFailed;
|
||||||
|
return RecoveryResult::Velocity(zeroTwist(), status_).withMessage("invalid dt");
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed_ += dt;
|
||||||
|
if (config_.timeout > 0.0 && elapsed_ > config_.timeout)
|
||||||
|
{
|
||||||
|
status_ = RecoveryStatus::kFailed;
|
||||||
|
return RecoveryResult::Velocity(zeroTwist(), status_).withMessage("timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
RecoveryResult result = onUpdate(dt);
|
||||||
|
result.elapsed = elapsed_;
|
||||||
|
status_ = result.status;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecoveryBehavior::cancel()
|
||||||
|
{
|
||||||
|
cancel_requested_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace recovery_core
|
} // namespace recovery_core
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
/*********************************************************************
|
|
||||||
* recovery_core — validate + đọc RecoveryConfig.
|
|
||||||
*
|
|
||||||
* Author: DuongTD
|
|
||||||
*********************************************************************/
|
|
||||||
#include <recovery_core/recovery_config.h>
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <robot/robot.h>
|
|
||||||
|
|
||||||
namespace recovery_core
|
|
||||||
{
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
constexpr double kDefaultControlFrequency = 20.0;
|
|
||||||
constexpr double kDefaultTimeout = 0.0;
|
|
||||||
|
|
||||||
void appendError(std::string* error, const std::string& message)
|
|
||||||
{
|
|
||||||
if (error == nullptr)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!error->empty())
|
|
||||||
{
|
|
||||||
*error += "; ";
|
|
||||||
}
|
|
||||||
*error += message;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool invalidControlFrequency(double value)
|
|
||||||
{
|
|
||||||
return !std::isfinite(value) || value <= 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool invalidTimeout(double value)
|
|
||||||
{
|
|
||||||
return !std::isfinite(value) || value < 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool RecoveryConfig::validate(std::string* error) const
|
|
||||||
{
|
|
||||||
if (error != nullptr)
|
|
||||||
{
|
|
||||||
error->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool valid = true;
|
|
||||||
if (invalidControlFrequency(control_frequency))
|
|
||||||
{
|
|
||||||
appendError(error, "control_frequency must be finite and > 0 Hz");
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalidTimeout(timeout))
|
|
||||||
{
|
|
||||||
appendError(error, "timeout must be finite and >= 0 s");
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
RecoveryConfig RecoveryConfig::fromNodeHandle(robot::NodeHandle& nh)
|
|
||||||
{
|
|
||||||
RecoveryConfig config;
|
|
||||||
nh.param("control_frequency", config.control_frequency, kDefaultControlFrequency);
|
|
||||||
nh.param("timeout", config.timeout, kDefaultTimeout);
|
|
||||||
|
|
||||||
std::string error;
|
|
||||||
if (config.validate(&error))
|
|
||||||
{
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
robot::log_warning("[recovery_core] Invalid common recovery config: %s. "
|
|
||||||
"Replacing invalid values with defaults.",
|
|
||||||
error.c_str());
|
|
||||||
|
|
||||||
if (invalidControlFrequency(config.control_frequency))
|
|
||||||
{
|
|
||||||
config.control_frequency = kDefaultControlFrequency;
|
|
||||||
}
|
|
||||||
if (invalidTimeout(config.timeout))
|
|
||||||
{
|
|
||||||
config.timeout = kDefaultTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.validate(nullptr))
|
|
||||||
{
|
|
||||||
return RecoveryConfig{};
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace recovery_core
|
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
#include <recovery_core/recovery_types.h>
|
#include <recovery_core/recovery_types.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace recovery_core
|
namespace recovery_core
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ RecoveryResult RecoveryResult::Succeeded()
|
|||||||
RecoveryResult result;
|
RecoveryResult result;
|
||||||
result.status = RecoveryStatus::kSucceeded;
|
result.status = RecoveryStatus::kSucceeded;
|
||||||
result.output_type = RecoveryOutputType::kNone;
|
result.output_type = RecoveryOutputType::kNone;
|
||||||
|
result.progress = 1.0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +35,14 @@ RecoveryResult RecoveryResult::Failed()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecoveryResult RecoveryResult::Cancelled()
|
||||||
|
{
|
||||||
|
RecoveryResult result;
|
||||||
|
result.status = RecoveryStatus::kCancelled;
|
||||||
|
result.output_type = RecoveryOutputType::kNone;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
RecoveryResult RecoveryResult::Velocity(const robot_geometry_msgs::Twist& command,
|
RecoveryResult RecoveryResult::Velocity(const robot_geometry_msgs::Twist& command,
|
||||||
RecoveryStatus status)
|
RecoveryStatus status)
|
||||||
{
|
{
|
||||||
@@ -39,6 +50,10 @@ RecoveryResult RecoveryResult::Velocity(const robot_geometry_msgs::Twist& comman
|
|||||||
result.status = status;
|
result.status = status;
|
||||||
result.output_type = RecoveryOutputType::kVelocity;
|
result.output_type = RecoveryOutputType::kVelocity;
|
||||||
result.command = command;
|
result.command = command;
|
||||||
|
if (status == RecoveryStatus::kSucceeded)
|
||||||
|
{
|
||||||
|
result.progress = 1.0;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +64,24 @@ RecoveryResult RecoveryResult::PathOut(const robot_nav_msgs::Path& path,
|
|||||||
result.status = status;
|
result.status = status;
|
||||||
result.output_type = RecoveryOutputType::kPath;
|
result.output_type = RecoveryOutputType::kPath;
|
||||||
result.path = path;
|
result.path = path;
|
||||||
|
if (status == RecoveryStatus::kSucceeded)
|
||||||
|
{
|
||||||
|
result.progress = 1.0;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecoveryResult& RecoveryResult::withProgress(double progress_value, double remaining_value)
|
||||||
|
{
|
||||||
|
progress = progress_value < 0.0 ? 0.0 : (progress_value > 1.0 ? 1.0 : progress_value);
|
||||||
|
remaining = remaining_value < 0.0 ? 0.0 : remaining_value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecoveryResult& RecoveryResult::withMessage(std::string text)
|
||||||
|
{
|
||||||
|
message = std::move(text);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace recovery_core
|
} // namespace recovery_core
|
||||||
|
|||||||
@@ -152,7 +152,9 @@ void testLoadPlugins()
|
|||||||
|
|
||||||
// 4. Chuyển quyền giữ library vào storage sống lâu dài.
|
// 4. Chuyển quyền giữ library vào storage sống lâu dài.
|
||||||
libraries_.push_back(std::move(library));
|
libraries_.push_back(std::move(library));
|
||||||
behavior->initialize(plugin.name, nullptr, &global_path_, nullptr, nullptr);
|
recovery_core::RecoveryContext ctx;
|
||||||
|
ctx.global_path = &global_path_;
|
||||||
|
behavior->configure(plugin.name, ctx);
|
||||||
creators_.push_back(behavior);
|
creators_.push_back(behavior);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
@@ -170,18 +172,28 @@ for(const auto& behavior : creators_)
|
|||||||
{
|
{
|
||||||
if(behavior->getNameRecoveryBehavior() == "rotation_rc")
|
if(behavior->getNameRecoveryBehavior() == "rotation_rc")
|
||||||
{
|
{
|
||||||
const recovery_core::RecoveryResult first = behavior->computeCommand(0.1);
|
// Caller đặt góc quay RUNTIME = pi/2 (90 độ) cho lượt này.
|
||||||
|
recovery_core::RecoveryGoal goal;
|
||||||
|
goal.angle = 1.57079632679;
|
||||||
|
|
||||||
|
const recovery_core::RecoveryResult started = behavior->start(goal);
|
||||||
|
expect(started.status == recovery_core::RecoveryStatus::kRunning,
|
||||||
|
"rotate must be running right after start");
|
||||||
|
|
||||||
|
const recovery_core::RecoveryResult first = behavior->update(0.1);
|
||||||
expect(first.status == recovery_core::RecoveryStatus::kRunning,
|
expect(first.status == recovery_core::RecoveryStatus::kRunning,
|
||||||
"rotate first cycle must be running");
|
"rotate first cycle must be running");
|
||||||
expect(first.output_type == recovery_core::RecoveryOutputType::kVelocity,
|
expect(first.output_type == recovery_core::RecoveryOutputType::kVelocity,
|
||||||
"rotate must return velocity output");
|
"rotate must return velocity output");
|
||||||
expect(first.command.angular.z > 0.0, "rotate must command positive angular.z by default");
|
expect(first.command.angular.z > 0.0, "rotate must command positive angular.z for +angle");
|
||||||
|
expect(first.progress >= 0.0 && first.progress < 1.0,
|
||||||
|
"rotate progress must advance within [0,1)");
|
||||||
|
expect(first.remaining > 0.0, "rotate must report remaining angle while running");
|
||||||
|
|
||||||
recovery_core::RecoveryResult last = first;
|
recovery_core::RecoveryResult last = first;
|
||||||
for (int i = 0; i < 100 && last.status == recovery_core::RecoveryStatus::kRunning; ++i)
|
for (int i = 0; i < 100 && last.status == recovery_core::RecoveryStatus::kRunning; ++i)
|
||||||
{
|
{
|
||||||
last = behavior->computeCommand(0.1);
|
last = behavior->update(0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(last.status == recovery_core::RecoveryStatus::kSucceeded,
|
expect(last.status == recovery_core::RecoveryStatus::kSucceeded,
|
||||||
@@ -190,6 +202,39 @@ for(const auto& behavior : creators_)
|
|||||||
"rotate final result must still be a velocity output");
|
"rotate final result must still be a velocity output");
|
||||||
expect(std::abs(last.command.angular.z) < 1e-9,
|
expect(std::abs(last.command.angular.z) < 1e-9,
|
||||||
"rotate must return a zero angular command when complete");
|
"rotate must return a zero angular command when complete");
|
||||||
|
expect(std::abs(last.progress - 1.0) < 1e-9, "rotate must report full progress on success");
|
||||||
|
expect(last.remaining < 1e-9, "rotate must report zero remaining on success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void testBackupPlugin()
|
||||||
|
{
|
||||||
|
for(const auto& behavior : creators_)
|
||||||
|
{
|
||||||
|
if(behavior->getNameRecoveryBehavior() == "backward_rc")
|
||||||
|
{
|
||||||
|
// Caller đặt khoảng lùi RUNTIME = 0.3 m cho lượt này.
|
||||||
|
recovery_core::RecoveryGoal goal;
|
||||||
|
goal.distance = 0.3;
|
||||||
|
|
||||||
|
behavior->start(goal);
|
||||||
|
const recovery_core::RecoveryResult first = behavior->update(0.1);
|
||||||
|
expect(first.status == recovery_core::RecoveryStatus::kRunning,
|
||||||
|
"backup first cycle must be running");
|
||||||
|
expect(first.command.linear.x < 0.0, "backup must command negative linear.x");
|
||||||
|
|
||||||
|
recovery_core::RecoveryResult last = first;
|
||||||
|
for (int i = 0; i < 1000 && last.status == recovery_core::RecoveryStatus::kRunning; ++i)
|
||||||
|
{
|
||||||
|
last = behavior->update(0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last.status == recovery_core::RecoveryStatus::kSucceeded,
|
||||||
|
"backup must finish within bounded cycles");
|
||||||
|
expect(std::abs(last.command.linear.x) < 1e-9,
|
||||||
|
"backup must return a zero command when complete");
|
||||||
|
expect(std::abs(last.progress - 1.0) < 1e-9, "backup must report full progress on success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +253,8 @@ global_path_.push_back(pose2);
|
|||||||
{
|
{
|
||||||
if(behavior->getNameRecoveryBehavior() == "regen_path_rc")
|
if(behavior->getNameRecoveryBehavior() == "regen_path_rc")
|
||||||
{
|
{
|
||||||
const recovery_core::RecoveryResult result = behavior->runBehavior();
|
behavior->start(recovery_core::RecoveryGoal());
|
||||||
|
const recovery_core::RecoveryResult result = behavior->update(0.1);
|
||||||
|
|
||||||
expect(result.status == recovery_core::RecoveryStatus::kSucceeded,
|
expect(result.status == recovery_core::RecoveryStatus::kSucceeded,
|
||||||
"regen path must succeed with a non-empty global path");
|
"regen path must succeed with a non-empty global path");
|
||||||
@@ -234,6 +280,7 @@ int main()
|
|||||||
|
|
||||||
testRegenPathPlugin();
|
testRegenPathPlugin();
|
||||||
testRotatePlugin();
|
testRotatePlugin();
|
||||||
|
testBackupPlugin();
|
||||||
|
|
||||||
std::cout << "[PASS] recovery_core plugin loader contract" << std::endl;
|
std::cout << "[PASS] recovery_core plugin loader contract" << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user