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

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: 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 dtelapsed 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, 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::RecoveryBehaviorrobot_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.