step.motion.drive_until_impact ============================== .. py:module:: step.motion.drive_until_impact .. autoapi-nested-parse:: Drive straight until an obstacle is hit, recording how far the robot got. Drives at constant velocity (no heading correction) and watches the IMU's gravity-compensated linear acceleration for a collision spike, exactly like :mod:`wall_align`. The difference is the *result*: instead of settling flush against a wall, this step snapshots the odometer at start and stores the distance travelled at the moment of impact. The point is collision-aware distancing. You drive forward expecting to reach some nominal distance, but a game piece may be in the way. When the bump fires early, ``step.impact_result.forward_cm`` tells you how far you actually got, so a following step can drive back exactly that far (minus a clearance) instead of a fixed amount. Classes ------- .. autoapisummary:: step.motion.drive_until_impact.ImpactResult step.motion.drive_until_impact.DriveUntilImpact Functions --------- .. autoapisummary:: step.motion.drive_until_impact.drive_until_impact_back Module Contents --------------- .. py:class:: ImpactResult Outcome of a :func:`drive_until_impact` run. .. py:attribute:: impacted :type: bool True if a collision spike stopped the step; False if it reached ``max_cm`` (or timed out) without hitting anything. .. py:attribute:: forward_cm :type: float Distance the robot actually drove before stopping, in centimeters. Always positive regardless of drive direction. This is the value you feed back into a drive-back step. .. py:attribute:: accel_magnitude :type: float XY linear-acceleration magnitude at impact in m/s² (0.0 if no bump was detected). .. py:class:: DriveUntilImpact(max_cm: float = 50.0, speed: float = 0.5, accel_threshold: float = 0.5, grace_period: float = 0.3, grace_distance_cm: float = 3.0, max_duration: float = 8.0, backward: bool = False) Bases: :py:obj:`step.motion.motion_step.MotionStep` Drive straight until a collision is detected, measuring the distance driven. The robot drives at constant velocity with no heading correction while the IMU's gravity-compensated horizontal acceleration is monitored. A spike above ``accel_threshold`` is treated as an impact: the step stops immediately and records how far it drove. If no impact occurs, the step stops cleanly once ``max_cm`` is reached (or after ``max_duration`` as a safety backstop). Distance is measured relative to where *this step* started (the odometer is snapshotted in ``on_start``), because motion steps no longer reset odometry. Detection only arms after an initial guard so the robot's own launch acceleration isn't mistaken for a collision: it stays disabled until BOTH ``grace_period`` seconds have elapsed AND the robot has driven at least ``grace_distance_cm`` clear of the start. This factory drives in and stops on impact — use it when you only need the "stop when I hit something" behaviour. To *reuse the measured distance* (e.g. drive back exactly as far as you came), reach for :func:`drive_until_impact_back`, which captures the live step instance and wires up the drive-back for you. Reading ``impact_result`` off the value this factory returns does NOT work: the factory returns a builder that ``seq`` resolves into a *fresh* step instance, so the object you hold never runs and its ``impact_result`` stays ``None``. Prerequisites: A working IMU (``raccoon.hal.IMU``) and odometry. Detection quality depends on ``accel_threshold`` being tuned above the cruise-noise floor on real hardware; the mock simulator does not model collision impacts, so the *triggering* can only be validated on the robot. :param max_cm: Nominal distance to drive in centimeters if nothing is hit (default 50.0). Doubles as a safety cap on travel. :param speed: Drive speed in m/s (default 0.5). Lower is gentler and makes the bump cleaner and less likely to shove the game piece. :param accel_threshold: Minimum XY linear-acceleration magnitude in m/s² to classify as an impact (default 0.5). Lower = more sensitive but more prone to false triggers on rough surfaces. :param grace_period: Seconds to ignore acceleration after starting, so the robot's own acceleration doesn't trigger detection (default 0.3). :param grace_distance_cm: Centimeters the robot must drive clear of the start before impact detection arms (default 3.0). Guards against the launch lurch or an obstacle already touching the robot at rest triggering instantly. Combined with ``grace_period`` — both must be satisfied before detection is active. :param max_duration: Safety timeout in seconds — the step finishes even if neither an impact nor ``max_cm`` is reached (default 8.0). :param backward: Drive in reverse instead of forward (default False). :returns: A DriveUntilImpact step that drives in and stops on impact. Example:: from raccoon.step.motion import drive_until_impact # Nudge forward up to 30 cm, stopping the instant something is hit. drive_until_impact(max_cm=30, speed=0.4) .. py:attribute:: impact_result :type: ImpactResult | None :value: None .. py:method:: on_start(robot: raccoon.robot.api.GenericRobot) -> None .. py:method:: on_update(robot: raccoon.robot.api.GenericRobot, dt: float) -> bool .. py:function:: drive_until_impact_back(max_cm: float = 50.0, clearance_cm: float = 0.0, speed: float = 0.5, accel_threshold: float = 0.5, grace_period: float = 0.3, grace_distance_cm: float = 3.0, max_duration: float = 8.0) Drive forward until impact, then reverse exactly as far as was driven. Collision-aware distancing in one step. The robot drives forward up to ``max_cm``, stopping early if it bumps a game piece (IMU acceleration spike). It then reverses by the distance it actually travelled, minus ``clearance_cm``. Hit a piece at 22 cm and it backs out ~22 cm; reach the full ``max_cm`` untouched and it backs out that far — so the return leg always matches how far you really got. Internally this constructs a live :class:`DriveUntilImpact` instance, captures it in a closure, and appends a ``defer(...)`` that reads its ``impact_result`` at runtime to build the drive-back leg. This is the supported way to reuse the measured distance — see :func:`drive_until_impact` for why holding the plain factory's return value does not work. If the measured return distance rounds to zero (e.g. ``clearance_cm`` is larger than the distance driven), the drive-back leg is skipped. Prerequisites: A working IMU (``raccoon.hal.IMU``) and odometry. ``accel_threshold`` must be tuned above the cruise-noise floor on real hardware; the mock simulator does not model collision impacts, so the *triggering* can only be validated on the robot. :param max_cm: Nominal forward distance in centimeters if nothing is hit (default 50.0). Doubles as a safety cap on travel. :param clearance_cm: Centimeters to leave when returning — the drive-back is ``driven - clearance`` (default 0.0 = return to the exact start). :param speed: Drive speed in m/s (default 0.5). Lower is gentler and makes the bump cleaner and less likely to shove the game piece. :param accel_threshold: Minimum XY linear-acceleration magnitude in m/s² to classify as an impact (default 0.5). :param grace_period: Seconds to ignore acceleration after starting, so the robot's own acceleration doesn't trigger detection (default 0.3). :param grace_distance_cm: Centimeters the robot must drive clear of the start before impact detection arms (default 3.0), so the launch lurch doesn't trigger instantly. Combined with ``grace_period``. :param max_duration: Safety timeout in seconds for the forward leg (default 8.0). :returns: drive-until-impact followed by a deferred drive-back. :rtype: A ``seq`` step Example:: from raccoon.step.motion import drive_until_impact_back # Drive up to 40 cm forward; if a cube stops us early, back out just # as far, leaving a 3 cm clearance. drive_until_impact_back(max_cm=40, clearance_cm=3.0, speed=0.4)