VEX V5 PYTHON | 10.1 AUTONOMOUS SELECTION

Guide objective

Select an autonomous routine on the Brain screen, confirm its physical starting pose, and capture a fixed plan with fresh sensor baselines at autonomous entry.

01 | SELECT THE ROUTINE BEFORE THE MATCH

A team may have different starting-side routes, a skills routine and a no-motion option. A Brain-screen selector lets students choose clearly without changing code immediately before a run.

Selection, placement confirmation and execution are separate steps. Tapping a route must not start its motors.

The starter defaults to NO MOTION and dispatches a preview only. Use it to validate the interface and start-pose handling before connecting a tested motion runner.

02 | DEFINE WHAT A ROUTE RECORD CONTAINS

The starter stores the first three fields and previews dispatch
FieldPurposeExample
Stable IDUsed by dispatch; independent of the display labelLEFT
Visible labelRecognisable choice for the teamLEFT PRACTICE
Starting poseReference point x/y and physical heading(500, 500, 0°)
Route implementationGuarded routine matching this startTeam’s tested left route
Configuration versionRecorded alongside test resultsYour route/code version label

Use IDs rather than interpreting display text. Keep the route’s geometry, allowed mechanism state and starting pose together in your project configuration.

The example coordinates are local practice values. Replace them with measured starts that follow the current game setup and your chosen field frame.

03 | A STARTING POSE IS A PHYSICAL CLAIM

A pose describes a chosen robot reference point, not whichever bumper is easiest to measure. State exactly which point your odometry follows and use it for placement.

Maintain the course convention: +x right, +y forward at heading 0°, and clockwise-positive heading. Units are millimetres and degrees.

Use field marks or a repeatable placement guide. Check heading, wheel contact, tracking-wheel position, mechanism travel limits and clearance before confirming.

Assigning (x, y, heading) in software does not move the robot to that location. The confirmation button records the operator’s assertion that placement is correct.

04 | USE CLEAR TOUCH TARGETS

The V5 Brain screen uses a 480 × 240 pixel coordinate area. Screen x increases rightward and y increases downward. These UI pixels are separate from field millimetres.

Buttons have gaps and avoid the screen boundary
ControlTop-left pixelSize
NO MOTION(10, 40)220 × 42
LEFT PRACTICE(250, 40)220 × 42
RIGHT PRACTICE(10, 94)220 × 42
SKILLS PREVIEW(250, 94)220 × 42
CONFIRM PLACEMENT(10, 180)220 × 48
EDIT / UNCONFIRM(250, 180)220 × 48

Use short labels, a visible selected state and a start-pose line. The example uses MONO15 and redraws only when the displayed state changes.

It records the button under the initial press, then fires once on release after 80–2000 ms. It does not treat a held finger as repeated taps. The API reports the last press coordinates, so this is not a continuous drag-tracking interface.

05 | CONFIRM, THEN LOCK THE PLAN

A displayed choice alone does not authorize a movement route
State or eventPolicy
Project startsNO MOTION selected; no plan confirmed
Choose a different routeInvalidate previous confirmation
Confirm a movement routeRequire ready sensors and disabled mode; record current baselines
Robot moves after confirmationInvalidate when measured wheel/rotation change exceeds thresholds
Robot enabledIgnore selector touches
Autonomous callback beginsConsume confirmation and use a local immutable plan
Driver mode or return to disabledRequire confirmation again
UI or starting-sensor faultInvalidate or reject the plan

The standalone code has no movement outputs. When integrating it, every real route still needs its own feedback checks, cancellation, shared deadline and cleanup.

06 | CAPTURE FRESH BASELINES AT THE ACTUAL START

Read wheel positions and inertial rotation at confirmation, then read them again at autonomous entry. Reject a movement route if the observations changed beyond the placement thresholds.

context = {
    "pose": selected_start_pose,
    "wheel_baseline": current_wheel_positions,
    "rotation_origin": current_inertial_rotation
}
# Field heading later:
# start_heading + (current_rotation - rotation_origin)

