Eric Ratliff 706f420aaa feat: Layer 2 device library system with TMP36 reference driver
Add embedded device library registry with full lifecycle management,
automated test integration, and pin assignment workflow.

Library system:
- Library registry embedded in binary via include_dir! (same as templates)
- library.toml metadata format: name, version, bus type, pins, provided files
- anvil add <name> extracts headers to lib/drivers/<name>/, test files to test/
- anvil remove <name> cleans up all files, config entries, and include paths
- anvil lib lists installed libraries with status
- anvil lib --available shows registry with student-friendly wiring summary
- [libraries] section in .anvil.toml tracks installed libraries and versions
- CMake auto-discovers lib/drivers/*/ for include paths at configure time

TMP36 analog temperature sensor (reference implementation):
- TempSensor abstract interface (readCelsius/readFahrenheit/readRaw)
- Tmp36Analog: real hardware impl via Hal::analogRead with configurable Vref
- Tmp36Mock: programmable values, read counting, no hardware needed
- Tmp36Sim: deterministic noise via seeded LCG for repeatable system tests
- test_tmp36.cpp: 21 Google Test cases covering mock, analog, sim, polymorphism
- TMP36 formula: voltage_mV = raw * Vref_mV / 1024, temp_C = (mV - 500) / 10

Automated test integration:
- Library test files (test_*.cpp) route to test/ during extraction
- CMakeLists.txt auto-discovers test_*.cpp via GLOB, builds each as own target
- anvil remove cleans up test files alongside driver headers
- Zero manual CMake editing: add library, run test --clean, tests appear

Pin assignment integration:
- anvil add <name> --pin A0 does extract + pin assignment in one step
- Without --pin, prints step-by-step wiring guidance with copy-paste commands
- anvil pin --audit flags installed libraries with unassigned pins
- Audit works even with zero existing pin assignments (fixed early-return bug)
- LibraryMeta helpers: wiring_summary(), pin_roles(), default_mode()
- Bus-aware guidance: analog pins, I2C bus registration, SPI with CS selection

UX improvements:
- anvil lib --available shows "Needs: 1 analog pin (e.g. A0)" not raw metadata
- anvil add prints app code example, test code example, and next step
- anvil pin --audit prints exact commands to resolve missing library pins
- anvil remove shows test file deletion in output

Files added:
- libraries/tmp36/library.toml
- libraries/tmp36/src/tmp36.h, tmp36_analog.h, tmp36_mock.h, tmp36_sim.h
- libraries/tmp36/src/test_tmp36.cpp
- src/library/mod.rs

Files modified:
- src/lib.rs, src/main.rs, src/commands/mod.rs
- src/commands/lib.rs (add/remove/list/list_available with --pin support)
- src/commands/pin.rs (audit library pin warnings, print_library_pin_warnings)
- src/project/config.rs (libraries HashMap field)
- templates/basic/test/CMakeLists.txt.tmpl (driver + test auto-discovery)

Tests: 254 total (89 unit + 165 integration)
- 12 library/mod.rs unit tests (registry, extraction, helpers)
- 2 commands/lib.rs unit tests (class name derivation)
- 30+ new integration tests covering library lifecycle, pin integration,
  audit flows, file routing, CMake discovery, config roundtrips, ASCII
  compliance, polymorphism contracts, and idempotent add/remove cycles
2026-02-21 13:14:43 -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%