We’re moving to a new home! Our website is currently in test mode while we update and transfer our content. Some pages and resources may be temporarily unavailable. We’ll be back with all resources shortly. Thank you for your patience.

VEX V5 PYTHON | 02.6 TIMEOUTS AND STOP CONDITIONS

Guide objective

Give movements a deadline, monitor stop conditions and prevent the next route step from running after a failed or cancelled action.

01 | EVERY ACTION NEEDS AN EXIT

A route that assumes every move succeeds can continue from the wrong position. Before starting an action, decide: what target should it reach, how long may it try, and what should cancel it?

For example, turn right 45 degrees, allow three seconds, and stop if the robot leaves autonomous mode or the operator requests cancellation. Continue only if the measured target is confirmed.

02 | WHAT A TIMEOUT DOES

drivetrain.set_timeout(3, SECONDS)
drivetrain.drive_for(FORWARD, 300, MM)

The drivetrain timeout limits the time allowed for a target movement. It does not measure success. If the robot cannot complete its target within that time, the command can end and execution can continue.

Do not solve every short move by increasing its timeout. Check load, traction, geometry, speed and the physical path first.

A timeout on target commands does not replace an explicit timed stop for continuous drivetrain.drive or motor.spin commands. Those commands need their own bounded control logic.

03 | BLOCKING VS MONITORED MOVEMENT

drivetrain.turn_for(RIGHT, 45, DEGREES)
# The next line waits for this command to end.

drivetrain.turn_for(RIGHT, 45, DEGREES, wait=False)
# The next line runs while the turn is in progress.

wait=False allows your code to monitor the action. It also makes it possible to accidentally start the next movement too soon. Start one action, monitor it, stop or verify it, and only then decide whether to continue.

A monitoring loop should yield with wait, so other runtime work can proceed. A 20 ms polling interval is a starting example, not a guaranteed response time.

04 | CHOOSE THE STOP CONDITIONS

A stopped action is not always a successful action
ConditionWhat it meansRoute response
Target confirmedCommand ended and measured turn is within toleranceAllow the next step
TimeoutSoftware action deadline reachedStop and abandon this route
CancellationOperator request has been latchedStop and keep the route cancelled
Mode changedDisabled or no longer autonomousStop autonomous actions
Not readyPreparation did not finishDo not start
Ended off targetCommand ended outside measured toleranceStop and investigate

This example gives cancellation and mode checks priority over completion. It checks the deadline before accepting success, so a result discovered after the deadline is treated as a timeout.

Other projects may add a limit switch or validated sensor condition. Define how disconnected, stale or implausible readings are handled before trusting them.

05 | MEASURE REAL ELAPSED TIME

start_ms = brain.timer.time(MSEC)
elapsed_ms = brain.timer.time(MSEC) - start_ms

Read a timer rather than assuming every loop takes exactly 20 ms. Sensor reads and other tasks also take time.

Do not clear the shared Brain timer while an action is measuring an elapsed interval. A separate Timer object is useful when another part of the project needs to reset its own timer.

The software deadline in this guide includes the short settling interval. The drivetrain’s own timeout is set slightly longer as a second bound, so the monitor normally handles the deadline first.

06 | CANCELLATION SHOULD STAY CANCELLED

cancel_requested = False

# A monitored check can latch the request:
if controller.buttonB.pressing():
    cancel_requested = True

A cancellation flag records the request. Releasing B must not restart the route. Reset it only when deliberately starting a new autonomous invocation.

Polling can miss a very brief press, so hold B until cancellation appears. This is a software test control, not a hardware emergency stop. Verify the normal competition disable control independently.

The flag itself does not stop a motor. The movement task must check it, issue a stop and return without starting another action.

07 | CHECK THE TARGET, NOT JUST IS_DONE

is_done tells you the drivetrain command has finished. Treat that as a reason to inspect the outcome, rather than a success certificate.

For a relative turn, record the initial accumulated rotation and add a signed target angle: right is positive and left is negative. Unlike heading, accumulated rotation does not wrap at 360.

target_rotation = start_rotation + signed_angle
error = abs(target_rotation - drivetrain.rotation(DEGREES))

The sample accepts a turn within three degrees after a short settling wait. This tolerance is an example to test and tune. It confirms the sensor’s angle estimate, not field position or obstacle clearance.

For straight travel, compare left and right encoder changes against the calibrated wheel-travel target. Encoder agreement still cannot prove ground distance when wheels slip. Add independent measurement where needed.

