VEX V5 PYTHON | 04.5 HOMING AND TRAVEL LIMITS
Guide objective
Establish a verified mechanism zero with a limit switch, then apply shared physical-switch and software-position guards to every movement command.
01 | KNOW WHERE THE MECHANISM STARTS
An encoder can report movement, but a useful travel limit needs a physical reference. Homing moves a mechanism deliberately to a known reference, confirms it and assigns a position value.
A limit switch provides a contact signal. Combined with rotation feedback, it can establish a lower reference and help prevent later commands from pushing beyond the working range.
Homing is an intentional motor action. It must be slow, bounded and tested for the actual mechanism.
02 | READ A PHYSICAL SWITCH
lower_limit = Limit(brain.three_wire_port.a)
if lower_limit.pressing():
print("Lower switch pressed")Limit switches use the Brain’s 3-Wire ports, not numbered Smart Ports. The example uses A for lower and B for upper contact. pressing() returns the current Boolean state.
Test each switch manually with motor movement disabled. Confirm that pressing and releasing produce the expected readings before connecting those readings to motion.
Mount the actuator so the switch triggers before the mechanism reaches a damaging hard stop. Do not use the switch body as a structural stop.
03 | THREE LAYERS HAVE DIFFERENT ROLES
| Layer | Role | Limitation |
|---|---|---|
| Physical switch | Detect contact at a mounted reference or boundary | Can fail, disconnect or be mounted incorrectly |
| Software travel limit | Block commands beyond a calibrated position range | Depends on valid zero and sensor feedback |
| Mechanical design | Provide appropriate support, clearance and robust travel boundaries | Must not be repeatedly driven against damaging stops |
A Boolean switch is not a complete safety system. A disconnected switch can look unpressed, and a stuck switch can look like a valid contact. Timeouts and plausibility checks reduce some risks but cannot detect every failure.
04 | HOME, THEN ENABLE NORMAL TRAVEL
- Mark the mechanism unhomed before starting.
- If the lower switch is already pressed, move away slowly until a confirmed release.
- Seek the lower switch slowly in the homing direction.
- Stop immediately when it presses.
- Confirm the press while stopped.
- Set rotation position to zero and mark homing successful.
The initial release check helps detect a switch that remains pressed. If the switch is initially unpressed, the starter seeks it directly. More demanding homing systems can add a coarse seek followed by release and a second slow approach.
Switch zero is the trigger position, not necessarily the mechanism’s absolute hard-bottom position. Define your travel measurements from the same reference.
05 | DEBOUNCE WITHOUT DRIVING INTO THE SWITCH
A mechanical switch can briefly alternate between pressed and released around contact. A confirmation interval helps reject a transient signal.
First stop the motor, then confirm the state. Do not continue driving into a pressed switch while waiting for it to remain pressed for 60 ms.
The example retries the seek if the signal becomes unstable, under the same overall homing deadline. Choose the interval from tests, not from an assumption that every switch behaves identically.
06 | BOUND HOMING EVEN BEFORE ZERO IS KNOWN
Before homing, ordinary absolute software limits cannot be trusted. The homing action instead uses a short deadline, a maximum relative sensor-travel bound, low command power and physical switch conditions.
If the switch never appears, report failure, stop and keep the mechanism unhomed. Do not reset the encoder merely because the timer ended.
The relative bound assumes the sensor responds correctly. A frozen or disconnected reading can defeat it; the timeout remains a separate bound. Check motor/sensor sign and switch operation before enabling homing.
07 | PUT NORMAL LIMITS INSIDE THE WORKING RANGE
For an illustrative six-turn span, normal software travel might be limited to 0.15–5.85 sensor turns. These margins must be measured for the actual build and stopping behaviour.
| Condition | Blocked command | Normally allowed recovery |
|---|---|---|
| At lower switch or lower software limit | Down / negative | Up / positive |
| At upper switch or upper software limit | Up / positive | Down / negative |
| Both switches pressed | Both directions | Inspect and deliberately recover |
| Not homed or implausible position | Both directions | Verified homing or fault investigation |
Do not re-zero whenever a switch activates during normal operation. Unexpected contact may indicate a fault. The starter only establishes zero during an explicit successful homing action.
08 | SHARE THE GUARD BETWEEN DRIVER AND AUTONOMOUS
def guard_output(position, requested, lower_pressed, upper_pressed, ready, failed):
if not ready or failed or (lower_pressed and upper_pressed):
return 0
if not -0.1 <= position <= FULL_TRAVEL_TURNS + 0.1:
return 0
if requested < 0 and (lower_pressed or position <= SOFT_MIN_TURNS):
return 0
if requested > 0 and (upper_pressed or position >= SOFT_MAX_TURNS):
return 0
return max(-MAX_OUTPUT_PERCENT, min(MAX_OUTPUT_PERCENT, requested))
def set_mechanism_output(requested):
global fault, homed
position = mechanism_rotation.position(TURNS)
lower = lower_limit.pressing()
upper = upper_limit.pressing()
if lower and upper:
fault = "SWITCH_CONFLICT"
homed = False
if homed and not -0.1 <= position <= FULL_TRAVEL_TURNS + 0.1:
fault = "POSITION_FAULT"
homed = False
output = guard_output(position, requested, lower, upper, homed, fault is not None)
if not MOTION_CONFIGURED or not competition.is_enabled() or controller.buttonB.pressing():
output = 0
if abs(output) < 0.01:
stop_mechanism()
else:
direction = FORWARD if output > 0 else REVERSE
mechanism_motor.spin(direction, abs(output), PERCENT)
return output
Driver button commands and autonomous target corrections both pass through set_mechanism_output. The helper checks the switches, homed state, fault state and position before sending a motor command.
Faults remain latched until a deliberate new homing attempt after inspection. Changing modes does not silently clear a switch conflict or bad reference.
Homing has a separate, tightly bounded low-output path because it needs to establish the reference. It does not provide a general driver override of normal limits.
09 | COMPLETE MECHANISM-ONLY COMPETITION STARTER
Keep generated imports and Brain setup above this block. Paste below # Begin project code, outside the generated region, replacing previous starters. This project has one mechanism and no drivetrain.
The example uses motor port 9, Rotation Sensor port 19, lower 3-Wire A and upper 3-Wire B. Match directions, cartridges, ports and measured travel to the build. Avoid duplicate device declarations.
MOTION_CONFIGURED starts False, so movement is blocked until the configuration and physical switch tests are complete. Autonomous homes only unless RUN_AUTO_TARGET is deliberately enabled.
# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | SINGLE-MECHANISM HOMING STARTER
# =============================================================================
# CONFIGURATION
# - Mechanism motor: Smart Port 9, green cartridge (example)
# - Rotation Sensor: Smart Port 19
# - Lower limit switch: Brain 3-Wire A
# - Upper limit switch: Brain 3-Wire B
# - Positive motor/sensor direction must mean increasing mechanism travel.
# CONTROLS
# - Driver X rising edge: request homing
# - L1/L2: up/down after successful homing
# - Controller B: cancel current action / stop while held
# - No drivetrain in this mechanism-only example.
# Keep generated VEX imports and Brain setup above this code.
mechanism_motor = Motor(Ports.PORT9, GearSetting.RATIO_18_1, False)
mechanism_rotation = Rotation(Ports.PORT19, False)
lower_limit = Limit(brain.three_wire_port.a)
upper_limit = Limit(brain.three_wire_port.b)
controller = Controller(PRIMARY)
MOTION_CONFIGURED = False # Enable only after directions/switches are tested
RUN_AUTO_TARGET = False # False: autonomous homes only
FULL_TRAVEL_TURNS = 6.0 # Measured sensor span; teaching example
SOFT_MIN_TURNS = 0.15
SOFT_MAX_TURNS = 5.85
HOME_PERCENT = 8
MAX_OUTPUT_PERCENT = 15
HOMING_TIMEOUT_MS = 5000
HOMING_MAX_TRAVEL_TURNS = 7.0 # Bound relative sensor travel during homing
MOVE_TIMEOUT_MS = 4000
SWITCH_CONFIRM_MS = 60
POLL_MS = 20
TARGET_TOLERANCE_TURNS = 0.05
POSITION_KP_PERCENT_PER_TURN = 20
STOP_MODE = BRAKE # Choose for the supported actual mechanism
homed = False
fault = None
def stop_mechanism():
mechanism_motor.stop(STOP_MODE)
def status(text):
brain.screen.clear_screen()
brain.screen.set_cursor(1, 1)
brain.screen.print(text)
print("MECHANISM", text)
def mode_active(mode):
if not competition.is_enabled():
return False
return competition.is_autonomous() if mode == "AUTO" else competition.is_driver_control()
def abort_reason(mode, start_ms, timeout_ms):
if not mode_active(mode):
return "MODE_CHANGED"
if controller.buttonB.pressing():
return "CANCELLED"
if brain.timer.time(MSEC) - start_ms >= timeout_ms:
return "TIMEOUT"
if lower_limit.pressing() and upper_limit.pressing():
return "SWITCH_CONFLICT"
return None
def wait_switch_state(switch, pressed, mode, start_ms):
# Motors are already stopped before this confirmation pause.
confirmation_start = brain.timer.time(MSEC)
while brain.timer.time(MSEC) - confirmation_start < SWITCH_CONFIRM_MS:
reason = abort_reason(mode, start_ms, HOMING_TIMEOUT_MS)
if reason is not None:
return reason
if switch.pressing() != pressed:
return "UNSTABLE"
wait(POLL_MS, MSEC)
return "CONFIRMED"
def home_mechanism(mode):
global homed, fault
homed = False
if not MOTION_CONFIGURED:
stop_mechanism()
return "CONFIGURATION_REQUIRED"
fault = None # A deliberate new home attempt, after inspection.
start_ms = brain.timer.time(MSEC)
start_turns = mechanism_rotation.position(TURNS)
phase = "BACK_OFF" if lower_limit.pressing() else "SEEK"
status("Homing: " + phase)
try:
while True:
reason = abort_reason(mode, start_ms, HOMING_TIMEOUT_MS)
if reason is not None:
fault = reason
return reason
travel = abs(mechanism_rotation.position(TURNS) - start_turns)
if not travel <= HOMING_MAX_TRAVEL_TURNS:
fault = "HOMING_TRAVEL_FAULT"
return fault
if phase == "BACK_OFF":
if upper_limit.pressing():
fault = "UPPER_LIMIT_DURING_BACKOFF"
return fault
if lower_limit.pressing():
mechanism_motor.spin(FORWARD, HOME_PERCENT, PERCENT)
else:
stop_mechanism()
result = wait_switch_state(lower_limit, False, mode, start_ms)
if result == "CONFIRMED":
phase = "SEEK"
elif result != "UNSTABLE":
fault = result
return result
else:
if lower_limit.pressing():
# Stop immediately; do not drive into the switch during debounce.
stop_mechanism()
result = wait_switch_state(lower_limit, True, mode, start_ms)
if result == "CONFIRMED":
mechanism_rotation.set_position(0, TURNS)
homed = True
return "HOMED"
if result != "UNSTABLE":
fault = result
return result
else:
mechanism_motor.spin(REVERSE, HOME_PERCENT, PERCENT)
wait(POLL_MS, MSEC)
finally:
stop_mechanism()
def guard_output(position, requested, lower_pressed, upper_pressed, ready, failed):
if not ready or failed or (lower_pressed and upper_pressed):
return 0
if not -0.1 <= position <= FULL_TRAVEL_TURNS + 0.1:
return 0
if requested < 0 and (lower_pressed or position <= SOFT_MIN_TURNS):
return 0
if requested > 0 and (upper_pressed or position >= SOFT_MAX_TURNS):
return 0
return max(-MAX_OUTPUT_PERCENT, min(MAX_OUTPUT_PERCENT, requested))
def set_mechanism_output(requested):
global fault, homed
position = mechanism_rotation.position(TURNS)
lower = lower_limit.pressing()
upper = upper_limit.pressing()
if lower and upper:
fault = "SWITCH_CONFLICT"
homed = False
if homed and not -0.1 <= position <= FULL_TRAVEL_TURNS + 0.1:
fault = "POSITION_FAULT"
homed = False
output = guard_output(position, requested, lower, upper, homed, fault is not None)
if not MOTION_CONFIGURED or not competition.is_enabled() or controller.buttonB.pressing():
output = 0
if abs(output) < 0.01:
stop_mechanism()
else:
direction = FORWARD if output > 0 else REVERSE
mechanism_motor.spin(direction, abs(output), PERCENT)
return output
def move_to_percent(target_percent):
if not isinstance(target_percent, (int, float)) or not 0 <= target_percent <= 100:
raise ValueError("Use a percentage from 0 to 100")
target = target_percent / 100 * FULL_TRAVEL_TURNS
if not SOFT_MIN_TURNS <= target <= SOFT_MAX_TURNS:
raise ValueError("Target must be inside software travel limits")
if not homed or fault is not None:
stop_mechanism()
return "NOT_HOMED_OR_FAULTED"
start_ms = brain.timer.time(MSEC)
try:
while True:
reason = abort_reason("AUTO", start_ms, MOVE_TIMEOUT_MS)
if reason is not None:
return reason
if not homed or fault is not None:
return "NOT_HOMED_OR_FAULTED"
position = mechanism_rotation.position(TURNS)
if not -0.1 <= position <= FULL_TRAVEL_TURNS + 0.1:
set_mechanism_output(0)
return "POSITION_FAULT"
error = target - position
if abs(error) <= TARGET_TOLERANCE_TURNS:
return "POSITION_THRESHOLD_REACHED"
if set_mechanism_output(error * POSITION_KP_PERCENT_PER_TURN) == 0:
return "LIMIT_STOP"
wait(POLL_MS, MSEC)
finally:
stop_mechanism()
def autonomous():
result = home_mechanism("AUTO")
status(result)
if result == "HOMED" and RUN_AUTO_TARGET:
status(move_to_percent(30))
def driver_control():
previous_x = controller.buttonX.pressing()
try:
while mode_active("DRIVER"):
current_x = controller.buttonX.pressing()
if current_x and not previous_x:
stop_mechanism()
status(home_mechanism("DRIVER"))
current_x = controller.buttonX.pressing()
previous_x = current_x
up = controller.buttonL1.pressing()
down = controller.buttonL2.pressing()
requested = MAX_OUTPUT_PERCENT * (int(up) - int(down))
set_mechanism_output(requested)
wait(POLL_MS, MSEC)
finally:
stop_mechanism()
stop_mechanism()
status("Unhomed: configure and test before motion")
competition = Competition(driver_control, autonomous)
while True:
wait(100, MSEC)
In enabled driver mode, a new X press requests homing. L1/L2 request up/down after success; pressing both requests zero. B cancels the current homing or autonomous action and stops normal driver output while held.
The example does not home while disabled. Driver-triggered homing runs in the same motor-owning callback, so the normal button loop does not compete with it.
The 30% autonomous target is 1.8 sensor turns for the illustrative six-turn span. Targets at 0% or 100% are outside this example’s software-safe range and are rejected.
10 | CHOOSE STOPPING FOR THE ACTUAL MECHANISM
BRAKE is the starter’s placeholder for a supported test mechanism. A loaded lift may require suitable holding behaviour or a mechanical support arrangement.
Powered HOLD cannot support a load after power loss or during conditions that prevent motor actuation. Do not put hands under a raised mechanism while relying on code to hold it.
Measure run-on and choose margins accordingly. The target helper reports a position threshold, not a verified stationary dwell. Add a settling check if the application requires it.
11 | RECOVER DELIBERATELY
After failed homing, remain unhomed. Investigate wiring, switch mounting, sensor coupling and direction before requesting another attempt.
If the mechanism moves while disabled, slips or is repositioned manually, the old reference may no longer be valid. Re-home rather than assuming that an earlier success remains trustworthy.
The starter retains a successful reference between normal mode changes when no fault occurs. Decide whether your actual mechanism requires homing every enable cycle, and document that policy.
A deliberate re-home request clears the software fault to attempt recovery, but it cannot repair the physical cause. Do not repeatedly retry a jammed mechanism.
12 | TEST WITHOUT PUSHING AGAINST HARD STOPS
| Test | Expected behaviour |
|---|---|
| Manual switch test, movement blocked | Pressed/released readings agree with contact |
| Lower switch pressed at home request | Controlled back-off and release before seek |
| Homing switch absent in a simulated test | Timeout; no zero assigned |
| Both switches pressed | Conflict fault; no normal output |
| Lower/upper normal limit | Into-boundary command blocked; retreat allowed |
| Cancel or disable during homing | Stop; no successful reference assigned |
| Restart from an unknown position | Unhomed until deliberate successful homing |
Start with conservative travel margins and a supported mechanism. Do not unplug sensors while commanding movement or deliberately drive into a hard stop to create a failure.
Record switch repeatability, homing time, sensor zero and final physical position across three trials. A change in trigger location affects every later target.
CHALLENGES | ESTABLISH AND PROTECT A REFERENCE
- Configure: label Smart Ports and 3-Wire ports on a mechanism diagram.
- Explain: distinguish a physical switch, a software limit and a mechanical stop.
- Trace: describe homing when the lower switch starts pressed and when it starts released.
- Verify: show why the motor stops before the confirmation pause.
- Guard: test into-limit and away-from-limit requests with simulated positions.
- Abort: demonstrate cancellation and timeout without assigning a new zero.
- Repeat: record three homing trials and compare the physical reference.
- Document: write a recovery policy for a fault or manual mechanism movement.
Success evidence
A labelled setup, verified switch/direction checks, a saved project and a test log. Show that normal motion stays blocked before homing and that both driver and autonomous commands use the shared guard.
OFFICIAL REFERENCES
The homing and travel-limit logic are team-written examples. This is an independent learning resource; VEX and VEXcode are trademarks of their respective owners.