Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
eb0a1ea
add ethernet connection
Garklein Jan 7, 2025
6e4535b
clean up transitions
Garklein Jan 7, 2025
60d7624
add instructions
Garklein Jan 7, 2025
6537cac
readme typo
Garklein Jan 7, 2025
8e70acd
add tests
Garklein Jan 7, 2025
5cb9a36
clean up state machine code
Garklein Jan 8, 2025
00037b8
clean up code, document, add command to query possible transitions
Garklein Jan 10, 2025
31b918c
add gui, make communications reliable
Garklein Jan 18, 2025
b2c1099
New changes
AnsonMoorey Jul 19, 2025
dc546e2
Added Fuel fill and LOX fill states, also updated python script
WillytheCHEESE Jul 21, 2025
ccec46e
added commments and adjusted logic
WillytheCHEESE Jul 21, 2025
0c29642
increase font size
Garklein Jul 22, 2025
54331c6
oops broke it
Garklein Jul 22, 2025
af175c8
test if gui blocks when arduino delays
Garklein Jul 22, 2025
3a9936a
3 /= 30
Garklein Jul 22, 2025
7d36671
I added the Valve_Testing state and I corrected some of the delays to…
AnsonMoorey Jul 26, 2025
1d96e98
good stuff
AnsonMoorey Jul 29, 2025
5f7eb20
hopefully fix blocking
Garklein Aug 14, 2025
2878308
Changed the GUI
WillytheCHEESE Aug 21, 2025
5d1c5ed
GUI changes
WillytheCHEESE Aug 22, 2025
c454553
changes
WillytheCHEESE Aug 23, 2025
e637cf9
for ethan
WillytheCHEESE Sep 1, 2025
da4b451
better readme
Garklein Sep 1, 2025
e80dfae
LabJack
WillytheCHEESE Sep 5, 2025
2a3deb9
Changes for ethernet
WillytheCHEESE Sep 7, 2025
526caaa
Changes
WillytheCHEESE Sep 24, 2025
0250d4b
update docs
Garklein Sep 28, 2025
550d93f
changes
WillytheCHEESE Oct 18, 2025
37482db
fix: delay the back to back send commands to allow arduino to fully c…
darrenrahnemoon Nov 21, 2025
6aa1c47
some tweaks
darrenrahnemoon Nov 21, 2025
97a953f
fix gitignore
darrenrahnemoon Nov 21, 2025
9288b35
improvement: base station and phil cart restructure
darrenrahnemoon Nov 26, 2025
f2d8e45
improvement: phil cart arduino code rewrite
darrenrahnemoon Nov 26, 2025
99a5a1a
fix: valves header files
darrenrahnemoon Nov 26, 2025
a7ca07c
untrack __pycache__ files (already in .gitignore)
seofernando25 Jul 25, 2026
8c9fc00
sloppify codebase
seofernando25 Jul 25, 2026
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
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
.vscode/
.vscode/c_cpp_properties.json
.DS_Store
*.pyc
*.pyo
*.pyd
.venv/
.build/
__pycache__/
base_station/logs/client/**/*
!/base_station/logs/client/.gitignore
base_station/logs/labjack/**/*
!/base_station/logs/labjack/.gitignore
base_station/data/
3 changes: 3 additions & 0 deletions .vscode/arduino.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"board": "P1AM-100:samd:P1AM-100_native"
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"python-envs.pythonProjects": [],
"python.defaultInterpreterPath": "${workspaceFolder}/base_station/.venv/bin/python",
"pylint.interpreter": ["${workspaceFolder}/base_station/.venv/bin/python"]
}
1 change: 1 addition & 0 deletions base_station/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
27 changes: 27 additions & 0 deletions base_station/Base Station.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@echo off
setlocal

rem Ensure we run from the directory that contains this script so uv finds the project files.
set "SCRIPT_DIR=%~dp0"
cd /d "%SCRIPT_DIR%"

where uv >nul 2>&1
if errorlevel 1 (
echo uv is not installed or not on PATH. Install it from https://docs.astral.sh/uv/getting-started/installation/
pause
exit /b 1
)

