VEX V5 PYTHON | 10.5 MODULAR CODE

Guide objective

Organise robot code into clear responsibilities, capture validated settings for each run and use version control to preserve tested changes.

01 | FROM ONE LONG SCRIPT TO CLEAR RESPONSIBILITIES

Your autonomous now includes movement, mechanisms, sensors, routes, telemetry and recovery. If every function reads every variable, a small edit can change behaviour somewhere unexpected.

Modular code separates responsibilities behind clear interfaces. A route chooses actions; an action handles its progress; hardware functions translate requests into device calls. Configuration supplies named settings.

A module can begin as a labelled section in one file. Separate files are an organisational choice, not a requirement for good structure.

02 | GIVE EACH PART ONE JOB

Suggested boundaries for the code developed in this course
ResponsibilityOwnsShould avoid
ConfigurationPort map, units, calibration, limits, route settingsStarting motors or spawning tasks
Hardware adapterDevice reads and output callsChoosing the next scoring action
State estimationPose and measurement qualityOverwriting motor requests
Actions / controllerProgress, deadlines, completion and faultsHard-coded route selection
Route orchestrationAction order and prerequisitesReimplementing encoder maths
Telemetry / screenBounded observations and displayChanging active control settings

Keep one owner for each motor output. A tuning screen, telemetry task and route must not independently command the same drivetrain.

A useful interface describes inputs, units, outputs and failure behaviour. For example: update(now_ms, feedback) returns status and left/right percent requests. The output owner applies the requests once per cycle.

03 | RESPECT THE GENERATED CONFIGURATION

In VEXcode, preserve the region labelled VEXcode Generated Robot Configuration. Put your own code below # Begin project code. VEXcode can regenerate device configuration, so that region is not a reliable home for handwritten logic.

Choose one device-configuration method. If Devices already creates a motor, Brain or sensor, use that object. Do not create a second object for the same device just to match a tutorial.

# =============================================================================
# TEAM [YOUR TEAM NAME / NUMBER]
# =============================================================================
# ROBOT CONFIGURATION RECORD
# Left drive: ports 3 and 4, blue 6:1 cartridges
# Right drive: ports 1 and 2, reversed, blue 6:1 cartridges
# Intake: ports 7 and 6
# Inertial: port 20
# Wheel travel: measured value in mm per motor revolution
# Configuration owner: Devices OR manual declarations, never both

# =============================================================================
# VALIDATED SETTINGS AND UNITS
# =============================================================================

# =============================================================================
# HARDWARE READS AND OUTPUT OWNER
# =============================================================================

# =============================================================================
# ESTIMATION AND REUSABLE ACTIONS
# =============================================================================

# =============================================================================
# ROUTES AND COMPETITION CALLBACKS
# =============================================================================

# =============================================================================
# TELEMETRY, SCREEN AND STARTUP
# =============================================================================

This is an organisation outline, not a runnable robot program. Keep the actual declarations visible in the generated section or in your chosen manual configuration section. Comments must match the physical robot.

04 | NAME SETTINGS WITH THEIR UNITS

Replace unexplained values with names such as DRIVE_MAX_PCT, POSITION_TOLERANCE_MM and ACTION_TIMEOUT_MS. A value named SPEED does not explain whether it means percent, rpm or millimetres per second.

Illustrative settings, not universal robot calibration
SettingExampleMeaning
WHEEL_MM_PER_MOTOR_REV219.44Illustrative travel; replace with measured calibration including external gearing
DRIVE_MAX_PCT20Teaching output limit, not a calibrated ground speed
POSITION_TOLERANCE_MM5Completion position band
SETTLE_TIME_MS200Required continuous settling interval
CONFIG_IDpractice-v3Identifier changed when meaningful settings change

Validate finite numeric values, allowed ranges, unique device ports and required sensors before arming. Keep port numbers and motor reversal directions tied to a named hardware revision.

Capture settings when the run starts. Edits become pending settings for the next run. An active route should not silently inherit a new gain because somebody tapped a screen button.

05 | COMPLETE ORGANISATION DEMO

Run this example on a computer with Python 3. It has no VEX imports and no motor outputs. It demonstrates validated configuration, a frozen run snapshot, a conversion function and a replaceable hardware boundary.

"""Computer-based organisation demo. No VEX imports or motor outputs."""
import math

CODE_ID = "organisation-demo-v1"
DEFAULT_CONFIG = {
    "config_id": "training-v1",
    "wheel_mm_per_rev": 219.44,
    "drive_speed_pct": 15.0,
    "max_segment_mm": 1000.0,
}

def finite_number(value):
    return (isinstance(value, (int, float)) and not isinstance(value, bool)
            and math.isfinite(value))

