⚡ EZ Template · Intermediate Autonomous

Sensor-Based Autonomous

Time-based auton guesses. Sensor-based auton knows. This guide shows you how to replace fixed delays and drive times with conditions the robot actually measures — and why that change alone can double your skills consistency.

// Section 01
The Problem With Time-Based Auton
If your routine uses pros::delay() or fixed drive times to control positioning, it will drift. Every time. The question is only by how much.

What Time-Based Auton Actually Does

A time-based autonomous says: "run the motors for 800 milliseconds, then stop." The assumption is that 800ms always moves the robot the same distance. That assumption is wrong.

🚫
These four variables change how far 800ms moves your robot — and none of them are in your code:
  • Battery voltage — a 50% battery produces less torque than a full one. Your robot drives shorter distances late in a match.
  • Carpet friction — your practice mat and the competition tile have different friction coefficients. The same drive time produces different displacement.
  • Wheel wear — worn omnis slip slightly differently than new ones.
  • Defense contact — even a glancing hit during qualifications shifts your robot's position. Your next time-based move starts from the wrong place.

What This Looks Like in Practice

✅ Saturday morning — full battery
Robot drives 800ms, stops at 280mm from wall. Auton scores perfectly. You're confident.
❌ Saturday afternoon — third match
Battery at 60%. Robot drives 800ms, stops at 340mm from wall. Scoring attempt misses by 60mm. Same code. Different result.

This is why teams that dominate skills runs don’t use time-based positioning — they use sensors. The distance a robot travels in 800ms varies with:

What Sensor-Based Auton Changes

You don't need to throw out your existing auton. Sensor-based positioning is additive. Keep your EZ Template PID moves for navigation. Add sensor-based stops only at the critical alignment points — typically the 2–4 positions where scoring happens.
// Section 02
Core Concept: Move Until a Condition Is Met
One idea. Every sensor-based move is a variation of it.
Time-based vs sensor-based autonomous comparison Time-Based drive for X milliseconds full charge low battery stopping position varies with battery Sensor-Based drive until sensor reads target full charge low battery both stop at the same position

The Mental Model

Time-based auton asks: "how long?"
Sensor-based auton asks: "until when?"

Instead of:

// Time-based — guessing chassis.drive_set(80, 80); pros::delay(800); // hope 800ms = the right distance chassis.drive_set(0, 0);

You write:

// Sensor-based — knowing chassis.drive_set(60, 60); while (dist_sensor.get() > 300) pros::delay(10); chassis.drive_set(0, 0); // stops at exactly 300mm — every time

The robot drives until the distance sensor reads 300 mm or less — regardless of battery, floor, or weight. This is the core advantage: the condition is measured reality, not a timed assumption.

Open-Loop vs Closed-Loop

Open-Loop (Time / Encoder)
  • Output based on a command, not feedback
  • No way to correct for real-world variation
  • Error accumulates over the run
  • Used for: general navigation where ±1 inch is acceptable
Closed-Loop (Sensor-Based)
  • Output adjusts based on measured feedback
  • Corrects for battery, friction, and drift
  • Errors reset at each sensor checkpoint
  • Used for: scoring alignment, skills position resets
ℹ️
EZ Template's PID is also closed-loop — it reads encoders and corrects continuously. Sensor-based stops with the distance sensor add a second layer of closed-loop control for position. EZ Template handles getting close; the distance sensor confirms you're actually there.

When to Use Each

  • EZ Template pid_drive_set() — general navigation, turning, anything not near a wall
  • Distance sensor stop — final alignment at scoring positions, position resets against walls
  • Both together — EZ PID gets you close; sensor stop confirms and locks the position
// Section 03
Code Structure in EZ Template
Sensor declaration, reading values, the loop pattern, and exit conditions — everything you need to write your first sensor-based move.

Step 1 — Declare the Sensor

Add once to robot-config.cpp and expose it in the header.

src/robot-config.cpp
pros::Distance dist_sensor(3); // port 3 — change to your actual port
include/robot-config.hpp
extern pros::Distance dist_sensor;

