Cup orientation detection: Optical Sensor reflectivity readings differ between transparent-up and opaque-up cups.
Sources & confidence: Hardware specs (proximity range, color detection <100mm, RGB+HSV+gesture) and part number 276-7043 verified against the official VEX Library and VEX Robotics product page. PROS API references verified against pros.cs.purdue.edu/v5 (the pros::Optical class). The Purdue SIGBots Wiki (the maintainers of PROS) also confirms PROS-vs-VEXcode API differences. EZ-Template integration patterns drawn from EZ-Template's public docs at ez-robotics.github.io/EZ-Template. Override-specific use cases reflect the v0.1 game manual (released April 27, 2026); cup orientation detection (Section 6) is the primary new application unique to Override.
// Section 01
V5 Optical Sensor — Overview 🔮
🗺️ Flowchart — Optical sensor — proximity + color detection
Optical returns both a proximity number (0-255) and a hue angle (0-360). Combine the two for color-aware grab logic — useful when the field has same-shape pieces in different colors.
flowchart TD
Start([Need to check for game piece]) --> Read[Read sensor: prox = claw_optical.get_proximity hue = claw_optical.get_hue]
Read --> Q1{Proximity above threshold? prox > 200}
Q1 -->|"No"| NoObject[No object in claw range]
Q1 -->|"Yes"| Q2{Hue within color band?}
Q2 -->|"Wrong color"| WrongColor[Opponent's piece release or skip]
Q2 -->|"Our color"| Right[Right object commit to grab]
style Start fill:#1e293b,stroke:#22d3ee,stroke-width:2px,color:#e2e8f0
style Q1 fill:#fbbf24,color:#0f172a,stroke:#fbbf24
style Q2 fill:#fbbf24,color:#0f172a,stroke:#fbbf24
style NoObject fill:#1e293b,color:#94a3b8,stroke:#334155
style WrongColor fill:#7f1d1d,color:#fecaca,stroke:#ef4444
style Right fill:#14532d,color:#bbf7d0,stroke:#22c55e
⚠ PROS 4.1.1 note for Spartan teams:
Your installed PROS kernel (4.1.1) ships with the legacy color-signature
vision sensor (pros::Vision) but does not include
aivision.hpp. The pros::AIVision class shown below
ships in PROS 4.2+ — until you upgrade your kernel, use VEXcode V5 for
AprilTag work, or substitute the legacy pros::Vision for
color-signature detection.
A close-range multi-mode sensor that combines color detection, proximity, ambient light, and gesture recognition in one Smart Port device. Cheaper than AI Vision, narrow-purpose, but extremely useful for ring sorting, intake control, and game-element detection.
🔮
5
Readings
Color, proximity, ambient light, hue, gesture — one sensor, five outputs.
📐
~3"
Range
Close-range only. Mount close to what you want to detect.
🌈 Color Detection📍 Proximity Sensing🏆 V5RC / VEX U / VEX AI Legal
Hardware Summary
VEX Part Number
276-7043
Sensor Type
Combination ambient light, color, proximity, and gesture sensor — all in one housing.
Effective Color Range
Best detection when object is < 100mm (~4 inches) from the sensor face. Beyond that, color reliability drops sharply.
Proximity Range
Returns 0–255 (PROS API). Higher values = closer object. Nominal sensing range is roughly 10cm. Values are affected by ambient light and object reflectivity.
Connection
One V5 Smart Port via Smart Cable. Counts as a sensor port.
Mounting
Two mounting tabs with slotted holes. Width fits inside C-channel, but requires a 1/4″ standoff (275-1013) or 8mm plastic spacer (276-2019) for Smart Port clearance.
White LED
Built-in. Programmable PWM 0–100. Helps maintain consistent color readings under variable competition lighting.
Toolchain Compatibility
PROS C++ via pros::Optical (full feature support). Also works in VEXcode V5 / EXP. Per Purdue SIGBots Wiki: PROS exposes more features than VEXcode (saturation, full proximity range).
Why You Want One on Your Override Robot
The Optical Sensor solves problems the AI Vision Sensor cannot — small, fast, close-range tasks:
Ring/object color detection in your intake. "Is the ring I just picked up red or blue?" The AI Vision Sensor has too wide a field of view and looks too far ahead for this; the Optical Sensor is in the right place to read the object as it passes through.
Proximity-based intake stop. "Has a ring entered my intake yet?" Use the proximity reading to detect the moment a game element enters, then trigger the next mechanism stage.
Ambient light detection for line-following. The white LED + brightness reading can be used to detect tape lines on the field tile.
Auto-sort mechanism. Read color of an incoming ring, then route it (alliance ring → score) or reject it (opponent ring → eject).
📋
Optical vs AI Vision — complementary, not redundant. The AI Vision Sensor sees far and identifies objects/tags. The Optical Sensor sees close (<100mm) and reads color/proximity instantly. Top teams use BOTH: AI Vision for navigation, Optical for in-mechanism sensing.
1 of 7
// Section 02
What the Sensor Detects 🔭
Five distinct readings, each accessed through its own PROS function. Knowing which mode does what is the difference between fast detection and noisy readings.
📌 Quick Take
Five readings: get_rgb() / get_proximity() / get_brightness() / get_hue() / get_gesture(). Pick the one that matches your problem — using the wrong reading is the #2 source of "why doesn't this work" bugs.
📚V5 Optical Sensor · VEX Library
VEX’s official Optical Sensor documentation — color, hue, brightness, proximity.
The hue is the angular position on the color wheel. Returned as a double from 0 to 359.99.
Red
~0° (or ~360°)
Yellow
~60°
Green
~120°
Cyan
~180°
Blue
~240°
Magenta
~300°
Detection works best when the object is < 100mm from the sensor. At greater distances, hue readings become inconsistent and noisy.
PROS function: optical.get_hue()
2. Saturation (0.0–1.0)
How "pure" the color is. 0.0 = grayscale (no color), 1.0 = fully saturated. Useful for distinguishing actual colored objects from ambient white reflections or grayscale field tiles.
PROS function: optical.get_saturation()
3. Brightness (0.0–1.0)
How bright the detected object is. Useful for distinguishing white from black, light from dark.
PROS function: optical.get_brightness()
4. Proximity (0–255)
The IR-based proximity reading. Higher values = closer object. Per PROS docs: range is 0 to 255. Per VEX docs: nominal sensor range is ~10cm.
⚠️
Proximity is affected by ambient light AND object reflectivity. The same object at the same distance can return different proximity values under different lighting or if the object is more/less reflective. Calibrate per-robot, per-object, per-venue. Do not hard-code thresholds without testing in the actual competition lighting.
Per VEX docs: proximity uses reflected IR (infrared) energy from an integrated IR LED, NOT the white LED. The white LED is for color detection only.
Important PROS limitation: Proximity is NOT available when gesture detection is enabled. Pick one or the other.
PROS function: optical.get_proximity()
5. Gesture Detection
Detects four directional motions of an object passing in front of the sensor: UP, DOWN, LEFT, RIGHT. Returned as optical_direction_e_t enum (NO_GESTURE, UP, DOWN, LEFT, RIGHT, ERROR).
Note: Useful for detecting things like a ring passing through your intake (motion event), but you lose proximity readings when gesture mode is on. For most competition uses, leave gestures disabled and use proximity directly.
6. RGB Components
If you need raw red/green/blue values (e.g., to do your own color matching with custom thresholds), PROS provides:
optical.get_rgb() — returns a struct with red, green, blue, and brightness fields.
PROS exposes saturation and full RGB separately; per Purdue SIGBots Wiki, this is more granular than VEXcode's API.
7. Integration Time (Update Rate)
How often the sensor updates internally. Lower = faster updates but noisier; higher = slower but more stable.
PROS functions: optical.set_integration_time(ms) — range 3–712ms. Default is fine for most use cases. Lower it if you need faster reaction (~10–20ms) for sorting fast-moving game elements.
2 of 7
// Section 03
Mounting the Sensor 🔧
The Optical Sensor's effectiveness depends entirely on placement. Wrong location = useless data. Right location = match-changing capability.
📌 Quick TakeMount close to what you want to detect. The sensor's range is short (~3″) and its field of view is narrow. Wrong location guarantees failure no matter how well-tuned the code is.
The Cardinal Rule: Stay Within 100mm
Color detection works best when the object is < 100mm (~4 inches) from the sensor face. This is the single most important constraint. If you mount the sensor far from where game elements travel, you will get unreliable readings or no readings at all.
Recommended Mounting Locations
Inside the Intake Throat (Best for Color Sort)
Mount the sensor pointing inward at the narrow throat of your intake, where rings/objects pass through one at a time. The object is briefly very close to the sensor face. Best location for alliance-color sorting. Use polycarbonate or 3D-printed brackets to position precisely.
Above the Intake Floor (Proximity Trigger)
Mount the sensor at the top of your intake throat, pointing down at the path objects travel. Use proximity reading to detect "an object is now in my intake" and trigger downstream mechanisms (conveyor on, scoring sequence start).
Lift Mechanism Stage (Mid-Path Detection)
If you have a multi-stage lift or conveyor, mount one Optical Sensor mid-path to detect "the object has reached this stage" transitions. Useful for choreographed scoring sequences.
Bottom-Facing Floor Detector
Mount underneath the robot pointing at the floor for tape-line detection (using brightness and the white LED). Be aware of foam tile color — field tile reflectivity is not uniform.
Mechanical Mounting Details
Per VEX Library on the Optical Sensor:
The sensor housing has two mounting tabs with slotted holes.
The width fits inside a C-channel.
Use a 1/4″ standoff (part 275-1013) or 8mm plastic spacer (276-2019) when mounting to a C-channel — this provides clearance for the V5 Smart Port on the side of the sensor.
The Smart Port is on the side of the sensor, not the bottom or back. Plan your cable routing accordingly.
The optical window is on the face of the sensor — do not block this with structure or wires.
🔧
Mounting on a Clawbot claw? VEX’s Mounting and Wiring the V5 Vision Sensor ↗ article documents the canonical gusset-and-bracket technique — 90° Flat Gusset on top of the claw, Angle Gusset to the back of the sensor, two #8-32 × 3/8″ screws for each, bend the Angle Gusset to set viewing angle. The article explicitly states “other sensors can use the same mounting method,” making this the canonical reference for the Optical Sensor on the Clawbot claw too. The Optical Sensor is smaller than the Vision Sensor, so the same mount has more clearance. The V5 STEM Lab walkthrough ↗ shows the same technique step-by-step with photos.
White LED Considerations
The integrated white LED is a critical feature. It illuminates objects close to the sensor for consistent color detection regardless of ambient light.
Set LED to ~50–75% PWM for general use. optical.set_led_pwm(50) in PROS.
The LED is what makes the sensor reliable across competition venues. Different gymnasiums have different lighting; the LED counters this by being your dominant light source for objects right next to the sensor.
The LED is NOT used for proximity detection (the IR LED is used for that). Turning the white LED off does not disable proximity sensing.
Don't leave the LED at 100% — it can wash out the colors of very close objects, leading to incorrect hue readings.
Calibration Pre-Match
Before every competition (or at least at every new venue):
Place each game element you care about (red ring, blue ring, mobile goal, etc.) at the position the sensor will see it during a match.
Read and log the hue, saturation, brightness, and proximity values for each.
Build a hue-range table: e.g., red rings = hue 350–15°, blue rings = hue 215–245°.
Use these ranges in your code for color classification, not raw equality.
You can build a small calibration utility that prints values to the V5 Brain screen and runs in opcontrol — press a button to log the current readings.
3 of 7
// Section 04
PROS Code Patterns 💻
Verified PROS V5 C++ API for the Optical Sensor. Reference: pros.cs.purdue.edu — pros::Optical class.
⚠️
EN4 reminder: Use these patterns to understand the approach. Rewrite the code in your own structure, comments, and variable names for the engineering notebook. Don't copy-paste.
Verified PROS API Reference
Class
pros::Optical
Header
#include "pros/optical.hpp" (or just use pros/api.h)
Constructor
pros::Optical optical(uint8_t port);
Hue
double hue = optical.get_hue(); — range 0 to 359.99
Saturation
double sat = optical.get_saturation(); — range 0.0 to 1.0
Brightness
double br = optical.get_brightness(); — range 0.0 to 1.0
Proximity
int prox = optical.get_proximity(); — range 0 to 255
White LED
optical.set_led_pwm(50); — range 0 to 100
RGB struct
auto rgb = optical.get_rgb(); — struct with .red, .green, .blue, .brightness
Update rate
optical.set_integration_time(20); — ms; range 3 to 712
Determine if a detected object is a red ring, blue ring, or unknown. Use hue ranges from your pre-match calibration.
// PROS C++ — alliance color sort
// EN4: rewrite in your own words and structure for your notebook.
#include "pros/apix.h"
#include "pros/optical.hpp"
// Sensor on Smart Port 5
pros::Optical optical(5);
enum RingColor { NONE, RED, BLUE, UNKNOWN };
RingColor classify_ring() {
// Object must be very close to read color reliably
if (optical.get_proximity() < 200) return NONE;
// Saturation gate: ignore washed-out / grayscale readings
if (optical.get_saturation() < 0.3) return UNKNOWN;
double hue = optical.get_hue();
// Red wraps around 0/360 -- check both ranges
if (hue >= 350.0 || hue <= 15.0) return RED;
if (hue >= 210.0 && hue <= 245.0) return BLUE;
return UNKNOWN;
}
void initialize() {
// Turn on white LED at 60% for consistent color reading
optical.set_led_pwm(60);
// Faster updates for catching fast-moving rings
optical.set_integration_time(15);
}
Pattern 2: Proximity-Triggered Intake Stop
Run intake until proximity hits a threshold (object detected), then stop. Avoids over-stuffing the intake.
// PROS C++ — proximity-triggered intake stop
// EN4: rewrite in your own words for your notebook.
pros::Motor intake(11);
pros::Optical optical(5);
void intake_until_loaded() {
intake.move(127); // full power forward
// Poll proximity until ring is detected close to sensor
while (optical.get_proximity() < 220) {
pros::delay(10);
}
// Ring detected -- stop intake
intake.move(0);
intake.brake();
}
Pattern 3: Auto-Sort (Reject Wrong-Color Rings)
Combine color detection with intake control to ONLY keep alliance-colored rings.
// PROS C++ — auto-sort wrong-color rings
// EN4: rewrite in your own words for your notebook.
pros::Motor intake(11);
pros::Motor ejector(12); // a small mechanism that ejects rejected rings
pros::Optical optical(5);
const RingColor MY_ALLIANCE = RED; // set per match
// Background task: continuously sorts what enters the intake
void sort_task_fn(void* p) {
bool ring_present = false;
while (true) {
int prox = optical.get_proximity();
bool now_present = (prox > 200);
// Rising edge: ring just entered
if (!ring_present && now_present) {
pros::delay(50); // let it settle into the read window
RingColor c = classify_ring();
if (c != MY_ALLIANCE && c != UNKNOWN) {
// Wrong color -- eject!
intake.move(-127); // reverse intake briefly
ejector.move(127); // engage ejector
pros::delay(300);
intake.move(127); // resume normal intake
ejector.move(0);
}
}
ring_present = now_present;
pros::delay(10);
}
}
void initialize() {
optical.set_led_pwm(60);
optical.set_integration_time(15);
pros::Task sort_task(sort_task_fn, nullptr, "sort");
}
Pattern 4: Calibration Helper
A simple opcontrol-mode utility that prints live sensor values to the V5 Brain screen. Use it during pit setup to record values for your hue ranges.
// PROS C++ — calibration print loop
// EN4: rewrite in your own words for your notebook.
void calibration_loop() {
pros::lcd::initialize();
optical.set_led_pwm(60);
while (true) {
double hue = optical.get_hue();
double sat = optical.get_saturation();
double br = optical.get_brightness();
int prox = optical.get_proximity();
pros::lcd::print(0, "Hue: %.1f deg", hue);
pros::lcd::print(1, "Sat: %.2f", sat);
pros::lcd::print(2, "Brt: %.2f", br);
pros::lcd::print(3, "Prox: %d", prox);
pros::delay(50);
}
}
Hold a red ring in front of the sensor: hue should read ~0° (or ~360). Try a blue ring: ~230. Try a mobile goal in your alliance color: log it. Build your tables from this.
4 of 7
// Section 05
EZ-Template & LemLib Integration 🔗
How sensors fit into chassis-library workflows. Spoiler: they don't plug into EZ-Template directly — you compose them with chassis calls.
📌 Quick TakeEZ-Template and LemLib do NOT auto-integrate with optical sensors. You initialize and read the sensor yourself, then use the result to trigger chassis movement. Spoiler in the section: it's easier than it sounds.
The Honest Picture
EZ-Template and LemLib are chassis movement libraries. They handle:
Drivetrain PID (drive, turn, swing turns, arcs)
Odometry with Pure Pursuit and Boomerang motion
Asynchronous PID with blocking and non-blocking calls
Joystick control modes (tank, arcade, single-stick)
Autonomous selectors with SD-card-saved selections
The IMU is wrapped (it's part of the chassis constructor)
They do NOT wrap:
The Optical Sensor
The AI Vision Sensor
The Distance Sensor (for non-drivetrain uses)
The Rotation Sensor (for non-drivetrain uses)
Custom subsystems (intake, lift, climb)
For sensors and custom mechanisms, you use the standard PROS API directly. Integration with EZ-Template happens in your autonomous code — you compose chassis motion calls with sensor reads.
Pattern A: Sensor-Triggered Motion Exit
Start an EZ-Template chassis motion, watch a sensor in parallel, cancel the motion when sensor condition met.
// EZ-Template + Optical Sensor — drive until ring detected
// EN4: rewrite in your own words for your notebook.
#include "main.h"
#include "EZ-Template/api.hpp"
#include "pros/optical.hpp"
extern Drive chassis;
extern pros::Optical optical;
extern pros::Motor intake;
// Drive forward up to 36 inches, but exit early if intake detects a ring
void drive_until_ring() {
intake.move(127); // intake on
chassis.pid_drive_set(36_in, DRIVE_SPEED, true); // start motion (async-friendly)
while (chassis.pid_wait_quick_chain() == false) {
if (optical.get_proximity() > 200) {
chassis.pid_targets_reset(); // cancel current PID motion
chassis.drive_set(0, 0); // hard stop
break;
}
pros::delay(20);
}
intake.move(0);
}
Pattern B: Pre-Move Sensor Check
Before issuing a chassis movement, check if the sensor reports the expected condition. Use this for preconditions in auton sequences.
// EZ-Template + Optical — conditional auton step
// EN4: rewrite in your own words for your notebook.
void auton_score_if_correct_color() {
// Step 1: drive to scoring zone
chassis.pid_drive_set(24_in, DRIVE_SPEED, true);
chassis.pid_wait();
// Step 2: verify we have the right ring before scoring
RingColor c = classify_ring();
if (c == MY_ALLIANCE) {
// Score it
chassis.pid_drive_set(6_in, DRIVE_SPEED, true);
chassis.pid_wait();
// ...activate scoring mechanism...
} else if (c == UNKNOWN || c == NONE) {
// No ring or unsure -- skip scoring, save time
return;
} else {
// Wrong color -- eject before continuing
eject_ring();
}
}
Pattern C: Background Sensor Task + Chassis Auton
Run sensor monitoring in a background task; main auton calls EZ-Template chassis motions in sequence. Sensor task can preempt or signal main flow.
// Background task pattern
// EN4: rewrite in your own words for your notebook.
// Shared state
struct SensorState {
pros::Mutex mutex;
bool ring_loaded = false;
RingColor last_color = NONE;
};
SensorState sensor_state;
void optical_task_fn(void* p) {
while (true) {
bool present = optical.get_proximity() > 200;
RingColor c = present ? classify_ring() : NONE;
sensor_state.mutex.take();
sensor_state.ring_loaded = present;
sensor_state.last_color = c;
sensor_state.mutex.give();
pros::delay(20);
}
}
void initialize() {
optical.set_led_pwm(60);
pros::Task t(optical_task_fn, nullptr, "optical");
}
// Auton can read state without blocking
void autonomous() {
chassis.pid_drive_set(24_in, DRIVE_SPEED, true);
chassis.pid_wait();
sensor_state.mutex.take();
bool have_ring = sensor_state.ring_loaded;
sensor_state.mutex.give();
if (have_ring) { /* score */ }
else { /* go pick one up */ }
}
LemLib Equivalent Pattern
The same patterns apply with LemLib. Method names differ:
EZ-Template chassis.pid_drive_set → LemLib chassis.moveTo or chassis.moveToPoint
Both libraries support async motion with sensor-triggered exits. Pick the library your team is most comfortable with — the integration patterns work in either.
Where to Define Your Sensor
In an EZ-Template-based PROS project, the standard convention is to declare global sensor instances in subsystems.hpp (declared extern) and define them in subsystems.cpp. EZ-Template's example project follows this convention.
// subsystems.hpp
#pragma once
#include "EZ-Template/api.hpp"
#include "api.h"
extern Drive chassis;
extern pros::Optical optical; // your Optical Sensor
extern pros::AIVision aivision; // your AI Vision Sensor
extern pros::Motor intake;
extern ez::Piston doinker;
// subsystems.cpp (or main.cpp)
// Definitions
pros::Optical optical(5); // port 5
pros::AIVision aivision(8); // port 8
pros::Motor intake(11);
ez::Piston doinker('A');
5 of 7
// Section 06
Override Use Cases 🎯
Confirmed Override applications based on the v0.1 game manual (released April 27, 2026). The Optical Sensor has one critical Override use case (cup orientation detection) plus several secondary applications.
📌 Quick Take
Override-specific uses: cup orientation detection (clear vs opaque side), pin color identification, and goal proximity for autonomous scoring. All three are confirmed legal and useful in v0.1.
📍
Status: Override 2026-27 confirmed. Cups have a transparent half and an opaque half (Glossary p B3). Cup orientation when stacked on a pin determines whether the pin halves underneath score, per SC3. The Optical Sensor is the canonical sensor for detecting which half is which.
Application 1: Cup Orientation Detection (Primary Override Use)
This is the most distinctive Override application of the Optical Sensor — it doesn't exist in past V5RC games and it matters more than any other use here.
The problem: A cup is hourglass-shaped with one transparent half and one opaque half. When the cup is stacked over a pin in a goal, only the half facing the transparent side of the cup is visible to scoring. If your manipulator places the cup with the opaque side over the wrong pin half, you've just suppressed scoring on that half. For yellow halves with an active toggle, that's a 10-point loss per accidental misplacement.
The fix: Mount an Optical Sensor inside the cup-handling part of the manipulator. As the cup is held, the sensor reads the brightness of the surface adjacent to it:
Opaque (gray) side near sensor: low light return, characteristic gray RGB signature.
Transparent (clear) side near sensor: high light return (passes through to whatever is behind), or significantly different RGB.
From the reading, the code knows the cup's current orientation. Before placement, the manipulator can flip the cup if needed (a single-acting pneumatic on a paddle works well; see advanced-robot Chapter 5 for the cup-flip mechanism pattern).
// Read cup orientation from optical sensor mounted on manipulatordouble hue = cup_optical.get_hue();
double brightness = cup_optical.get_brightness();
bool is_opaque_side = (brightness < 0.30);
if (is_opaque_side && should_show_yellow_half) {
// Wrong orientation. Flip the cup before placing.
cup_flip_solenoid.set_value(true);
pros::delay(200);
cup_flip_solenoid.set_value(false);
}
⚡
Brightness threshold tuning is field-dependent. Lighting varies between practice gym, scrimmage, and competition. Calibrate your threshold at every venue: hold a known-orientation cup against the sensor, read the brightness, save it as the reference. Run this calibration in initialize(), not as a hardcoded constant.
Application 2: Pin Color Identification
Pins come in 4 color combinations: red/yellow, blue/yellow, yellow/yellow, and red/blue (4 scarce ones). Each pin has 2 halves. Knowing which half is which lets your robot decide:
Whether to pick up this pin (your alliance color present? skip if it's opponent-only).
Which way to rotate the pin before placing (so the high-value half faces correctly).
Whether the "rare" red/blue pin is in front of you (highest strategic priority — only 4 exist).
Mount an Optical Sensor inside the pin gripper or near the intake throat. Read hue:
Pin Half
Hue Range
Notes
Red
~0° or ~360°
Rolls over zero; check for <30 OR >330
Yellow
~50-65°
Distinctive band, rarely confused
Blue
~210-240°
Wide field-lighting tolerance
Application 3: 1+1 Possession State Detection
Override's possession limit (SG6) is 1 cup + 1 pin maximum. The Optical Sensor's proximity reading confirms what your robot is holding:
Cup gripper sensor: same pattern, separate sensor.
Code checks both before allowing intake of a third object — refuse to intake if SG6 would be violated.
This is identical to the "loaded vs empty" pattern in past V5RC games but specifically gated on Override's 1+1 limit. The two-sensor configuration (one per gripper) is canonical for dual-grip manipulators; single-grip manipulators only need one.
Application 4: Toggle Color Confirmation (Optional)
Toggles in each quadrant change which alliance owns yellow halves. After your robot activates a toggle, you may want to confirm it actually flipped to your color (in case the activation was incomplete or the toggle bounced back). An Optical Sensor mounted to face the toggle housing can read the active color from a few inches away.
Less critical than Applications 1-3 (drivers can usually see the toggle from the field), but useful for autonomous routines where the robot can't pause to verify visually.
Pattern: Multi-Sensor Override Robot
A competitive Override robot uses Optical Sensors in two specific locations:
In the cup-handling part of the manipulator — Application 1 (orientation detection). This is the highest-leverage placement.
In the pin gripper or intake throat — Applications 2 and 3 (color ID + possession state).
Plus typically one AI Vision Sensor on the chassis front for navigation (color-based goal/element identification, AprilTag fiducials if confirmed). See sensors-apriltags for the navigation half of the sensor stack.
The V5 Brain has 21 Smart Ports, so port count is rarely the constraint. Two Optical Sensors + one AI Vision + one IMU + drivetrain motors fit comfortably.
Override Hardware Order Plan
For Spartan teams ordering Override sensors:
Order at least 1 AI Vision Sensor (276-8659). One per robot is sufficient for chassis-front navigation.
Order at least 2 Optical Sensors (276-7043). One for cup orientation (Application 1), one for pin gripper (Applications 2 + 3).
Order Smart Cables and the right standoffs/spacers (1/4″ standoff 275-1013 or 8mm spacer 276-2019 for Optical mounting).
If AprilTag deployment is confirmed (watch May 14 Q&A), print AprilTag PDF for practice (kb.vex.com).
Forum discussion notes that AI Vision Sensor demand exceeds supply at season starts — order early.
6 of 7
// Section 07
Skills Run & Driver Tips 🏆
How the Optical Sensor specifically improves auton skills and driver skills runs. Different from the AI Vision Sensor — the Optical lives in your mechanisms, not your navigation.
📌 Quick Take
Skills runs gain the most: color-locked autonomous routines (different routine for red vs blue cup), proximity-triggered intake, and gesture-controlled driver overrides. Each adds reliability without adding driver complexity.
Auton Skills (60-second Programming Skills)
Use Case 1: Reliable Object Loading
Don't use timed intakes. Use the Optical Sensor proximity reading to detect when an object is loaded, then move on. Saves time when objects load fast; prevents disasters when they jam. The single biggest skills-score improvement most teams can make is replacing timed intake commands with sensor-triggered ones.
Use Case 2: Object Counter for Possession Limits
Track how many objects you've loaded with a counter that increments on each rising edge of the proximity reading. When you hit your possession limit, stop intaking automatically. Avoids rule violations during fast skills runs.
Use Case 3: Color-Based Skip Decisions
If your skills run includes picking up unknown-color objects, scan them on intake. Skip scoring (or eject) anything wrong-colored. Don't waste cycle time scoring opponent-colored objects.
Driver Skills (60-second Driver Skills)
Driver Assist 1: Auto-Stop Intake on Load
Driver presses intake button to start. Optical Sensor stops the intake when the ring is loaded. Driver doesn't have to manually stop — faster cycles. Implement as a held-button macro: while held, intake runs and auto-stops on detection.
Driver Assist 2: Color-Locked Scoring
A scoring macro that ONLY fires if the loaded object is alliance-colored. Driver presses scoring button; if Optical Sensor reads correct color, the scoring sequence runs. If wrong color, the robot beeps and ejects instead. Prevents accidental opponent scoring (which gives the opponent points).
Driver Assist 3: Status Display on Brain Screen
During driver skills, display real-time sensor state on the V5 Brain screen: "LOADED [BLUE]" or "EMPTY". The driver can glance at the screen to confirm what they're holding without looking at the robot. Surprisingly useful when the action is fast.
Implementation Tips
Always have a sensor failure fallback. If the Optical Sensor unplugs (loose Smart Cable, contact damage), all sensor-dependent macros stop working. Provide manual override buttons that bypass sensor checks.
Run sensor logic in a dedicated PROS task. Don't poll sensors inline in opcontrol — you'll cause control delays. Background task pattern (shown in Pattern C above) is the right approach.
Match-time recalibration. Some teams add a hidden controller-button combination that re-runs hue calibration mid-pit-time. If lighting changes between rounds, you can recalibrate without restarting.
Test under tournament-typical lighting BEFORE the tournament. Gym lighting is often dimmer or more orange-tinted than classroom LED lighting. The white LED helps but doesn't fully compensate. Practice in conditions like the venue when possible.
Common Pitfalls
⚠️ Things That Will Bite You
Mounting too far. > 100mm and color readings become unreliable. The single most common Optical Sensor failure.
Forgetting to enable the white LED. Default LED state may be off. Set LED to 50–75% in initialize().
Hard-coding hue thresholds. Different venues = different lighting = different readings. Always use ranges and re-calibrate.
Reading hue when proximity is low. Far objects return garbage hue values. Always gate color decisions on proximity threshold.
Reading hue when saturation is low. Grayscale objects (white tile, black structure) return arbitrary hue values that look like real readings. Gate on saturation.
Enabling gestures by accident. Disables proximity. Make sure gestures are explicitly disabled if you need proximity.
Polling too fast. Reading at > 100Hz wastes CPU and doesn't give you faster updates than the integration time allows. Match your poll rate to your integration time.
Cable strain. The Smart Port is on the side of the sensor. Strain on the Smart Cable can disconnect it during a match. Use cable management.