def freeze_config(settings):
    """Validate once, then capture an immutable run configuration."""
    if not isinstance(settings.get("config_id"), str) or not settings["config_id"].strip():
        raise ValueError("A configuration ID is required")
    for key in ("wheel_mm_per_rev", "drive_speed_pct", "max_segment_mm"):
        if not finite_number(settings.get(key)):
            raise ValueError("Invalid numeric setting: " + key)
    if not 0 < settings["wheel_mm_per_rev"] <= 2000:
        raise ValueError("Wheel travel must be positive and at most 2000 mm/rev")
    if not 0 < settings["drive_speed_pct"] <= 20:
        raise ValueError("Demo speed must be greater than 0 and at most 20 percent")
    if not 0 < settings["max_segment_mm"] <= 1000:
        raise ValueError("Demo segment limit must be greater than 0 and at most 1000 mm")
    return (settings["config_id"], float(settings["wheel_mm_per_rev"]),
            float(settings["drive_speed_pct"]), float(settings["max_segment_mm"]))

def millimetres_to_revolutions(distance_mm, wheel_mm_per_rev):
    if not finite_number(distance_mm) or not finite_number(wheel_mm_per_rev) or wheel_mm_per_rev <= 0:
        raise ValueError("Invalid distance or wheel calibration")
    return distance_mm / wheel_mm_per_rev

class SimulatedDrive:
    """Records requests only. It does not simulate physics or measure arrival."""
    def __init__(self):
        self.requests = []
        self.stopped = True

    def request_segment(self, revolutions, speed_pct):
        self.stopped = False
        self.requests.append((revolutions, speed_pct))

    def stop(self):
        self.stopped = True


def run_plan(drive, config, segments_mm):
    """Validate the whole plan before dispatch; always request a final stop."""
    config_id, wheel_mm_per_rev, speed_pct, limit_mm = config
    try:
        segments = tuple(segments_mm)
        if not segments:
            raise ValueError("Plan is empty")
        if any(not finite_number(d) or abs(d) > limit_mm for d in segments):
            raise ValueError("Invalid or oversized route segment")
        for distance_mm in segments:
            drive.request_segment(
                millimetres_to_revolutions(distance_mm, wheel_mm_per_rev), speed_pct)
        return {"code_id": CODE_ID, "config_id": config_id,
                "segments_requested": len(segments), "outcome": "REQUESTS_RECORDED"}
    finally:
        drive.stop()


def main():
    settings = dict(DEFAULT_CONFIG)
    config = freeze_config(settings)
    settings["drive_speed_pct"] = 18.0  # Pending edit does not change this run.
    drive = SimulatedDrive()
    result = run_plan(drive, config, (300.0, -150.0))
    print(result)
    print("Recorded requests:", drive.requests)
    print("Stop requested:", drive.stopped)

if __name__ == "__main__":
    main()

The two distances produce approximately 1.367 motor revolutions and -0.684 motor revolutions, both using the captured 15% setting. The pending edit to 18% does not alter the run.

REQUESTS_RECORDED means only that requests were recorded. The adapter does not wait for movement, simulate physics or establish arrival. Do not replace it with consecutive non-blocking motor calls and assume the route will execute correctly.

For the real robot, connect the interface to the non-blocking action scheduler from 09.1 and 09.2. Advance only after checked completion; retain mode gates, deadlines, sensor validation and cancellation. A final stop request is useful cleanup, but cannot guarantee a physical stop if a device call fails.

06 | WHEN SHOULD YOU USE SEPARATE FILES?

For a VEXcode project, start with clear sections and functions in the existing project. VEXcode saves native Python projects as .v5python files. Renaming a plain .py file does not turn it into a native project.

The VEX VS Code extension supports creating projects and importing VEXcode projects. Before splitting robot code into imported Python files, verify that your particular build and download workflow packages and resolves those files on the Brain.

A desktop Python import working on your laptop does not prove the same module is deployed to V5. First test a tiny helper with no device side effects through the complete save, build, download and run cycle.

If verified multi-file deployment is available, config.py, hardware.py, actions.py and routes.py are possible boundaries. Keep dependency direction clear: configuration does not import routes; reusable actions do not import the program entry point. Avoid circular imports.

Importing a helper should not start a motor, calibrate a sensor or launch a control loop. Perform startup deliberately at the entry point and calibrate only in the appropriate stationary, disabled setup.

07 | KEEP THE PROJECT AND READABLE SOURCE TOGETHER

A useful repository contains the native VEX project, a readable source snapshot when needed, configuration records, test evidence and a short README.

Example repository contents, not a required VEX deployment structure
File / folderPurpose
robot.v5pythonNative project opened in VEXcode
source_snapshot.pyReadable export or copied snapshot of the same saved project
README.mdHow to open, configure, download and test this version
hardware.mdPort map, reversal, wheel geometry and robot revision
tests/Computer-based checks for pure logic
test-results/Trial summaries linked to code and configuration IDs

If the native project is the editing source, regenerate the readable snapshot after saving. Review that both describe the same code before committing. Mark the snapshot as a review copy so teammates do not edit two competing versions.