Step 2 — Read the Value

dist_sensor.get() returns distance in millimeters. Print it to the Brain screen during testing to find your real-world target values.

// In initialize() during testing — live readout while (true) { pros::screen::print(pros::E_TEXT_MEDIUM, 1, "Dist: %d mm", dist_sensor.get()); pros::delay(50); }
Always find your target by measuring, not guessing. Push the robot to your desired scoring position. Read the Brain screen. That number is your target. Write it as a named constant: const int SCORE_DIST = 295;

Step 3 — The Loop Pattern

Three lines. This is the entire sensor-stop pattern:

chassis.drive_set(speed, speed); // start driving while (dist_sensor.get() > target_mm) // wait until condition met pros::delay(10); // check every 10ms chassis.drive_set(0, 0); // stop

Step 4 — Add a Tolerance Band

A single exact value (== 300) can be missed if the robot moves faster than the sensor can read. Use a range instead:

// ✗ Fragile — exact value can be skipped at high speed while (dist_sensor.get() != 300) pros::delay(10); // ✓ Correct — threshold: stop when AT or BELOW target while (dist_sensor.get() > 300) pros::delay(10); // ✓ Also correct — tolerance band around a target while (dist_sensor.get() > 310 || dist_sensor.get() < 290) pros::delay(10);

Step 5 — Add a Noise Filter

Sensors occasionally return a bad reading. Require two consecutive confirmations to eliminate false stops:

