Architecture - LLUAV SDK Docs

# LLUAV SDK Architecture

This document describes the internal architecture of the LLUAV SDK across Python, C++, and JavaScript implementations.

## Design Goals

1. Strict Request/Response Serialization: The drone firmware processes one command at a time and returns exactly one response. The SDK enforces this via a local FIFO command queue.
2. Resilience: Network interruptions are common in UAV operations. The SDK automatically reconnects with exponential backoff and re-authenticates.
3. Low Latency for Urgent Commands: While commands are queued, the queue is lightweight and the worker thread is always ready to dispatch.
4. Resource Efficiency: Heartbeats and periodic tasks share the same queue as user commands, avoiding duplicate WebSocket frames.

## High-Level Component Diagram

    ┌─────────────────────────────────────────────────────────────┐
    │                        User Application                      │
    │  (calls takeoff(), get_state(), add_periodic_task(), etc.) │
    └─────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
    ┌─────────────────────────────────────────────────────────────┐
    │                      High-Level API Layer                    │
    │    (typed wrappers: takeoff → send_cmd("takeoff", ""))     │
    └─────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
    ┌─────────────────────────────────────────────────────────────┐
    │                     Command Queue Manager                    │
    │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
    │  │  QueuedCmd 1 │→│  QueuedCmd 2 │→│  QueuedCmd N │      │
    │  │  + promise   │  │  + promise   │  │  + promise   │      │
    │  │  + deadline  │  │  + deadline  │  │  + deadline  │      │
    │  └──────────────┘  └──────────────┘  └──────────────┘      │
    └─────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
    ┌─────────────────────────────────────────────────────────────┐
    │                         Worker Thread/Task                   │
    │  while running:                                              │
    │    1. peek queue head                                        │
    │    2. check deadline (timeout)                               │
    │    3. ensure connection (reconnect if needed)                │
    │    4. ws.send(cmd)                                           │
    │    5. ws.recv()  ←── blocks until response                   │
    │    6. parse JSON, validate ret                               │
    │    7. fulfill promise / resolve future                       │
    └─────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
    ┌─────────────────────────────────────────────────────────────┐
    │                      WebSocket Transport                     │
    │              (ws://<host>:53001, text frames)                │
    └─────────────────────────────────────────────────────────────┘

## Core Modules

### 1. Connection Manager

Responsibilities: - Open/close the WebSocket. - Track connection state (`connected` flag). - On disconnect: trigger auto-reconnect if enabled. - On reconnect: re-send `login` if password is configured.

Reconnection Strategy:

    delay = reconnect_interval (default 1s)
    while not connected and not shutdown:
        try connect
        if password: login
        if success: return true
        sleep(delay)
        delay = min(delay * 2, max_reconnect_interval)  # exponential backoff

All queued commands are preserved during reconnection. If a command is in-flight when the connection drops, it is returned to the head of the queue for retry (up to `max_retries`).

### 2. Command Queue

Why a queue? - The firmware is strictly request/response. Sending two commands back-to-back without waiting for the first response causes the responses to be indistinguishable. - A local queue decouples the user thread from network latency.

Queue Behavior: - FIFO: Commands are processed in insertion order. - Deadline: Each command carries a `deadline = now + timeout`. Expired commands are rejected immediately without sending. - Retry: On send failure, timeout, or connection drop mid-flight, the command is retried (max 3 by default). - Fire-and-forget: Heartbeats and periodic tasks are enqueued like normal commands but their results are discarded. This guarantees they never interrupt an in-flight user command from the wire perspective.

### 3. Heartbeat Keeper

Purpose: Prevent NAT/firewall session timeouts and detect half-open connections early.

Behavior: - Runs on a timer (default 5s). - Only enqueues a heartbeat command when the user queue is empty. - Uses a lightweight command such as `state` or `help`. - If the heartbeat itself fails, the connection is marked dead and reconnection begins.

### 4. Periodic Task Scheduler

Purpose: Allow users to register recurring operations (e.g., telemetry polling) without managing their own timers.

Behavior: - Maintains a map of `name → (interval, cmd, args, last_run)`. - Scans tasks every 200ms. - When a task is due, it calls `_enqueue(cmd, args)` just like a user command. - Because it shares the same queue, periodic tasks are naturally rate-limited by queue depth and respect the one-in-flight rule.

### 5. Timeout & Retry Manager

Per-command timeout: - Default: 10 seconds (configurable per client and per command). - Timer starts when the command reaches the head of the queue, not at enqueue time.

Retry logic:

    if send fails or recv times out:
        retry_count += 1
        if retry_count <= max_retries:
            keep at queue head and retry after 500ms
        else:
            reject with "Max retries exceeded"

## Language-Specific Implementation Notes

### Python (Synchronous)

- Concurrency: `threading.Thread` for Worker, Heartbeat, and Periodic loops.
- Synchronization: `threading.Lock` + `threading.Condition` for the queue.
- Blocking API: `concurrent.futures.Future` is returned by `_enqueue()`. `future.result()` blocks the caller until the response arrives.
- Non-blocking mode: `send_cmd(..., block=False)` returns the `Future` immediately, allowing the caller to compose async-style logic in a sync runtime.

### Python (Async)

- Concurrency: `asyncio.Task` for Worker, Heartbeat, and Periodic loops.
- Queue: `asyncio.Queue` with `async/await` semantics.
- API: All methods are `async def` and return `asyncio.Future`. Users `await` directly.

### C++

- WebSocket Library: `websocketpp` with Asio transport (`asio_no_tls_client`).
- Concurrency:
- One Asio thread runs `client_.run()` (event loop).
- One Worker thread dequeues and sends.
- One Heartbeat thread, one Periodic thread.
- Synchronization:
- `std::mutex` + `std::condition_variable` for the queue.
- `std::promise<json>` / `std::future<json>` for command completion.
- Reconnection: Uses `client_.start_perpetual()` to keep the Asio thread alive across reconnects. A dedicated `login_promise` handles reconnection authentication without interfering with the normal command queue.
- Fire-and-forget: Obtains a `std::future` for heartbeat commands and stores them in a cleanup list to prevent `broken_promise` exceptions.

### JavaScript

- WebSocket: Native `WebSocket` (browser) or `ws` library (Node.js).
- Concurrency: Single-threaded event loop. No extra threads needed.
- Queue: Plain JavaScript array (`this._queue`).
- State Machine:
- `_currentRequest` holds the command currently awaiting a response.
- `_processQueue()` is called after every connection open, message receipt, and timeout.
- Reconnection: `setTimeout` schedules reconnect attempts with exponential backoff. The `onclose` handler automatically returns the in-flight request to the queue head.
- Periodic Tasks: `setInterval` every 200ms checks task deadlines and calls `_enqueue()`.

## Thread/Task Safety Matrix

Operation | Python Sync | Python Async | C++ | JS
--- | --- | --- | --- | ---
Enqueue command | Thread-safe | Event-loop safe | Thread-safe | Event-loop safe
Queue pop | Worker thread only | Worker task only | Worker thread only | Event loop only
WS send | Worker thread only | Worker task only | Worker thread → Asio thread | Event loop only
WS recv | Worker thread only | Worker task only | Message handler → CV notify | `onmessage` callback
Reconnect | Worker thread | Worker task | Worker thread | `setTimeout` callback
Periodic enqueue | Periodic thread | Periodic task | Periodic thread | `setInterval` callback

## Data Flow Example: `takeoff()`

1. User calls `client.takeoff()` → internally calls `send_cmd("takeoff")`.
2. `_enqueue("takeoff")` creates a `QueuedCommand` with `deadline = now + 10s` and appends it to the queue. Returns a `Future`.
3. Worker wakes up (or is already running), sees the new command at queue head.
4. Worker checks connection → connected, proceed.
5. Worker sends `"takeoff"` over WebSocket.
6. Worker blocks on `recv()` (or awaits `ws.recv()` / waits on CV).
7. Server receives, processes, and sends back JSON response.
8. Worker parses JSON, sees `ret == 0`, sets `Future` result.
9. `Future.get()` / `await` returns the JSON dict to the user.
10. Worker loops back to process the next queue item.

## Data Flow Example: Connection Drop During Flight

1. Worker sends command `C` and waits for response.
2. TCP/WebSocket connection drops. `recv()` throws / `onclose` fires.
3. Worker marks connection dead.
4. Command `C` stays at queue head, `retry_count` incremented.
5. Worker enters reconnect loop (exponential backoff).
6. Reconnection succeeds; auto-login completes.
7. Worker re-sends command `C`.
8. Response arrives; Future is fulfilled.
9. Queue continues normally.