Eric Ratliff ca855dd3af Layer 3: Templates as pure data, weather template, .anvilignore refresh system
Templates are now composed declaratively via template.toml -- no Rust code
changes needed to add new templates. The weather station is the first
composed template, demonstrating the full pattern.

Template engine:
- Composed templates declare base, required libraries, and per-board pins
- Overlay mechanism replaces base files (app, sketch, tests) cleanly
- Generic orchestration: extract base, apply overlay, install libs, assign pins
- Template name tracked in .anvil.toml for refresh awareness

Weather template (--template weather):
- WeatherApp with 2-second polling, C/F conversion, serial output
- TMP36 driver: TempSensor interface, Tmp36 impl, Tmp36Mock, Tmp36Sim
- Managed example tests in test_weather.cpp (unit + system)
- Minimal student starters in test_unit.cpp and test_system.cpp
- Per-board pin defaults (A0 for uno, A0 for mega, A0 for nano)

.anvilignore system:
- Glob pattern matching (*, ?) with comments and backslash normalization
- Default patterns protect student tests, app code, sketch, config
- anvil refresh --force respects .anvilignore
- anvil refresh --force --file <path> overrides ignore for one file
- anvil refresh --ignore/--unignore manages patterns from CLI
- Missing managed files always recreated even without --force
- .anvilignore itself is in NEVER_REFRESH (cannot be overwritten)

Refresh rewrite:
- Discovers all template-produced files dynamically (no hardcoded list)
- Extracts fresh template + libraries into temp dir for byte comparison
- Config template field drives which files are managed
- Separated missing-file creation from changed-file updates

428 tests passing on Windows MSVC, 0 warnings.
2026-02-21 21:40:15 -06:00
2026-02-18 20:36:57 -06:00

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 project.

Install

Download the latest release binary for your platform:

# 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:

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:

anvil new blink
cd blink

On Linux/macOS:

./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:

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

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

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:

# 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

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:

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:

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:

[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:

port = "COM3"
vid_pid = "0403:6001"

Building from Source

cargo build --release

The release binary is at target/release/anvil (Linux) or target\release\anvil.exe (Windows).

License

MIT -- see LICENSE.

Description
No description provided
Readme MIT 952 KiB
Languages
Rust 81.6%
C++ 9.7%
Shell 4.5%
Batchfile 3%
PowerShell 1.1%