Operator console, wire protocol and flight-hardware firmware for a two-board rocket ground support station:
- Ground board — STMicroelectronics
NUCLEO-H753ZI(DigiKey21348937) connected to the operator laptop over USB / ST-LINK Virtual COM Port (USART3, configurable9600–460800 Bd) and bridged to either:- DX-SMART
DX-LR42-24T12D2.4 GHz Semtech SX1281 UART transceiver (AmazonB0H8NR5QPY) overUSART6(configurable2400–115200 Bd), or - CDEBYTE
E28-2G4M27S2.4 GHz SemtechSX1280/SX1281500 mW(+27 dBm) SPI + PA/LNA LoRa transceiver overSPI1(4 MHz).
- DX-SMART
- Vehicle board — paired
NUCLEO-H753ZI+DX-LR42-24T12DorE28-2G4M27Son the rocket driving the fill valve servo (FILL), vehicle servos (SRV) and igniter relay (IGN).
The host application (gse-app) is a single dependency-free Rust binary. It
serves an embedded terminal-style browser console on port 8765, speaks a
compact COBS + CRC-16 framed protocol over USB serial (or TCP against the
built-in simulator), and enforces deterministic launch interlocks across two
selectable operating modes (LIVE and TEST). Every safety rule is
independently re-checked in the no_std vehicle firmware — the vehicle never
trusts the ground station.
- Terminal-style web operator console served locally (
127.0.0.1:8765by default) with per-session token auth, live Server-Sent Events (/api/events), and zero external/CDN assets. - Selectable
LIVEandTESToperating modes in Settings (or by clicking theSETUPstatus pill in the header):LIVEmode (op_mode: "live", default) — streamlined 2-button launch interface (FILL[ON/OFF]+ combinedSRV+IGN[ON/OFF]) with a configurable on-screen launch countdown (default5 s).TESTmode (op_mode: "test") — 3-button checkout interface (FILL[ON/OFF],SRV[ON/OFF],IGN[PWR/SAFE]) for testing vehicle channels independently with full safety interlocks.
- Configurable sequence timers & pause/resume/abort controls in Settings (
1..=600 s):- Fill duration (
fill_seconds, default60 s) — auto-closesFILLat expiry and marks the fill sequence complete. TurningFILL -> OFFprior to expiry pauses the fill timer (fill_remaining_mspreserved), unlocksSRV(TEST) /SRV+IGN(LIVE), and lets subsequentFILL -> ONresume from the paused remaining time. - Ignition countdown (
countdown_seconds, default5 s) — displayed live on screen beforeSRV+IGN -> ON(LIVE) orIGN -> PWR(TEST), and can be aborted mid-countdown at any time (cancelling ignition immediately, returningSRV -> OFFif powered, and keepingFILLprogress intact).
- Fill duration (
- Live dropdown selectors & flash persistence for baud rates and radio modules in Settings:
- USB CDC baud rate (
USART3host ↔ ground board:9600,19200,38400,57600,115200,230400,460800baud). - UART baud rate (
USART6MCU ↔ UART radio:2400,4800,9600,19200,38400,57600,115200baud — liveAT+BAUDxreconfiguration). - Radio module selector (
DX-LR42-24T12DUART LoRa vsE28-2G4M27S500 mW+27 dBmSPI1LoRa) — non-destructively probesSX1280/SX1281overSPI1, orchestrates an atomic two-phaseVehicle → Ground → Hosthandover with zero lost ACKs, and persists settings to STM32 Bank 2 Sector 7 internal flash (0x081E_0000) across power cycles.
- USB CDC baud rate (
- Rebindable keyboard shortcuts — defaults:
F—FILLtoggle (ON/OFF)L—SRV+IGNtoggle (LIVEmode)S—SRVtoggle (TESTmode)I—IGNtoggle (TESTmode)E—ALL SAFE · E-STOPShift+R—Reset controls(forcesOFF/SAFEand clears latches)R—Reset fill seqShift+C—Clear faults
- Live telemetry & system monitor rail — ground/vehicle link status, RTT, firmware version, device UID, safety-gate status, decoded vehicle flags, latched faults, and frame counters.
- Session logging — real-time filterable log stream in the UI plus
append-only
session-<timestamp>.logon disk and one-click CSV download. - Cross-platform native serial — Win32 Comm API on Windows and POSIX
termios/pollon Linux and macOS, with automatic ground-board port discovery (port: "auto"). - Hardware-accurate simulator (
gse-sim) — executes the exactno_stdground-bridge and vehicle state machines over TCP with configurable RF latency, packet drops and bit errors.
Operators can switch between LIVE and TEST in Settings → Operating
Mode whenever all vehicle outputs are OFF/SAFE and no launch countdown or
activating command is in progress.
| Aspect | LIVE Mode (op_mode: "live", default) |
TEST Mode (op_mode: "test") |
|---|---|---|
| Console controls | FILL[ON/OFF] + SRV+IGN[ON/OFF] (2 buttons) |
FILL[ON/OFF], SRV[ON/OFF], IGN[PWR/SAFE] (3 buttons) |
| Fill sequence & pause/resume | FILL ON → auto FILL OFF after fill_seconds (FILL_SEQ_COMPLETE), or manual FILL OFF prior to expiry pauses fill_remaining_ms (FILL_PAUSED) so FILL ON resumes |
Identical (FILL ON → auto FILL OFF, or manual FILL OFF pauses timer and FILL ON resumes) |
| Pre-fill lock | SRV+IGN starts OFF/SAFE and unlocks once FILL completes or is paused OFF |
SRV and IGN start locked and SRV unlocks once FILL completes or is paused OFF; IGN additionally requires SRV = ON |
| Firing sequence | Pressing SRV+IGN runs an on-screen countdown (countdown_seconds, default 5 s) holding outputs OFF/SAFE until T-0.0 s, then commands Control::SrvIgn (ON) → ON/PWR |
Operator enables SRV ON, then pressing IGN (optional confirm dialog) runs the countdown_seconds countdown before commanding Control::Ign (ON) → PWR |
| Safe shutoff | Pressing SRV+IGN while ON/PWR commands Control::SrvIgn (OFF) → safes IGN first, then SRV → OFF/SAFE |
Operator commands IGN SAFE, then SRV OFF (SRV OFF is blocked while IGN is PWR) |
| Countdown abort | Clicking ABORT, toggling SRV+IGN (L), or posting abort_countdown immediately cancels the countdown with outputs OFF/SAFE and preserves FILL state |
Clicking ABORT, toggling IGN (I), or posting abort_countdown immediately cancels IGN, commands SRV -> OFF, and preserves FILL state |
- LIVE sequence:
FILL ON → (auto FILL OFF, or early FILL OFF pause / FILL ON resume) → press SRV+IGN → (abortable countdown) → SRV+IGN ON/PWR → SRV+IGN OFF/SAFE - TEST sequence:
FILL ON → (auto FILL OFF, or early FILL OFF pause / FILL ON resume) → SRV ON → press IGN → (abortable countdown) → IGN PWR → IGN SAFE → SRV OFF
Every gate is evaluated in gse-app (greying out buttons with human-readable
reasons and rejecting API/keyboard requests) and re-evaluated independently
on the STM32H753ZI vehicle board. There is no override in either layer.
| # | Rule | Where enforced |
|---|---|---|
| 1 | FILL and SRV can never be ON simultaneously |
safety.rs, fsm.rs, vehicle.rs |
| 2 | FILL closes automatically after fill_seconds (FILL_SEQ_COMPLETE), while manual FILL OFF before expiry pauses fill_remaining_ms (FILL_PAUSED) for later resume; only uncommanded fault closes (E-STOP / RF timeout mid-fill) set FILL_SEQ_ABORTED |
engine.rs auto-off on board timer + vehicle close_fill & 2 s hardware backstop in vehicle.rs |
| 3 | SRV ON (TEST), IGN PWR (TEST), and SRV+IGN ON (LIVE) stay locked until FILL has completed or been paused OFF without an uncommanded abort |
safety.rs + fsm.rs + vehicle.rs |
| 4 | In TEST mode, IGN PWR requires SRV = ON, runs an abortable countdown_seconds countdown, and SRV OFF is refused while IGN = PWR |
safety.rs + engine.rs + fsm.rs + vehicle.rs |
| 5 | Aborting an active countdown (IGN or SRV+IGN) immediately cancels ignition, closes SRV if powered, and keeps FILL progress intact |
safety.rs + engine.rs |
| 6 | Operating mode (LIVE ↔ TEST) cannot be changed while any output is active, targeted active, or counting down |
http.rs (HTTP 400) + engine.rs |
| 7 | Losing the RF link forces all outputs OFF/SAFE after 5 s and latches INHIBITED |
vehicle.rs (check_link + LINK_LOSS_SAFE) |
| 8 | ALL SAFE · E-STOP always wins immediately, cancelling any active countdown or in-flight command and latching ESTOP |
engine.rs + vehicle.rs |
| 9 | Hardware watchdog timeout (~4 s) or MCU fault resets/de-energises every pin | STM32H753 IWDG1 + panic safe-park handler |
| 10 | Every session and reconnect starts in INIT with all controls locked until the vehicle confirms FILL=OFF SRV=OFF IGN=SAFE |
engine.rs startup safe handshake |
| 11 | Reset controls always sends SAFE_ALL followed by RESET_FILL, returning the system to initial OFF/SAFE with latches cleared |
engine.rs + fsm.rs + vehicle.rs |
| 12 | Shutting down gse-app commands ALL SAFE first and waits up to 2.5 s for the vehicle ACK before exiting |
engine.rs (begin_shutdown) |
FILL, SRV and IGN are governed by a single pure transition table in
crates/gse-proto/src/fsm.rs, shared verbatim by
gse-app, gse-sim and both STM32 firmware images.
Confirmed states are FILL OFF|ON, SRV OFF|ON and IGN SAFE|PWR, starting at
FILL OFF · SRV OFF · IGN SAFE. Every event returns one of three outcomes:
| Outcome | Meaning | Console | Vehicle |
|---|---|---|---|
Allowed(next) |
legal transition to next |
button enabled | actuates output, waits settle time, sends ACK (Ok) |
NoOp(same) |
already in target state | button disabled with "already …" |
sends ACK (Ok), no actuation |
Blocked(reason) |
safety gate refuses move | button disabled with blocking reason | sends ACK (RejectedSafety, reason) |
Confirmed state in the console updates only when the vehicle ACK lands.
While a command is in flight, the console displays the target after → without
changing confirmed state badges.
| Event | Preconditions | Result |
|---|---|---|
FILL ON |
FILL=OFF, SRV=OFF, IGN=SAFE |
FILL=ON (resumes saved fill_remaining_ms if FILL_PAUSED, else starts fill_seconds) |
FILL OFF |
FILL=ON |
FILL=OFF (pauses fill_remaining_ms + unlocks downstream channels if closed before expiry) |
SRV ON (TEST) |
FILL=OFF, IGN=SAFE, (fill_seq_complete || fill_paused), !fill_seq_aborted, !inhibited, !estop |
SRV=ON (Blocked otherwise) |
SRV OFF (TEST) |
SRV=ON, IGN=SAFE |
SRV=OFF (Blocked while IGN=PWR) |
IGN PWR (TEST) |
FILL=OFF, SRV=ON, (fill_seq_complete || fill_paused), !fill_seq_aborted, !inhibited, !estop |
IGN=PWR (Blocked otherwise) |
IGN SAFE (TEST) |
IGN=PWR |
IGN=SAFE (NoOp if already SAFE) |
SRV+IGN ON (LIVE) |
FILL=OFF, (fill_seq_complete || fill_paused), !fill_seq_aborted, !inhibited, !estop |
SRV=ON, IGN=PWR (NoOp if already ON/PWR, Blocked otherwise) |
SRV+IGN OFF (LIVE) |
SRV=ON or IGN=PWR |
SRV=OFF, IGN=SAFE (NoOp if already OFF/SAFE) |
SAFE ALL / RESET |
any state | FILL=OFF, SRV=OFF, IGN=SAFE |
cargo build --workspace
cargo run -p gse-app -- --sim # starts gse-sim automatically; console at http://127.0.0.1:8765/Use --no-browser on headless machines and --host 0.0.0.0 when serving to a
remote browser or container preview:
./target/debug/gse-app --sim --no-browser --host 0.0.0.0 --http-port 8765To simulate degraded RF conditions (latency, packet drops and bit errors):
cargo run -p gse-sim -- --latency 60 --noise # terminal 1
cargo run -p gse-app -- --sim # terminal 2- Flash
gse-groundandgse-vehicle(see Firmware and docs/HARDWARE.md). - Plug the ground NUCLEO-H753ZI USB cable into the laptop.
- Launch the console:
gse-app # auto-detects the ground board on any serial port
gse-app --port /dev/ttyACM0 # Linux explicit port
gse-app --port COM5 --verbose # Windows
gse-app --port /dev/tty.usbmodem14201Linux users need serial group permission once:
sudo usermod -aG dialout "$USER" # log out and back ingse-app [OPTIONS]
--sim use the built-in TCP simulator (tcp:127.0.0.1:8787)
--port <PORT> serial port, tcp:host:port, or 'auto' (default)
--baud <RATE> serial baud rate (default 115200)
--config <PATH> path to configuration JSON file
--host <ADDR> HTTP bind address (default 127.0.0.1)
--http-port <PORT> HTTP console port (default 8765)
--no-browser do not open a browser window on startup
--open force opening a browser even in headless environments
--verbose print protocol diagnostics on stderr
-h, --help / -V, --version
Persisted atomically as pretty-printed JSON on every change in Settings:
| OS | Default path |
|---|---|
| Linux | ~/.config/gse/config.json (honours XDG_CONFIG_HOME) |
| macOS | ~/Library/Application Support/GSE/config.json |
| Windows | %APPDATA%\GSE\config.json |
{
"op_mode": "live",
"fill_seconds": 60,
"countdown_seconds": 5,
"port": "auto",
"baud": 115200,
"uart_baud": 9600,
"radio": "dx-lr42-24t12d",
"confirm_ign": true,
"sound": true,
"host": "127.0.0.1",
"http_port": 8765,
"shortcuts": {
"fill_toggle": "F",
"srv_ign_toggle": "L",
"srv_toggle": "S",
"ign_toggle": "I",
"estop": "E",
"reset_controls": "Shift+R",
"reset_fill": "R",
"clear_faults": "Shift+C"
}
}Session logs are written alongside state files under logs/session-<timestamp>.log.
- Top bar: shows live
GROUND,VEHICLE,LINKRTT,SETUP(LIVEorTEST, clickable to open Settings), andTARGET(SIMULATORorHARDWARE) pills, plusconnect,disconnectandsettings. SYS // MONITORleft rail: displays process health (setup,gse-app,gse-sim,dx-lr42-rf), live interlock gates (init safe,fill ↔ srv,fill seq,srv+ign/ign gate), and bus dispatch state.- Control toggles: horizontal terminal switches (
FILL[ON/OFF],SRV+IGN[ON/OFF]inLIVE;FILL[ON/OFF],SRV[ON/OFF],IGN[PWR/SAFE]inTEST). Whenever a gate is closed, the button is disabled and displays the exact reason underneath. - On-screen countdown & abort (
LIVE&TESTmodes): pressingSRV+IGN(LIVE) orIGN(TEST) displays#countdown-banner(T - X.X s, progress bar, andABORTbutton) counting down to0.0 s. Aborting during the countdown immediately cancels ignition, closesSRVif powered, and keepsFILLprogress intact. - Settings modal: configures Operating Mode (
LIVE/TEST), serial port & baud rate, fill duration (seconds) (1..=600), ignition countdown (seconds) (1..=600),ask before IGN PWR(TESTmode),audible alerts, and click-to-record keyboard shortcuts.
All endpoints require the per-session X-GSE-Token header (or ?token= query
parameter on GET /api/events and GET /api/log.csv), except GET /, static
assets, and GET /api/health.
| Endpoint | Method | Purpose |
|---|---|---|
/api/health |
GET |
Liveness check ({"ok":true,"subscribers":N}) |
/api/state |
GET |
Full JSON snapshot (config, ground, vehicle, controls, fill, countdown, actions, stats) |
/api/events |
GET |
SSE stream emitting state and log events |
/api/command |
POST |
Dispatch action (fill_on, fill_off, srv_on, srv_off, ign_pwr, ign_safe, srv_ign_on, srv_ign_off, abort_countdown, reset_controls, reset_fill, clear_faults, estop) |
/api/config |
POST |
Patch configuration (op_mode, fill_seconds, countdown_seconds, port, baud, confirm_ign, sound) |
/api/shortcuts |
POST |
Rebind {action, combo} or reset {reset: true} |
/api/ports |
GET |
Enumerate available serial ports |
/api/connect / /api/disconnect |
POST |
Open/rescan or close the serial/TCP link |
/api/log / /api/log.csv / /api/log/clear |
GET / POST |
Query recent log entries, export CSV, or clear in-memory log |
/api/shutdown |
POST |
Command ALL SAFE, await ACK, and stop gse-app |
firmware/stm32h753 contains the no_std STM32H753ZI package with two
binaries:
gse-ground— USB CDC (USART3,PD8/PD9) ↔DX-LR42-24T12D(USART6,PG14/PG9+PG10AUX+PG12/PG13M0/M1) transparent frame bridge + AT self-recovery +LINKSTATdiagnostics.gse-vehicle—DX-LR42-24T12Dtransceiver +TIM150 Hz servo PWM (PE9FILL,PE11SRV), push-pullIGNoutput (PE13), 250 ms telemetry, 5 s RF link-loss failsafe, and 4 s hardware watchdog (IWDG1).
rustup target add thumbv7em-none-eabihf
cd firmware/stm32h753
cargo build --release --bin gse-ground
cargo build --release --bin gse-vehicle
# flash via probe-rs:
cargo run --release --bin gse-vehicleTwo factory-fresh DX-LR42-24T12D modules work out of the box (9600 8N1,
2424 MHz, LEVEL 3 10 kbps, +13 dBm on 5V). To inspect or provision a
custom channel / 3.7 km long-range LEVEL 1 preset over a 3.3 V USB-UART
adapter:
python3 scripts/configure_dx_lr42.py --port /dev/ttyUSB0 --inspect
python3 scripts/configure_dx_lr42.py --port /dev/ttyUSB0 --preset long-range --channel 2440MHz --key 27182 --lock-pinsSee docs/HARDWARE.md for the full 7-pin wiring table, 5.0 V
VCC / 3.3 V CMOS rules, fail-safe pull-downs, and bench bring-up checklist.
crates/gse-proto no_std wire framing (COBS, CRC-16), DX-LR42 constants & shared FSM
crates/gse-fw-core no_std ground bridge, vehicle FSM & DX-LR42 UART stream adapter
crates/gse-app host service, safety gates, HTTP/SSE server & embedded web UI
crates/gse-sim two-board TCP RF link simulator using real firmware cores
crates/gse-fw-check host unit tests for STM32 register, ring-buffer & AUX helpers
firmware/stm32h753 NUCLEO-H753ZI hardware abstraction & firmware binaries
scripts/ smoke tests, SSE state trace & DX-LR42 AT provisioning helper
docs/ protocol spec, hardware/wiring guide & recorded state trace
cargo test --workspace --lib --bins --tests
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --checkEnd-to-end verification against compiled gse-sim + gse-app binaries:
cargo build --workspace
./scripts/smoke.sh # 51 end-to-end checks (LIVE + TEST modes, custom countdown, E-STOP, reconnect & shutdown ALL SAFE)
python3 scripts/state_trace.py # full SSE transition trace across LIVE & TEST sequencesSee docs/state-machine-trace.md for a recorded trace and docs/PROTOCOL.md for the byte-level specification.
| Symptom | Check |
|---|---|
No serial ports found |
Use a data-capable USB cable, power the board, verify ST-LINK VCP driver |
No ground board responded … |
Confirm gse-ground is flashed, baud is 115200, user is in dialout on Linux |
Ground online, vehicle stays pinging |
Check VCC (5V, CN8 pin 9), PG9/PG14 ↔ DX-LR42-24T12D Pin 4 TX / Pin 3 RX (3.3 V TTL), matching AT+CHANNEL / AT+LEVEL / AT+KEY, M0/M1 (PG12/PG13), and 2.4 GHz antennas |
SRV+IGN (LIVE) or SRV (TEST) greyed out |
Run FILL and let it auto-complete or click FILL again to pause it OFF (if an earlier fault set FILL_SEQ_ABORTED, press Reset fill seq first) |
| Mode switch refused in Settings | Return all controls to OFF/SAFE and cancel any active countdown before switching LIVE ↔ TEST |
| Shortcut binding rejected | Key combination is already bound to another action (named in the error toast) |
MIT OR Apache-2.0.