step.params¶
Typed, string-free mission parameters entered via the setup UI.
A parameter is a single value the operator dials in on the robot’s screen during the setup mission (e.g. a positional offset, a speed) and that the normal mission code then reads back for its calculations.
The design goal is to never expose a string key at the call site. A
parameter is declared once as a typed descriptor on a ParamSet.
Python’s __set_name__ hands the descriptor its own attribute name, so the
internal storage key is derived automatically — a typo becomes an
AttributeError at import time instead of a silently-wrong 0.0 at
runtime, and IDE autocomplete/rename work across the whole codebase.
Declare parameters once:
from raccoon import NumberParam, ParamSet
class P(ParamSet):
cube_offset = NumberParam(default=0.0, unit="cm", persist=True)
ramp_speed = NumberParam(default=0.5)
Ask for them in the setup mission — .ask() returns a ready-to-use step and
pulls unit/range/default straight off the descriptor:
class MySetup(SetupMission):
def sequence(self) -> Sequential:
return seq(
[
P.cube_offset.ask("Cube-row offset"),
P.ramp_speed.ask("Ramp speed"),
]
)
Read them in normal mission code — typed, autocompleted, rename-safe:
def sequence(self) -> Sequential:
off = P.cube_offset.get() # -> float
return seq([drive_forward(cm=60 + off)])
Or bind the value lazily inside a declarative seq([...]) tree with the
existing defer() primitive, so the calculation happens at execution
time regardless of build order:
seq(
[
defer(lambda r: drive_forward(cm=60 + P.cube_offset.get())),
]
)
Persistence is opt-in: persist=True mirrors the value into
racoon.calibration.yml (via the shared CalibrationStore) so it
survives a process restart and is reused under --no-calibrate. Without it
a parameter lives only in RAM for the current process — set once in setup,
read by every later mission.
Profiles (game-table / side scoping)¶
Some parameters differ per physical game table and per starting side. Mark
such a parameter scoped=True and it is stored under the active profile
— a (game-table, side) pair chosen once during setup — instead of
globally. .get() transparently resolves against whatever profile is
active, so mission code never mentions the table or side:
class P(ParamSet):
cube_offset = NumberParam(default=0.0, unit="cm", scoped=True, persist=True)
drum_speed = NumberParam(default=0.5) # global — unaffected by profile
Drive the whole flow from the setup mission with a single step: it shows one
keypad screen where the operator types the table number and taps A / B
(e.g. 1B) and — if a complete set of saved values already exists for that
profile — offers to reuse them (asking first) instead of re-entering every
value:
class MySetup(SetupMission):
def sequence(self) -> Sequential:
return seq(
[
configure_mission(
params=[
(P.cube_offset, "Cube-row offset"),
],
),
]
)
Un-scoped parameters ignore the active profile completely, so mixing scoped and global parameters in one mission is fine.
Attributes¶
Classes¶
A numeric mission parameter, declared once as a |
|
Base for a group of parameter declarations. |
|
Setup step: ask the operator for a numeric parameter via the screen. |
|
Setup step: enter a game-table / side profile, then fill scoped params. |
Functions¶
|
Clear all runtime parameter values (test helper). |
|
Select the active |
|
Return the active |
|
Forget the active profile (test helper; persisted YAML is untouched). |
|
Restore and activate the last persisted profile, if any. |
|
Ask the operator for a numeric parameter during setup. |
|
Enter a game-table / side profile, then fill the scoped parameters. |
Module Contents¶
- step.params.reset_params() None¶
Clear all runtime parameter values (test helper).
Only touches the in-memory cache and the active profile; persisted YAML entries are left alone.
- step.params.set_active_profile(table: str, side: str, *, persist: bool = True) None¶
Select the active
(game-table, side)profile for scoped parameters.- Parameters:
table – Game-table name (as shown in the setup menu).
side – Starting side (e.g.
"A"/"B").persist – When
True(default) the selection is mirrored into the calibration YAML so a restart /--no-calibraterun can restore it without re-asking.
- step.params.get_active_profile() tuple[str, str] | None¶
Return the active
(game-table, side)profile, orNone.
- step.params.clear_active_profile() None¶
Forget the active profile (test helper; persisted YAML is untouched).
- step.params.load_persisted_profile() tuple[str, str] | None¶
Restore and activate the last persisted profile, if any.
Used on the
--no-calibratepath so a headless run reuses the profile (and therefore the scoped values) picked during an earlier interactive setup. Returns the activated profile, orNonewhen nothing is stored.
- class step.params.NumberParam(default: float = 0.0, *, unit: str = '', persist: bool = False, scoped: bool = False, key: str | None = None)¶
A numeric mission parameter, declared once as a
ParamSetfield.The descriptor owns the value’s metadata (default, unit, persistence) and, via
__set_name__, its own storage key — so callers interact with the attribute, never a string:class P(ParamSet): cube_offset = NumberParam(default=0.0, unit="cm") P.cube_offset.get() # -> float P.cube_offset.ask("Offset...") # -> Step for the setup mission
- Parameters:
default – Value returned by
get()before anything is entered.unit – Display unit shown on the input screen (e.g.
"cm").persist – When
True, entered values are mirrored toracoon.calibration.ymland reused across restarts /--no-calibrate.scoped – When
True, the value is stored per active profile (game-table + side) rather than globally — seeconfigure_mission(). Combine withpersist=Trueso saved per-profile values survive a restart and can be reused.key – Explicit storage key. Normally omitted — the attribute name is used automatically. Only needed when declaring a parameter outside a class body (e.g. a module-level constant).
- default¶
- unit = ''¶
- persist = False¶
- scoped = False¶
- ask(prompt: str, *, title: str = 'Setup') step.base.Step¶
Return a setup step that asks the operator for this value.
The input screen is pre-filled with the current value (persisted or default) and shows the declared unit. Cancelling keeps the existing value. Under
--no-calibratethe UI is skipped and the persisted/default value is used as-is.- Parameters:
prompt – Question shown above the numeric keypad.
title – Screen title. Defaults to
"Setup".
- Returns:
An
AskNumberstep, ready to drop into aseq([...]).
- class step.params.ParamSet¶
Base for a group of parameter declarations.
Subclassing is optional —
__set_name__works on any class — but it providesall()for iterating declared parameters (handy for tests or a “reset everything” screen).Example:
class P(ParamSet): cube_offset = NumberParam(default=0.0, unit="cm") ramp_speed = NumberParam(default=0.5)
- classmethod all() list[NumberParam]¶
Return every
NumberParamdeclared on this class.
- class step.params.AskNumber(param: NumberParam, prompt: str, *, title: str = 'Setup')¶
Bases:
raccoon.ui.step.UIStepSetup step: ask the operator for a numeric parameter via the screen.
Prefer
param.ask("...")or theask()factory over constructing this directly. Shows a numeric keypad pre-filled with the parameter’s current value and stores whatever the operator confirms back into the parameter.Under
--no-calibratethe screen is skipped entirely and the existing (persisted or default) value is kept, matching the behaviour of the calibration steps.
- step.params.ask(param: NumberParam, prompt: str, *, title: str = 'Setup') step.base.Step¶
Ask the operator for a numeric parameter during setup.
Free-function form of
NumberParam.ask(); both build the same step. Some preferask(P.cube_offset, "...")overP.cube_offset.ask("...").- Parameters:
param – The
NumberParamto fill in.prompt – Question shown above the numeric keypad.
title – Screen title. Defaults to
"Setup".
- Returns:
An
AskNumberstep, ready to drop into aseq([...]).
Example:
from raccoon import ask, NumberParam, ParamSet class P(ParamSet): cube_offset = NumberParam(default=0.0, unit="cm") seq([ask(P.cube_offset, "Cube-row offset")])
- step.params.ParamPrompt¶
- class step.params.ConfigureMission(params: list[ParamPrompt], *, title: str = 'Setup')¶
Bases:
raccoon.ui.step.UIStepSetup step: enter a game-table / side profile, then fill scoped params.
Prefer the
configure_mission()factory over constructing this directly. At runtime it drives the full profile flow:shows one keypad screen where the operator types the table number and taps
A/B(e.g.1B),activates that
(table, side)profile so every scoped parameter now resolves under it,if a complete set of saved values already exists for the profile, offers to reuse them (asking first) and skips the questions,
otherwise asks for each parameter in turn and stores it under the profile.
Under
--no-calibratethe UI is skipped entirely: the last persisted profile is restored (so scoped values read back from YAML) and no questions are asked.
- step.params.configure_mission(params: list[ParamPrompt], *, title: str = 'Setup') step.base.Step¶
Enter a game-table / side profile, then fill the scoped parameters.
One-stop setup step for missions whose parameters differ per physical game table and starting side. It shows a single keypad screen where the operator types the table number and taps
A/B(e.g.1B), activates that profile (so everyscoped=Trueparameter now reads and writes under it), and then either reuses a complete set of previously saved values (after confirming) or asks for each parameter in turn.- Prerequisites:
The parameters passed here should be declared with
scoped=True(and usuallypersist=Trueso saved values survive a restart). Un-scoped parameters are unaffected and can still be asked withask().
- Parameters:
params –
(param, prompt)pairs — the scoped parameters to fill and the question shown for each.title – Screen title for every screen in the flow. Defaults to
"Setup".
- Returns:
A
ConfigureMissionstep, ready to drop into aseq([...]).
Example:
from raccoon import NumberParam, ParamSet, configure_mission class P(ParamSet): cube_offset = NumberParam(default=0.0, unit="cm", scoped=True, persist=True) ramp_speed = NumberParam(default=0.5, scoped=True, persist=True) seq( [ configure_mission( params=[ (P.cube_offset, "Cube-row offset"), (P.ramp_speed, "Ramp approach speed"), ], ), ] )