14 KiB
Gaugecontroller v2.0 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Centralise all gauge configuration in gauge_config.h and make the three inconsistently-written parsers use sscanf like the rest.
Architecture: A new header file holds one constexpr GaugeConfig table — pin assignments and motion defaults merged — and derives GAUGE_COUNT from its length. The sketch includes this header, removes the old GaugePins struct and all hardcoded defaults, and initialises Gauge runtime state from the table in setup(). Three parse functions are then rewritten from manual string splitting to sscanf.
Tech Stack: Arduino (AVR/Mega), arduino-cli, C++11 constexpr.
Note on testing: This is a bare-metal Arduino sketch with no unit-test framework. Each task's verification step is a clean compile with
arduino-cli. Functional testing requires the physical hardware; the plan notes what to check over serial when hardware is available.
File Map
| File | Action | Responsibility |
|---|---|---|
Gaugecontroller/gauge_config.h |
Create | All pin assignments and motion defaults; GAUGE_COUNT |
Gaugecontroller/Gaugecontroller.ino |
Modify | Remove GaugePins, add include, strip Gauge defaults, update all references, rewrite 3 parsers |
Task 1: Create gauge_config.h
Files:
-
Create:
Gaugecontroller/gauge_config.h -
Step 1: Create the file
Create
Gaugecontroller/gauge_config.hwith the following content:#pragma once #include <stdint.h> struct GaugeConfig { // Hardware uint8_t dirPin; uint8_t stepPin; int8_t enablePin; // -1 = no enable pin bool dirInverted; bool stepActiveHigh; bool enableActiveLow; // Motion defaults (integers; cast to float in setup()) long minPos; long maxPos; long homingBackoffSteps; int maxSpeed; // steps/s int accel; // steps/s² int homingSpeed; // steps/s }; constexpr GaugeConfig gaugeConfigs[] = { // dir step en dirInv stepHi enLow min max backoff speed accel homeSpd { 48, 49, -1, false, true, true, 0, 3780, 3800, 4000, 6000, 500 }, { 8, 9, -1, true, true, true, 0, 3780, 3800, 4000, 6000, 500 }, { 52, 53, -1, false, true, true, 0, 3780, 3800, 4000, 6000, 500 }, { 50, 51, -1, false, true, true, 0, 3780, 3800, 4000, 6000, 500 }, }; static const uint8_t GAUGE_COUNT = sizeof(gaugeConfigs) / sizeof(gaugeConfigs[0]);To add a fifth gauge later: append one row to
gaugeConfigs[]. Nothing else changes.
Task 2: Wire gauge_config.h into Gaugecontroller.ino
Files:
- Modify:
Gaugecontroller/Gaugecontroller.ino
This task makes six targeted edits in order. Each edit is shown as old → new. Do them top-to-bottom so line numbers don't shift unexpectedly.
-
Step 1: Add the include
After the existing three
#includelines at the top, add:#include "gauge_config.h"The top of the file should now read:
#include <Arduino.h> #include <avr/interrupt.h> #include <math.h> #include "gauge_config.h" -
Step 2: Remove
GAUGE_COUNTandGaugePinsDelete these two blocks entirely (they are now in
gauge_config.h):static const uint8_t GAUGE_COUNT = 4;struct GaugePins { uint8_t dirPin; uint8_t stepPin; int8_t enablePin; // -1 means there is no enable pin bool dirInverted; bool stepActiveHigh; bool enableActiveLow; }; constexpr GaugePins gaugePins[GAUGE_COUNT] = { // dir, step, en, dirInv, stepHigh, enActiveLow {48, 49, -1, false, true, true}, // Gauge 0 {8, 9, -1, true, true, true}, // Gauge 1 {52, 53, -1, false, true, true}, // Gauge 2 {50, 51, -1, false, true, true}, // Gauge 3 }; -
Step 3: Strip hardcoded defaults from
struct GaugeIn the
Gaugestruct definition, remove the numeric defaults from the six motion fields. Change:long minPos = 0; long maxPos = 3780; long homingBackoffSteps = 3800; // Deliberately a touch past full reverse travel. float velocity = 0.0f; float maxSpeed = 4000.0f; float accel = 6000.0f; float homingSpeed = 500.0f;To:
long minPos = 0; long maxPos = 0; long homingBackoffSteps = 0; float velocity = 0.0f; float maxSpeed = 0.0f; float accel = 0.0f; float homingSpeed = 0.0f;These will be populated from
gaugeConfigs[i]insetup()(Step 5). -
Step 4: Update
gaugePins→gaugeConfigsreferences outsidesetup()Three functions reference
gaugePins. Update each one:writeDirectionPin(~line 106):// Before bool level = gaugePins[id].dirInverted ? !forward : forward; // After bool level = gaugeConfigs[id].dirInverted ? !forward : forward;writeStepPin(~line 111):// Before bool level = gaugePins[id].stepActiveHigh ? active : !active; // After bool level = gaugeConfigs[id].stepActiveHigh ? active : !active;configureStepperHardware(~line 152):// Before stepperHardware[id].stepPort = portOutputRegister(digitalPinToPort(gaugePins[id].stepPin)); stepperHardware[id].stepMask = digitalPinToBitMask(gaugePins[id].stepPin); stepperHardware[id].dirPort = portOutputRegister(digitalPinToPort(gaugePins[id].dirPin)); stepperHardware[id].dirMask = digitalPinToBitMask(gaugePins[id].dirPin); // After stepperHardware[id].stepPort = portOutputRegister(digitalPinToPort(gaugeConfigs[id].stepPin)); stepperHardware[id].stepMask = digitalPinToBitMask(gaugeConfigs[id].stepPin); stepperHardware[id].dirPort = portOutputRegister(digitalPinToPort(gaugeConfigs[id].dirPin)); stepperHardware[id].dirMask = digitalPinToBitMask(gaugeConfigs[id].dirPin);setEnable(~line 291):// Before int8_t pin = gaugePins[id].enablePin; if (pin < 0) return; bool level = gaugePins[id].enableActiveLow ? !en : en; // After int8_t pin = gaugeConfigs[id].enablePin; if (pin < 0) return; bool level = gaugeConfigs[id].enableActiveLow ? !en : en; -
Step 5: Update
setup()— pin references and add motion default initIn
setup(), theforloop currently reads:for (uint8_t i = 0; i < GAUGE_COUNT; i++) { pinMode(gaugePins[i].dirPin, OUTPUT); pinMode(gaugePins[i].stepPin, OUTPUT); configureStepperHardware(i); digitalWrite(gaugePins[i].dirPin, LOW); digitalWrite(gaugePins[i].stepPin, gaugePins[i].stepActiveHigh ? LOW : HIGH); if (gaugePins[i].enablePin >= 0) { pinMode(gaugePins[i].enablePin, OUTPUT); setEnable(i, true); } gauges[i].lastUpdateMicros = micros(); }Replace it with:
for (uint8_t i = 0; i < GAUGE_COUNT; i++) { pinMode(gaugeConfigs[i].dirPin, OUTPUT); pinMode(gaugeConfigs[i].stepPin, OUTPUT); configureStepperHardware(i); digitalWrite(gaugeConfigs[i].dirPin, LOW); digitalWrite(gaugeConfigs[i].stepPin, gaugeConfigs[i].stepActiveHigh ? LOW : HIGH); if (gaugeConfigs[i].enablePin >= 0) { pinMode(gaugeConfigs[i].enablePin, OUTPUT); setEnable(i, true); } gauges[i].minPos = gaugeConfigs[i].minPos; gauges[i].maxPos = gaugeConfigs[i].maxPos; gauges[i].homingBackoffSteps = gaugeConfigs[i].homingBackoffSteps; gauges[i].maxSpeed = (float)gaugeConfigs[i].maxSpeed; gauges[i].accel = (float)gaugeConfigs[i].accel; gauges[i].homingSpeed = (float)gaugeConfigs[i].homingSpeed; gauges[i].lastUpdateMicros = micros(); } -
Step 6: Compile and verify clean
arduino-cli compile --fqbn arduino:avr:mega GaugecontrollerExpected: zero errors, zero warnings about
gaugePinsorGAUGE_COUNT. If the compiler reports "use of undeclared identifier 'gaugePins'", grep for any remaining reference:grep -n "gaugePins" Gaugecontroller/Gaugecontroller.inoShould return nothing.
-
Step 7: Commit
git add Gaugecontroller/gauge_config.h Gaugecontroller/Gaugecontroller.ino git commit -m "refactor: centralise gauge config in gauge_config.h"
Task 3: Rewrite parseSpeed, parseAccel, parseSweep to use sscanf
Files:
-
Modify:
Gaugecontroller/Gaugecontroller.ino -
Step 1: Replace
parseSpeedFind and replace the entire
parseSpeedfunction:// Before (~15 lines) bool parseSpeed(const String& line) { int firstSpace = line.indexOf(' '); int secondSpace = line.indexOf(' ', firstSpace + 1); if (firstSpace < 0 || secondSpace < 0) return false; if (line.substring(0, firstSpace) != "SPEED") return false; int id = line.substring(firstSpace + 1, secondSpace).toInt(); float speed = line.substring(secondSpace + 1).toFloat(); if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; } if (speed <= 0.0f) { sendReply("ERR BAD_SPEED"); return true; } gauges[id].maxSpeed = speed; sendReply("OK"); return true; }// After bool parseSpeed(const String& line) { int id; float speed; if (sscanf(line.c_str(), "SPEED %d %f", &id, &speed) == 2) { if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; } if (speed <= 0.0f) { sendReply("ERR BAD_SPEED"); return true; } gauges[id].maxSpeed = speed; sendReply("OK"); return true; } return false; } -
Step 2: Replace
parseAccel// Before (~15 lines) bool parseAccel(const String& line) { int firstSpace = line.indexOf(' '); int secondSpace = line.indexOf(' ', firstSpace + 1); if (firstSpace < 0 || secondSpace < 0) return false; if (line.substring(0, firstSpace) != "ACCEL") return false; int id = line.substring(firstSpace + 1, secondSpace).toInt(); float accel = line.substring(secondSpace + 1).toFloat(); if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; } if (accel <= 0.0f) { sendReply("ERR BAD_ACCEL"); return true; } gauges[id].accel = accel; sendReply("OK"); return true; }// After bool parseAccel(const String& line) { int id; float accel; if (sscanf(line.c_str(), "ACCEL %d %f", &id, &accel) == 2) { if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; } if (accel <= 0.0f) { sendReply("ERR BAD_ACCEL"); return true; } gauges[id].accel = accel; sendReply("OK"); return true; } return false; } -
Step 3: Replace
parseSweep// Before (~20 lines) bool parseSweep(const String& line) { int firstSpace = line.indexOf(' '); int secondSpace = line.indexOf(' ', firstSpace + 1); int thirdSpace = line.indexOf(' ', secondSpace + 1); if (firstSpace < 0 || secondSpace < 0 || thirdSpace < 0) return false; if (line.substring(0, firstSpace) != "SWEEP") return false; int id = line.substring(firstSpace + 1, secondSpace).toInt(); float accel = line.substring(secondSpace + 1, thirdSpace).toFloat(); float speed = line.substring(thirdSpace + 1).toFloat(); if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; } Gauge& g = gauges[id]; if (accel <= 0.0f || speed <= 0.0f) { g.sweepEnabled = false; g.velocity = 0.0f; stopTimerStepping(id); sendReply("OK"); return true; } g.accel = accel; g.maxSpeed = speed; g.sweepEnabled = true; g.sweepTowardMax = true; atomicWriteLong(g.targetPos, g.maxPos); sendReply("OK"); return true; }// After bool parseSweep(const String& line) { int id; float accel, speed; if (sscanf(line.c_str(), "SWEEP %d %f %f", &id, &accel, &speed) == 3) { if (id < 0 || id >= GAUGE_COUNT) { sendReply("ERR BAD_ID"); return true; } Gauge& g = gauges[id]; if (accel <= 0.0f || speed <= 0.0f) { g.sweepEnabled = false; g.velocity = 0.0f; stopTimerStepping(id); sendReply("OK"); return true; } g.accel = accel; g.maxSpeed = speed; g.sweepEnabled = true; g.sweepTowardMax = true; atomicWriteLong(g.targetPos, g.maxPos); sendReply("OK"); return true; } return false; } -
Step 4: Verify no
indexOf/substringremain in anyparse*functiongrep -n "indexOf\|substring" Gaugecontroller/Gaugecontroller.inoExpected: no output. If any lines appear, check which function still uses the old pattern and redo that step.
-
Step 5: Compile and verify clean
arduino-cli compile --fqbn arduino:avr:mega GaugecontrollerExpected: zero errors.
-
Step 6: Commit
git add Gaugecontroller/Gaugecontroller.ino git commit -m "refactor: uniform sscanf parsing in parseSpeed, parseAccel, parseSweep"
Optional hardware smoke-test (when board is available)
After uploading, send these commands over serial and confirm expected replies:
PING → PONG
HOMEALL → OK (then each gauge homes; HOMED 0..3 appear on debug port)
POS? → POS 0 0 0 1 0 0 (×4, one per gauge)
SET 0 1000 → OK
SPEED 0 2000 → OK
ACCEL 0 8000 → OK
SWEEP 0 6000 4000 → OK
SWEEP 0 0 0 → OK (stops sweep)
No new error codes were introduced; all existing commands should behave identically to v1.