Spaces:
Sleeping
Sleeping
| # 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. | |