Skip to content

Commit 8c9c882

Browse files
Merge pull request #2 from uorocketry/fix/rf-serial-command-contract
2 parents 85d3895 + 36ecb92 commit 8c9c882

117 files changed

Lines changed: 4066 additions & 3250 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ pi/dist/
77
stm32/.deps/
88
stm32/app/build/
99
stm32/tests/test_build_info
10-
stm32/tests/test_can_control
1110
stm32/tests/test_can_protocol
12-
stm32/tests/test_can_queue
11+
stm32/tests/test_can_rx_queue
1312
stm32/tests/test_can_tx_queue
1413
stm32/tests/test_diagnostics
15-
stm32/tests/test_protocol
1614
stm32/tests/test_protocol_vectors
15+
stm32/tests/test_rf_commands
16+
stm32/tests/test_rf_plan
1717
**/__pycache__/
1818
**/.pytest_cache/
1919
*.pyc

AGENTS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Repository instructions
2+
3+
## Protocol and API changes
4+
5+
- Implement one current protocol and one current API.
6+
- Do not add backward-compatibility paths, legacy fallbacks, dual-format decoding, deprecated aliases, migration shims, or compatibility wrappers unless the user explicitly requests them.
7+
- Do not preserve old wire layouts, payload padding rules, status formats, numeric values, or function signatures solely for compatibility.
8+
- Breaking changes are allowed on active development branches. Update firmware, clients, tools, tests, simulation, and documentation together.
9+
- Prefer exact payload lengths and one canonical code path.
10+
- Remove superseded code instead of retaining it behind feature checks or version branches.
11+
12+
## Reference code
13+
14+
- Use only repositories, branches, or implementations named by the user.
15+
- Do not inspect unrelated rewrites as design references unless the user explicitly asks.
16+
17+
## Firmware layout
18+
19+
- Keep production firmware under `stm32/app/src/{can,rf,platform}` with matching headers under `stm32/app/include/`.
20+
- Keep board GPIO and alternate-function assignments in `platform/board.h`.
21+
- Keep bench experiments and captured media under `stm32/bringup/`, not production source directories.
22+
- Name modules by responsibility: CAN transport/codec, RF planning/execution, or platform support.

CONTRIBUTING.md

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,52 @@
11
# Contributing
22

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

6-
## Repo layout
5+
## Layout
76

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

14-
## The one command interface
14+
## Commands
1515

16-
Everything runs through the root `Makefile`. Do not scatter setup instructions.
16+
Use the root `Makefile`:
1717

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

26-
## Before you open a PR
26+
Before a PR:
2727

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

3733
## Style
3834

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

43-
## Dependencies
40+
## Managed dependencies
4441

45-
- `libopencm3` is pinned by commit + SHA256 in `stm32/third_party/libopencm3.lock`
46-
and fetched by `tools/fetch_libopencm3.py` into gitignored `stm32/.deps/`.
47-
- The ARM cross-toolchain is fetched into gitignored `.tools/` by
48-
`tools/fetch_arm_toolchain.py`.
49-
- `make setup` bootstraps the pinned `uv` binary into `.tools/`; only a host
50-
Python 3.10+ interpreter and the documented OS packages are needed first.
51-
- Repository automation is Python. Do not add new shell scripts.
42+
`make setup` installs pinned tools only in gitignored paths:
43+
44+
- `.tools/`: `uv`, ARM GNU toolchain, caches
45+
- `stm32/.deps/`: libopencm3
46+
- `pi/.venv/`: Python environment
47+
48+
libopencm3 is pinned in `stm32/third_party/libopencm3.lock`.
5249

5350
## Releases
5451

55-
Normal CI builds and smoke-tests the ARM64 Raspberry Pi 5 deployment bundle and
56-
uploads it as a temporary artifact. Permanent GitHub Releases are created
57-
automatically when a `v<version>` tag matching `pi/pyproject.toml` is pushed.
58-
See [`docs/operations/releases.md`](docs/operations/releases.md).
52+
CI builds a temporary ARM64 Pi bundle. A matching `v<version>` tag creates a GitHub Release. See [releases](docs/operations/releases.md).

README.md

Lines changed: 38 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,73 @@
11
# BeamControl
22

