Files
anvil/README.md
2026-02-18 20:36:57 -06:00

266 lines
8.3 KiB
Markdown

# Anvil
**Arduino project generator and build tool -- forges clean embedded projects.**
A single binary that scaffolds self-contained Arduino projects with hardware
abstraction, Google Mock infrastructure, and a streamlined build/upload/monitor
workflow. Works on Linux and Windows.
Generated projects are fully standalone -- they only need `arduino-cli` in
PATH. The Anvil binary is a scaffolding and diagnostic tool, not a runtime
dependency.
Anvil is a [Nexus Workshops](https://nxlearn.net) project.
## Install
Download the latest release binary for your platform:
```bash
# Linux
chmod +x anvil
sudo mv anvil /usr/local/bin/
# Windows -- add anvil.exe to a directory in your PATH
```
Then run first-time setup:
```bash
anvil setup
```
This checks for `arduino-cli`, installs the `arduino:avr` core, and verifies
your system is ready. If something is missing, Anvil tells you exactly what
to do.
## Your First Project
Create a project, plug in your board, and upload:
```bash
anvil new blink
cd blink
```
On **Linux/macOS**:
```bash
./build.sh # compile only (verify)
./upload.sh # compile + upload to board
./upload.sh --monitor # compile, upload, open serial monitor
./monitor.sh # serial monitor (no compile)
./test/run_tests.sh # host-side unit tests (no board needed)
```
On **Windows**:
```bat
build REM compile only
upload REM compile + upload
upload --monitor REM compile, upload, open serial monitor
monitor REM serial monitor
test\run_tests REM host-side unit tests
```
Every script reads its settings from `.anvil.toml` in the project root.
You edit that file to change the board, baud rate, include paths, or
compiler flags. No Anvil binary required for any of this -- students clone
the repo, plug in a board, and run `upload`.
## Telling Anvil Which Board to Use
When you run `upload` without specifying a port, Anvil's scripts auto-detect
the board. They prefer USB serial ports over legacy motherboard COM ports,
which is usually the right thing. But if you have multiple boards plugged in,
or you want a consistent setup, you can pin a specific device.
### See what's connected
```bash
anvil devices
```
This lists every serial port with its board name, protocol, and -- for USB
devices -- its VID:PID identifier. The VID:PID is a pair of hex codes that
uniquely identifies the USB chip on your board (e.g. `0403:6001` for an FTDI,
`1a86:7523` for a CH340).
### Save a default device
```bash
anvil devices --set COM3 # save a specific port
anvil devices --set # auto-detect the best port and save it
```
Both forms write a `.anvil.local` file in your project directory. This file
stores the port name *and* the VID:PID of the device on that port. It looks
like this:
```toml
# Machine-specific Anvil config (not tracked by git)
port = "COM3"
vid_pid = "0403:6001"
```
The `.anvil.local` file is gitignored by default. Each machine that works on
the project keeps its own copy with its own port assignment.
### Why VID:PID matters
On Windows, if you unplug a USB device and plug it back in to a different
port, it often gets a new COM number (COM3 becomes COM5). The VID:PID
doesn't change -- it's baked into the USB chip. When the scripts see a
VID:PID in `.anvil.local`, they search for that device on whatever port
it's currently on. If it moved, you'll see:
```
info Device 0403:6001 found on COM5 (moved from COM3)
```
No manual intervention needed.
### Check what's saved
```bash
anvil devices --get
```
This shows the saved port and VID:PID for the current project, whether the
device is currently connected, and whether it has moved to a different port.
### Port resolution priority
When the scripts need a port, they check in this order:
1. **`-p` flag** -- `upload.bat -p COM3` always wins
2. **VID:PID** from `.anvil.local` -- finds the device regardless of port number
3. **Saved port** from `.anvil.local` -- fallback if VID:PID lookup fails
4. **Auto-detect** -- prefers USB serial over legacy COM ports
### Specifying a project directory
Both `--set` and `--get` default to the current directory, but you can point
them at another project:
```bash
anvil devices --set COM3 -d ../other-project
anvil devices --get -d ../other-project
```
## Updating Project Scripts
When you upgrade Anvil, your existing projects still have the old build
scripts. The `refresh` command updates them to the latest versions without
touching your source code, configuration, or test files:
```bash
anvil refresh # check current project (dry run)
anvil refresh --force # overwrite differing scripts
anvil refresh ../other-project # check a different project
```
Refresh only replaces build infrastructure: `build.sh`, `build.bat`,
`upload.sh`, `upload.bat`, `monitor.sh`, `monitor.bat`, `_detect_port.ps1`,
and `test/run_tests.sh`, `test/run_tests.bat`. It never touches `.anvil.toml`,
`.anvil.local`, your `.ino` files, HAL headers, app code, mocks, or test
sources.
## What Anvil Does vs. What the Project Does
| Need Anvil for | Don't need Anvil for |
|---------------------------------|-------------------------------|
| `anvil new` (create project) | `./build.sh` (compile) |
| `anvil setup` (install core) | `./upload.sh` (flash) |
| `anvil doctor` (diagnose) | `./monitor.sh` (serial) |
| `anvil devices` (port scan) | `./test/run_tests.sh` (test) |
| `anvil refresh` (update scripts)| |
Once a project is created, Anvil is optional for daily work.
## Commands
| Command | Description |
|----------------------------------|------------------------------------------------|
| `anvil new NAME` | Create a new project with HAL and test scaffold |
| `anvil setup` | Install arduino-cli and AVR core |
| `anvil doctor` | Check system prerequisites |
| `anvil devices` | List connected boards and serial ports |
| `anvil devices --set [PORT]` | Save default port + VID:PID to .anvil.local |
| `anvil devices --get` | Show saved port for this project |
| `anvil refresh [DIR] [--force]` | Update project scripts to latest templates |
## Project Structure
Every generated project follows the same layout:
```
your-project/
your-project/your-project.ino Entry point (setup + loop)
lib/
hal/
hal.h Hardware abstraction interface
hal_arduino.h Real hardware implementation
app/
your-project_app.h Application logic (testable)
test/
mocks/
mock_hal.h Google Mock HAL
sim_hal.h Stateful simulator HAL
test_unit.cpp Unit tests
CMakeLists.txt Test build system
run_tests.sh / .bat Test runner
build.sh / build.bat Compile sketch
upload.sh / upload.bat Compile + upload to board
monitor.sh / monitor.bat Serial monitor
_detect_port.ps1 Port detection helper (Windows)
.anvil.toml Project config (tracked by git)
.anvil.local Machine-specific config (gitignored)
```
All hardware access goes through the `Hal` interface in `lib/hal/hal.h`.
Application code in `lib/app/` depends only on `Hal`, never on `Arduino.h`
directly. This means the app logic compiles and runs on the host for testing
with Google Mock -- no board required.
## Configuration
### .anvil.toml (tracked by git)
Shared project settings. Edit this to change the board, baud rate, or
compiler flags:
```toml
[project]
name = "blink"
anvil_version = "1.0.0"
[build]
fqbn = "arduino:avr:uno"
warnings = "more"
include_dirs = ["lib/hal", "lib/app"]
extra_flags = ["-Werror"]
[monitor]
baud = 115200
```
### .anvil.local (not tracked by git)
Machine-specific settings, created by `anvil devices --set`:
```toml
port = "COM3"
vid_pid = "0403:6001"
```
## Building from Source
```bash
cargo build --release
```
The release binary is at `target/release/anvil` (Linux) or
`target\release\anvil.exe` (Windows).
## License
MIT -- see [LICENSE](LICENSE).