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
| Condition | What it means | Route response |
|---|---|---|
| Target confirmed | Command ended and measured turn is within tolerance | Allow the next step |
| Timeout | Software action deadline reached | Stop and abandon this route |
| Cancellation | Operator request has been latched | Stop and keep the route cancelled |
| Mode changed | Disabled or no longer autonomous | Stop autonomous actions |
| Not ready | Preparation did not finish | Do not start |
| Ended off target | Command ended outside measured tolerance | Stop 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 = TrueA 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 | Controlled setup | Expected evidence |
|---|---|---|
| Normal completion | Short low-speed turn in a clear area | Target confirmed; second turn begins |
| Deadline | Temporarily choose a very short deadline | Timeout; no next turn |
| Cancellation | Hold B during a test turn | Cancelled; release does not resume |
| Disable | Use normal competition controls | Robot stops; autonomous does not continue |
| Off-target logic | Simulate a completed command with incorrect rotation | Ended 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
- Trace: explain the stop-condition priority in monitored_turn.
- Time: compare a real timer with adding a fixed amount per loop.
- Cancel: demonstrate that releasing B does not resume the route.
- Abort: show that a failed first turn prevents the second turn.
- Log: record outcome, elapsed time and measured angle for three trials.
- Design: describe how to verify a forward-distance target and where wheel slip limits that check.
- Ownership: list each actuator in your robot and the task responsible for controlling and stopping it.
- 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.