Skip to content

stall_detector.hpp

StallDetector — the jam/stall decision for motor mechanisms.

This header declares 2 types (7 members).

Extracted from include/shulib/manipulation/stall_detector.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.

Contents

struct StallConfig

struct StallConfig

The three numbers that define "stalled" for ONE mechanism. There are NO defaults, deliberately: a 5.5 W and an 11 W motor stall at different currents, and a lift and an intake tolerate different windows, so any library-chosen value would be silently wrong for half of them. Concrete mechanisms pick their own and register them; hardware measurement replaces the estimates later. Validation of all three happens in StallDetector's constructor, not here.

struct, declared at include/shulib/manipulation/stall_detector.hpp:37.

StallConfig::currentAtLeast

units::Current currentAtLeast

Current at or above this (amps, > 0) counts as stall-grade.

field, declared at include/shulib/manipulation/stall_detector.hpp:39.

StallConfig::speedAtMost

units::AngularVelocity speedAtMost

|shaft velocity| at or below this (rad/s, >= 0) counts as not turning.

field, declared at include/shulib/manipulation/stall_detector.hpp:41.

StallConfig::persistence

units::Time persistence

Both conditions must hold CONTINUOUSLY for this long (seconds, >= 0; 0 = trip on the first qualifying sample — legitimate for a mechanism with no spin-up, wrong for most).

field, declared at include/shulib/manipulation/stall_detector.hpp:45.

class StallDetector

class StallDetector

The jam/stall decision for a motor mechanism: HIGH CURRENT with the shaft NOT TURNING, held continuously for a persistence window. That window is the whole difference between a jam and a spin-up — accelerating from rest draws stall-grade current for the first few ticks with the shaft still nearly stopped, and without a window every start reads as a jam. Both signals are compared by MAGNITUDE, so direction is irrelevant: a mechanism jammed while running in reverse trips exactly the same way. STATEFUL — it remembers when the current window opened, so what update() answers depends on the samples that came before it and not on this one alone. Re-feeding an IDENTICAL sample is harmless: windowStart_ moves only when a CLOSED window opens, so a duplicate read within one tick returns the same answer and leaves the window where it was. What the sequence does buy is the other direction — one healthy sample in the middle closes the window, and the persistence count starts over from the next qualifying sample. It holds NO clock: the caller supplies now, which is what makes it loop-rate independent and testable without one. HONEST LIMIT, stated rather than discovered: it knows only what the sensors say. A motor whose encoder reports "spinning" under a true stall passes straight through it. That is why the operation layer's watchdog is the backstop — the watchdog needs no sensor honesty at all.

class, declared at include/shulib/manipulation/stall_detector.hpp:67.

StallDetector::StallDetector

explicit StallDetector(const StallConfig& config)

Thresholds are required and CHECKED, not clamped: currentAtLeast must be finite and > 0, speedAtMost and persistence finite and >= 0; anything else is a precondition failure. The config is COPIED here, so mutating the caller's StallConfig afterward changes nothing. No window is open yet — the first update() carrying the signature is what starts the clock.

function, declared at include/shulib/manipulation/stall_detector.hpp:73.

StallDetector::reset

void reset() noexcept

Re-arm (forget any partial window) — an operation's start() calls this.

function, declared at include/shulib/manipulation/stall_detector.hpp:86.

StallDetector::update

[[nodiscard]] bool update(units::Time now, units::Current current, units::AngularVelocity velocity)

Feed one sample; true once the stall signature has held for the full persistence window. The window RESETS on any healthy sample — a jam that intermittently slips is a chatter problem for R4's thresholds, not a reason to remember stale evidence.

function, declared at include/shulib/manipulation/stall_detector.hpp:92.

StallDetector::config

[[nodiscard]] const StallConfig& config() const noexcept

The thresholds this detector is using — the constructor's copy, so it is the authority on what was actually accepted. Read-only: there is no setter, and re-tuning means constructing a new detector. Useful for telemetry that wants to report the threshold a trip fired at.

function, declared at include/shulib/manipulation/stall_detector.hpp:110.

Design commentary, from the header

The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.

The header’s own reasoning — 22 lines
 StallDetector — the jam/stall decision for motor mechanisms (chunk F1).
 The physical signature of a stalled or jammed shaft is HIGH CURRENT with the
 shaft NOT TURNING, held for long enough to rule out a start-up transient
 (spin-up draws stall-grade current for the first few ticks with the shaft
 still accelerating from rest — a persistence window is what keeps that from
 reading as a jam). motor.hpp names current() "the PRIMARY capture/stall
 signal for manipulation sensor-confirm"; this class is that sentence made
 executable.

 The thresholds are REQUIRED constructor parameters with NO defaults, on
 C2's waitUntil precedent (an explicit finite timeout, "no invented default
 constant"): a 5.5 W and an 11 W motor stall at different currents, a lift and
 an intake tolerate different windows, and a library default would be wrong
 for half of them silently. F3's concrete primitives choose values per
 mechanism and register them; R4 measures them.

 Honest limits, stated: the detector can only know what the sensors say. A
 motor whose encoder lies "spinning" under a true stall passes right through
 it — which is why the operation layer's watchdog is the backstop and needs no
 sensor honesty at all (mechanism_op.hpp), and why the F1 hostile suite
 includes exactly that lying motor.

Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.