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.
This commit is contained in:
100
templates/weather/lib/app/__name___app.h.tmpl
Normal file
100
templates/weather/lib/app/__name___app.h.tmpl
Normal file
@@ -0,0 +1,100 @@
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
|
||||
#include <hal.h>
|
||||
#include "tmp36.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* WeatherApp -- Reads a TMP36 temperature sensor and reports to Serial.
|
||||
*
|
||||
* Every READ_INTERVAL_MS (2 seconds), reads the temperature and prints
|
||||
* a formatted line like:
|
||||
*
|
||||
* Temperature: 23.5 C (74.3 F)
|
||||
*
|
||||
* The sensor is injected through the TempSensor interface, so this
|
||||
* class works with real hardware, mocks, or simulations.
|
||||
*
|
||||
* Wiring (TMP36):
|
||||
* Pin 1 (left, flat side facing you) -> 5V
|
||||
* Pin 2 (middle) -> A0 (analog input)
|
||||
* Pin 3 (right) -> GND
|
||||
*/
|
||||
class WeatherApp {
|
||||
public:
|
||||
static constexpr unsigned long READ_INTERVAL_MS = 2000;
|
||||
|
||||
WeatherApp(Hal* hal, TempSensor* sensor)
|
||||
: hal_(hal)
|
||||
, sensor_(sensor)
|
||||
, last_read_ms_(0)
|
||||
, last_celsius_(0.0f)
|
||||
, last_fahrenheit_(0.0f)
|
||||
, read_count_(0)
|
||||
{}
|
||||
|
||||
// Call once from setup()
|
||||
void begin() {
|
||||
hal_->serialBegin(115200);
|
||||
hal_->serialPrintln("WeatherApp started");
|
||||
last_read_ms_ = hal_->millis();
|
||||
readAndReport();
|
||||
}
|
||||
|
||||
// Call repeatedly from loop()
|
||||
void update() {
|
||||
unsigned long now = hal_->millis();
|
||||
if (now - last_read_ms_ >= READ_INTERVAL_MS) {
|
||||
readAndReport();
|
||||
last_read_ms_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Accessors for testing ------------------------------------------------
|
||||
float lastCelsius() const { return last_celsius_; }
|
||||
float lastFahrenheit() const { return last_fahrenheit_; }
|
||||
int readCount() const { return read_count_; }
|
||||
|
||||
private:
|
||||
void readAndReport() {
|
||||
last_celsius_ = sensor_->readCelsius();
|
||||
last_fahrenheit_ = sensor_->readFahrenheit();
|
||||
read_count_++;
|
||||
|
||||
// Format: "Temperature: 23.5 C (74.3 F)"
|
||||
// Use integer math for AVR compatibility (no %f on AVR printf)
|
||||
char buf[48];
|
||||
formatOneDp(buf, sizeof(buf), last_celsius_);
|
||||
hal_->serialPrint("Temperature: ");
|
||||
hal_->serialPrint(buf);
|
||||
hal_->serialPrint(" C (");
|
||||
formatOneDp(buf, sizeof(buf), last_fahrenheit_);
|
||||
hal_->serialPrint(buf);
|
||||
hal_->serialPrintln(" F)");
|
||||
}
|
||||
|
||||
// Format a float as "23.5" or "-5.3" using integer math only.
|
||||
static void formatOneDp(char* buf, size_t len, float value) {
|
||||
int whole = (int)value;
|
||||
int frac = (int)(value * 10.0f) % 10;
|
||||
if (frac < 0) frac = -frac;
|
||||
|
||||
// Handle -0.x case (e.g. -0.3 C)
|
||||
if (value < 0.0f && whole == 0) {
|
||||
snprintf(buf, len, "-0.%d", frac);
|
||||
} else {
|
||||
snprintf(buf, len, "%d.%d", whole, frac);
|
||||
}
|
||||
}
|
||||
|
||||
Hal* hal_;
|
||||
TempSensor* sensor_;
|
||||
unsigned long last_read_ms_;
|
||||
float last_celsius_;
|
||||
float last_fahrenheit_;
|
||||
int read_count_;
|
||||
};
|
||||
|
||||
#endif // APP_H
|
||||
Reference in New Issue
Block a user