08 | COMPLETE MONITORED TURN PROJECT

This starter deliberately focuses on turns so the outcome can be checked with the inertial sensor. It preserves the explicit four-motor configuration and established split-stick driver controls.

Keep generated imports and Brain setup above it, including import math. Paste below # Begin project code, outside the generated region, replacing the earlier starter. Do not duplicate manually declared devices in the Devices panel.

Match ports, cartridges, reversal flags and geometry to your robot. The example assumes direct drive and an inertial sensor on port 20. Verify sensor connection and readings, then calibrate while still and disabled.

# =============================================================================
# TEAM [MY TEAM NAME / NUMBER]
# =============================================================================
# ROBOT CONFIGURATION
# - FOUR-motor drivetrain:
#       Left  = Ports 3 and 4
#       Right = Ports 1 and 2
# - Example: blue cartridges, left normal, right reversed.
#   VERIFY your actual ports, cartridges and direction settings.
# DRIVER CONTROLS
# - Left joystick vertical (axis3): forward/back
# - Right joystick horizontal (axis1): turn
# - This program MOVES in autonomous and driver-control modes.
# - Inertial sensor: Port 20 (example; check your robot)
# - Autonomous: monitored right 45, then left 45 degrees.
# - Hold controller B to cancel autonomous (software test control).
# - Example geometry assumes DIRECT drive (external ratio 1).
# - Add BELOW # Begin project code, outside generated configuration.
# - Do not also define these drive motors in the Devices panel.
# - Keep generated from vex import * and brain = Brain() above.

# =============================================================================
# FOUR-MOTOR DRIVETRAIN
# =============================================================================
left_motor_1 = Motor(Ports.PORT3, GearSetting.RATIO_6_1, False)
left_motor_2 = Motor(Ports.PORT4, GearSetting.RATIO_6_1, False)
right_motor_1 = Motor(Ports.PORT1, GearSetting.RATIO_6_1, True)
right_motor_2 = Motor(Ports.PORT2, GearSetting.RATIO_6_1, True)

left_drive = MotorGroup(left_motor_1, left_motor_2)
right_drive = MotorGroup(right_motor_1, right_motor_2)

# =============================================================================
# MEASURED GEOMETRY: EXAMPLES, REPLACE WITH YOUR MEASUREMENTS
# =============================================================================
WHEEL_DIAMETER_MM = 101.6
WHEEL_TRAVEL_MM = math.pi * WHEEL_DIAMETER_MM
TRACK_WIDTH_MM = 300
WHEEL_BASE_MM = 250
EXTERNAL_GEAR_RATIO = 1.0  # This example is direct drive only

inertial_sensor = Inertial(Ports.PORT20)

drivetrain = SmartDrive(
    left_drive, right_drive, inertial_sensor,
    WHEEL_TRAVEL_MM, TRACK_WIDTH_MM, WHEEL_BASE_MM,
    MM, EXTERNAL_GEAR_RATIO
)

# =============================================================================
# CONTROLLER, SETTINGS AND HELPERS
# =============================================================================
controller = Controller(PRIMARY)  # Do not duplicate a generated Controller
DEADBAND = 5
DRIVE_LIMIT_PERCENT = 30          # Start low; increase only after testing
TURN_GAIN = 0.7
LOOP_DELAY_MS = 20
AUTON_SPEED_PERCENT = 20
AUTON_DRIVE_PERCENT = 20
AUTON_TURN_PERCENT = 15
AUTON_STOP_MODE = BRAKE
CALIBRATION_LIMIT_MS = 5000
setup_ready = False
ACTION_LIMIT_MS = 3000
POLL_MS = 20
SETTLE_MS = 100
TURN_TOLERANCE_DEGREES = 3
cancel_requested = False
PAUSE_MS = 500

def apply_deadband(value):
    return 0 if abs(value) <= DEADBAND else value

def drive_mix(forward, turn):
    left = forward + turn
    right = forward - turn
    largest = max(100, abs(left), abs(right))
    left = left * 100 / largest
    right = right * 100 / largest
    return (left * DRIVE_LIMIT_PERCENT / 100,
            right * DRIVE_LIMIT_PERCENT / 100)

def set_side_output(side, output):
    if abs(output) < 0.01:
        side.stop(BRAKE)
    elif output > 0:
        side.spin(FORWARD, output, PERCENT)
    else:
        side.spin(REVERSE, -output, PERCENT)