Track calibration needed to reproduce the robot. Ignore temporary editor files and caches, but do not accidentally ignore the only native project. Keep large videos separately and record their references.

08 | YOUR FIRST GIT COMMIT

Git records snapshots of selected files. The working folder is what you are editing; the staging area is what the next commit will record; the repository holds committed history.

Open a terminal in a dedicated project folder containing the files above. Install Git on the computer first. These commands run on the computer, not the V5 Brain.

git init
git status
git add robot.v5python source_snapshot.py README.md hardware.md
git diff --cached
git commit -m "Record baseline robot configuration and autonomous"
git log --oneline -5

If Git requests an identity, set the name and email you intend to associate with this project using git config user.name and git config user.email. Repository-local settings avoid changing other projects.

git add stages the file contents at that moment. If you edit again, stage again. Review git diff for unstaged text changes and git diff --cached for staged text changes. Native project files may not produce a useful text diff, which is why a matching readable snapshot helps.

To remove a file from the staging area after an initial commit, use git restore --staged source_snapshot.py. This preserves its working edits. A restore command without --staged can discard working changes; do not use it casually.

09 | CHANGE ONE THING, TEST, THEN RECORD IT

Create a branch for a focused change, preserve the baseline and commit a clear result. A branch name is a movable reference to a line of commits; it is not a second physical robot.

git switch -c tune-drive-heading
# Edit, save native project and update readable snapshot.
# Run logic checks, then download and carry out physical trials.
git status
git diff
git add robot.v5python source_snapshot.py test-results/heading-trials.csv
git diff --cached
git commit -m "Reduce heading oscillation; record matched trial results"

Use commit messages that explain the behaviour changed. “Changed stuff” gives the next programmer no useful evidence.

Merge through your team’s review process after checking the combined program. If two people changed the native project, do not assume selecting one copy resolves both changes correctly. Reconcile the source, reopen the native project, download and retest.

10 | LABEL TESTED VERSIONS AND TIE THEM TO LOGS

After saving and committing the exact tested files, check for uncommitted changes and label a tested milestone.

git status --short
git rev-parse --short HEAD
git tag -a practice-tested-v1 -m "Validated practice route; see recorded trials"
git show practice-tested-v1

An empty status output means no reported changes to tracked files and no visible untracked files under the current ignore rules. It does not prove the downloaded Brain program matches the checkout.

Record the commit ID, configuration ID, hardware revision, selected route and download slot with the trial. If you tested uncommitted edits, label the record as dirty and preserve the actual files. A commit ID alone cannot identify those edits.

Obtain the Git ID on the computer. V5 Python should not try to run Git commands. A manually entered code label can go stale, so verify it against the saved and downloaded build record.

Do not change a tested tag to point to a different program. Make a new tag for the new validated version. Restoring an old commit restores code, not earlier wiring, geometry, calibration or field conditions.

11 | COLLABORATION AND RECOVERY

Agree who owns each change and how the native project and snapshot stay synchronised. Pull or fetch shared changes before starting, review differences and test the combined result.

A local Git repository lives on that computer. Keep a separate backup or approved remote copy. Local commits alone do not protect you if the computer is lost.

When using a remote, pushing a branch and pushing a tag are separate operations. Verify the files and tested tag are present in the intended remote. Keep passwords, tokens and personal student information out of the repository.

To inspect a tested version without rewriting current work, save your edits first and use a separate checkout or worktree. Reopen and download the intended native project explicitly, check its slot and validate it on the current robot.

12 | COMMON MISTAKES

  • Splitting files before defining interfaces: move responsibilities first.
  • Hidden output owners: keep motor writes under one controller.
  • Editing generated configuration: preserve the VEXcode boundary.
  • Conflicting source copies: identify the editing source and refresh its snapshot.
  • Changing settings mid-run: capture validated settings at entry.
  • Calling dispatched requests successful motion: require observed completion.
  • Tagging different files from those tested: verify saved, committed and downloaded versions.
  • Ignoring robot changes: record hardware and calibration alongside code.

CHALLENGES | STRUCTURE, TEST AND TRACE

  1. Divide an existing route into configuration, hardware, actions, orchestration and telemetry responsibilities.
  2. Write an interface contract giving units, completion conditions and failure behaviour for one action.
  3. Run the demo. Change pending settings and demonstrate that the captured run stays unchanged.
  4. Test an oversized segment and confirm that no requests are recorded and cleanup is called.
  5. Make a first Git commit containing the native project, matching snapshot and hardware record.
  6. Use a branch to change one parameter. Record trial evidence and explain the result in the commit message.
  7. Tag a tested milestone and attach its commit, configuration and hardware IDs to a trial row.
  8. Ask a teammate to reproduce the setup from the README and identify anything missing.
Success evidence

Clear ownership, a visible port map, validated run settings, matching project and source records, meaningful tests and a reproducible tested version.

REFERENCES