CogniFlight Cloud
Ground station for an aviation fatigue-monitoring capstone: MQTT telemetry, time-series analytics and a desktop-OS dashboard UI I built for operators.
- role
- Frontend · pilot-enrolment API
- status
- archived
- date
- 2025
- links
- repo ↗
- React
- Go
- Gin
- WebSockets
- MQTT
- InfluxDB
- Telegraf
- MongoDB
- Python
- Docker
- Traefik
mqtt ▶ telegraf ▶ influx │ │ ▼ ▼ [ go api ]──▶ desktop ui │ ▲ ▼ │ unix socket [ ml-engine ]
- capstone mark
- 97%
- services behind Traefik
- 6
- person capstone team
- 8
The system
CogniFlight detects pilot fatigue in real time. Edge devices in the cockpit stream fatigue indicators — eye-aspect ratio, yawning, heart rate, heart-rate variability, environmental data — and the cloud platform turns that stream into live dashboards, alerts and analysis for operators on the ground.
Six Docker services behind Traefik make up the ground station: a Go + Gin backend, a Python ML engine, a Mosquitto MQTT broker with custom auth, Telegraf feeding InfluxDB time-series storage, MongoDB, and the React dashboard. This was our final-year capstone, built by a team of eight split into two sub-teams — Brian Felgate led the cloud team, I led the edge team — and it earned 97%.
My part: the operators' desktop
The frontend was my seat. Air-traffic controllers juggle many aircraft at once, so instead of a single scrolling dashboard I built the UI as a windowed desktop: a window manager with taskbar, start menu and notifications, where each concern — live edge-node dashboards, pilot management, file explorer, camera feeds, a terminal — is an app operators arrange side by side.
The centerpiece is the edge-node dashboard: live attitude indicators and telemetry charts streaming over a WebSocket, with the ML engine's fatigue reasoning surfaced inline, so an operator sees "microsleep indicators, EAR below threshold" next to the raw signal that caused it. I also seeded the facial-recognition embedding API used to enroll pilots.
The operator's minute
The workflow the desktop is built around is a single minute: an edge node's fatigue score climbs, the dashboard window for that node lights up, and the operator needs to answer "is this real?" without leaving the screen. So the telemetry chart, the ML engine's reasoning for the flag, the camera feed and the pilot's enrolment record open as windows next to each other rather than as tabs that hide one another. Two infrastructure choices exist for that minute. Telemetry lands in a time-series store because every reading is an append-only sample queried by time window — "show me the last ninety seconds of this node" is the only question anyone asks of it. And the devices publish over MQTT because cockpit links drop: the broker's session semantics tell the ground station a node has gone quiet before any dashboard would notice the chart stopped moving.
Details that changed how I think
These two designs are the work of Brian Felgate, who led the cloud team — not mine — but I built against them daily, and they raised my bar for what an interface can be.
The backend is a shell. The browser doesn't call REST endpoints for most things — it opens a WebSocket running an actual command shell with 27 commands (ls, cat, chmod, flux, mqtt, ml-rpc…), frames encoded in MessagePack. The frontend terminal app I built is a real client of the same interface every other app uses.
Permissions are tags, not roles. Every entry in the MongoDB-backed virtual filesystem carries read/write/execute tag lists, matched against the user's tags — a capability-style ACL with a meta-rule that you cannot grant tags you don't hold:
func (p FsEntryPermissions) IsAllowed(mode FsAccessMode, tags []string) bool {
var check []string
switch mode {
case ReadMode: check = p.ReadTags
case WriteMode: check = p.WriteTags
case ExecuteMode: check = p.ExecuteTags
case UpdatePermissionsMode: check = p.UpdatePermissionTags
}
for _, tag := range tags {
if slices.Contains(check, tag) {
return true
}
}
return false
}Processes talk over sockets, not networks. The Go backend and the Python ML engine (InsightFace face embeddings, threshold-based fatigue reasoning) speak JSON-RPC 2.0 over a Unix domain socket shared through a Docker volume — no exposed ports, no auth surface.
What it taught me
Working on a team of eight with a service-oriented codebase forced clean contracts: my frontend consumed the WebSocket shell and the telemetry fan-out exactly as any other client would. The two-team structure taught the same lesson at a larger scale: because Brian and I stayed in constant contact, edge and cloud were built in parallel and integrated continuously — the contract never drifted far enough to bite. And building an operating-system metaphor for operators here directly shaped the interface of Exequtech OS — and, in a way, the site you're reading.