From 54e1ba14d1a7c6295037f14dc2d1eb99ab0d0cf8 Mon Sep 17 00:00:00 2001 From: Eric Ratliff Date: Sun, 22 Feb 2026 07:57:55 -0600 Subject: [PATCH] Updated readme --- README.md | 439 +++++++++++++++++++++++++---------------- docs/terminal-demo.svg | 90 +++++++++ 2 files changed, 355 insertions(+), 174 deletions(-) create mode 100644 docs/terminal-demo.svg diff --git a/README.md b/README.md index 616a333..8dc1dbd 100644 --- a/README.md +++ b/README.md @@ -1,265 +1,356 @@ # Anvil -**Arduino project generator and build tool -- forges clean embedded projects.** +**Forge clean, testable Arduino projects from a single command.** -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. +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. -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 terminal demo +

-Anvil is a [Nexus Workshops](https://nxlearn.net) project. +Anvil is a [Nexus Workshops](https://nxlearn.net) project, built for FTC +robotics teams and embedded systems students. -## Install +--- -Download the latest release binary for your platform: +## Getting Started -```bash -# Linux -chmod +x anvil -sudo mv anvil /usr/local/bin/ - -# Windows -- add anvil.exe to a directory in your PATH -``` +### Install +Download the release binary for your platform and add it to 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. +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. -## Your First Project - -Create a project, plug in your board, and upload: +### Create a project ```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 +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 -anvil devices +./build.sh # compile (verify it builds) +./upload.sh # compile + upload to board +./monitor.sh # serial monitor +./test.sh # host-side tests (no board needed) ``` -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). +On Windows, use `build.bat`, `upload.bat`, `monitor.bat`, `test.bat`. +Every script reads settings from `.anvil.toml` -- no Anvil binary required. -### Save a default device +--- + +## 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//` 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.]` 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 -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: +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. -```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. +## Refresh and .anvilignore -### 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 +When you upgrade Anvil, existing projects still have old infrastructure. +Refresh updates managed files without touching your code: ```bash -anvil devices --get +anvil refresh # dry run -- shows what would change +anvil refresh --force # update managed files ``` -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. +### What's protected -### Port resolution priority +An `.anvilignore` file (generated automatically) protects student-authored +files from refresh. The defaults protect: -When the scripts need a port, they check in this order: +- 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.) -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 +Managed infrastructure (build scripts, mock headers, CMakeLists.txt, library +drivers, template example tests) gets updated. Missing files are always +recreated, even without `--force`. -### Specifying a project directory - -Both `--set` and `--get` default to the current directory, but you can point -them at another project: +### Fine-grained control ```bash -anvil devices --set COM3 -d ../other-project -anvil devices --get -d ../other-project +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 ``` -## Updating Project Scripts +Patterns support globs: `test/*.cpp`, `lib/app/*`, `*.h`. -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) + your-project/your-project.ino Sketch (thin shell, no logic) lib/ hal/ - hal.h Hardware abstraction interface - hal_arduino.h Real hardware implementation + hal.h Hardware abstraction (pure virtual) + hal_arduino.h Real Arduino implementation app/ - your-project_app.h Application logic (testable) + your-project_app.h Your application logic (testable) + drivers/ + tmp36/ Sensor driver (interface/impl/mock/sim) + button/ Actuator driver (same pattern) 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) + 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 ``` -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. +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 | + +--- ## Configuration ### .anvil.toml (tracked by git) -Shared project settings. Edit this to change the board, baud rate, or -compiler flags: - ```toml [project] -name = "blink" +name = "weather_station" anvil_version = "1.0.0" +template = "weather" [build] -fqbn = "arduino:avr:uno" +default = "uno" warnings = "more" -include_dirs = ["lib/hal", "lib/app"] +include_dirs = ["lib/hal", "lib/app", "lib/drivers/tmp36"] extra_flags = ["-Werror"] -[monitor] +[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 (not tracked by git) - -Machine-specific settings, created by `anvil devices --set`: +### .anvil.local (gitignored) ```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). +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 diff --git a/docs/terminal-demo.svg b/docs/terminal-demo.svg new file mode 100644 index 0000000..5a8cfb2 --- /dev/null +++ b/docs/terminal-demo.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + anvil -- terminal + + + + $ + anvil new weather_station --template weather --board uno + + + + create weather_station/weather_station/weather_station.ino + create weather_station/lib/app/weather_app.h + create weather_station/lib/hal/hal.h + create weather_station/lib/drivers/tmp36/tmp36.h + create weather_station/test/test_weather.cpp + create weather_station/test/test_unit.cpp + create weather_station/build.sh, build.bat + create weather_station/.anvil.toml + create weather_station/.anvilignore + ... 28 more files + done Project ready. cd weather_station && ./test.sh + + + + $ + cd weather_station && ./test.sh + + + + [==========] Running 14 tests from 4 test suites. + [ PASSED ] WeatherMockTest.ReadsTemperature + [ PASSED ] WeatherSimTest.NoisyReadingsInRange + ... 10 more tests + [ PASSED ] Tmp36PolymorphismTest.AllImplsWorkThroughBasePointer + + + [==========] 14 tests ran. (12 ms total) + [ PASSED ] 14 tests. + + + + $ + ./upload.sh --monitor + + + + +