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
6 changes: 3 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ pi/dist/
stm32/.deps/
stm32/app/build/
stm32/tests/test_build_info
stm32/tests/test_can_control
stm32/tests/test_can_protocol
stm32/tests/test_can_queue
stm32/tests/test_can_rx_queue
stm32/tests/test_can_tx_queue
stm32/tests/test_diagnostics
stm32/tests/test_protocol
stm32/tests/test_protocol_vectors
stm32/tests/test_rf_commands
stm32/tests/test_rf_plan
**/__pycache__/
**/.pytest_cache/
*.pyc
22 changes: 22 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Repository instructions

## Protocol and API changes

- Implement one current protocol and one current API.
- Do not add backward-compatibility paths, legacy fallbacks, dual-format decoding, deprecated aliases, migration shims, or compatibility wrappers unless the user explicitly requests them.
- Do not preserve old wire layouts, payload padding rules, status formats, numeric values, or function signatures solely for compatibility.
- Breaking changes are allowed on active development branches. Update firmware, clients, tools, tests, simulation, and documentation together.
- Prefer exact payload lengths and one canonical code path.
- Remove superseded code instead of retaining it behind feature checks or version branches.

## Reference code

- Use only repositories, branches, or implementations named by the user.
- Do not inspect unrelated rewrites as design references unless the user explicitly asks.

## Firmware layout

- Keep production firmware under `stm32/app/src/{can,rf,platform}` with matching headers under `stm32/app/include/`.
- Keep board GPIO and alternate-function assignments in `platform/board.h`.
- Keep bench experiments and captured media under `stm32/bringup/`, not production source directories.
- Name modules by responsibility: CAN transport/codec, RF planning/execution, or platform support.
70 changes: 32 additions & 38 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,52 @@
# Contributing

Thanks for helping with the uORocketry BeamControl beamforming software. This is a small
team project, so keep changes minimal and reviewable.
Keep changes small, tested, and reviewable.

## Repo layout
## Layout

- `pi/` — Raspberry Pi CAN controller (Python, `src/beamcontrol` package, console entry points `beamctl` and `beamd`).
- `stm32/` — STM32 embedded firmware (C, libopencm3). CAN protocol v1.1 + prioritized TX queue.
- `docs/` — project knowledge base (protocol spec, hardware, operations).
- `protocol/` — shared, language-neutral protocol test vectors.
- `tools/` — repo orchestration (toolchain/dependency fetch, generators, bundle builder).
- `pi/`: Python 3.11 controller, `beamctl`, `beamd`
- `stm32/`: STM32F072 firmware in C/libopencm3
- `protocol/`: shared Python/C vectors
- `simulation/`: Docker, SocketCAN, Renode E2E
- `tools/`: setup, generators, bundles
- `docs/`: protocol, RF, operations

## The one command interface
## Commands

Everything runs through the root `Makefile`. Do not scatter setup instructions.
Use the root `Makefile`:

```bash
make setup # fetch pinned uv + ARM toolchain + libopencm3, sync Python env
make doctor # diagnose required and optional environment capabilities
make test # pi tests + native firmware unit tests + protocol contract
make check # lint + tests + one representative firmware build
make setup
make doctor
make test
make check
make firmware NODE=1
```

## Before you open a PR
Before a PR:

- `make check` must pass clean (lint, all tests, and a representative firmware build).
- New/edited Python must be covered by a test in `pi/tests/` (unit with a fake
transport, or integration on the virtual CAN bus).
- Protocol changes must update `protocol/v1.1-vectors.toml` and regenerate the
C header: `python3 tools/generate-protocol-vectors.py`. Both Python and C
tests consume the same vectors, so they cannot silently drift.
- Firmware queue/priority logic goes in `can_tx_queue.c/h` (host-testable), not
buried in `can_bus.c`.
- Run `make check`.
- Add Python tests under `pi/tests/`.
- For protocol changes, edit `protocol/v2.1-vectors.toml` and run `python3 tools/generate-protocol-vectors.py`.
- Keep queue/priority logic in host-testable `can_tx_queue.c/h`.

## Style

- Python: `ruff` (see `pi/pyproject.toml`), line length 100, target 3.11.
- C: `-Wall -Wextra -Werror`, the firmware never builds with warnings.
- No em dashes in prose; keep messages human and direct.
- Python: Ruff, 100 columns, Python 3.11.
- C: C2x, `-Wall -Wextra -Werror -pedantic`.
- Markdown: direct, concise, no duplicated explanations.
- Repository automation: Python, not new shell scripts.

## Dependencies
## Managed dependencies

- `libopencm3` is pinned by commit + SHA256 in `stm32/third_party/libopencm3.lock`
and fetched by `tools/fetch_libopencm3.py` into gitignored `stm32/.deps/`.
- The ARM cross-toolchain is fetched into gitignored `.tools/` by
`tools/fetch_arm_toolchain.py`.
- `make setup` bootstraps the pinned `uv` binary into `.tools/`; only a host
Python 3.10+ interpreter and the documented OS packages are needed first.
- Repository automation is Python. Do not add new shell scripts.
`make setup` installs pinned tools only in gitignored paths:

- `.tools/`: `uv`, ARM GNU toolchain, caches
- `stm32/.deps/`: libopencm3
- `pi/.venv/`: Python environment

libopencm3 is pinned in `stm32/third_party/libopencm3.lock`.

## Releases

