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 — 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.
Details that changed how I think
These two designs are the work of our backend lead, Brian Felgate — 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. 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.