def show_status(message):
    brain.screen.clear_screen()
    brain.screen.set_cursor(1, 1)
    brain.screen.print(message)

def stop_drive():
    left_drive.stop(BRAKE)
    right_drive.stop(BRAKE)

def autonomous_ready():
    return (setup_ready and competition.is_enabled()
            and competition.is_autonomous())

def stop_reason():
    global cancel_requested
    if controller.buttonB.pressing():
        cancel_requested = True
    if cancel_requested:
        return "CANCELLED"
    if not setup_ready:
        return "NOT_READY"
    if not (competition.is_enabled() and competition.is_autonomous()):
        return "MODE_CHANGED"
    return None

def finish_action(reason):
    drivetrain.stop(AUTON_STOP_MODE)
    show_status(reason)
    return reason

def monitored_turn(direction, angle_degrees, speed=None):
    if direction not in (LEFT, RIGHT):
        raise ValueError("Use LEFT or RIGHT")
    if (not isinstance(angle_degrees, (int, float))
            or not 0 < angle_degrees <= 180):
        raise ValueError("This starter accepts angles above 0 up to 180")
    selected = AUTON_TURN_PERCENT if speed is None else speed
    if not isinstance(selected, (int, float)) or not 0 < selected <= 100:
        raise ValueError("Speed must be above 0 and at most 100")
    reason = stop_reason()
    if reason is not None:
        return finish_action(reason)

    start_rotation = drivetrain.rotation(DEGREES)
    signed_angle = angle_degrees if direction == RIGHT else -angle_degrees
    target_rotation = start_rotation + signed_angle
    start_ms = brain.timer.time(MSEC)
    drivetrain.set_stopping(AUTON_STOP_MODE)
    drivetrain.set_turn_velocity(selected, PERCENT)
    # The software deadline should normally stop first.
    drivetrain.set_timeout(ACTION_LIMIT_MS + 500, MSEC)
    drivetrain.turn_for(direction, angle_degrees, DEGREES, wait=False)
    # Yield after starting the asynchronous command before inspecting status.
    wait(POLL_MS, MSEC)
    while True:
        reason = stop_reason()
        if reason is not None:
            return finish_action(reason)
        elapsed_ms = brain.timer.time(MSEC) - start_ms
        if elapsed_ms >= ACTION_LIMIT_MS:
            return finish_action("TIMEOUT")
        if drivetrain.is_done():
            drivetrain.stop(AUTON_STOP_MODE)
            wait(SETTLE_MS, MSEC)
            reason = stop_reason()
            if reason is not None:
                return finish_action(reason)
            if brain.timer.time(MSEC) - start_ms >= ACTION_LIMIT_MS:
                return finish_action("TIMEOUT")
            error = abs(target_rotation - drivetrain.rotation(DEGREES))
            if error <= TURN_TOLERANCE_DEGREES:
                return finish_action("TARGET_CONFIRMED")
            return finish_action("ENDED_OFF_TARGET")
        wait(POLL_MS, MSEC)

# =============================================================================
# PRE-AUTON: CALLED ONCE AT PROJECT STARTUP
# =============================================================================
def pre_autonomous():
    global setup_ready
    setup_ready = False
    stop_drive()
    show_status("Keep robot still")
    inertial_sensor.calibrate()
    elapsed_ms = 0
    while inertial_sensor.is_calibrating() and elapsed_ms < CALIBRATION_LIMIT_MS:
        wait(10, MSEC)
        elapsed_ms += 10
    if inertial_sensor.is_calibrating():
        show_status("Calibration timeout")
        return
    # Place the robot facing your chosen route-zero direction first.
    drivetrain.set_heading(0, DEGREES)
    drivetrain.set_stopping(BRAKE)
    setup_ready = True
    show_status("Setup ready")

# =============================================================================
# AUTONOMOUS: A FINITE ROUTINE
# =============================================================================
def autonomous():
    global cancel_requested
    # A new autonomous invocation starts a new cancellation session.
    # A held B button will immediately cancel it again.
    cancel_requested = False
    try:
        result = monitored_turn(RIGHT, 45)
        if result != "TARGET_CONFIRMED":
            return
        result = monitored_turn(LEFT, 45)
        if result != "TARGET_CONFIRMED":
            return
        show_status("Route targets confirmed")
    finally:
        # Normal return and Python exceptions pass through this cleanup.
        stop_drive()

