Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
python-version: "3.12"
cache: pip

# Mosquitto is the MQTT broker for tests/integration (novaMock -> backend).
# Mosquitto is the MQTT broker for tests/integration (novaSim -> backend).
- name: Install
run: |
sudo apt-get update -q
Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ python -m pytest -q --cov=app

CI runs the same tests on every PR and prints coverage.

`tests/integration/` starts a real Mosquitto broker and `tools/novaMock.py`
`tests/integration/` starts a real Mosquitto broker and `tools/novaSim.py`
and checks that telemetry reaches a websocket client. It needs Mosquitto
installed (on PATH, or in `C:\Program Files\mosquitto`) and is skipped
without it. CI sets `NOVA_REQUIRE_INTEGRATION=1` so it fails instead.

## Simulator Test (level 2)

- `tools/novaMock.py`, `tools/nova_dummy.py`, `tools/novaSystem_dummy.py`:
- `tools/novaSim.py`, `tools/nova_dummy.py`, `tools/novaSystem_dummy.py`:
simulated novaGround and FAS traffic over MQTT
- `tools/sim_control.html`: drive the simulators from a browser
- `tools/sim_control.html`: the control panel `novaSim.py --ui` serves, to
drive the simulators from a browser

Then go through the
[testing procedure](https://github.com/UTATRocketry/Nova-Collected/blob/main/docs/development/testing-procedure.md)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ On Linux or macOS, start the backend and the simulator separately:

```bash
./scripts/dev_linux.sh --broker localhost
python tools/novaMock.py --broker localhost
python tools/novaSim.py --broker localhost
```

Open `http://localhost:8000/docs` for the live API reference. Run the tests:
Expand Down
17 changes: 13 additions & 4 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ The programs in `tools/`. All need `paho-mqtt`; the FAS bridge also needs
| Tool | What it's for |
|---|---|
| `fas_bridge.py` | The production serial ↔ MQTT link to the flight computer (FMC) |
| `novaMock.py` | **The main simulator**: stands in for novaGround and the FAS bridge together |
| `novaSim.py` | **The main simulator**: stands in for novaGround and the FAS bridge together |
| `nova_dummy.py` | Lighter novaGround simulator |
| `serial_probe.py` | See what's on a serial port |
| `sim_control.html` | Browser controls for the simulators |
| `sim_control.html` | The control panel `novaSim.py --ui` serves |
| `novaGround_dummy.py`, `novaSystem_dummy.py` | Legacy simulators; `dev_linux.sh --with-dummy` still uses `novaSystem_dummy.py` |

## fas_bridge.py
Expand Down Expand Up @@ -42,16 +42,25 @@ A missing or unplugged port is retried in the background instead of stopping
the bridge. The link state is published on `nova/console` as `console_serial`
and in flight telemetry as `data.fas_link`.

## novaMock.py
## novaSim.py

```bash
python tools/novaMock.py --broker localhost --ui
python tools/novaSim.py --broker localhost --ui
```

Publishes GCS and FAS engine packets and the nested flight snapshot, and
answers the same commands as novaGround and the FAS bridge, so the backend and
frontend can be exercised end to end with no hardware.

`--ui` serves the control panel on `--ui-port` (8765 by default). The page is
`tools/sim_control.html`, read from disk on each request, so edits to it show
on a browser refresh without restarting the simulator.

The UI's "Launch" button replays a trajectory. Point it at an OpenRocket CSV
export (11 numeric columns) with `--sim-csv`, or set `NOVA_SIM_CSV` so you
don't have to pass it every time; with neither, it synthesizes a generic
flight profile.

## nova_dummy.py

```bash
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ include = ["app*"]
testpaths = ["tests"]
addopts = "-q"
markers = [
"integration: starts Mosquitto and novaMock; skipped if Mosquitto isn't installed",
"integration: starts Mosquitto and novaSim; skipped if Mosquitto isn't installed",
]

[tool.pylint.MASTER]
Expand Down
4 changes: 2 additions & 2 deletions scripts/dev_windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ try {
$children = @()

if ($WithMock) {
$mockArgs = "tools\novaMock.py --broker ${Broker} --port ${brokerPort} --ui"
$mockArgs = "tools\novaSim.py --broker ${Broker} --port ${brokerPort} --ui"
$proc = Start-Process -FilePath $python -ArgumentList $mockArgs `
-WorkingDirectory $repoRoot -PassThru
$children += $proc
Write-Host "Started novaMock.py (PID $($proc.Id))" -ForegroundColor Cyan
Write-Host "Started novaSim.py (PID $($proc.Id))" -ForegroundColor Cyan
}