The estimator is seeded with the selected starting pose and current incremental baselines. Do not reset physical sensor readings just to make them equal field coordinates.

Use continuous rotation for the movement check; wrapping away a full revolution would hide a changed start. If a robot is lifted and repositioned without wheel or angular evidence, this check cannot detect the translation. Physical placement confirmation still matters.

07 | COMPLETE BRAIN-SCREEN PREVIEW SELECTOR

Use a standalone preview project containing no other movement code. Paste below # Begin project code, keeping generated VEX imports and Brain setup unchanged. Avoid duplicate device declarations or Competition objects.

The example uses forward Rotation Sensor 17, sideways Rotation Sensor 19 and Inertial Sensor 20. Verify sensor signs, travel scales and installation before changing SENSORS_CONFIGURED to True. Keep the robot stationary and disabled during calibration.

You can test NO MOTION confirmation without enabling the sensor configuration. Movement-route confirmation requires the configured sensors to be ready. All dispatch paths remain previews with no motor commands.

# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | BRAIN SELECTOR AND STARTING POSES
# =============================================================================
# Keep generated VEX imports and Brain setup above '# Begin project code'.
# Standalone PREVIEW project: no motor commands, no other movement code.
import math
forward_tracker = Rotation(Ports.PORT17, False)
side_tracker = Rotation(Ports.PORT19, False)
inertial = Inertial(Ports.PORT20)
SENSORS_CONFIGURED = False
FORWARD_MM_PER_TURN = 219.44
SIDE_MM_PER_TURN = 219.44
MOVE_INVALIDATE_MM = 5.0
TURN_INVALIDATE_DEG = 2.0
MAX_READ_MS = 50
POLL_MS = 20
MIN_TOUCH_MS = 80
MAX_TOUCH_MS = 2000
# Immutable route records: ID, label, start (x mm, y mm, clockwise heading deg).
# These local practice poses are examples, not official game starting locations.
ROUTES = (
    ('NONE','NO MOTION',None),
    ('LEFT','LEFT PRACTICE',(500.0,500.0,0.0)),
    ('RIGHT','RIGHT PRACTICE',(1500.0,500.0,0.0)),
    ('SKILLS','SKILLS PREVIEW',(500.0,500.0,90.0)),
)
# Hit rectangles: key, x, y, width, height.
BUTTONS = [('NONE',10,40,220,42),('LEFT',250,40,220,42),
           ('RIGHT',10,94,220,42),('SKILLS',250,94,220,42),
           ('CONFIRM',10,180,220,48),('EDIT',250,180,220,48)]
ready = False
run_active = False

def route_for(key):
    for route in ROUTES:
        if route[0] == key:
            return route
    raise ValueError('Unknown route')

def hit(x,y):
    for key,bx,by,w,h in BUTTONS:
        if bx <= x < bx+w and by <= y < by+h:
            return key
    return None

def read_baseline():
    before = brain.timer.time(MSEC)
    if not inertial.installed() or inertial.is_calibrating():
        raise ValueError('Inertial unavailable')
    sample = (forward_tracker.position(TURNS)*FORWARD_MM_PER_TURN,
              side_tracker.position(TURNS)*SIDE_MM_PER_TURN,
              inertial.rotation(DEGREES))
    if not all(math.isfinite(v) for v in sample):
        raise ValueError('Invalid starting readings')
    if brain.timer.time(MSEC)-before > MAX_READ_MS:
        raise ValueError('Starting read too slow')
    return sample

def changed(a,b):
    # Continuous rotation: do not wrap away a complete physical revolution.
    return (max(abs(a[0]-b[0]),abs(a[1]-b[1])) > MOVE_INVALIDATE_MM
            or abs(a[2]-b[2]) > TURN_INVALIDATE_DEG)