# =============================================================================
# DRIVER CONTROL: REPEATING UPDATES WHILE ENABLED
# =============================================================================
def driver_control():
    if not setup_ready:
        stop_drive()
        return
    show_status("Driver control")
    while competition.is_enabled() and competition.is_driver_control():
        forward = apply_deadband(controller.axis3.position())
        turn = apply_deadband(controller.axis1.position()) * TURN_GAIN
        left_output, right_output = drive_mix(forward, turn)
        set_side_output(left_drive, left_output)
        set_side_output(right_drive, right_output)
        wait(LOOP_DELAY_MS, MSEC)
    stop_drive()

# =============================================================================
# PROGRAM START: SETUP, THEN CALLBACK REGISTRATION
# =============================================================================
pre_autonomous()
competition = Competition(driver_control, autonomous)

# Keep the main task alive without running a driving loop here.
while True:
    wait(100, MSEC)

The autonomous routine attempts right 45, then left 45. Any result other than TARGET_CONFIRMED prevents the next step. A finally block stops the drivetrain on normal returns and Python exceptions.

Do not rely on finally running after forced thread termination or project shutdown. Competition disable and explicit motor shutdown need their own verification. The software cancellation applies to this autonomous routine; normal driver control remains a separate callback.

09 | STOPPING A TASK IS DIFFERENT FROM STOPPING A MOTOR

A task is code executing over time. A motor command is an instruction sent to hardware. Ending a task must not be assumed to undo the last motor command.

Cooperative cancellation is the preferred starting pattern here: request cancellation, let the task observe it, stop the actuators it owns and return. Do not have multiple tasks continuously issue commands to the same drivetrain.

VEX Thread starts a background function. Thread.stop terminates that thread; it is not a general motor-stop command. A stopped thread cannot be restarted, so a new Thread object is needed for another run.

def report_heading():
    while True:
        print(drivetrain.heading(DEGREES))
        wait(100, MSEC)

# Standalone demonstration, after drivetrain configuration:
reporter = Thread(report_heading)
wait(1, SECONDS)
reporter.stop()

This reporting task does not own motors, so terminating it needs no motor cleanup. A motor-owning worker needs a cancellation protocol and coordinated shutdown. Stopping motors before killing an active worker is insufficient if it can immediately issue another command.

A simple single-owner movement monitor avoids that race. Add threads only when you can explain who owns each actuator and who stops each worker.

10 | PLAN CLEANUP FOR THE WHOLE ROBOT

The sample’s stop_drive stops only its drivetrain motors. A real robot may also have an intake, lift and scoring mechanism. Define cleanup for every actuator used by that routine.

Choose stopping behaviour per mechanism. A loaded lift may need supported handling and appropriate holding behaviour; do not blindly coast every motor. Powered hold is not a mechanical support during disable or power loss.

Record the reason before returning. Avoid automatic retries until you know whether repeating the action is appropriate. A timeout should not silently trigger the same blocked movement again.

11 | TEST THE FAILURE PATHS

Test failures without deliberately jamming the robot
TestControlled setupExpected evidence
Normal completionShort low-speed turn in a clear areaTarget confirmed; second turn begins
DeadlineTemporarily choose a very short deadlineTimeout; no next turn
CancellationHold B during a test turnCancelled; release does not resume
DisableUse normal competition controlsRobot stops; autonomous does not continue
Off-target logicSimulate a completed command with incorrect rotationEnded off target; no next action

Verify directions with supported wheels first, then test on a clear level surface with room to turn. Restore the normal deadline after the timeout test.

Use simulated readings or a test harness for off-target logic. Do not intentionally obstruct or stall a robot to create a failure. Record requested angle, result, elapsed time and observed behaviour.

CHALLENGES | MAKE FAILURE BEHAVIOUR PREDICTABLE

  1. Trace: explain the stop-condition priority in monitored_turn.
  2. Time: compare a real timer with adding a fixed amount per loop.
  3. Cancel: demonstrate that releasing B does not resume the route.
  4. Abort: show that a failed first turn prevents the second turn.
  5. Log: record outcome, elapsed time and measured angle for three trials.
  6. Design: describe how to verify a forward-distance target and where wheel slip limits that check.
  7. Ownership: list each actuator in your robot and the task responsible for controlling and stopping it.
  8. Extend: design a sensor stop condition with a policy for invalid readings before adding it to your robot.
Success evidence

A saved project and test log covering completion, timeout, cancellation and disable. You can explain why task termination, command completion and target confirmation are different.

OFFICIAL REFERENCES

This is an independent learning resource; VEX and VEXcode are trademarks of their respective owners.