step.condition

Composable stop conditions for motion steps.

A StopCondition determines when a motion step should terminate. Conditions can be combined with | (any/or), & (all/and), > / + (then/sequence) operators.

Example:

from raccoon.step.condition import on_black, on_white, after_seconds, after_cm, over_line

# Stop when sensor sees black OR after 10 seconds
drive_forward(speed=1.0).until(on_black(sensor) | after_seconds(10))

# Sequential: first wait for black, then travel 10 more cm
# Use + instead of > to avoid Python's chained-comparison gotcha
drive_forward(speed=1.0).until(on_black(sensor) + after_cm(10))

# Cross a line (black then white) three times
drive_forward(speed=1.0).until(over_line(sensor) + over_line(sensor) + over_line(sensor))

Classes

StopCondition

Base class for composable stop conditions.

on_black

Stop when an IR sensor detects a black surface.

on_white

Stop when an IR sensor detects a white surface.

after_seconds

Stop after a fixed duration.

after_cm

Stop after traveling a distance (uses odometry path length).

after_forward_cm

Stop after a forward/backward displacement along the reference line.

after_lateral_cm

Stop after a lateral (sideways) displacement along the reference line.

after_degrees

Stop after turning a given angle (degrees).

on_incline

Stop once the robot is tilted off horizontal by at least an angle.

on_level

Stop once the robot is back to (near-)horizontal.

on_digital

Stop when a digital sensor reads a given state.

on_analog_above

Stop when an analog sensor reading exceeds a threshold.

on_analog_below

Stop when an analog sensor reading drops below a threshold.

stall_detected

Stop when a motor appears stalled.

custom

Stop based on a user-provided callable.

Functions

over_line(→ StopCondition)

Stop after crossing a line (black then white).

over_ramp(→ StopCondition)

Stop after crossing a ramp (tilt up, then back to level).

Module Contents

class step.condition.StopCondition

Base class for composable stop conditions.

start(robot: raccoon.robot.api.GenericRobot) None

Called once when the motion step starts. Override to initialize state.

check(robot: raccoon.robot.api.GenericRobot) bool

Return True to stop the motion. Called each update cycle.

Public entry point. Delegates to the subclass _evaluate() and emits a one-shot debug log the moment the condition first fires, so mission logs show which condition completed and its state at completion.

_state() (which may poll sensors/odometry) is only evaluated on the firing tick, so this adds no per-tick cost to the 100 Hz motion loop.

class step.condition.on_black(sensor: raccoon.sensor_ir.IRSensor, threshold: float = 0.7)

Bases: StopCondition

Stop when an IR sensor detects a black surface.

class step.condition.on_white(sensor: raccoon.sensor_ir.IRSensor, threshold: float = 0.7)

Bases: StopCondition

Stop when an IR sensor detects a white surface.

class step.condition.after_seconds(seconds: float)

Bases: StopCondition

Stop after a fixed duration.

start(robot: raccoon.robot.api.GenericRobot) None

Called once when the motion step starts. Override to initialize state.

class step.condition.after_cm(cm: float, *, absolute: bool = False)

Bases: StopCondition

Stop after traveling a distance (uses odometry path length).

By default operates in relative mode: the distance is measured from the robot’s position when the condition starts. In absolute mode (absolute=True) the distance is measured from the odometry origin (last reset()), so centimeters traveled before the condition started also count.

Uses the cumulative path length (odometer) so it works correctly regardless of travel direction (forward, strafe, curved paths).

start(robot: raccoon.robot.api.GenericRobot) None

Called once when the motion step starts. Override to initialize state.

class step.condition.after_forward_cm(cm: float, *, heading: float | None = None, absolute: bool = False)

Bases: _AxisDisplacementCondition

Stop after a forward/backward displacement along the reference line.