3-
BeamControl controls a four-channel RF receiver board from a Raspberry Pi 5 over CAN.
4-
The repository contains the Pi software, STM32 firmware, shared protocol tests, deployment
5-
tooling, and hardware notes.
6-
7-
## Implemented architecture
8-
9-
```text
10-
Operator / browser / beamctl
11-
|
12-
Raspberry Pi 5 + CAN HAT CAN controller node 0
13-
|
14-
CAN 2.0 bus
15-
|
16-
STM32 receiver board CAN receiver node 1..30
17-
|- RF channel 0
18-
|- RF channel 1
19-
|- RF channel 2
20-
`- RF channel 3
21-
```
22-
23-
One complete STM32 receiver board is one CAN node. Its four RF paths are channels inside
24-
that node. Phase shifters, DVGAs, LNAs, filters, detectors, and antenna elements are not CAN
25-
nodes. The Arduino/Wi-Fi and external ADC path shown in the REV3 design drawing is not part
26-
of the implemented software or CAN protocol.
3+
BeamControl controls four-channel RF receiver boards from a Raspberry Pi 5 over CAN.
274

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

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

34-
## Repository layout
14+
## Layout
3515

3616
| Path | Purpose |
3717
|:--|:--|
38-
| `pi/` | Python 3.11 controller package, CLI, FastAPI dashboard, and Raspberry Pi deployment files |
39-
| `stm32/` | STM32F072 firmware for one receiver board |
40-
| `protocol/` | Shared Python/C protocol vectors |
41-
| `simulation/` | Docker Compose, Renode platform, and virtual end-to-end test |
42-
| `tools/` | Reproducible setup, diagnostics, bundle building, and checks |
43-
| `docs/` | Current architecture, operations, and hardware design notes |
18+
| `pi/` | Python client, CLI, monitor, dashboard, deployment |
19+
| `stm32/` | STM32F072 firmware |
20+
| `protocol/` | Shared Python/C vectors |
21+
| `simulation/` | Docker/SocketCAN/Renode E2E |
22+
| `tools/` | Setup, checks, bundles |
23+
| `docs/` | Protocol, RF, operations |
4424

45-
## Develop and test
25+
## Develop
4626

4727
```bash
4828
make setup
4929
make doctor
5030
make test
5131
make check
52-
```
53-
54-
`make check` runs linting, all host tests, the protocol contract, and one representative
55-
STM32 build. CI does not prebuild firmware for arbitrary receiver addresses.
56-
57-
Run the real controller and STM32 ELF together over container-local virtual CAN with:
58-
59-
```bash
6032
make simulation-test
6133
```
6234

63-
See [`simulation/README.md`](simulation/README.md) for scope and interactive use.
64-
65-
## Build STM32 firmware
66-
67-
A node ID is required and must be unique on the physical CAN bus:
35+
## Firmware
6836

6937
```bash
7038
make firmware NODE=1
7139
make firmware-size NODE=1
7240
```
7341

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

77-
## Use the Pi controller
44+
## Controller
7845

7946
```bash
8047
beamctl discover
8148
beamctl ping 1
82-
beamctl set-phase 1 --channel 2 --state 128
49+
50+
# Individual
51+
beamctl set-phase 1 --state 128 --channel 2
52+
beamctl set-vga 1 --attenuation 8 --channel 2
53+
beamctl set-combined 1 --state 128 --attenuation 8 --channel 2
54+
55+
# Bulk, channel order 0..3
56+
beamctl set-phase 1 --states 128 64 32 16
57+
beamctl set-vga 1 --attenuations 8 9 10 11
58+
beamctl set-combined 1 --states 64 65 66 67 --attenuations 12 13 14 15
59+
60+
beamctl enter-safe 1 --channel 2
8361
beamd --config /etc/uorocketry/beamcontrol.toml
8462
```
8563

86-
The first positional ID is the receiver-board CAN node. `--channel` selects one of that
87-
board's four RF channels (`0..3`).
88-
89-
`beamd` owns the CAN status monitor and serves a read-only FastAPI/Jinja2/HTMX dashboard on
90-
port `8080`. The dashboard remains available when CAN hardware or receiver boards are offline.
64+
`beamd` serves a read-only dashboard on port `8080` and stays available when CAN is offline.
9165

92-
For installation and releases, see:
66+
## Docs
9367

68+
- [Overview](docs/overview.md)
69+
- [CAN protocol](docs/can-protocol.md)
70+
- [RF encoding](docs/rf-control.md)
9471
- [Developer setup](docs/operations/developer-setup.md)
95-
- [Raspberry Pi 5 deployment](docs/operations/pi-provisioning.md)
96-
- [Release process](docs/operations/releases.md)
72+
- [Pi deployment](docs/operations/pi-provisioning.md)
73+
- [Releases](docs/operations/releases.md)

0 commit comments

Comments
 (0)