-
Notifications
You must be signed in to change notification settings - Fork 2
Architecture
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) |
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.
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).
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.
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.
| 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) |
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.