Delivery_robot / ARCHITECTURE.md
minhnghiem32131024429
Deploy delivery robot app
5e8b911
|
Raw
History Blame Contribute Delete
3.31 kB
# Architecture Notes
## Long-Poll Pattern (ESP32 ↔ Server)
The ESP32 polls `GET /api/elevator/command` when IDLE. Instead of returning immediately with "no command", the server *holds* the HTTP connection open for up to 10 seconds using `asyncio.wait_for`:
```
ESP32 Server
|---GET /api/elevator/command-->|
| | await event.wait() (up to 10s)
| |
| (dashboard sends POST) |
| | event.set() → command available
|<------{"cmd":"CALL_3"}---------| returns immediately
| |
|---GET /api/elevator/command-->| (next cycle after move completes)
| | no command → timeout after 10s
|<------{"cmd":"NONE"}-----------| returns after 10s
```
**Why this matters:** The naive approach polls every 500 ms — 120 requests/minute. Long-poll reduces this to ~6 requests/minute (one per timeout cycle), and delivers commands with near-zero latency when they do arrive.
**Implementation detail:** A single `asyncio.Event` guards a single command slot. After the ESP32 reads the command, both the slot and the Event are cleared so the next poll cycle starts clean. The ESP32 sets `http.setTimeout(15000)` to ensure the socket stays open past the 10-second server hold.
## SSE Pattern (Server → Dashboard)
The dashboard opens one persistent HTTP connection to `GET /api/events`. The server keeps this connection alive and pushes JSON-encoded events whenever elevator or robot state changes:
```
Browser Server
|---GET /api/events------------>|
| | registers asyncio.Queue
|<--data:{"type":"elevator"...}-| on ESP32 POST /status
|<--data:{"type":"robot"...}----| on Jetson POST /status
| |
| (client disconnects) |
| | queue removed from set (finally block)
```
**Why this matters:** Polling every 500 ms from the browser creates 120 requests/minute regardless of whether anything changed. SSE pushes updates only when state changes, with zero polling overhead and no perceptible latency.
**Implementation detail:** Each connected browser gets its own `asyncio.Queue`. The `broadcast()` function in `state.py` pushes a copy of every state change to every registered queue. When the browser disconnects (tab closed, network drop), the `finally` block in the async generator removes the queue from the set, preventing memory leaks.
## Component Interaction Summary
```
[ESP32] [Jetson / ROS2] [Browser]
| | |
|--POST /api/elevator/status--> SSE <--|
|<--GET /api/elevator/command-- (long-poll) |
| |--POST /api/robot/status-->
| |<--GET /api/robot/goal-- (long-poll)
| POST /api/robot/goal <--|
|<-- (elevator_cmd_event set by robot/goal handler) |
```
`POST /api/robot/goal` is the single mission-dispatch entry point. It sets both the robot nav goal and the elevator call command in one atomic operation, ensuring the elevator is already summoned when the robot arrives.