libstp.step.motion.lineup.single ================================ .. py:module:: libstp.step.motion.lineup.single .. autoapi-nested-parse:: Single-sensor lineup on a line with known width. Drives across the line, measures the apparent crossing width, and computes the angular displacement from ``acos(actual_width / apparent_width)``. Then executes a corrective turn. The direction of the correction cannot be determined from a single crossing, so it must be provided by the caller. Classes ------- .. autoapisummary:: libstp.step.motion.lineup.single.CorrectionSide libstp.step.motion.lineup.single.SingleSensorLineupConfig libstp.step.motion.lineup.single.SingleSensorCrossing Functions --------- .. autoapisummary:: libstp.step.motion.lineup.single.single_sensor_lineup libstp.step.motion.lineup.single.forward_single_lineup libstp.step.motion.lineup.single.backward_single_lineup Module Contents --------------- .. py:class:: CorrectionSide(*args, **kwds) Bases: :py:obj:`enum.Enum` Which direction to apply the angular correction. .. py:attribute:: LEFT :value: 'left' .. py:attribute:: RIGHT :value: 'right' .. py:class:: SingleSensorLineupConfig .. py:attribute:: sensor :type: libstp.sensor_ir.IRSensor .. py:attribute:: line_width_cm :type: float .. py:attribute:: correction_side :type: CorrectionSide .. py:attribute:: forward_speed :type: float :value: 0.15 .. py:attribute:: entry_threshold :type: float :value: 0.7 .. py:attribute:: exit_threshold :type: float :value: 0.3 .. py:class:: SingleSensorCrossing(config: SingleSensorLineupConfig) Bases: :py:obj:`libstp.step.motion.motion_step.MotionStep` Drive across a line with one IR sensor and measure the apparent crossing width. The robot drives at the configured speed while monitoring a single IR sensor's black-surface confidence. The crossing proceeds in two phases: * **Phase 1 (searching):** Drive until the sensor reads above ``entry_threshold`` -- this marks the leading edge of the line. The odometry position is recorded. * **Phase 2 (on line):** Continue driving until the sensor drops below ``exit_threshold`` -- this marks the trailing edge. The odometry position is recorded again. The apparent crossing width (leading-to-trailing distance) is compared against the known physical ``line_width_cm`` to compute the angular displacement via ``acos(actual_width / apparent_width)``. A wider apparent crossing means the robot is more skewed relative to the line. Because a single sensor cannot determine *which direction* the robot is skewed, the caller must supply ``correction_side`` to indicate the turn direction. This is an internal building-block step -- use ``forward_single_lineup`` or ``backward_single_lineup`` instead. :param config: A ``SingleSensorLineupConfig`` containing the sensor, line width, thresholds, speed, and correction direction. .. py:attribute:: config .. py:attribute:: apparent_width_m :value: 0.0 .. py:attribute:: crossing_angle_rad :value: 0.0 .. py:method:: to_simulation_step() -> libstp.step.SimulationStep Convert this step to a simulation-friendly summary. The default implementation uses timing history only when it can query the tracker synchronously; otherwise it returns conservative defaults. Override in subclasses that know their motion delta or exact duration. .. py:method:: on_start(robot: libstp.robot.api.GenericRobot) -> None Called once before the loop. Override to set up motion/velocity. .. py:method:: on_update(robot: libstp.robot.api.GenericRobot, dt: float) -> bool Called each cycle with dt in seconds. Return True when motion is complete. .. py:function:: single_sensor_lineup(sensor: libstp.sensor_ir.IRSensor, line_width_cm: float = 5.0, correction_side: CorrectionSide = CorrectionSide.LEFT, forward_speed: float = 0.15, entry_threshold: float = 0.7, exit_threshold: float = 0.3) -> libstp.step.Sequential Compose a single-sensor crossing measurement with a deferred corrective turn. Drives across the line with one sensor, measures the apparent crossing width, computes the angular displacement from the known line width, and executes a corrective turn in the specified direction. If the measured skew is less than 1 degree the turn is skipped. This is an internal helper -- prefer ``forward_single_lineup`` or ``backward_single_lineup``. :param sensor: The IR sensor instance used to detect the line. :param line_width_cm: Known physical width of the line in centimeters. :param correction_side: Direction to turn for correction (LEFT or RIGHT). Must be determined by the caller since a single sensor cannot detect skew direction. :param forward_speed: Approach speed in m/s (negative for backward). :param entry_threshold: Black confidence (0--1) for detecting the leading edge of the line. :param exit_threshold: Black confidence (0--1) for detecting the trailing edge of the line. Should be lower than ``entry_threshold`` to avoid false exits. :returns: A two-step sequence (crossing measurement + corrective turn). :rtype: Sequential .. py:function:: forward_single_lineup(sensor: libstp.sensor_ir.IRSensor, line_width_cm: float = 5.0, correction_side: CorrectionSide = CorrectionSide.LEFT, forward_speed: float = 0.15, entry_threshold: float = 0.7, exit_threshold: float = 0.3) -> libstp.step.Sequential Drive forward across a black line, measure skew, and correct with a turn. The robot drives forward at ``forward_speed`` while a single IR sensor watches for a black line. As it crosses, the apparent width of the line is measured via odometry and compared against the known ``line_width_cm`` to compute the angular displacement using ``acos(line_width / apparent_crossing_width)``. A corrective turn is then executed in the direction given by ``correction_side``. Because a single sensor cannot determine which way the robot is skewed, the caller must know (from the arena layout or prior steps) whether to correct left or right. Prerequisites: One IR line sensor and odometry. No lateral-drive capability required. :param sensor: The IR sensor instance used to detect the line. :param line_width_cm: Known physical width of the line in centimeters. :param correction_side: Direction to turn for correction (LEFT or RIGHT). :param forward_speed: Approach speed in m/s. Always driven forward (the absolute value is used). :param entry_threshold: Black confidence (0--1) for detecting the leading edge of the line. :param exit_threshold: Black confidence (0--1) for detecting the trailing edge. Should be lower than ``entry_threshold``. :returns: crossing measurement + corrective turn. :rtype: Sequential Example:: from libstp.step.motion.lineup.single import ( forward_single_lineup, CorrectionSide, ) step = forward_single_lineup( sensor=robot.center_line_sensor, line_width_cm=5.0, correction_side=CorrectionSide.LEFT, forward_speed=0.10, ) step.run(robot) .. py:function:: backward_single_lineup(sensor: libstp.sensor_ir.IRSensor, line_width_cm: float = 5.0, correction_side: CorrectionSide = CorrectionSide.LEFT, forward_speed: float = 0.15, entry_threshold: float = 0.7, exit_threshold: float = 0.3) -> libstp.step.Sequential Drive backward across a black line, measure skew, and correct with a turn. Identical to ``forward_single_lineup`` but the robot reverses into the line instead of driving forward. The ``forward_speed`` value is negated internally so the robot always moves backward regardless of the sign passed in. The same ``acos(line_width / apparent_crossing_width)`` computation is used to determine the corrective angle. Prerequisites: One IR line sensor and odometry. No lateral-drive capability required. :param sensor: The IR sensor instance used to detect the line. :param line_width_cm: Known physical width of the line in centimeters. :param correction_side: Direction to turn for correction (LEFT or RIGHT). :param forward_speed: Approach speed in m/s. The absolute value is negated so the robot always drives backward. :param entry_threshold: Black confidence (0--1) for detecting the leading edge of the line. :param exit_threshold: Black confidence (0--1) for detecting the trailing edge. Should be lower than ``entry_threshold``. :returns: crossing measurement (backward) + corrective turn. :rtype: Sequential Example:: from libstp.step.motion.lineup.single import ( backward_single_lineup, CorrectionSide, ) step = backward_single_lineup( sensor=robot.center_line_sensor, line_width_cm=5.0, correction_side=CorrectionSide.RIGHT, forward_speed=0.12, ) step.run(robot)