6.2 KiB
Kiến trúc recovery_core
Vị trí
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.
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.
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:
startsauconfigure,updatesaustart,configurechỉ một lần; - cổng bắt buộc theo
outputKind()— họ velocity không cóPoseProvider/CollisionCheckerthìconfigure()trảfalsengay, 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
dtvàelapsedbằng đồng hồ thật, éptimeout; - 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, nh) # 1 lần. Kiểm cổng bắt buộc, đọc timeout, gọi onConfigure(nh).
│
start(goal, now) # mỗi lượt. Validate goal, mốc thời gian, gọi onStart(goal) -> bool.
│
├── 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}
│
cancel() # tick kế tiếp gọi onCancel() -> stop output + kCancelled.
Quyết định thiết kế
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.