FastAPI backend for the Nova rocket ground-support system. Receives sensor telemetry from novaGround over MQTT, exposes a REST + WebSocket API to the frontend, translates frontend actuator commands into typed MQTT command frames, and manages config, data files, and system state.
novaOps-back/
├── app/
│ ├── context.py # AppContext — shared state passed to services
│ ├── models.py # Pydantic models: sensors, actuators, bindings, config
│ ├── routes/ # FastAPI routers (actuators, sensors, config, data-files, …)
│ ├── services/
│ │ ├── command_service.py # Command parsing and MQTT publish
│ │ ├── config_service.py # System config load/reload
│ │ ├── mqtt_service.py # MQTT client wrapper
│ │ └── sensor_service.py # Telemetry ingestion and calibration
│ └── static/
├── config/ # system.yaml lives here
├── data/ # CSV data files uploaded from novaGround
├── tools/
│ ├── nova_dummy.py # Simulates current novaGround (nova/telemetry, FAS, console)
│ ├── fas_bridge.py # Direct FAS RS-422 → MQTT bridge (runs without novaGround)
│ ├── novaGround_dummy.py # Legacy GCS simulator (nova/telemetry/engine)
│ └── novaSystem_dummy.py # Legacy system simulator
├── scripts/ # Dev environment scripts (Linux + Windows)
├── tests/
├── docker-compose.yml
└── Dockerfile
- MQTT subscribe/publish integration with novaGround and FAS
- WebSocket channel delivering parsed sensor data and actuator states in real time
- Config-driven sensor and actuator parsing via
config/system.yaml - Linear interpolation and polynomial calibration, rolling average smoothing
- FAS actuator support: EPB PWM, load switches, IMC arm/disarm, GPIO
- REST API for config, flags, data files, sensors, and actuators
- Logging to console and rotating file logs
- Unit tests for parsing and command translation
| Direction | Topic | Description |
|---|---|---|
| Subscribe | nova/telemetry |
Sensor + FAS telemetry from novaGround |
| Subscribe | nova/console |
Raw FAS frame passthrough (console mode) |
| Subscribe | nova/control |
Physical lockout state from novaLock |
| Publish | nova/command |
Actuator commands to novaGround |
| Publish | nova/control |
Data-saving control to novaGround |
Environment variables:
NOVA_MQTT_BROKER MQTT broker host (default: localhost)
NOVA_MQTT_PORT MQTT broker port (default: 1883)
NOVA_PUBLIC_BASE_URL Base URL the FAS bridge should use to download staged
soundboard clips (default: the requesting URL, which
may be localhost and unreachable from the bridge host)
NOVA_CLIP_TTL_S Seconds a staged clip is kept if nobody fetches it (900)
NOVA_SOUND_CLIP_MAX_BYTES Sanity cap on a transcoded clip (default 8 MiB; the
real limit is the soundboard's free flash)
NOVA_SOUND_SOURCE_MAX_BYTES Sanity cap on the uploaded source file (default 64 MiB)
All commands are published as:
{"source": "novaOps", "command": {"type": "<type>", ...}}{
"source": "novaGround",
"sensors": [{"hat_id": 0, "channel_id": 0, "value": 1.234, "timestamp": 12345}],
"gpios": [{"pin_id": 17, "state": 0}],
"fas_boards": [{"key": "EPB:0", "online": true, "uptime_ms": 4200}],
"fas_imc": {"board_id": 0, "armed": false, "arm_line": false, "disarm_line": false}
}FAS EPB ADC channels appear in sensors at hat_id = 100 + board_id.
| Method | Path | Description |
|---|---|---|
| GET | /api/actuators |
All actuator states |
| GET | /api/sensors |
Latest sensor readings |
| GET | /api/config |
Current system config |
| POST | /api/config/upload |
Upload a new config file |
| PUT / PATCH | /api/config |
Update config fields |
| POST | /api/config/reload |
Reload config from disk |
| POST | /api/flags/calibration |
Toggle calibration mode |
| POST | /api/flags/data-saving |
Start/stop data saving |
| GET | /api/data-files |
List saved CSV files |
| GET | /api/data-files/{file_name} |
Download a CSV file |
| POST | /api/commands |
Send an actuator command |
| WS | /ws?role=operator|pad|viewer|dev |
Real-time telemetry and state |
config/ in this repo holds the published defaults. On the ground station,
ops/Nova.ps1 points the backend at station folders outside the checkout, so
changing code versions never touches live calibrations or recordings:
| Variable | Default (unset) | On the station | What |
|---|---|---|---|
NOVA_CONFIG_DIR |
config/ |
C:\Nova\config\<env> |
Live config the web interface edits. An empty folder is seeded from config/ on first start. |
NOVA_CONFIG_HISTORY_DIR |
<config dir>/history |
C:\Nova\config\history |
Every config ever used, stored once by content hash, plus history.jsonl (who changed what, when). |
NOVA_DATA_DIR |
data/ |
C:\Nova\data\<env> |
Raw recordings and their metadata. |
NOVA_ENV, NOVA_RELEASE |
empty | prod / v2026.10.04 |
Stamped into each recording's metadata. |
Every save, load, upload or reload is snapshotted automatically, including half-finished edits. Nobody needs to commit anything.
When a recording starts, the backend picks its name and writes
<name>.meta.json into the data folder. It records the environment, release,
backend commit, start and stop times, and the hash of the active config,
including any config change made during the recording. Loggers write raw
values, so this file is what makes a CSV interpretable later.
Actuators:
- name: MAIN_VALVE
type: solenoid
binding:
target: FAS
board_type: EPB # board kind string
board_id: 0 # 0-based index (default 0 if omitted)
relay_channel: 2
actions:
solenoid_type: nominally_open
- name: TVC_SERVO
type: servo
binding:
target: FAS
board_type: EPB
board_id: 1
servo_channel: 0
relay_channel: 1 # power relay on the same EPB
actions:
position_aliases: [retract, extend]
positions: [1000, 2000]node (legacy string form "EPB_1") is still accepted for backward compatibility and is automatically converted to board_type/board_id.
value is always a pulse width in microseconds.
All tools are in tools/ and require paho-mqtt (pip install paho-mqtt).
Simulates a live novaGround instance. Publishes nova/telemetry at a configurable rate with:
- Sine-wave ADC channels for 2 mock MCC DAQ hats
- FAS EPB ADC samples at
hat_id = 100 + board_id - 2 FAS EPB boards always online
- FAS IMC state that follows arm/disarm commands
Handles all command types including both FAS shapes and console start/stop.
python tools/nova_dummy.py --broker localhost:1883 --verbosity 2
python tools/nova_dummy.py --helpConnects a FAS RS-422 serial port directly to the MQTT broker. Use this when novaGround hardware is unavailable but FAS hardware is present, or to run FAS from a laptop instead of a Pi.
Publishes nova/telemetry in the same format as novaGround. Translates inbound nova/command FAS commands to wire frames. All non-FAS command types are silently dropped.
pip install paho-mqtt pyserial
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
python tools/fas_bridge.py --port /dev/ttyUSB0 --broker localhost:1883
python tools/fas_bridge.py --help--port is optional (it also reads NOVA_FAS_PORT). Started without one, the
bridge runs idle on MQTT until novaOps picks a port — send a console
list_ports then configure command (POST /api/console/command, or a
console_command WebSocket message). A configured port that is missing or gets
unplugged is retried in the background instead of taking the bridge down; the
link state is published on nova/console as console_serial and mirrored in
flight telemetry as data.fas_link.
Soundboard clip uploads are fetched over HTTP from the backend rather than
carried over MQTT, so when the bridge runs on a different host than the backend
give it the backend's address (or set NOVA_OPS_URL):
python tools/fas_bridge.py --port COM3 --broker 10.0.0.5:1883 --ops-url http://10.0.0.5:8000# Linux
bash scripts/dev_linux.sh --broker localhost
bash scripts/dev_linux.sh --broker hivemq --with-dummy
# Windows (PowerShell)
powershell -ExecutionPolicy Bypass -File scripts/dev_windows.ps1 -Broker localhost
powershell -ExecutionPolicy Bypass -File scripts/dev_windows.ps1 -Broker hivemq -WithDummyBroker shortcuts: localhost / local, hivemq (maps to broker.hivemq.com), or any host string.
sudo docker-compose up --buildAccess the app at http://localhost:8000. To view container stdout/stderr:
docker attach <CONTAINER>To stop:
sudo docker-compose down-
Clone and enter the repo:
git clone https://github.com/UTATRocketry/novaOps-back.git cd novaOps-back -
Update the log path in
initial_config.sh(line 4). -
Make scripts executable and run them:
chmod +x initial_config.sh post_reboot_config.sh ./initial_config.sh # After reboot: ./post_reboot_config.sh
The server starts at http://192.168.0.1:8000 (Ethernet) or http://raspberrypi.local:8000.
Can't connect from a client:
- Check server is up:
curl http://0.0.0.0:8000/api/config - Check static IP:
ip addr show eth0 - Check DHCP:
sudo systemctl status dhcpcd - Check DNS:
sudo systemctl status dnsmasq - Check Docker:
sudo docker ps
Stop and remove containers:
sudo docker-compose down