class Selector:
    def __init__(self):
        self.selected = 'NONE'
        self.confirmed = None  # Immutable (route_record, confirmation_readings).
        self.message = 'SELECT THEN CONFIRM'
        self.was_pressed = False
        self.pending = None
        self.pressed_at = None
        self.revision = 0
    def invalidate(self, message):
        self.confirmed = None
        self.message = message
        self.revision += 1
    def touch(self, now, pressed, x, y, enabled):
        if enabled:
            self.pending = None
            self.was_pressed = pressed
            return None
        event = None
        if pressed and not self.was_pressed:
            self.pending = hit(x,y)
            self.pressed_at = now
        elif not pressed and self.was_pressed:
            if (self.pending is not None and self.pressed_at is not None
                    and MIN_TOUCH_MS <= now-self.pressed_at <= MAX_TOUCH_MS):
                event = self.pending
            self.pending = None
        self.was_pressed = pressed
        return event
    def choose(self,key):
        route_for(key)
        self.selected = key
        self.invalidate('CHECK PLACEMENT; CONFIRM')
    def confirm(self,baseline):
        self.confirmed = (route_for(self.selected),baseline)
        self.message = 'CONFIRMED; DO NOT MOVE'
        self.revision += 1
    def take(self):
        plan = self.confirmed
        self.invalidate('CONFIRM AGAIN FOR NEXT RUN')
        return plan  # Consume confirmation, so this plan cannot be rerun implicitly.

selector = Selector()

def handle_event(key):
    if competition.is_enabled() or run_active:
        return
    if key in ('NONE','LEFT','RIGHT','SKILLS'):
        selector.choose(key)
    elif key == 'EDIT':
        selector.invalidate('EDIT; PLACE THEN CONFIRM')
    elif key == 'CONFIRM':
        if selector.selected == 'NONE':
            selector.confirm(None)
        elif not ready or not SENSORS_CONFIGURED:
            selector.invalidate('VERIFY SENSOR SETUP FIRST')
        else:
            try:
                baseline = read_baseline()
                # Field mode can change during an API call.
                if not competition.is_enabled() and not run_active:
                    selector.confirm(baseline)
            except Exception:
                selector.invalidate('START SENSOR FAULT')

def draw_selector():
    screen = brain.screen
    screen.clear_screen(Color.BLACK)
    screen.set_font(FontType.MONO15)
    screen.set_pen_color(Color.WHITE)
    screen.print_at('AUTON: '+selector.message,x=10,y=24,opaque=False)
    for key,x,y,w,h in BUTTONS:
        label = ('CONFIRM PLACEMENT' if key=='CONFIRM' else 'EDIT / UNCONFIRM'
                 if key=='EDIT' else route_for(key)[1])
        selected = key==selector.selected
        screen.set_fill_color(Color.BLUE if selected else Color.BLACK)
        screen.set_pen_color(Color.GREEN if selected else Color.WHITE)
        screen.draw_rectangle(x,y,w,h)
        screen.set_pen_color(Color.WHITE)
        screen.print_at(label,x=x+10,y=y+27,opaque=False)
    pose = route_for(selector.selected)[2]
    text = ('Start: not applicable' if pose is None else
            'Start x='+str(int(pose[0]))+' y='+str(int(pose[1]))+' h='+str(int(pose[2]))+' deg')
    screen.print_at(text,x=10,y=160,opaque=False)

# =============================================================================
# ROUTE DISPATCH: PREVIEW ONLY; CONNECT A GUARDED RUNNER HERE LATER
# =============================================================================
def dispatch_preview(route,context):
    print('PREVIEW ROUTE',route[0],route[1])
    print('START POSE',context['pose'],'WHEEL BASELINE',context['wheel_baseline'],
          'INERTIAL ROTATION ORIGIN',context['rotation_origin'])
    return 'PREVIEW_ONLY_NO_MOTOR_OUTPUT'