int confirm = 0; chassis.drive_set(55, 55); while (confirm < 2) { if (dist_sensor.get() <= 300) confirm++; else confirm = 0; // reset on any bad reading pros::delay(10); } chassis.drive_set(0, 0);
🔬 Check for Understanding
Your sensor-based stop uses while (dist == 300) pros::delay(10);. At competition the robot never stops. What's wrong?
The sensor is on the wrong port
The condition is wrong — it runs while equal to 300, which is almost never true. It should be while (dist > 300)
The delay is too short — use 100ms
You need to call dist_sensor.reset() first
Correct. while (dist == 300) keeps looping only while the distance is exactly 300 mm — which almost never happens at speed. The robot flies past 300 and the loop exits immediately when the condition becomes false (because the sensor reads something below 300). Always use a threshold condition: while (dist_sensor.get() > 300).
// Section 04
Building a Repeatable Routine
How to structure a full skills-run sequence so every cycle resets position before scoring.

The Principle: Reset at Every Wall Touch

Each time your robot approaches a wall, you have an opportunity to reset its known position. Top skills teams exploit this deliberately:

Step-by-Step Routine Structure

01
Fix the Starting Position
Before the match starts, place the robot in a fixed position against the alliance wall or a defined starting tile edge. The distance sensor confirms it. Every run begins from the same place.
02
Navigate to the Scoring Zone
Use EZ Template PID for bulk navigation — pid_drive_set(), pid_turn_set(). This is fast and reliable for general movement. Don't use the sensor here.
03
Sensor Stop for Final Alignment
Switch to sensor-based drive for the last 400–600 mm before the goal. This is the critical zone where exact position matters. The sensor stop puts you at the same place every cycle.
04
Score
Execute the scoring action from a known position. Because the position is consistent, the scoring action can be tuned once and trusted every time.
05
Reset and Repeat
Back away using EZ PID. Collect next game element. Return to sensor stop. Each cycle the robot resets its position at the wall — drift doesn't accumulate.

Full Routine Code Example

A single scoring cycle showing how sensor stops integrate with EZ Template PID moves:

src/autons.cpp — one full sensor-based scoring cycle
void sensor_cycle() { // ── 1. Navigate to goal zone via EZ Template PID ──────────────── chassis.pid_drive_set(36, 110); // drive 36 inches toward goal chassis.pid_wait(); chassis.pid_turn_set(90, 90); // face the goal wall chassis.pid_wait(); // ── 2. Sensor-based final alignment ───────────────────────────── chassis.drive_set(55, 55); // slow approach while (dist_sensor.get() > 295) // target: 295mm from goal wall pros::delay(10); chassis.drive_set(0, 0); pros::delay(120); // brief settle // ── 3. Score from known position ───────────────────────────────── intake_run(127); pros::delay(500); intake_run(0); // ── 4. Back away to collect next game element ──────────────────── chassis.pid_drive_set(-24, 110); // back away via EZ PID chassis.pid_wait(); // ── 5. Next cycle starts from a clean EZ PID reset ─────────────── // Position drift resets at the next sensor stop }

Skills Strategy: Walls as Anchors

⚠️
Test on competition tiles before your first event. Your practice mat and competition tiles have different friction. The same sensor target distance may produce slightly different stopping behavior. Measure on comp tiles once and update your target constant accordingly.
// Section 05
Advanced Combinations, Reusable Functions & Common Mistakes
Combine sensors, build a reusable function library, avoid the traps.

Sensor Autonomous Debugging

Symptom Likely Cause Fix
Robot never stops at the wallSensor condition never triggers — threshold too tight or sensor not readingPrint sensor value in the loop; widen the distance threshold
Robot stops too far from wallDistance threshold too large, or sensor field too wideReduce the threshold value; check sensor angle with mounting guide
Auton triggers inconsistentlyGame pieces blocking sensor line-of-sight mid-routineRelocate sensor; add a minimum time before sensor condition is evaluated
Distance sensor returns -1 or 9999Sensor port number wrong, or cable not connectedVerify port in Brain Devices menu; check Smart cable seating
Robot drifts even with sensor stopPID settle not completed — robot stops before fully settledAdd chassis.pid_wait() after the sensor-triggered movement

Combining Sensors

A single distance sensor handles most use cases. When you need more precision or want to catch edge cases, combine it with the IMU or encoders.

Distance + IMU (Heading Correction)

During a sensor-based approach, the robot can drift off-angle. Adding IMU correction keeps the approach perpendicular to the wall — which keeps the stopping distance accurate.

// IMU-corrected approach — stays square to wall float locked = chassis.drive_imu_get(); // lock heading at start chassis.drive_set(55, 55); while (dist_sensor.get() > 295) { float err = locked - chassis.drive_imu_get(); float corr = err * 0.9; // small kP chassis.drive_set(55 + corr, 55 - corr); pros::delay(10); } chassis.drive_set(0, 0);

Distance + Encoder Safety Timeout

If the sensor cable fails mid-match, the distance loop runs forever — the robot drives into the wall. A timeout using encoder distance prevents this:

// Safety: stop if sensor fails and we've driven more than 60 inches chassis.drive_set(55, 55); int start_pos = chassis.drive_sensor_right(); while (dist_sensor.get() > 295) { int traveled = abs(chassis.drive_sensor_right() - start_pos); if (traveled > 1500) break; // ~60 inch safety limit pros::delay(10); } chassis.drive_set(0, 0);

Reusable Function Library

Write once, use everywhere. These three functions cover the most common sensor-based scenarios. Put declarations in autons.hpp.

void driveToWall(int target_mm, int speed = 55)
Basic sensor stop. Drives at speed until dist_sensor.get() ≤ target_mm. Default speed is 55 — safe for most approaches.
void driveToWallSmooth(int target_mm)
Two-phase approach: fast (90/127) until 200mm, then slow (35/127) to target. Use when starting more than 4 feet from the wall.
void alignToWall(int target_mm)
Sensor stop + IMU heading correction + 2-confirmation filter. The most reliable version. Use for final scoring alignment.
src/autons.cpp — full implementations
void driveToWall(int target_mm, int speed) { chassis.drive_set(speed, speed); while (dist_sensor.get() > target_mm) pros::delay(10); chassis.drive_set(0, 0); pros::delay(100); } void driveToWallSmooth(int target_mm) { chassis.drive_set(90, 90); while (dist_sensor.get() > 200) pros::delay(10); chassis.drive_set(35, 35); while (dist_sensor.get() > target_mm) pros::delay(10); chassis.drive_set(0, 0); pros::delay(100); } void alignToWall(int target_mm) { float locked = chassis.drive_imu_get(); int confirm = 0; chassis.drive_set(45, 45); while (confirm < 2) { float corr = (locked - chassis.drive_imu_get()) * 0.9; chassis.drive_set(45 + corr, 45 - corr); if (dist_sensor.get() <= target_mm) confirm++; else confirm = 0; pros::delay(10); } chassis.drive_set(0, 0); pros::delay(120); }

Common Mistakes

✖ Mixing Time and Sensor Wrong

Adding a pros::delay(800) after the sensor stop "just to be safe" defeats the purpose — the robot may not actually be at the right position when the timer expires.

Fix: Replace any delay that controls positioning with a sensor condition. A short settle delay (100–150ms after stopping) is fine — a positioning delay is not.
✖ Using == Instead of > or <

The loop condition dist == 300 is almost never true at speed. The robot passes through 300mm in a single loop cycle and the condition is missed.

Fix: Always use a threshold: while (dist_sensor.get() > 300). The loop exits the moment you reach or pass the target.
✖ Approaching Too Fast

At 100+/127 speed, momentum carries the robot 30–60mm past the target before the motors can stop. The sensor reads the target correctly but the robot overshoots.

Fix: Use 40–65/127 for sensor-approach moves. For longer distances, use a two-phase approach: fast until 200mm from target, then switch to 35/127 for the final approach.
✖ No Safety Timeout

If the sensor cable disconnects mid-match, the loop condition is never met and the robot drives full speed into the wall until the match ends.

Fix: Add an encoder-based safety break inside the loop. If the robot has traveled more than a maximum expected distance, exit regardless of the sensor reading.
🔬 Practice Drill
Stop at 300 mm Repeatedly — Measure Consistency

This drill separates "works sometimes" from "competition-ready." Run 10 consecutive reps from different starting distances. Measure and log the actual stopping position each time.

  1. Run driveToWall(300, 55) from 6 feet, 4 feet, and 2 feet away
  2. After each stop, measure the actual distance from the sensor face to the wall with a tape measure
  3. Log: starting distance, measured stop, pass (within ±10mm) or fail
  4. Repeat 10 times total across different starting positions
  • Target: 9/10 stops within ±10mm of 300mm
  • If failing: reduce approach speed from 55 → 40 and re-run
  • If passing: upgrade to driveToWallSmooth(300) and repeat the drill at higher speed
  • Competition ready: 10/10 within ±8mm — this is the bar
Run this drill on competition tiles, not just your practice mat. Surface friction affects stopping distance. If results differ, update your target constant for comp tiles specifically.
⚙ STEM Highlight
Technology: Closed-Loop Control & Feedback Systems
Sensor-based autonomous is an implementation of closed-loop feedback control — the robot continuously measures its state and adjusts behavior until a condition is satisfied. This is the same principle behind cruise control in cars, thermostats in HVAC systems, and autopilot in aircraft. The alternative — time-based or open-loop control — ignores real-world feedback, which is why it fails when conditions change.

The distance sensor stop loop is the simplest possible feedback controller: a binary comparator that checks one value against a threshold. More sophisticated versions (like PID) use the magnitude of the error to scale the response — but even this simple loop already captures the core advantage of closed-loop design: the system responds to what is actually happening, not what was predicted.
🎤 Interview line: "We replaced time-based positioning with closed-loop sensor stops. The robot drives until the distance sensor confirms it's at the target position — so battery voltage, carpet friction, and starting variation don't affect where it stops. It reacts to reality instead of guessing."

Key Takeaways

Related Guides
📏 Distance Sensor Positioning → ⚡ Exit Conditions → 🔬 PID Diagnostics → 🏆 Auton Strategy → 📍 Odometry →
▶ Next Step

Sensor stops working. Now chain movements together efficiently with exit conditions to recover 1–2 seconds per auton run.

⚡ Exit Conditions & Chained Movements →
📝
← ALL GUIDES