Normal CI builds and smoke-tests the ARM64 Raspberry Pi 5 deployment bundle and
uploads it as a temporary artifact. Permanent GitHub Releases are created
automatically when a `v<version>` tag matching `pi/pyproject.toml` is pushed.
See [`docs/operations/releases.md`](docs/operations/releases.md).
CI builds a temporary ARM64 Pi bundle. A matching `v<version>` tag creates a GitHub Release. See [releases](docs/operations/releases.md).
99 changes: 38 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,73 @@
# BeamControl

BeamControl controls a four-channel RF receiver board from a Raspberry Pi 5 over CAN.
The repository contains the Pi software, STM32 firmware, shared protocol tests, deployment
tooling, and hardware notes.

## Implemented architecture

```text
Operator / browser / beamctl
|
Raspberry Pi 5 + CAN HAT CAN controller node 0
|
CAN 2.0 bus
|
STM32 receiver board CAN receiver node 1..30
|- RF channel 0
|- RF channel 1
|- RF channel 2
`- RF channel 3
```

One complete STM32 receiver board is one CAN node. Its four RF paths are channels inside
that node. Phase shifters, DVGAs, LNAs, filters, detectors, and antenna elements are not CAN
nodes. The Arduino/Wi-Fi and external ADC path shown in the REV3 design drawing is not part
of the implemented software or CAN protocol.
BeamControl controls four-channel RF receiver boards from a Raspberry Pi 5 over CAN.

The protocol uses controller node `0`, receiver-board nodes `1..30`, and broadcast address
`31`. See [`docs/can-protocol.md`](docs/can-protocol.md).
```mermaid
flowchart LR
operator["Operator<br/>beamctl / dashboard"] --> pi["Raspberry Pi 5<br/>CAN node 0"]
pi -->|"CAN 2.0B<br/>500 kbit/s"| stm32["STM32 receiver board<br/>node 1..30"]
stm32 --> channels["RF channels 0..3"]
```

For a concise explanation suitable for a walkthrough or presentation—including what was
added beyond the original STM32 prototype—see [`docs/overview.md`](docs/overview.md).
One board is one CAN node. Phase shifters and DVGAs are board-local devices.

## Repository layout
## Layout

| Path | Purpose |
|:--|:--|
| `pi/` | Python 3.11 controller package, CLI, FastAPI dashboard, and Raspberry Pi deployment files |
| `stm32/` | STM32F072 firmware for one receiver board |
| `protocol/` | Shared Python/C protocol vectors |
| `simulation/` | Docker Compose, Renode platform, and virtual end-to-end test |
| `tools/` | Reproducible setup, diagnostics, bundle building, and checks |
| `docs/` | Current architecture, operations, and hardware design notes |
| `pi/` | Python client, CLI, monitor, dashboard, deployment |
| `stm32/` | STM32F072 firmware |
| `protocol/` | Shared Python/C vectors |
| `simulation/` | Docker/SocketCAN/Renode E2E |
| `tools/` | Setup, checks, bundles |
| `docs/` | Protocol, RF, operations |

## Develop and test
## Develop

```bash
make setup
make doctor
make test
make check
```

`make check` runs linting, all host tests, the protocol contract, and one representative
STM32 build. CI does not prebuild firmware for arbitrary receiver addresses.

Run the real controller and STM32 ELF together over container-local virtual CAN with:

```bash
make simulation-test
```

See [`simulation/README.md`](simulation/README.md) for scope and interactive use.

## Build STM32 firmware

A node ID is required and must be unique on the physical CAN bus:
## Firmware

```bash
make firmware NODE=1
make firmware-size NODE=1
```

Valid receiver-board IDs are `1..30`. The build writes `beamcontrol.elf`,
`beamcontrol.bin`, and `beamcontrol.map` under `stm32/app/build/`.
Node IDs are `1..30` and must be unique. Outputs are under `stm32/app/build/`.

## Use the Pi controller
## Controller

```bash
beamctl discover
beamctl ping 1
beamctl set-phase 1 --channel 2 --state 128

# Individual
beamctl set-phase 1 --state 128 --channel 2
beamctl set-vga 1 --attenuation 8 --channel 2
beamctl set-combined 1 --state 128 --attenuation 8 --channel 2

# Bulk, channel order 0..3
beamctl set-phase 1 --states 128 64 32 16
beamctl set-vga 1 --attenuations 8 9 10 11
beamctl set-combined 1 --states 64 65 66 67 --attenuations 12 13 14 15

beamctl enter-safe 1 --channel 2
beamd --config /etc/uorocketry/beamcontrol.toml
```

The first positional ID is the receiver-board CAN node. `--channel` selects one of that
board's four RF channels (`0..3`).

`beamd` owns the CAN status monitor and serves a read-only FastAPI/Jinja2/HTMX dashboard on
port `8080`. The dashboard remains available when CAN hardware or receiver boards are offline.
`beamd` serves a read-only dashboard on port `8080` and stays available when CAN is offline.

For installation and releases, see:
## Docs

- [Overview](docs/overview.md)
- [CAN protocol](docs/can-protocol.md)
- [RF encoding](docs/rf-control.md)
- [Developer setup](docs/operations/developer-setup.md)
- [Raspberry Pi 5 deployment](docs/operations/pi-provisioning.md)
- [Release process](docs/operations/releases.md)
- [Pi deployment](docs/operations/pi-provisioning.md)
- [Releases](docs/operations/releases.md)
Loading
Loading