Unlike after_cm (which uses cumulative path length), this measures the signed forward component of displacement relative to the reference axis — the robot’s heading at the moment this condition started. Any turning that happens after start() does not rotate the axis: it stays pinned to the original heading, so “20 cm forward” means 20 cm along that line. A line-follow correcting around the reference therefore needs slightly more path to reach the target, and a robot that turns 90° off the reference can never reach it.

A positive cm triggers after the robot has moved that far forward of its baseline along the reference; a negative cm triggers after moving that far backward.

By default the reference line is the robot’s heading at start(). Pass heading=<deg> to pin it to a targeted absolute heading instead — e.g. after_forward_cm(20, heading=0) means “20 cm along the heading-0 line”, robust to the robot being crooked when the condition starts.

In absolute mode (absolute=True) the displacement is read directly from get_distance_from_origin().forward — i.e. measured from the odometry origin, projected onto the origin heading.

class step.condition.after_lateral_cm(cm: float, *, heading: float | None = None, absolute: bool = False)

Bases: _AxisDisplacementCondition

Stop after a lateral (sideways) displacement along the reference line.

Measures the signed lateral component of displacement relative to the reference axis — the robot’s heading at the moment this condition started. Sign convention matches DistanceFromOrigin.lateral: positive values point along the +lateral axis as defined there.

Only meaningful on drivetrains that support lateral motion (e.g. mecanum / omni). On a differential drive this will stay near zero unless the robot rotates and then drives forward — in which case the world-frame displacement has a component sideways of the original heading.

In absolute mode (absolute=True) the displacement is read directly from get_distance_from_origin().lateral.

class step.condition.after_degrees(degrees: float)

Bases: StopCondition

Stop after turning a given angle (degrees).

Reads the heading from robot.odometry.get_pose().heading — the same source the motion controllers regulate on — so the measured rotation and the executed feedback share one frame. Tracks total unsigned rotation, so it works regardless of turn direction.

start(robot: raccoon.robot.api.GenericRobot) None

Called once when the motion step starts. Override to initialize state.

class step.condition.on_incline(min_deg: float = 8.0, *, up_axis: str = 'z', smoothing: float = 0.2)

Bases: _InclinationCondition

Stop once the robot is tilted off horizontal by at least an angle.

Detects when the chassis pitches (or rolls) up onto a slope — e.g. the front wheels climbing onto a ramp. The inclination is derived from the IMU DMP orientation quaternion relative to the flat reference captured at step start (see _InclinationCondition); the condition fires as soon as the smoothed tilt reaches min_deg.

Pair it with on_level() to bracket a ramp: on_incline() marks entering the slope, on_level() marks cresting back onto flat ground. over_ramp() wires both together.

Prerequisites:

A working IMU. See _InclinationCondition for tuning notes on up_axis and smoothing.

Parameters:
  • min_deg – Tilt threshold in degrees (0–90). Fires when the measured inclination is at least this large. A 20° ramp reads ~20°, so a threshold around 8–12° detects entry robustly while ignoring floor roughness and driving jitter. Default 8.0.

  • up_axis – Deprecated / no effect (the quaternion tilt is yaw-invariant); accepted only for backward compatibility.

  • smoothing – EMA factor in (0, 1] applied to the tilt angle to reject any residual jitter (default 0.2).

Returns:

A StopCondition that fires while tilted.

Example:

from raccoon.step.motion import drive_forward
from raccoon.step.condition import on_incline

# Drive until the robot noses up onto the ramp.
drive_forward(speed=0.4).until(on_incline(10))
class step.condition.on_level(max_deg: float = 4.0, *, up_axis: str = 'z', smoothing: float = 0.2)

Bases: _InclinationCondition

Stop once the robot is back to (near-)horizontal.

The counterpart to on_incline(): it fires when the smoothed tilt (from the IMU DMP orientation, relative to the flat start reference) drops to at most max_deg — i.e. the robot has crested a ramp and settled onto flat ground, or finished descending a slope.

