Skip to content

Architecture

Judah Paul edited this page Jul 16, 2026 · 8 revisions

Stack

CanaryGC is a single SvelteKit application. The front end is Svelte 5 (runes) with TypeScript and Tailwind; the server routes run on the Node adapter (@sveltejs/adapter-node).

Concern Technology
App framework SvelteKit 2, Svelte 5 runes
Build Vite
2D map Leaflet
3D map MapLibre GL
MAVLink node-mavlink (common + ardupilotmega dialects)
Serial / TCP link serialport, Node net
Auth Lucia v3
Database SQLite via @libsql/client
Camera MediaMTX (WebRTC)

Server and client split

The browser never speaks MAVLink directly. All MAVLink and database access lives in server routes and src/lib/server, and the front end talks to those routes over HTTP:

browser (Svelte components, stores)
        |  fetch /api/*
SvelteKit server routes (+server.ts)
        |
   src/lib/server/mavlink.ts  -> autopilot over serial or TCP
   src/lib/server/db.ts       -> SQLite
   src/lib/server/auth.ts     -> Lucia sessions

src/lib/server/mavlink.ts holds the single open link and the running telemetry state on a globalThis singleton, so dev-server module reloads reuse the open connection. A supervisor loop started with the server redials whenever no packet has arrived within a few seconds, which keeps the autopilot link alive without a browser session and survives silent stalls. The front end subscribes to a server-sent-events feed at /api/mavlink/stream that pushes each telemetry line as it is parsed, so the marker and HUD update in real time, and polls /api/mavlink/heartbeat for link status and to drain any buffered lines. See HTTP API.

MAVLink transport

The link target depends on the environment:

  • Production: a serial port at /dev/ttyACM0, 115200 baud.
  • Development: a TCP connection to the ArduPilot SITL container at sitl:5760.

Incoming bytes are piped through MavLinkPacketSplitter and MavLinkPacketParser. On the first packet of a connection the server sets a message interval for every message the app consumes: attitude at 10 Hz, global position at 5 Hz, GPS, battery, vibration, and mission current at 1 Hz, and home position at 0.5 Hz. A serial autopilot whose stream-rate parameters sit at zero then behaves the same as a SITL. The server also beats a 1 Hz GCS heartbeat back at the vehicle and keeps a structured snapshot of the latest identity, position, and home for server-side flows such as the lost-operator failsafe (see Configuration).

Authentication

Auth uses Lucia sessions backed by SQLite. src/hooks.server.ts gates every request:

  • Public pages: /, /login, /register, /forgot-password, /reset-password.
  • Public API prefix: /api/auth/.
  • Any other page without a valid session redirects to /login.
  • Any other API route without a valid session returns 401.

A session cookie is set on login and register and invalidated on logout. The first registered account is the only account: register returns 403 once a user exists, which makes the station single-operator by default. The operator account carries an email; /api/auth/forgot mails a hashed, one-hour reset token over SMTP and /api/auth/reset sets a new password and invalidates existing sessions.

Database schema

src/lib/server/db.ts runs ordered migrations tracked by SQLite's PRAGMA user_version, so a fresh deployment needs only a writable directory for the database file (DATABASE_PATH). Each migration applies in one transaction that bumps the version last; new schema changes append to the list rather than editing a shipped migration.

CREATE TABLE user (
    id TEXT NOT NULL PRIMARY KEY,
    username TEXT NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    email TEXT
);

CREATE TABLE session (
    id TEXT NOT NULL PRIMARY KEY,
    expires_at INTEGER NOT NULL,
    user_id TEXT NOT NULL
);

CREATE TABLE mission (
    id TEXT NOT NULL PRIMARY KEY,
    title TEXT NOT NULL,
    actions TEXT NOT NULL,
    isLoaded BOOLEAN NOT NULL
);

CREATE TABLE password_reset (
    token_hash TEXT NOT NULL PRIMARY KEY,
    user_id TEXT NOT NULL,
    expires_at INTEGER NOT NULL
);

CREATE TABLE app_setting (
    key TEXT NOT NULL PRIMARY KEY,
    value TEXT NOT NULL
);

The mission.actions column stores the waypoint list as JSON. app_setting is a key-value store for integration settings (SMTP, airspace API keys) and alert preferences.

Pages

Route Purpose
/ Landing / dashboard entry
/dashboard Telemetry widgets, controls, camera feed
/mission-planner Map, waypoint editor, path optimization, safety
/parameters Read and write autopilot parameters
/calibration Accelerometer, gyro, compass, level, and ESC calibration
/firmware Flight-controller firmware flashing
/integrations SMTP, airspace API keys, operator email
/alerts Telemetry alert types and recipient
/event-log MAVLink and MSP log stream, console, and flight logs
/login, /register Auth
/forgot-password, /reset-password Password reset over SMTP
/version Build info (release tag, commit, build time)

Front-end state

Svelte stores under src/stores hold shared client state: authStore, mavlinkStore (telemetry), mapStore, missionPlanStore, safetyStore (limits, airspace zones, overlay toggle), notificationCountStore, and customizationStore (theme colors, dark mode).

Shared client logic lives in src/lib: flight-modes.ts ([[Autopilot Support]]), mavlink-client.ts (command wrappers), path-planning.ts, safety.ts, preflight.ts, airspace.ts (airspace popup and colors), and geo.ts (Mission Planning). Modals and notifications are created through the overlays.ts factory rather than per-component instances.

callouts.ts subscribes to the telemetry stores and speaks short standardized phrases on flight-state transitions (arm and disarm, mode changes, battery, GPS, failsafe, link) over the Web Speech API. The audio toggle in the navigation gates it and persists to local storage, defaulting on.

alerts.ts watches the same transitions and, for the alert types enabled under /alerts, posts the live telemetry to /api/alerts/notify, which emails the operator through the SMTP mailer (src/lib/server/mailer.ts) using the integration settings (src/lib/server/settings.ts). alert-types.ts is the shared catalog of alert ids, labels, and the telemetry payload shape.

Clone this wiki locally