Skip to content

Application Architecture

The HausBots tablet app is a React Native presentation layer over a native Ethernet discovery and HB2 command session. Responsibilities are deliberately split so a UI refactor cannot grow the robot command surface:

Layer Responsibility
Go robot-agent Read-only /api/v1/identity and /api/v1/health; merges local ROS health with a bounded loopback probe.
Go simulator Laptop-only HTTP + rosbridge test double with deterministic deadlines and command logs.
Kotlin discovery Chooses TRANSPORT_ETHERNET, scans bounded IPv4 CIDRs, validates identity/health, and probes rosbridge on that Network.
Kotlin HB2 session Owns rosbridge envelopes, lease/heartbeat, sequences, readiness, input liveness, exact command allowlist, and neutral/release stop.
TypeScript Renders Launchpad/Control, stores user intent, binds joystick/HID input, and preserves camera/capture UI.

Launchpad scans HTTP first, then health, then the identity’s rosbridge port. Each request uses the selected Android Ethernet Network, no proxy, bounded response bytes, shared concurrency, and one total scan deadline. A candidate launches only when this scan reached the robot over rosbridge and the robot reports ready health with a ready controller; a valid identity with degraded health or an unavailable rosbridge stays visible as degraded for diagnostics. The robot’s observedAt is reported as a diagnostic and is never compared with the tablet clock: an isolated switch gives the two devices no shared time source, so drift must not hide a healthy robot or block launch. The tablet’s own address, network address, and broadcast address are excluded; CIDR overrides over 1,024 hosts are rejected rather than truncated.

sequenceDiagram
    participant UI as Launchpad
    participant N as Kotlin discovery
    participant A as Robot-agent :8080
    participant R as Rosbridge :9090
    UI->>N: scan(saved/detected CIDR)
    N->>A: GET /api/v1/identity (Ethernet Network)
    N->>A: GET /api/v1/health (Ethernet Network)
    N->>R: WebSocket readiness probe (Ethernet Network)
    N-->>UI: verified/degraded candidate + health + latency
    UI->>N: Launch verified candidate

Control opens without acquiring a lease. Start waits for fresh TabletStatus, controller readiness, and HTTP health, then calls /hb2/app/acquire_control with the persisted device client_id. An accepted lease enters awaiting-fan; only an explicitly accepted fan command enables drive. Drive uses a native latest-value scheduler at the persisted 5–50 Hz rate (default 20 Hz). The local source lease is touched at 10 Hz and expires at 250 ms; it is not the ROS heartbeat. Heartbeat cadence is derived from the returned lease timeout and runs only while foreground input is valid.

Every command is one of the five publish topics in the public contract. A single writer allocates command sequences; heartbeat has its own counter. Command results are correlated by sequence and command and rejected reasons are shown to the operator. Attachments are limited to MIRA, PUNDIT, and LIFTER with the documented action pairs. Stop freezes new work, sends neutral drive, then performs bounded release; fan output is intentionally retained.

The camera URL is derived from the robot’s own address (port 8554, fixed /ptz path), not carried by identity. The robot-side setup for both feeds is in Camera Streams with MediaMTX. RTSP playback remains a native view and camera-only capture continues to use the existing native coordinator/storage pipeline. The RTSP stack must use the selected Ethernet Network; if a dependency cannot accept a socket factory, that limitation is an explicit release check with Wi-Fi enabled. Capture finalization remains independent of ROS stop semantics and follows its existing foreground, disconnect, route, and surface lifecycles.

The native Kotlin HB2 session is the sole production ROS transport. TypeScript owns presentation and user intent; no JavaScript ROS client is shipped.