def autonomous():
    global run_active
    if run_active:
        print('REJECTED_OVERLAPPING_CALLBACK')
        return
    run_active = True
    try:
        plan = selector.take()  # Local immutable plan, consumed before dispatch.
        if plan is None:
            print('NO_CONFIRMED_PLAN: no motion')
            return
        route,confirmed_readings = plan
        if route[0] == 'NONE':
            print('NO_MOTION_SELECTED')
            return
        if (not ready or not competition.is_enabled()
                or not competition.is_autonomous()):
            print('START_NOT_AUTHORIZED')
            return
        current = read_baseline()  # Fresh incremental baselines at actual start.
        if not competition.is_enabled() or not competition.is_autonomous():
            print('START_INTERRUPTED')
            return
        if confirmed_readings is None or changed(confirmed_readings,current):
            print('START_MOVED_SINCE_CONFIRMATION: no motion')
            return
        context = {'pose':route[2],'wheel_baseline':current[:2],
                   'rotation_origin':current[2]}
        # Your odometry starts from route pose and these current baselines.
        # Preserve the physical heading origin; no hardware set_heading is needed.
        print(dispatch_preview(route,context))
    except Exception:
        print('START_OR_DISPATCH_FAULT: no preview completed')
    finally:
        # Real movement integration needs resource cleanup here and on cancellation.
        run_active = False

def driver_control():
    selector.invalidate('DRIVER MODE; RECONFIRM LATER')
    # Preview project has no joystick or motor output.

competition = Competition(driver_control,autonomous)
if SENSORS_CONFIGURED and not competition.is_enabled():
    try:
        if not inertial.installed():
            raise ValueError('Inertial missing')
        inertial.calibrate()  # Disabled and stationary setup.
        ready = not inertial.is_calibrating() and not competition.is_enabled()
    except Exception:
        ready = False

shown = None
was_enabled = competition.is_enabled()
next_start_check = 0
try:
    while True:
        enabled = competition.is_enabled()
        if was_enabled and not enabled:
            selector.invalidate('DISABLED AGAIN; RECONFIRM')
        was_enabled = enabled
        pressed = brain.screen.pressing()
        event = selector.touch(brain.timer.time(MSEC),pressed,
                    brain.screen.x_position(),brain.screen.y_position(),enabled)
        if event is not None:
            handle_event(event)
        now = brain.timer.time(MSEC)
        if (not enabled and not run_active and selector.confirmed is not None
                and selector.confirmed[0][0] != 'NONE' and now>=next_start_check):
            next_start_check = now+100
            try:
                plan = selector.confirmed
                current = read_baseline()
                if selector.confirmed is plan and changed(plan[1],current):
                    selector.invalidate('MOVED; PLACE AND RECONFIRM')
            except Exception:
                selector.invalidate('START SENSOR FAULT')
        view = (selector.revision,enabled,run_active)
        if view != shown:
            # One screen writer; touch controls are ignored while enabled.
            if enabled:
                brain.screen.clear_screen(Color.BLACK)
                brain.screen.set_pen_color(Color.WHITE)
                brain.screen.set_font(FontType.MONO15)
                brain.screen.print_at('ENABLED: SELECTOR LOCKED',x=10,y=30)
            else:
                draw_selector()
            shown = view
        wait(POLL_MS,MSEC)
except Exception:
    selector.invalidate('UI FAULT; RESTART PROJECT')
    print('SELECTOR_UI_FAULT: no motion in this preview project')

Choosing LEFT, RIGHT or SKILLS captures a matching route record. On autonomous entry, the callback prints the route and the fresh pose context. It does not drive, score or implement a skills route.

The main loop owns screen drawing. Callbacks report to the console, reducing competing screen writers. Mode checks are sampled and shared-state handling is a teaching pattern, not a general atomic locking system.

08 | CONNECT YOUR GUARDED ROUTE RUNNER

Replace dispatch_preview only after validating the selector. Use the route ID to choose the corresponding tested routine and pass the captured pose context into its estimator setup.

