Files
anvil/README.md
Eric Ratliff ba402cc187
Some checks failed
CI / Test (Linux) (push) Has been cancelled
CI / Test (Windows MSVC) (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
Supporting auto complete and a build-release script
2026-02-22 08:22:22 -06:00

382 lines
11 KiB
Markdown

# Anvil
**Forge clean, testable Arduino projects from a single command.**
Anvil generates self-contained Arduino projects with hardware abstraction,
test infrastructure, sensor libraries, and a complete build/upload/test
workflow. Once generated, the project stands alone -- Anvil is a scaffolding
tool, not a runtime dependency.
<p align="center">
<img src="docs/terminal-demo.svg" alt="Anvil terminal demo" width="720"/>
</p>
Anvil is a [Nexus Workshops](https://nxlearn.net) project, built for FTC
robotics teams and embedded systems students.
---
## Getting Started
### Install
Download the release binary for your platform and add it to your PATH.
Then run first-time setup:
```bash
anvil setup
```
This installs `arduino-cli` and the `arduino:avr` core. If something is
already installed, Anvil skips it. Run `anvil doctor` at any time to
check your environment.
### Create a project
```bash
anvil new blink
cd blink
```
That's it. You have a complete project with build scripts, a HAL interface,
mock infrastructure, and a starter test file. Plug in your board and run:
```bash
./build.sh # compile (verify it builds)
./upload.sh # compile + upload to board
./monitor.sh # serial monitor
./test.sh # host-side tests (no board needed)
```
On Windows, use `build.bat`, `upload.bat`, `monitor.bat`, `test.bat`.
Every script reads settings from `.anvil.toml` -- no Anvil binary required.
---
## Templates
The default `basic` template gives you a blank canvas. For a richer starting
point, use a composed template:
```bash
anvil new weather_station --template weather --board uno
```
The weather template builds on basic, adding a `WeatherApp` with a TMP36
temperature sensor driver, managed example tests demonstrating both mock and
simulator patterns, and student test starters. To see available templates:
```bash
anvil new --list-templates
```
Templates are pure data -- each is a directory with a `template.toml`
declaring its base, required libraries, and per-board pin defaults. Adding
a new template requires zero Rust code changes.
---
## Libraries
Anvil ships sensor and actuator libraries, each with four files: an abstract
interface, a hardware implementation, a test mock, and a deterministic
simulator.
```bash
anvil add tmp36 --pin A0 # analog temperature sensor
anvil add button --pin 2 # digital pushbutton with debounce sim
```
See what's available and what's installed:
```bash
anvil lib --available # all libraries in the registry
anvil lib # libraries installed in this project
```
Remove a library:
```bash
anvil remove tmp36
```
Each library installs to `lib/drivers/<name>/` with its test file in `test/`.
The `CMakeLists.txt` auto-discovers driver directories, so adding a library
immediately makes it available to your tests.
### The mock/sim split
Every library provides two test doubles:
- **Mock** -- Returns exact values you set. Use in unit tests to verify your
application logic calls the sensor correctly and responds to specific values.
- **Simulator** -- Returns realistic values with configurable noise, bounce,
or drift. Use in system tests to verify your code handles real-world
sensor behavior (jitter, debounce timing, averaging).
This split teaches the difference between interaction testing and behavioral
testing -- a concept that transfers directly to professional software
development.
---
## Pin Management
Anvil knows the pinout of every supported board. Assignments are validated
at the command line, not when you discover a wiring bug at 9 PM.
```bash
anvil pin --assign led 13 --mode output
anvil pin --assign tmp36_data A0 --mode analog
anvil pin --assign spi --cs 10 # SPI bus with chip-select
anvil pin --assign i2c # I2C (pins auto-resolved)
```
Generate a `pins.h` header with `#define` constants:
```bash
anvil pin --generate
```
Audit your wiring against library requirements:
```bash
anvil pin --audit
```
Pin assignments are stored per-board in `.anvil.toml`, so switching between
an Uno and a Mega doesn't lose your wiring for either.
---
## Board Profiles
A single project can target multiple boards:
```bash
anvil board --add mega
anvil board --add nano --baud 57600
anvil board --default mega
```
Each board gets its own `[boards.<name>]` section in `.anvil.toml` with FQBN,
baud rate, and independent pin assignments. Scripts use the default board
unless you pass `--board`:
```bash
./upload.sh --board nano
```
List available presets:
```bash
anvil new --list-boards
```
---
## Device Detection
Anvil's scripts auto-detect your board, but you can pin a specific device:
```bash
anvil devices # list connected boards
anvil devices --set # auto-detect and save to .anvil.local
anvil devices --set COM3 # save a specific port
```
The `.anvil.local` file stores both the port name and the USB VID:PID. If
your board moves to a different port (common on Windows), the scripts find
it by VID:PID automatically.
---
## Refresh and .anvilignore
When you upgrade Anvil, existing projects still have old infrastructure.
Refresh updates managed files without touching your code:
```bash
anvil refresh # dry run -- shows what would change
anvil refresh --force # update managed files
```
### What's protected
An `.anvilignore` file (generated automatically) protects student-authored
files from refresh. The defaults protect:
- Your test files (`test/test_unit.cpp`, `test/test_system.cpp`)
- Your application code (`lib/app/*`)
- Your sketch (`*/*.ino`)
- Your config (`.anvil.toml`)
- Your project files (`.gitignore`, `README.md`, `.editorconfig`, etc.)
Managed infrastructure (build scripts, mock headers, CMakeLists.txt, library
drivers, template example tests) gets updated. Missing files are always
recreated, even without `--force`.
### Fine-grained control
```bash
anvil refresh --ignore "test/my_helper.h" # protect a custom file
anvil refresh --unignore "test/test_unit.cpp" # allow refresh to update it
anvil refresh --force --file test/test_unit.cpp # one-time override
```
Patterns support globs: `test/*.cpp`, `lib/app/*`, `*.h`.
---
## Project Structure
```
your-project/
your-project/your-project.ino Sketch (thin shell, no logic)
lib/
hal/
hal.h Hardware abstraction (pure virtual)
hal_arduino.h Real Arduino implementation
app/
your-project_app.h Your application logic (testable)
drivers/
tmp36/ Sensor driver (interface/impl/mock/sim)
button/ Actuator driver (same pattern)
test/
mocks/
mock_arduino.h Arduino API shims for host compile
mock_hal.h Google Mock HAL
sim_hal.h Stateful simulator HAL
test_weather.cpp Managed example tests (refreshable)
test_unit.cpp Your unit tests (protected)
test_system.cpp Your system tests (protected)
test_tmp36.cpp Library driver tests
CMakeLists.txt Fetches Google Test, compiles tests
build.sh / build.bat Compile sketch
upload.sh / upload.bat Compile + upload to board
monitor.sh / monitor.bat Serial monitor
test.sh / test.bat Run host-side tests
.anvil.toml Project config (tracked by git)
.anvil.local Machine-specific port (gitignored)
.anvilignore File protection rules for refresh
```
The key architectural rule: application code in `lib/app/` depends only on
the `Hal` interface, never on `Arduino.h`. The sketch creates the real HAL
and passes it in. Tests create a mock or simulator HAL instead. This is
constructor injection -- the simplest form of dependency inversion.
---
## Commands
| Command | Description |
|---|---|
| `anvil new NAME [--template T] [--board B]` | Create a new project |
| `anvil new --list-templates` | Show available templates |
| `anvil new --list-boards` | Show available board presets |
| `anvil setup` | Install arduino-cli and AVR core |
| `anvil doctor [--fix]` | Check system prerequisites |
| `anvil devices [--set] [--get] [--clear]` | Manage serial port assignment |
| `anvil add NAME [--pin P]` | Install a device library |
| `anvil remove NAME` | Remove a device library |
| `anvil lib [--available]` | List installed or available libraries |
| `anvil pin --assign NAME PIN [--mode M]` | Assign a pin |
| `anvil pin --generate` | Generate pins.h header |
| `anvil pin --audit [--brief]` | Check wiring against library requirements |
| `anvil pin --capabilities` | Show board pin capabilities |
| `anvil pin --init-from BOARD` | Copy pin assignments from another board |
| `anvil board --add NAME [--id FQBN] [--baud N]` | Add a board profile |
| `anvil board --remove NAME` | Remove a board profile |
| `anvil board --default NAME` | Set the default board |
| `anvil refresh [--force] [--file P] [--ignore P] [--unignore P]` | Update project infrastructure |
| `anvil completions SHELL` | Generate tab-completion script (bash, zsh, fish, powershell) |
---
## Tab Completion
Anvil can generate shell completion scripts so that pressing Tab completes
commands, flags, and arguments:
```bash
# Bash (add to ~/.bashrc)
eval "$(anvil completions bash)"
# Zsh (add to ~/.zshrc)
eval "$(anvil completions zsh)"
# Fish
anvil completions fish > ~/.config/fish/completions/anvil.fish
# PowerShell (add to $PROFILE)
anvil completions powershell | Out-String | Invoke-Expression
```
After setup, `anvil ref<Tab>` completes to `anvil refresh`, and
`anvil pin --<Tab>` shows all available flags.
---
## Configuration
### .anvil.toml (tracked by git)
```toml
[project]
name = "weather_station"
anvil_version = "1.0.0"
template = "weather"
[build]
default = "uno"
warnings = "more"
include_dirs = ["lib/hal", "lib/app", "lib/drivers/tmp36"]
extra_flags = ["-Werror"]
[boards.uno]
fqbn = "arduino:avr:uno"
baud = 115200
[boards.mega]
fqbn = "arduino:avr:mega:cpu=atmega2560"
baud = 115200
[libraries]
tmp36 = "0.1.0"
[pins.uno]
tmp36_data = { pin = 14, mode = "analog" }
```
### .anvil.local (gitignored)
```toml
port = "COM3"
vid_pid = "0403:6001"
```
---
## Building from Source
```bash
cargo build --release
```
Binary at `target/release/anvil` (Linux) or `target\release\anvil.exe`
(Windows). Requires Rust 2021 edition.
The test suite:
```bash
cargo test
```
615 tests (137 unit + 478 integration), ~4 seconds, zero warnings.
---
## License
MIT -- see [LICENSE](LICENSE).