Compare commits
No commits in common. "041ad2a18f209d201d2ce757a2b466a8df05e1ed" and "842d95eecafa2d7a56c7946f913c461eba9baaa7" have entirely different histories.
041ad2a18f
...
842d95eeca
|
|
@ -723,10 +723,6 @@ Three rules, each protecting against a different silent failure:
|
|||
- **A partial answer does not unlock the axis.** Most keys landing is the common
|
||||
case when a declaration is nearly complete, and it must not look like success.
|
||||
|
||||
> An axis waiting on its config reports `NEEDCFG` in the dashboard and `C` in the
|
||||
> `ST` line — deliberately not red, and deliberately not "no telemetry": the link
|
||||
> is fine and the axis is talking, it just has not been told what it is driving.
|
||||
>
|
||||
> **Status: the host half only.** The host answers `NEED` and the responder is
|
||||
> verified end to end, but the firmware that *asks* is not written yet. Set
|
||||
> `[Features] mock_needcfg = 1` to make the simulated controller run the
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ Phase 1 is deployable on its own. Phase 2 requires it.
|
|||
## Progress
|
||||
|
||||
Branch `worktree-hardware-config` (worktree at `.claude/worktrees/hardware-config`).
|
||||
298 tests pass, up from a 204 baseline.
|
||||
295 tests pass, up from a 204 baseline.
|
||||
|
||||
| § | Item | State |
|
||||
|----|------|-------|
|
||||
|
|
@ -435,14 +435,12 @@ Branch `worktree-hardware-config` (worktree at `.claude/worktrees/hardware-confi
|
|||
| 3 | `hardware.ini`: `[Board]` / `[Hardware]` typed, layered over `config.ini`, shadowed keys warned | **done**, 5 tests |
|
||||
| 4 | Resolution chain `--hardware` / `$FGC_HARDWARE` / `/etc/...` / beside `config.ini` | **done**, 2 tests |
|
||||
| 8 | `config/hardware.example.ini` documenting the shape | **done**, and the `hardware/lattepanda-iota/` setup kit now exists too (`install.sh`, `packages.txt`, three precondition files) |
|
||||
| 8 | `install.sh` writes the drivetrain block | **done.** One `[Motor.<axis>]` per declared axis, with `--r-sense` / `--run-current` / `--homing-current` / `--travel-deg` / `--steps-per-rev` / `--sw-mode` for what an installer knows and commented `; TODO` lines for what nothing can probe. Needed because the firmware now refuses to move undeclared |
|
||||
| 10 | Presence reconciliation (declared vs found) | **done**, 13 tests. `HardwarePresence.h` compares `[Hardware]` against what came up; warn-only by default, refusing under `[Features] strict_presence` with `--degraded` as the field-triage escape (decision 4) |
|
||||
| 11 | `test hardware` | **done**, 11 tests. Five leaves (`axes` / `motor` / `imu` / `env` / `camera`) under a new `hardware` subsystem; included in a bare `test`, and deliberately not gated on a homed gimbal. Nothing declared reports SKIP, never PASS |
|
||||
| 5 | Delete the `Application.cpp` speed literals | **done**, 13 tests. Decision 2 answered: speed is declared as `[Motor.<axis>] scan_speed_deg_s` (default **8.0 deg/s**) and converted per tower through `counts_per_deg` (`AxisSpeed.h`). The literals sent 36.4 deg/s against an intended 21.8 |
|
||||
| 6 | Config handshake — **host half** | **done**, 13 tests. `FirmwareConfig.h` parses `NEED` and plans the `SET` lines; unanswered keys and unknown keys are reported separately. `[Features] mock_needcfg` makes the simulated controller run the handshake, so the host half is verified with no firmware. **The firmware half is not written** |
|
||||
| 6 | Config handshake — **firmware half** | **done and built** on both targets 2026-09-18 (iota_rp2040 and leonardo, `./deploy.sh --compile-only`); behaviour still unproven on a running rig. `NEEDCFG` state (`ST` code `C`), `NEED` emitted with the banner, `SET <axis> <key> <value>`, `USE DEFAULTS`, and the init block split so the residual-motion stop still runs at boot. 33 native tests pass under `pio test -e native`. **Behaviour on a running rig is the one check still outstanding** |
|
||||
| 6 | Config handshake — **firmware half** | **written, NOT compile-verified.** `NEEDCFG` state (`ST` code `C`), `NEED` emitted with the banner, `SET <axis> <key> <value>`, `USE DEFAULTS`, and the init block split so the residual-motion stop still runs at boot. Parser verified locally (30 native tests, g++); the rest type-checks against stubs on both board branches but has never been built by PlatformIO or run on hardware |
|
||||
| 6 | Adoption check generalised | **done**, 9 tests. `DUMP` echoes every host-owned value plus a `cfg_rx` bitmask; `test hardware motor` checks all of them, and reports an absent field as unconfirmable rather than as a mismatch against -1. Round trip NEED -> SET -> DUMP -> PASS verified against `mock_needcfg` |
|
||||
| 6 | `NEEDCFG` understood host-side | **done**, 5 tests. `AxisState::NeedConfig` parsed from `ST` `C`, named in `DUMP` and the dashboard, and counted as telemetry arriving rather than as a dead link. The firmware enum APPENDS the state (5) rather than inserting it — inserting renumbered every later state and made a READY axis read back as ERROR |
|
||||
|
||||
Presence reconciliation (§10) changes nothing on a tower without a `hardware.ini`: absent is
|
||||
silent and `hardware.declared` stays false. Verified by running the binary both ways.
|
||||
|
|
|
|||
|
|
@ -299,25 +299,17 @@ commit `aab1925`.
|
|||
| 2 | **Phase 2 gate: RP2040 + TMC5160-BOB at 3.3 V** | **PASSED** on tower 121, 2026-09-15: `IOIN Y=0x3000001E version=0x30`, encoder reading (`X_ENC`), one axis, `speed=3000` |
|
||||
| 2 | `src/platform.h` — reset cause and free RAM behind one interface per MCU family | **done** |
|
||||
| 2 | `Serial.begin()` moved ahead of all hardware init, so a wedged image stays flashable | **done** |
|
||||
| 2 | `[env:iota_rp2040]` PlatformIO target (arduino-pico core, 8 MB flash) | **done and built.** Latest, 2026-09-18, with the config handshake: **iota_rp2040** 51716 B flash (0.6 % of 8 MB), 9108 B RAM (3.5 %); **leonardo** 22646 B flash (**79 % of 28 KB**), 939 B RAM (36.7 %). Both from `./deploy.sh --compile-only` against towers 121 and 111; 33 native tests pass (`pio test -e native`) |
|
||||
| 2 | `[env:iota_rp2040]` PlatformIO target (arduino-pico core, 8 MB flash) | **done and built** on tower 121, 2026-09-15: 49452 B flash (0.6 % of 8 MB), 9068 B RAM. `leonardo` still builds (19254 B) and the 22 native tests still pass |
|
||||
| 2 | Per-board pin map + `NUM_AXES` in `platform_leonardo.h` / `platform_iota.h`, as-built wiring | **done** — compile-time, not yet generated from `hardware.ini` |
|
||||
| 2 | Pins + `NUM_AXES` generated from `hardware.ini` | **not started** — needs the config plan's phase 2 first |
|
||||
| 3 | Yaw homing on the real drivetrain | **done** — the endstop was being sheared through at full torque; homing at 189 mA holds. See below |
|
||||
| 1 | Everything else | blocked on [hardware-config-plan.md](hardware-config-plan.md) |
|
||||
|
||||
> **Where the firmware gets built.** PlatformIO is not installed on the dev box; the firmware is built
|
||||
> *remotely on a tower* by `firmware/deploy.sh --compile-only`. Locally, the Arduino-free sources
|
||||
> (`command_parse.cpp` and its native tests) compile and run under plain g++, and the rest
|
||||
> syntax-checks against stub headers on both branches — the AVR under `gnu++11` and the RP2040 under
|
||||
> `gnu++17`, with an unrecognised architecture failing on the `#error` rather than compiling silently.
|
||||
> That catches typos and signatures; it says nothing about size or behaviour, so a remote build is
|
||||
> still the gate.
|
||||
>
|
||||
> **Flash headroom is now the constraint on the Delta fleet.** The ATmega32U4 build sits at **79 % of
|
||||
> its 28 KB** (6026 B left), up from 19254 B at the phase 2 gate — that growth is everything since:
|
||||
> `BOOTSEL`, `CURRENT`, the reduced homing current, `STOP` ending a homing, and the config handshake.
|
||||
> RP2040 flash is a non-issue at 0.6 % of 8 MB. Deleting the drivetrain fields from `MOTOR_CONFIG`
|
||||
> (config plan §14) now buys space as well as removing a duplicate definition.
|
||||
> **Not compile-verified.** PlatformIO is not installed on the dev box and the firmware is built
|
||||
> *remotely on the tower* by `firmware/deploy.sh`. What was verified locally: both branches of
|
||||
> `platform.h` syntax-check against stub headers — the AVR branch under `gnu++11` (what the Arduino AVR
|
||||
> core uses) and the RP2040 branch under `gnu++17` (what arduino-pico uses) — and an unrecognised
|
||||
> architecture fails on the `#error` rather than compiling silently.
|
||||
>
|
||||
> To actually build it: `cd firmware && ./deploy.sh --compile-only` compiles on the device without
|
||||
> flashing. That rsyncs over the tower's firmware source tree, so it is a deliberate step, not one to
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ Per-file reference for the refactored tree, plus the shared data structures.
|
|||
|
||||
### `MotorTelemetry` / `AxisTelemetry` ([IMotorController.h](../include/fgc/IMotorController.h))
|
||||
Per-axis `yaw` and `pitch` segments (`pitch_present` flag), each an `AxisTelemetry`: `state`
|
||||
(`AxisState` B/R/H/A/E/C — the last being NEEDCFG, a firmware waiting to be told its drivetrain), `xactual`, `xenc` (encoder counts), `drv_status`, `sg`/`cs`/`pwm`, and flags
|
||||
(`AxisState` B/R/H/A/E), `xactual`, `xenc` (encoder counts), `drv_status`, `sg`/`cs`/`pwm`, and flags
|
||||
`standstill`/`stall`/`deviation`/`overtemp`/`endstop_l`/`endstop_r`, with `moving()`/`ready()` helpers.
|
||||
Parsed from the firmware `ST Y:...[ P:...]` line; degrees are derived via `Geometry`.
|
||||
|
||||
|
|
|
|||
|
|
@ -230,26 +230,8 @@ Then prove the program finds it **with nothing in the environment** - no
|
|||
|
||||
### The drivetrain block
|
||||
|
||||
**`install.sh` now writes this block**, one `[Motor.<axis>]` per declared axis —
|
||||
it has to, because the firmware refuses to move until it has been told these
|
||||
(see the config handshake in [configuration.md](configuration.md)). What it still
|
||||
cannot do is *probe* them: no reading of a board tells you its sense resistor.
|
||||
|
||||
So pass what you know and it writes the values; omit anything and it writes a
|
||||
commented `; TODO` line naming what to measure:
|
||||
|
||||
```bash
|
||||
./install.sh --stdout --axes yaw \
|
||||
--r-sense 0.075 --run-current 756 --homing-current 200 \
|
||||
--travel-deg 340 --steps-per-rev 1210778 --sw-mode 0
|
||||
```
|
||||
|
||||
A `TODO` line is **commented, not a placeholder value**, and deliberately: these
|
||||
keys are numeric, and a non-numeric value makes the program refuse to start with
|
||||
a message about a config key. Left absent, it starts and names exactly which keys
|
||||
that axis is missing and that it will not move — the useful failure.
|
||||
|
||||
The block it writes looks like this:
|
||||
`install.sh` does not write this one - no probe can read a sense resistor off a
|
||||
board - so add it by hand, one `[Motor.<axis>]` block per axis:
|
||||
|
||||
```ini
|
||||
[Motor.yaw]
|
||||
|
|
|
|||
|
|
@ -20,22 +20,6 @@
|
|||
# --env none (default: none) ambient sensor model, or none
|
||||
# --cameras rgb (default: rgb) expected [Camera.<name>] blocks
|
||||
#
|
||||
# The drivetrain, per axis. The firmware REFUSES TO MOVE until it has been told
|
||||
# these (docs/configuration.md, the config handshake), so an axis left undeclared
|
||||
# is an axis that will sit in NEEDCFG. Pass what you know; what you omit is
|
||||
# written as a commented line naming what has to be measured:
|
||||
#
|
||||
# --r-sense 0.075 sense resistor on that axis's TMC board (ohms)
|
||||
# --run-current 756 coil current for normal motion (mA RMS)
|
||||
# --homing-current 200 coil current while hunting an endstop (mA RMS)
|
||||
# --travel-deg 340 mechanical angle between the hard stops
|
||||
# --steps-per-rev 1210778 measured full travel, in counts
|
||||
# --sw-mode 0 TMC SW_MODE: 0 = no switches (StallGuard homing)
|
||||
#
|
||||
# These take ONE value, applied to the first axis in --axes. A second axis gets a
|
||||
# commented template: nothing here can be probed, and inventing a sense resistor
|
||||
# is how a driver gets cooked.
|
||||
#
|
||||
# Other flags:
|
||||
# --force overwrite an existing hardware.ini (backs it up first)
|
||||
# --no-apt-update skip 'apt-get update'
|
||||
|
|
@ -50,10 +34,6 @@ AXES="yaw,pitch"
|
|||
IMU="none"
|
||||
ENV_SENSOR="none"
|
||||
CAMERAS="rgb"
|
||||
# Empty means "not known at install time" -> written commented out, never as a
|
||||
# sentinel: these are numeric keys, and a non-numeric value makes the program
|
||||
# refuse to start rather than report the axis as undeclared.
|
||||
R_SENSE="" RUN_CURRENT="" HOMING_CURRENT="" TRAVEL_DEG="" STEPS_PER_REV="" SW_MODE=""
|
||||
DO_DEPS=0 DO_PRE=0 DO_HW=0 DO_VIMBA=0 PROBE_ONLY=0 TO_STDOUT=0 FORCE=0 APT_UPDATE=1
|
||||
VIMBA_SRC=""
|
||||
EXPLICIT=0
|
||||
|
|
@ -69,12 +49,6 @@ while [[ $# -gt 0 ]]; do
|
|||
--imu) IMU="$2"; shift ;;
|
||||
--env) ENV_SENSOR="$2"; shift ;;
|
||||
--cameras) CAMERAS="$2"; shift ;;
|
||||
--r-sense) R_SENSE="$2"; shift ;;
|
||||
--run-current) RUN_CURRENT="$2"; shift ;;
|
||||
--homing-current) HOMING_CURRENT="$2"; shift ;;
|
||||
--travel-deg) TRAVEL_DEG="$2"; shift ;;
|
||||
--steps-per-rev) STEPS_PER_REV="$2"; shift ;;
|
||||
--sw-mode) SW_MODE="$2"; shift ;;
|
||||
--vimba) VIMBA_SRC="$2"; DO_VIMBA=1; EXPLICIT=1; shift ;;
|
||||
--force) FORCE=1 ;;
|
||||
--no-apt-update) APT_UPDATE=0 ;;
|
||||
|
|
@ -293,26 +267,6 @@ do_vimba() {
|
|||
}
|
||||
|
||||
# ------------------------------------------------------------ hardware.ini ---
|
||||
# Emit "key = value", or a commented-out line naming what has to be measured.
|
||||
#
|
||||
# Commented rather than a placeholder string on purpose: these keys are numeric,
|
||||
# and a non-numeric value makes the program refuse to start with a message about
|
||||
# a config key. Absent, it starts and reports the axis as undeclared - naming the
|
||||
# missing keys and which axis will not move, which is the useful failure.
|
||||
kv_or_todo() {
|
||||
local key="$1" value="$2" why="$3"
|
||||
if [[ -n $value ]]; then
|
||||
printf '%-18s = %s\n' "$key" "$value"
|
||||
return
|
||||
fi
|
||||
# Wrapped, and not merely for looks: ini.h sets INI_MAX_LINE to 200, and a
|
||||
# longer line is SPLIT rather than truncated, so its tail becomes a bogus
|
||||
# line and the whole file fails to parse. A generated file that cannot be
|
||||
# read is worse than one with a gap in it.
|
||||
printf '; TODO %s\n' "$key"
|
||||
printf '%s\n' "$why" | fold -s -w 72 | sed 's/^/; /' | sed 's/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
emit_hardware_ini() {
|
||||
local board mcu cam fw_env
|
||||
board="$(probe_board)"
|
||||
|
|
@ -375,60 +329,6 @@ i2c_addr = 0x44
|
|||
EOF
|
||||
fi
|
||||
|
||||
# One [Motor.<axis>] block per axis. The firmware holds none of these itself
|
||||
# any more - it asks, and refuses to move until answered - so an axis without
|
||||
# a block here is an axis that will not turn.
|
||||
#
|
||||
# A value we were not given is written COMMENTED OUT, with what to measure.
|
||||
# Not as a placeholder string: these keys are numeric, and a non-numeric value
|
||||
# makes the program refuse to start, which is a worse failure than the axis
|
||||
# reporting itself undeclared.
|
||||
local ax first=1
|
||||
for ax in ${AXES//,/ }; do
|
||||
ax="$(echo "$ax" | tr -d '[:space:]')"
|
||||
[[ -z $ax ]] && continue
|
||||
echo
|
||||
echo "[Motor.$ax]"
|
||||
if [[ $first -eq 1 ]]; then
|
||||
kv_or_todo "r_sense_ohm" "$R_SENSE" \
|
||||
"the resistor fitted to THIS driver board - read it off the board, do not assume"
|
||||
kv_or_todo "run_current_ma" "$RUN_CURRENT" \
|
||||
"coil current for normal motion, mA RMS, from the motor's nameplate"
|
||||
kv_or_todo "homing_current_ma" "$HOMING_CURRENT" \
|
||||
"current while hunting an endstop. LOWER than run_current wherever the stop can be out-pushed: on this rig a screw-head stop was sheared straight past its mate at full torque"
|
||||
kv_or_todo "travel_deg" "$TRAVEL_DEG" \
|
||||
"mechanical angle between the hard stops. Rough is fine to start; every bearing inherits its error proportionally until 'gimbal heading setup' supersedes it"
|
||||
kv_or_todo "steps_per_rev" "$STEPS_PER_REV" \
|
||||
"measured full travel IN COUNTS - read it off a homing run. The only guard against a PHANTOM endstop"
|
||||
kv_or_todo "sw_mode" "$SW_MODE" \
|
||||
"TMC SW_MODE. 0 = no physical switches (StallGuard homing); set the bits if this axis has REFL/REFR wired"
|
||||
else
|
||||
echo "; Nothing here can be probed, and this is not the first axis, so the"
|
||||
echo "; values below are a template. Measure them on THIS axis."
|
||||
kv_or_todo "r_sense_ohm" "" "sense resistor on this axis's driver board"
|
||||
kv_or_todo "run_current_ma" "" "coil current for normal motion, mA RMS"
|
||||
kv_or_todo "homing_current_ma" "" "current while hunting an endstop"
|
||||
kv_or_todo "travel_deg" "" "mechanical angle between the hard stops"
|
||||
kv_or_todo "steps_per_rev" "" "measured full travel, in counts"
|
||||
kv_or_todo "sw_mode" "" "TMC SW_MODE (0 = no physical switches)"
|
||||
fi
|
||||
cat <<'EOF'
|
||||
; Speeds are DEGREES PER SECOND, not VMAX: a register value means different
|
||||
; things on different gear ratios, so it is not portable between towers. The
|
||||
; host converts through counts_per_deg and sends SPEED.
|
||||
scan_speed_deg_s = 8.0
|
||||
homing_speed_deg_s = 2.0
|
||||
center_speed_deg_s = 6.0
|
||||
; |XACTUAL - XENC| that means "position untrustworthy". 1500 is this mechanism's
|
||||
; starting point; re-measure it whenever the homing current changes, because the
|
||||
; free-running gap is coupled to it.
|
||||
enc_deviation = 1500
|
||||
; Is this axis's encoder aligned to XACTUAL and usable for hold and DIAG?
|
||||
has_encoder = true
|
||||
EOF
|
||||
first=0
|
||||
done
|
||||
|
||||
local c
|
||||
for c in ${CAMERAS//,/ }; do
|
||||
c="$(echo "$c" | tr -d '[:space:]')"
|
||||
|
|
@ -470,12 +370,7 @@ do_hardware_ini() {
|
|||
board="$(probe_board)"
|
||||
if [[ $board != lattepanda-iota ]]; then
|
||||
warn "this kit is for the LattePanda IOTA; this machine reports '$board'"
|
||||
# --probe and --stdout change nothing on this machine: one reports what it
|
||||
# found, the other prints the file it WOULD write. Refusing them on a
|
||||
# non-IOTA only means the generator can never be exercised off a tower,
|
||||
# which is how a setup kit ends up untested.
|
||||
[[ $PROBE_ONLY -eq 1 || $TO_STDOUT -eq 1 ]] ||
|
||||
die "refusing to apply IOTA preconditions to a $board"
|
||||
[[ $PROBE_ONLY -eq 1 ]] || die "refusing to apply IOTA preconditions to a $board"
|
||||
fi
|
||||
|
||||
if [[ $PROBE_ONLY -eq 1 ]]; then probe_report; exit 0; fi
|
||||
|
|
|
|||
|
|
@ -6,13 +6,7 @@
|
|||
namespace fgc {
|
||||
|
||||
// Per-axis lifecycle state, from the firmware ST line's state char (B/R/H/A/E).
|
||||
// Per-axis lifecycle, from the firmware ST line's state char.
|
||||
// B Boot R Reset H Homing A reAdy E Error C NeedConfig
|
||||
//
|
||||
// NeedConfig means the firmware has been told nothing about the drivetrain it is
|
||||
// driving and is refusing to move until the host answers its NEED lines. It is
|
||||
// NOT a fault and NOT "no telemetry": the link is fine and the axis is talking.
|
||||
enum class AxisState { Boot, Reset, Homing, Ready, Error, NeedConfig, Unknown };
|
||||
enum class AxisState { Boot, Reset, Homing, Ready, Error, Unknown };
|
||||
|
||||
// One axis segment of a firmware `ST` status line:
|
||||
// ST Y:<state>,<xactual>,<xenc>,<drv_hex>,<sg>,<cs>,<pwm>,<flags> [P:...]
|
||||
|
|
|
|||
|
|
@ -109,9 +109,7 @@ public:
|
|||
// and main.cpp) so the host-side DumpParser is exercised without
|
||||
// hardware. Register values are illustrative, not simulated; the
|
||||
// encoder positions track the mock's live state.
|
||||
// 5 = NEEDCFG, appended to the firmware enum so the older codes keep
|
||||
// their meanings. See motor.h.
|
||||
const int st = (needcfg_ && !configured_) ? 5 : (homed_ ? 3 : 1);
|
||||
const int st = homed_ ? 3 : 1; // READY : RESET
|
||||
std::ostringstream d;
|
||||
d << "DUMP BEGIN build=mock uptime=0 mcusr=0x01 free_ram=4096\n";
|
||||
auto axis = [&](char L, const AxisTelemetry& a) {
|
||||
|
|
@ -242,10 +240,7 @@ private:
|
|||
a.standstill = false;
|
||||
}
|
||||
a.xactual = a.xenc;
|
||||
// An axis waiting on its config says so, rather than looking like a
|
||||
// healthy un-homed one - that distinction is the whole point of the state.
|
||||
a.state = (needcfg_ && !configured_) ? AxisState::NeedConfig
|
||||
: (homed_ ? AxisState::Ready : AxisState::Reset);
|
||||
a.state = homed_ ? AxisState::Ready : AxisState::Reset;
|
||||
a.deviation = deviation_;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1243,22 +1243,6 @@ struct Application::Impl {
|
|||
continue;
|
||||
}
|
||||
const bool is_yaw = need->axis == 'Y';
|
||||
|
||||
// An axis the tower says it does not have. That is not a missing
|
||||
// declaration - it is a firmware built for more axes than this rig,
|
||||
// and answering would configure a driver that is not there. Same
|
||||
// class of fault as presence reconciliation catches, so it is named
|
||||
// that way rather than as a hole in hardware.ini.
|
||||
const char* axis_name = is_yaw ? "yaw" : "pitch";
|
||||
if (cfg.hardware.declared && !cfg.hardware.hasAxis(axis_name)) {
|
||||
LOG_ERROR << "firmware asked for axis " << need->axis << " (" << axis_name
|
||||
<< "), which this tower does not declare (axes "
|
||||
<< cfg.hardware.axesLabel()
|
||||
<< ") -- the firmware was built for more axes than the rig has; "
|
||||
"rebuild it for this board rather than declaring an axis that is "
|
||||
"not fitted";
|
||||
continue;
|
||||
}
|
||||
const auto src = is_yaw
|
||||
? axisConfigSource("yaw", cfg.geometry.yaw, cfg.drivetrain.yaw,
|
||||
yaw_current_, cfg.yaw_soft_margin_counts)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ const char* stateName(int s) {
|
|||
case 2: return "HOMING";
|
||||
case 3: return "READY";
|
||||
case 4: return "ERROR";
|
||||
// Appended in the firmware enum rather than placed in lifecycle order,
|
||||
// because these numbers are on the wire: inserting it would renumber
|
||||
// every later state and a READY axis would read back as ERROR.
|
||||
case 5: return "NEEDCFG";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ AxisState stateFromChar(char c) {
|
|||
case 'H': case 'h': return AxisState::Homing;
|
||||
case 'A': case 'a': return AxisState::Ready;
|
||||
case 'E': case 'e': return AxisState::Error;
|
||||
case 'C': case 'c': return AxisState::NeedConfig;
|
||||
default: return AxisState::Unknown;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -867,9 +867,6 @@ void TestRunner::testHwAxes(TestReport& r) {
|
|||
// its own segment.
|
||||
MotorTelemetry t = motor_.telemetry();
|
||||
FoundHardware f;
|
||||
// NEEDCFG counts as telemetry arriving: the link is fine and the axis is
|
||||
// talking, it just has not been told what it is driving. Treating it as "no
|
||||
// telemetry" would blame the serial port for a configuration gap.
|
||||
f.axes_known = t.yaw.state != AxisState::Unknown;
|
||||
if (f.axes_known) {
|
||||
f.axes.push_back("yaw");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ const char* axisStateLabel(AxisState s) {
|
|||
case AxisState::Homing: return "HOMING";
|
||||
case AxisState::Ready: return "READY";
|
||||
case AxisState::Error: return "ERROR";
|
||||
case AxisState::NeedConfig: return "NEEDCFG";
|
||||
case AxisState::Unknown:
|
||||
default: return "----";
|
||||
}
|
||||
|
|
@ -24,9 +23,6 @@ UiColor axisStateColor(AxisState s) {
|
|||
case AxisState::Ready:
|
||||
case AxisState::Homing: return UiColor::Cyan;
|
||||
case AxisState::Error: return UiColor::Red;
|
||||
// Not red: nothing is broken, the firmware is waiting to be told what it
|
||||
// is driving. Yellow so it reads as "needs you", not as a fault.
|
||||
case AxisState::NeedConfig: return UiColor::Yellow;
|
||||
case AxisState::Boot:
|
||||
case AxisState::Reset: return UiColor::Dim;
|
||||
case AxisState::Unknown:
|
||||
|
|
|
|||
|
|
@ -201,25 +201,3 @@ TEST_CASE("a firmware predating the handshake leaves those fields absent, not ze
|
|||
CHECK(d.axes[0].sw_mode == -1);
|
||||
CHECK(d.axes[0].cfg_received == -1);
|
||||
}
|
||||
|
||||
TEST_CASE("the numeric state codes did not shift when NEEDCFG was added") {
|
||||
// NEEDCFG is appended (5), not inserted in lifecycle order, because these
|
||||
// numbers are on the wire. Had it gone in at 1, a READY axis (which would
|
||||
// have become 4) would read back as ERROR - a healthy tower reporting a
|
||||
// fault in every dump, and an old firmware misread by a new host.
|
||||
auto one = [](int n) {
|
||||
const std::string text =
|
||||
"DUMP BEGIN build=x uptime=1 mcusr=0x01 free_ram=1\n"
|
||||
"DUMP Y state=" + std::to_string(n) +
|
||||
" hsub=0 enabled=1 lim_neg=0 lim_pos=1 hold_target=0 speed=1 center=0 "
|
||||
"soft_margin=0 hold_corr=0 hold_peak=0 hold_cumul=0 has_encoder=1\n"
|
||||
"DUMP END\n";
|
||||
return fgc::parseDump(text).axes.at(0).state_name;
|
||||
};
|
||||
CHECK(one(0) == "BOOT");
|
||||
CHECK(one(1) == "RESET");
|
||||
CHECK(one(2) == "HOMING");
|
||||
CHECK(one(3) == "READY");
|
||||
CHECK(one(4) == "ERROR");
|
||||
CHECK(one(5) == "NEEDCFG");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,24 +82,3 @@ TEST_CASE("parseAxisSegment reads the encoder-deviation flag 'd'") {
|
|||
CHECK(e->stall == true);
|
||||
CHECK(e->deviation == true);
|
||||
}
|
||||
|
||||
TEST_CASE("the NEEDCFG state char is understood, not read as unknown") {
|
||||
// The firmware refuses to move until the host answers its NEED lines, and
|
||||
// says so with 'C'. A host that did not know the code would map it to
|
||||
// Unknown, which downstream reads as "no telemetry arrived" - blaming the
|
||||
// serial link for a configuration gap.
|
||||
auto t = fgc::parseTelemetryLine("ST Y:C,0,0,00000000,0,0,0,S");
|
||||
REQUIRE(t);
|
||||
CHECK(t->yaw.state == fgc::AxisState::NeedConfig);
|
||||
CHECK_FALSE(t->yaw.state == fgc::AxisState::Unknown);
|
||||
|
||||
auto lower = fgc::parseTelemetryLine("ST Y:c,0,0,00000000,0,0,0,S");
|
||||
REQUIRE(lower);
|
||||
CHECK(lower->yaw.state == fgc::AxisState::NeedConfig);
|
||||
}
|
||||
|
||||
TEST_CASE("an axis in NEEDCFG is not ready") {
|
||||
auto t = fgc::parseTelemetryLine("ST Y:C,0,0,00000000,0,0,0,S");
|
||||
REQUIRE(t);
|
||||
CHECK_FALSE(t->yaw.ready());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue