first commit
This commit is contained in:
122
docs/ARCHITECTURE.md
Normal file
122
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Kiến trúc
|
||||
|
||||
## Ba lớp
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ Biên (adapter) │
|
||||
│ │
|
||||
│ NavigationServer ──implements──▶ move_base_core::BaseNavigation │
|
||||
│ · quy 6 entry point di chuyển về 1 NavigationRequest │
|
||||
│ · nhận dữ liệu sensor từ host │
|
||||
│ · kết xuất trạng thái ra kiểu mà host mong đợi │
|
||||
└──────────────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────────────────▼───────────────────────────────────┐
|
||||
│ Điều phối │
|
||||
│ │
|
||||
│ ControlLoop — một control cycle: gom dữ liệu, chạy state machine, │
|
||||
│ thi hành output, phát lệnh vận tốc │
|
||||
└──────────────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────────────────▼───────────────────────────────────┐
|
||||
│ Lõi quyết định (logic thuần, không I/O) │
|
||||
│ │
|
||||
│ StateMachine — bảng chuyển state │
|
||||
│ VelocityArbiter — ai được phát lệnh, và lệnh đó có an toàn không │
|
||||
│ NavigationRequest — kiểu dữ liệu duy nhất đi vào lõi │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼ (qua port, không qua kiểu cụ thể)
|
||||
ClockPort PosePort PlannerPort ControllerPort
|
||||
RecoveryPort MissionPort ActionPort
|
||||
```
|
||||
|
||||
Lõi không đụng costmap, không đụng TF, không log, không cấp phát trong vòng lặp. Vì vậy nó kiểm được
|
||||
bằng bảng thay vì phải dựng runtime hay chạy robot.
|
||||
|
||||
## Chiều phụ thuộc
|
||||
|
||||
```
|
||||
move_base2 ──▶ mission framework
|
||||
──▶ recovery framework
|
||||
```
|
||||
|
||||
**Một chiều, không có đường ngược.** Hai framework kia không biết `move_base2` tồn tại và không biết
|
||||
lẫn nhau. Hệ quả có thật, không phải hình thức:
|
||||
|
||||
- Ba gói test được độc lập.
|
||||
- Không có vòng phụ thuộc trong build.
|
||||
- `move_base2` không bị khoá cứng vào một hiện thực mission hay recovery cụ thể — đổi framework chỉ
|
||||
cần viết lại lớp nối, không phải sửa lõi.
|
||||
|
||||
Ở Phase 1, chiều này còn được giữ ở mức mạnh hơn: **không file nào trong `move_base2` include hai
|
||||
framework kia**. Lớp nối (`MissionAdapterBridge`, `RecoveryRunner`) được thêm ở bước sau, và chúng
|
||||
mới là chỗ duy nhất được phép include.
|
||||
|
||||
Kiểm bằng:
|
||||
|
||||
```bash
|
||||
grep -rn "mission_adapters\|recovery_core" src/AMR_T800/Test/move_base2/include \
|
||||
src/AMR_T800/Test/move_base2/src
|
||||
```
|
||||
|
||||
## Vì sao là port chứ không phải gọi thẳng
|
||||
|
||||
Bảy port đều nhỏ và đều tồn tại vì một lý do vận hành cụ thể:
|
||||
|
||||
| Port | Lý do tồn tại |
|
||||
|---|---|
|
||||
| `ClockPort` | Không có nguồn thời gian tiêm được thì không cách nào kiểm hành vi khi control loop chạy chậm hơn chu kỳ cấu hình — đúng lớp lỗi mà dead-reckoning theo chu kỳ danh nghĩa mắc phải |
|
||||
| `PosePort` | Contract "trả false = dừng an toàn" phải kiểm được mà không cần dựng TF thật |
|
||||
| `PlannerPort` | Gộp hai overload `makePlan` của interface gốc thành một; "có Order hay không" chỉ là một nhánh nhỏ bên trong |
|
||||
| `ControllerPort` | Giữ nguyên bộ hàm và **thứ tự gọi** của interface được bọc (hỏi đã tới đích trước, chỉ khi chưa mới tính lệnh) |
|
||||
| `RecoveryPort` | Chuẩn hoá kết quả tick về ngôn ngữ của `move_base2`, để lõi không phải include recovery framework |
|
||||
| `MissionPort` | Luồng một chiều có callback, thay cho việc phía mission phải poll trạng thái navigation |
|
||||
| `ActionPort` | (D8) Runtime điều phối trọn một mission — nav xong chạy nốt action rồi mới báo kết quả. Tick-based cùng nhịp control loop như recovery, và tick của nó **không có** vận tốc: action cần chuyển động phải là motion profile, không phải action |
|
||||
|
||||
## Quyết định thiết kế đáng ghi lại
|
||||
|
||||
**Sáu entry point gộp thành một.** `moveTo` ×2, `dockTo` ×2, `moveStraightTo`, `rotateTo` của
|
||||
contract host chỉ khác nhau ở kiểu chuyển động và sai số mặc định. Bảng `ProfileBinding` mô tả đúng
|
||||
phần khác nhau đó; sáu hàm còn lại chỉ dựng struct rồi gọi một đường vào duy nhất.
|
||||
|
||||
**Một đường vào duy nhất.** `ControlLoop::submit()` là chỗ duy nhất một goal lọt được vào lõi, và
|
||||
`IDLE → PLANNING` là transition duy nhất bắt đầu một chặng. Nhờ đó việc chống hai nguồn goal tranh
|
||||
nhau là tính chất cấu trúc, không phải thứ phải nhớ khoá bằng tay.
|
||||
|
||||
**Từ chối tại cửa.** Goal có quaternion hỏng, toạ độ không hữu hạn, hoặc profile không nạp được
|
||||
planner đều bị từ chối ngay trong `submit()` kèm lý do — không để state machine bắt đầu một chặng rồi
|
||||
mới phát hiện không có planner nào chạy được.
|
||||
|
||||
**Phản hồi trễ một cycle.** State machine chạy với phản hồi thu được từ cycle trước, rồi mới gọi
|
||||
cổng. Điều này là tường minh và có chủ đích: nó cắt vòng "gọi để biết nên gọi gì", nhờ vậy output của
|
||||
state machine là một danh sách hành động thuần tuý.
|
||||
|
||||
**Lệnh 0 là tức thì.** Nguồn `kNone` phát đúng 0, không giảm tốc dần. Lệnh vận tốc bị chốt lại ở tầng
|
||||
dưới, nên nếu control loop dừng giữa lúc đang giảm tốc thì lệnh khác 0 cuối cùng vẫn còn hiệu lực.
|
||||
Việc giảm tốc theo động học thuộc về bộ điều khiển bánh xe, nơi biết tải và ma sát thật.
|
||||
|
||||
## Trạng thái hiện tại và phần còn thiếu
|
||||
|
||||
Đã có và chạy được:
|
||||
|
||||
- Toàn bộ lõi quyết định, có test phủ đủ bảng chuyển state.
|
||||
- `ControlLoop` chạy end-to-end với thành phần giả.
|
||||
- `NavigationServer` hiện thực đủ contract host.
|
||||
- `SensorGateway`: dữ liệu cảm biến đi từ contract host tới đúng layer của hai costmap, có phát lại
|
||||
static map nhận trước khi costmap tồn tại. Đây là file duy nhất trong gói include `robot_costmap_2d`.
|
||||
- `getTwist()` trả **lệnh** vận tốc từ `VelocityArbiter`, đóng dấu theo đồng hồ của control loop.
|
||||
- Plugin `libmove_base2.so` export alias `MoveBase2`.
|
||||
|
||||
Chưa có, thuộc bước nối dây runtime:
|
||||
|
||||
- Hiện thực thật của `PlannerPort` / `ControllerPort` / `PosePort`
|
||||
(bọc costmap, boost::dll, TF).
|
||||
- Dựng hai `Costmap2DROBOT` thật. Hiện `NavigationServer::attachCostmaps()` nhận `LayeredCostmap*`
|
||||
từ bên ngoài bơm vào — cố ý, để đường cảm biến kiểm được mà không cần TF và cây config thật.
|
||||
- Thread planner riêng và bộ đệm plan ba lớp. Hiện `ControlLoop` lập plan đồng bộ ngay trong cycle;
|
||||
tách như vậy để phần quyết định kiểm được mà không cần thread.
|
||||
- Lớp nối tới mission framework.
|
||||
- `setTwistLinear` / `setTwistAngular` (hiện trả `false` để host biết lệnh không có hiệu lực, thay vì
|
||||
âm thầm bỏ qua).
|
||||
Reference in New Issue
Block a user