Because it triggers on a low tilt, using it on its own from a flat start would fire immediately. It is meant to run after the robot is already tilted — typically as the second stage of a chain, e.g. on_incline(10) + on_level(4) (which is what over_ramp() builds).

Prerequisites:

A working IMU. See _InclinationCondition for tuning notes.

Parameters:
  • max_deg – Tilt threshold in degrees (0–90). Fires when the measured inclination is at most this large. Keep it a few degrees below the matching on_incline threshold so transitional wobble does not fire it early (hysteresis). Default 4.0.

  • up_axis – Deprecated / no effect (the quaternion tilt is yaw-invariant); accepted only for backward compatibility.

  • smoothing – EMA factor in (0, 1] applied to the tilt angle (default 0.2).

Returns:

A StopCondition that fires while (near) level.

Example:

from raccoon.step.condition import on_incline, on_level

# Climb onto the ramp, then keep going until back on the flat top.
drive_forward(speed=0.4).until(on_incline(10) + on_level(4))
class step.condition.on_digital(sensor: raccoon.hal.DigitalSensor, pressed: bool = True)

Bases: StopCondition

Stop when a digital sensor reads a given state.

By default stops when the sensor reads True (pressed). Set pressed to False to stop when the sensor is released instead.

class step.condition.on_analog_above(sensor: raccoon.hal.AnalogSensor, threshold: int)

Bases: StopCondition

Stop when an analog sensor reading exceeds a threshold.

class step.condition.on_analog_below(sensor: raccoon.hal.AnalogSensor, threshold: int)

Bases: StopCondition

Stop when an analog sensor reading drops below a threshold.

class step.condition.stall_detected(motor: raccoon.hal.Motor, threshold_tps: int = 10, duration: float = 0.25)

Bases: StopCondition

Stop when a motor appears stalled.

A motor is considered stalled when its position changes by fewer than threshold_tps ticks per second for at least duration seconds continuously.

start(robot: raccoon.robot.api.GenericRobot) None

Called once when the motion step starts. Override to initialize state.

class step.condition.custom(fn: collections.abc.Callable[[raccoon.robot.api.GenericRobot], bool])

Bases: StopCondition

Stop based on a user-provided callable.

The callable receives the robot and returns True to stop:

custom(lambda robot: robot.et_sensor.value() < 15)
step.condition.over_line(sensor: raccoon.sensor_ir.IRSensor, black_threshold: float = 0.7, white_threshold: float = 0.7) StopCondition

Stop after crossing a line (black then white).

Equivalent to on_black(sensor) > on_white(sensor) — waits for the sensor to see black, then waits for it to see white again.

Parameters:
  • sensor – IR sensor to monitor.

  • black_threshold – Probability threshold for the black phase.

  • white_threshold – Probability threshold for the white phase.

step.condition.over_ramp(enter_deg: float = 8.0, exit_deg: float = 4.0, *, up_axis: str = 'z', smoothing: float = 0.2) StopCondition

Stop after crossing a ramp (tilt up, then back to level).

Equivalent to on_incline(enter_deg) + on_level(exit_deg) — waits for the robot to tilt onto the slope, then waits for it to settle back onto flat ground once it crests. Both stages read the DMP orientation relative to the flat reference from mark_heading_reference() (which must have been called on flat ground beforehand), so no odometry or line sensor is needed. Use it to drive the whole way up and over a ramp with a single stop condition:

drive_forward(speed=0.4).until(over_ramp())

To act on just one edge, use on_incline() (entering) or on_level() (leaving) on their own.

Parameters:
  • enter_deg – Tilt at which the ramp is considered entered (default 8.0).

  • exit_deg – Tilt at or below which the robot is considered back on the flat (default 4.0). Keep it below enter_deg for hysteresis.

  • up_axis – Deprecated / no effect (kept for backward compatibility).

  • smoothing – EMA factor in (0, 1] for both stages (default 0.2).

Returns:

A StopCondition that fires once the ramp has been crossed.