rem Keep a local environment so the dashboard dependencies persist between runs.
set "UV_PROJECT_ENVIRONMENT=%SCRIPT_DIR%\.venv"
echo Ensuring dependencies are installed (first run may take a minute)...

uv run gui
set "STATUS=%ERRORLEVEL%"

if not "%STATUS%"=="0" (
echo Base Station exited with status %STATUS%
)

pause
exit /b %STATUS%
15 changes: 15 additions & 0 deletions base_station/BaseStation.command
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
if ! command -v uv >/dev/null 2>&1; then
echo "uv is not installed or not on PATH. Install it from https://docs.astral.sh/uv/getting-started/installation/"
read -p "Press Return to close."
exit 1
fi
uv run gui
status=$?
if [ $status -ne 0 ]; then
echo "Base Station exited with status $status"
fi
read -p "Press Return to close."
exit $status
93 changes: 93 additions & 0 deletions base_station/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Base Station

Local web dashboard for the PHIL cart state machine, LabJack data logging, and
P1AM firmware tooling. FastAPI and Jinja2 render the pages and authoritative UI
fragments; a pinned, vendored HTMX file swaps those fragments without a frontend
build system. Custom JavaScript is limited to the canvas telemetry charts and
their interaction controls.

## Prerequisites

- [uv](https://docs.astral.sh/uv/getting-started/installation/)
- [Arduino CLI](https://arduino.github.io/arduino-cli/latest/installation/)
- [LabJack LJM](https://support.labjack.com/docs/ljm-software-installer-downloads-t4-t7-t8-digit)
- The P1AM Arduino board core and the libraries listed in
[`../phil_cart/README.txt`](../phil_cart/README.txt)

Install the locked Python environment:

```bash
cd base_station
uv sync --locked
```

## Commands

Run these from the `base_station` directory:

```bash
# Launch the operator GUI
uv run gui

# Equivalent, more explicit name
uv run web

# Compile the P1AM firmware without touching the controller
uv run compile

# Auto-detect a connected P1AM, compile, and upload
uv run upload

# Select a serial port explicitly
uv run upload --port /dev/cu.usbmodem11301
```

`upload` replaces the firmware on the selected controller. `compile` is the
safe validation command for development and CI.

The macOS `BaseStation.command` and Windows `Base Station.bat` launchers also
run `uv run gui`.

## Web interface

- `http://127.0.0.1:8000/` is the sunlight-readable operator dashboard.
- `http://127.0.0.1:8000/diagnostics` shows detailed P1AM and LabJack health,
response timing, rack status, LabJack connection settings, controller restart,
errors, and filterable structured logs.
- `http://127.0.0.1:8000/runs` lists durable acquisition runs, opens tiered
history views, exports CSV files to the browser, and downloads database backups.
- `GET /api/status` supplies the live dashboard snapshot.
- `GET /api/logs` supplies up to 500 recent structured events and accepts the
optional `level`, `component`, and `limit` query parameters.

There is intentionally no Node.js project, `package.json`, or npm build step.
HTMX 2.0.10 is stored under `src/base_station/web/static/vendor/` so the operator
interface does not depend on an internet connection.

FastAPI uses one background poller per process and reads the P1AM's combined
`GET /api/status` endpoint. Browser count therefore does not multiply routine
controller traffic. HTMX action controls use its request classes plus shared
CSS indicators; no custom spinner JavaScript is used.

The LabJack scan rate is captured when a run starts and remains locked until it
stops. Start and Stop are idempotence-guarded by an explicit
starting/running/stopping/idle lifecycle, so repeated UI requests cannot create
multiple stream threads or stop the device twice.

Every acquisition is recorded automatically in `data/acquisition.sqlite3`.
There is no record checkbox and no CSV file is written into the repository;
CSV is generated as a streaming browser download from the Runs page. SQLite is
part of Python's standard library, so durable history adds no runtime dependency.
Dashboard and recorded-run detail share the same Full/Context/Detail navigator
and playback implementation. Drag the navigator to move through time. Drag a
main graph to inspect statistics, or hold Shift while dragging to make the
selected interval the Context view. The dashboard uses one red Record control
that becomes Stop recording and reports duration from the authoritative acquired
sample count.

Runs always store raw differential samples. Graph settings are display-only:
The server bounds every graph response with SQLite time buckets containing the
raw minimum, maximum, mean, and count for both channels. The browser applies the
moving-average window or exponential-moving-average time constant to those
bounded means, so display work stays predictable even for long runs. Changing a
graph filter never changes the raw database, min/max envelope, or CSV export.
Binary file added base_station/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Empty file.
22 changes: 22 additions & 0 deletions base_station/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "base-station"
version = "0.1.0"
description = "Operator GUI and firmware tooling for the liquid state machine"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.116.0,<1",
"jinja2>=3.1.6,<4",
"labjack-ljm>=1.23.0",
"uvicorn>=0.35.0,<1",
]

[project.scripts]
gui = "base_station.app:main"
web = "base_station.app:main"
compile = "base_station.cli:compile_firmware"
upload = "base_station.cli:upload_firmware"

[build-system]
requires = ["uv_build>=0.8.15,<0.9.0"]
build-backend = "uv_build"
2 changes: 2 additions & 0 deletions base_station/src/base_station/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Liquid state-machine base station application."""

9 changes: 9 additions & 0 deletions base_station/src/base_station/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def main() -> None:
"""Launch the local web dashboard."""
from base_station.web.server import run

run()


if __name__ == "__main__":
main()
96 changes: 96 additions & 0 deletions base_station/src/base_station/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Command-line entry points for the GUI and P1AM firmware."""

from __future__ import annotations

import argparse
import json
import shutil
import subprocess
from pathlib import Path

FQBN = "P1AM-100:samd:P1AM-100_native"
PROJECT_DIR = Path(__file__).resolve().parents[2]
REPOSITORY_DIR = PROJECT_DIR.parent
SKETCH_DIR = REPOSITORY_DIR / "phil_cart"
BUILD_DIR = PROJECT_DIR / ".build" / "arduino"


def _arduino_cli() -> str:
executable = shutil.which("arduino-cli")
if executable is None:
raise SystemExit(
"arduino-cli is not installed or is not on PATH. "
"See https://arduino.github.io/arduino-cli/latest/installation/"
)
return executable


def _run_arduino(*arguments: str) -> None:
if not SKETCH_DIR.is_dir():
raise SystemExit(f"Firmware sketch not found: {SKETCH_DIR}")

BUILD_DIR.mkdir(parents=True, exist_ok=True)
command = [
_arduino_cli(),
"compile",
"--fqbn",
FQBN,
"--build-path",
str(BUILD_DIR),
*arguments,
str(SKETCH_DIR),
]
raise SystemExit(subprocess.run(command, check=False).returncode)


def compile_firmware() -> None:
"""Compile the P1AM firmware without modifying connected hardware."""
_run_arduino()


def _detect_p1am_port() -> str:
result = subprocess.run(
[_arduino_cli(), "board", "list", "--format", "json"],
check=True,
capture_output=True,
text=True,
)
payload = json.loads(result.stdout)
detected_ports: list[str] = []

for item in payload.get("detected_ports", []):
port = item.get("port", {})
matching_boards = item.get("matching_boards", [])
if any(board.get("fqbn") == FQBN for board in matching_boards):
address = port.get("address")
if address:
detected_ports.append(address)

if not detected_ports:
raise SystemExit(
"No connected P1AM-100 was detected. Connect it or pass "
"`uv run upload --port /dev/cu...`."
)
if len(detected_ports) > 1:
ports = ", ".join(detected_ports)
raise SystemExit(
f"Multiple P1AM-100 boards detected ({ports}). "
"Select one with `uv run upload --port PORT`."
)
return detected_ports[0]


def upload_firmware() -> None:
"""Compile and upload the P1AM firmware."""
parser = argparse.ArgumentParser(
prog="upload",
description="Compile and upload the phil_cart firmware to a P1AM-100.",
)
parser.add_argument(
"--port",
help="Serial port to use; auto-detected when omitted.",
)
arguments = parser.parse_args()
port = arguments.port or _detect_p1am_port()
print(f"Uploading P1AM firmware through {port}...")
_run_arduino("--upload", "--port", port)
2 changes: 2 additions & 0 deletions base_station/src/base_station/web/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Local web dashboard for base-station operations."""

Loading