Skip to content

Architecture

The organizing principle: three embeddable libraries

Section titled “The organizing principle: three embeddable libraries”

Fjarr is not an application; it is three libraries a robot company embeds into systems they already have, each paired with a thin reference binary and a separated demo application.

flowchart LR
subgraph Robot["Customer robot (Ubuntu)"]
RSW["Customer robot software"] -- embeds --> LIB["libfjarr (C++)"]
AGD["fjarr-agent daemon"] -. alternative .-> LIB
end
subgraph Backend["Customer backend"]
CB["Customer services (any stack)"] -- "token / REST / webhooks<br/>(ADR-0015 contract)" --> SRV["fjarr-server sidecar (Rust)"]
CLOUD["Fjarr Cloud (managed twin)"] -. same contract .-> CB
end
subgraph Dash["Customer dashboard"]
DUI["Customer React app"] -- embeds --> RXL["@fjarr/core + @fjarr/react"]
end
LIB <-- "WSS signaling" --> SRV
RXL <-- "WSS signaling" --> SRV
LIB <== "WebRTC media + DataChannels<br/>(P2P, or TURN relay)" ==> RXL

Rules that keep this honest:

  1. Demos-as-integration-tests. demos/demo-robot, demos/demo-backend, demos/demo-dashboard play the customer’s role and may consume only public APIs and the ADR-0015 contract. If a demo needs a private hook, the public API is wrong.
  2. The customer’s backend stays out of the hot path. It mints session grants and receives webhooks; signaling and media never route through it.
  3. The customer’s identity is canonical. Fjarr authenticates their robot IDs and user identities; it never invents parallel ones.

On the robot, Fjarr separates two concerns into separately supervisable units (the relox seam — prior art):

  • Control plane — signaling connection, session lifecycle, capability registry, store-and-forward event queue, OTA orchestration (later). Small, restartable, holds no media state.
  • Media plane — GStreamer producer pipeline + per-session consumer pipelines. The part most likely to hit driver/hardware trouble; it can be torn down and rebuilt without dropping the control plane.

A lock or claim held by the media plane must fail open on staleness: a dead process may never leave a robot permanently “owned” (docs/10).

Adopted from the proven drever_webrtc v3 design (prior art):

capture ─ encode ─ appsink ──► FrameHub ──► per-session pipelines
(one producer, persistent) (fan-out) appsrc ! queue(leaky) ! valve
! rtp*pay ! webrtcbin
  • N viewers cost one encoder; session churn never touches capture.
  • Per-track valve enables/disables streams without renegotiation; re-enable waits for a keyframe.
  • Offers are caps-gated: created only when every enabled track has fixed RTP caps (payload type + SSRC), eliminating the classic webrtcbin race.
  • Every external callback is marshaled onto one main loop; callback contexts carry a per-session generation counter so late callbacks from a torn down session are no-ops.

Whether webrtcbin+FrameHub stays hand-rolled or is replaced by webrtcsink (which ships congestion control and fan-out natively) is decided by measurement in M1 — ADR-0007.

sequenceDiagram
participant D as Dashboard (@fjarr/react)
participant B as Customer backend
participant S as fjarr-server
participant A as Agent (libfjarr)
D->>B: user clicks "connect" (customer auth)
B->>B: mint session grant (JWT: tenant, robot, capabilities, exp)
B-->>D: grant
D->>S: WSS connect + session-request(grant)
S->>S: verify grant signature + expiry
S->>A: session-request(session_id, capabilities)
A->>A: build consumer pipeline, wait for fixed caps
A->>S: offer(sdp, track manifest)
S->>D: offer
D->>S: answer(sdp)
S->>A: answer
A-->>D: trickle ICE both ways (via S)
A-->>D: DTLS/SRTP established — media + DCs flow P2P
S->>B: webhook: session.started
Note over A,D: heartbeat on control DC; reconnect/ICE-restart on failure
S->>B: webhook: session.ended(reason, stats)
TopologyMedia pathWhen
LAN directhost↔hostCommissioning, factory
STUN P2PNAT-traversed directMost field robots
TURN relayrobot → coturn → operatorSymmetric NAT, carrier-grade NAT, strict firewalls — plan capacity for this (docs/16)

Signaling always flows through fjarr-server (WSS, 443-friendly). TURN uses ephemeral HMAC credentials minted per session (docs/10).

  1. Browser ↔ fjarr-server: session grant required; grants are short-lived and capability-scoped.
  2. Agent ↔ fjarr-server: per-device credential established at enrollment (docs/10); never a fleet-shared secret.
  3. fjarr-server ↔ customer backend: signed webhooks out, authenticated REST in.
  4. Inside the robot: the agent runs unprivileged; input injection that needs privileges goes through a minimal separate helper (ADR-0009).
ComponentLanguageSpecs it implements
agent/ (libfjarr, fjarr-agent)C++2005, 06, 07, 08, 09, 16
signaling/ (fjarr-signaling, fjarr-server)Rust08, 09, 10
web/packages (@fjarr/core, @fjarr/react)TypeScript05, 08, 09
demos/*C++/TS02 (demo rule), 09