# 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.