# Integration sketch, not additional standalone code:
# if route_id == "LEFT":
#     seed_pose(selected_pose, current_sensor_baselines)
#     return run_left_with_shared_deadline()
# elif route_id == "RIGHT":
#     seed_pose(selected_pose, current_sensor_baselines)
#     return run_right_with_shared_deadline()
# else:
#     return "ROUTE_NOT_CONNECTED"

Adapt the output ownership and cleanup approach from 09.4. An unimplemented route must return an explicit no-motion result. Do not substitute a different route silently.

A real driver callback must cancel or revoke the previous autonomous owner before handing over motor control. Include every mechanism in the cleanup policy. The preview callback contains no driver-control implementation.

09 | DO NOT CONFUSE START CHOICE WITH ROUTE MIRRORING

Left and right labels do not automatically produce mirrored paths. A reflection must transform every relevant point and heading in the route and its start pose, using one declared mirror line.

Review 07.6 before adding mirrored routes. Mechanism asymmetry, obstacles and scoring layout may require different actions rather than a geometric reflection alone.

The two practice records here are independent choices. They do not claim that any current game’s left and right routes are equivalent.

10 | PRE-RUN CHECKS FOR THE TEAM

  1. Confirm the code and route version.
  2. Select the intended route while disabled.
  3. Place the declared reference point and heading accurately.
  4. Confirm tracking-wheel contact, sensor readiness and mechanism state.
  5. Check battery and motor conditions from 09.6.
  6. Press CONFIRM PLACEMENT and avoid moving the robot.
  7. Read the displayed route and starting pose back as a team.
  8. Start through the appropriate competition-control test setup.

The selector does not enforce field legality, recognize the alliance or verify all mechanisms. Add only checks your sensors and setup can support, and keep the physical checks explicit.

11 | TEST THE INTERFACE AND CALLBACK HANDOVER

  1. Check every rectangle centre, edge and gap.
  2. Hold a button and verify only one release event.
  3. Reject a very brief touch and a hold longer than two seconds.
  4. Change the route after confirmation and verify the plan clears.
  5. Verify touches cannot edit an enabled selection.
  6. Move the wheel or inertial readings after confirmation and reject the start.
  7. Consume a plan and verify a second run requires reconfirmation.
  8. Use a different physical start and verify fresh baselines.

After connecting movement, test disable, driver handover, missing sensors and route failures on a clear practice field with conservative motion settings.

12 | COMMON MISTAKES

  • Tapping a route starts the robot: keep choice and execution separate.
  • Defaulting to a movement routine: start with a no-motion choice.
  • Reading a live selection during the route: capture a fixed plan.
  • Confusing UI pixels with field units: keep coordinate systems separate.
  • Changing pose without physical placement: confirm the actual reference point.
  • Reusing an old wheel baseline: capture current readings at start.
  • Allowing repeated implicit runs: consume confirmation.
  • Assuming a preview includes cleanup for real motors: integrate a tested output owner.

CHALLENGES | SELECT, CONFIRM AND DISPATCH

  1. Layout: add your team’s route labels without overlapping buttons or text.
  2. Pose: measure and document the robot reference point for each route.
  3. Touch: test boundary pixels, held presses and enabled-mode input.
  4. Invalidate: change the selection and simulate movement after confirmation.
  5. Capture: show that dispatch uses one fixed route record and fresh baselines.
  6. Consume: prove a second callback cannot implicitly rerun a consumed plan.
  7. Integrate: connect one conservative guarded practice route.
  8. Evaluate: explain a repositioning error that wheel and inertial checks cannot detect.
Success evidence

Screen-layout checks, touch-event traces, confirmed route records, rejected-start reasons and a pose-baseline record from each run. Physical tests must also demonstrate correct placement and interruption handling.

OFFICIAL REFERENCES

The selector, confirmation policy and preview dispatcher are independently written teaching examples requiring physical validation before connection to movement routines.