if ($WithFas) {
Expand Down
10 changes: 5 additions & 5 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Fixtures that run real processes: a Mosquitto broker and tools/novaMock.py.
"""Fixtures that run real processes: a Mosquitto broker and tools/novaSim.py.

Mosquitto must be installed. Without it these tests are skipped, unless
NOVA_REQUIRE_INTEGRATION is set (as it is in CI), in which case they fail.
Expand All @@ -16,7 +16,7 @@
import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]
NOVA_MOCK = REPO_ROOT / "tools" / "novaMock.py"
NOVA_SIM = REPO_ROOT / "tools" / "novaSim.py"

# Where the Windows installer puts it; it isn't on PATH by default.
_WINDOWS_MOSQUITTO = Path(r"C:\Program Files\mosquitto\mosquitto.exe")
Expand Down Expand Up @@ -88,11 +88,11 @@ def mqtt_broker(tmp_path_factory) -> tuple[str, int]:


@pytest.fixture
def nova_mock(mqtt_broker) -> subprocess.Popen:
"""tools/novaMock.py publishing simulated telemetry to the test broker."""
def nova_sim(mqtt_broker) -> subprocess.Popen:
"""tools/novaSim.py publishing simulated telemetry to the test broker."""
host, port = mqtt_broker
proc = subprocess.Popen(
[sys.executable, str(NOVA_MOCK), "--broker", host, "--port", str(port), "--hz", "20"],
[sys.executable, str(NOVA_SIM), "--broker", host, "--port", str(port), "--hz", "20"],
cwd=REPO_ROOT,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""novaMock -> MQTT -> backend -> websocket client, with real processes.
"""novaSim -> MQTT -> backend -> websocket client, with real processes.

The backend runs in-process (TestClient), but talks to a real Mosquitto
broker that novaMock publishes to, so the MQTT contract between the
broker that novaSim publishes to, so the MQTT contract between the
simulator and the backend is exercised for real.
"""
from __future__ import annotations
Expand All @@ -15,7 +15,7 @@

pytestmark = pytest.mark.integration

# One sensor from each side that novaMock simulates:
# One sensor from each side that novaSim simulates:
# PGSO: GCS hat 0 / channel 0, raw 0.99-3.99 V, calibrated here to 99-399 psi.
# PCC: FAS EPB_0 / channel 1, raw 0.2-0.9, passed through unconverted.
TEST_CONFIG = """
Expand Down Expand Up @@ -91,7 +91,7 @@ def _by_name(engine_data: dict) -> dict[str, dict]:
return {row["name"]: row for row in engine_data["data"]}


def test_websocket_client_receives_calibrated_engine_telemetry(backend_env, nova_mock) -> None:
def test_websocket_client_receives_calibrated_engine_telemetry(backend_env, nova_sim) -> None:
app = create_app()
with TestClient(app) as client:
_wait_for_engine_data(app.state.ctx)
Expand Down
2 changes: 1 addition & 1 deletion tools/fas_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def _fmc_altitude_m(pressure_pa: float) -> float:
ENGINE_STALE_S = 3.0

# ── Flight FSM taxonomy ───────────────────────────────────────────────────────
# Kept in sync with tools/novaMock.py. fas_state is the top-level avionics state;
# Kept in sync with tools/novaSim.py. fas_state is the top-level avionics state;
# flight_phase is the finer phase reported alongside it. FLIGHT_EVENTS maps each
# event name to (id, default severity); events are published on nova/console as
# {"type":"flight_event","event":{id,name,severity}}.
Expand Down
Loading
Loading