The same five steps work for every V5 sensor. Once you do it for one, you can do it for any — bump, pot, distance, vision, GPS, optical. This guide is the universal scaffold between "I have a working chassis" and the per-sensor deep dives.
Every per-sensor guide on this site — switches & pot, distance, AI Vision, optical, GPS — goes deep on what one specific sensor does. None of them teach the meta-pattern: the same five steps you go through every single time you add any sensor.
This is that pattern. Learn it once, apply it forever.
Smart port for cameras, distance, GPS, rotation. ADI port for switches, pot, line trackers. Match the sensor type to the right port type.
One line in subsystems.hpp. Use a named #define for the port number so it's easy to change later.
Print the value to the controller or brain screen. Wiggle the sensor by hand. Confirm the number changes the way you expect.
Find your real-world threshold values by reading the sensor at known positions. Save them as named constants.
Read the value, compare against your constants, drive an output. The basic if-then pattern that turns sensor data into robot behaviour.
Each step verifies the previous one. Skip any of them and you'll spend hours debugging the wrong layer:
9999 the entire time. Step 3 would have caught this in 30 seconds.The five-step rhythm protects you from this. You don't move to the next step until the previous step works. Sensor reads garbage on the brain screen? Don't write any code yet — fix the wiring or the port assignment first.
If you have all three, you're ready. The worked example at the end of this guide uses a bump switch because it's the simplest sensor to verify (binary input, no calibration needed) — but the same workflow scales up to anything.
Round 4-pin plug. Carries digital data. For sensors that have onboard processing — cameras, distance, GPS, rotation, optical, IMU. Also where motors plug in.
Rectangular 3-wire plug. Analog or simple digital. For sensors that report a raw voltage or a binary state — bump switch, limit switch, potentiometer, line tracker, LED.
| Sensor | Port type | Notes |
|---|---|---|
| Bump / Limit switch | ADI | Digital input. Returns 0 or 1. |
| Potentiometer V2 | ADI | Analog. Returns 0–4095 across ~330° rotation. |
| Line tracker | ADI | Analog. Returns brightness 0–4095. |
| LED indicator | ADI | Digital output. Set 0 or 1. |
| Inertial Sensor (IMU) | Smart | Required by EZ-Template. |
| V5 Distance Sensor | Smart | Returns mm. |
| V5 Rotation Sensor | Smart | For tracking wheels. |
| V5 Optical Sensor | Smart | Hue/saturation/proximity. |
| AI Vision Sensor | Smart | AprilTags + AI classes. |
| V5 GPS Sensor | Smart | Reads field code strips. |
Once you know the port type, pick a specific number that's not already used. The Clawbot uses ports 1, 10, 8, 3 for motors and 11 for the IMU — ports 12–21 are usually free for new sensors.
Before you write a single line of code, plug the sensor in and check the V5 Brain's Devices screen. The brain auto-detects every connected sensor and shows live readings.
If you're using the EZ-Template project structure (highly recommended), there's already an include/subsystems.hpp file. Every device declaration goes there. The motors, the sensors, the controller alias — one big wiring map you can read top-to-bottom.
If you don't have a subsystems.hpp yet, see Organizing Code Across Files. Don't put device declarations in main.cpp alongside the autonomous logic — it gets unmanageable fast once you have more than two or three sensors.
Every device declaration follows the same pattern: a #define for the port number, then an inline object on that port.
Each sensor has a different PROS class name. The pattern is consistent — PascalCase, in the pros:: namespace, sometimes nested under pros::adi:: for ADI devices.
| Sensor | PROS class | Header |
|---|---|---|
| Bump / Limit switch | pros::adi::DigitalIn | pros/adi.hpp |
| Potentiometer V2 | pros::adi::Potentiometer | pros/adi.hpp |
| Line tracker | pros::adi::AnalogIn | pros/adi.hpp |
| Inertial Sensor | pros::Imu | pros/imu.hpp |
| Distance Sensor | pros::Distance | pros/distance.hpp |
| Rotation Sensor | pros::Rotation | pros/rotation.hpp |
| Optical Sensor | pros::Optical | pros/optical.hpp |
| AI Vision Sensor | pros::AIVision | pros/aivision.hpp (PROS 4) |
| GPS Sensor | pros::Gps | pros/gps.hpp |
The headers are usually all included transitively by "main.h" — you usually don't need to #include them explicitly. If a class doesn't resolve, check the header path.
One small wrinkle worth noting:
inline in a Header?Without inline, including subsystems.hpp from two different .cpp files (say main.cpp and autons.cpp) creates two copies of each device, causing a linker error like multiple definition of 'distance'. The inline keyword tells the compiler "this is the same object across all files" — modern C++ (since C++17) lets you do this for variables.
extern in the header and a separate .cpp file with the actual definition. Both patterns work; inline is one less file to maintain. Pick one and stick with it across the project.The whole rest of the project assumes the sensor reading is meaningful. If the sensor is wired backwards, mounted at the wrong height, or has a flaky cable — the readings are garbage and every line of downstream code is built on quicksand.
Step 3 is the last point where verification is cheap. Once you've written 50 lines of autonomous logic, debugging a "why does my robot crash" problem is much harder than checking "what does the sensor read when I push it by hand."
8 lines of text on the V5 Brain screen. Easy to read, large font, but you have to look at the brain — not great if the robot is moving.
3 small lines on the V5 Controller. Easy to read while driving the robot, but limited to ~15 chars per line.
Drop this in opcontrol() for live debugging:
For something you want to see while driving:
master.print() every loop silently drops most of the prints — the screen looks frozen, even though your code is running fine. Rate-limit to roughly 100 ms (every 10 loops at the default delay).Once values are printing, do the wiggle test:
| Sensor | Wiggle test | Good result |
|---|---|---|
| Bump switch | Press and release | Value flips between 0 and 1. |
| Potentiometer | Rotate slowly through full range | Value sweeps smoothly across ~0–4095. |
| Distance sensor | Hold a flat object 5cm, 30cm, 1m away | Reading roughly matches in mm. |
| Optical sensor | Hold a coloured card in front | Hue value changes with colour. |
| IMU heading | Rotate the robot by hand | Heading changes smoothly with rotation. |
#define, sensor wired into the wrong port type, broken cable, sensor mounted backwards (some pots read reverse), sensor too far from its target. Fix the hardware/wiring before writing any sensor logic.Here's a beginner's first sensor-driven autonomous routine:
Six months later when the robot is rebuilt and the sensor is in a different position, no one remembers what 280 means or how to update it. The magic number is dangerous because it loses its meaning the moment you walk away from your computer.
Replace every magic number with a named constant. The constant carries the meaning forward.
The workflow is the same for every sensor:
Suppose you have a potentiometer on an arm. You want preset heights for "down," "mid," and "up."
Calibration is not a one-time job. Re-run the workflow whenever:
Run an output until a sensor reading crosses a threshold. The most common pattern in autonomous.
Refuse to drive an output if the sensor says it's unsafe. Used for protecting hard stops on arms and lifts.
Take different actions depending on what the sensor reports. Common with optical sensors and AprilTags.
pros::Distance(5).get() inside an autonomous routine duplicates the port number across the project. Change one wire, edit ten places. Always declare devices in subsystems.hpp and reference them by name.while (distance.get() == 300) almost never triggers — the value flies through 300 in one loop and the loop never exits. Use thresholds (>, <, <=) for analog sensors. Equality is fine for binary sensors like a bump switch.while (distance.get() > 280) runs forever and the auton hangs. Always add a safety cap — either a max travel distance or a timeout in milliseconds.Putting the four mistakes' inverses together — this is the template for every robust sensor-driven loop:
Once you've used this template a few times, it becomes muscle memory. Every sensor-driven loop in your project follows the same shape.
Mount a bump switch on the chassis, positioned so the arm presses it when the arm reaches its lowest safe point. The switch tells the code: "the arm is at the bottom — stop trying to lower it."
This is the simplest possible sensor work and it's also genuinely useful — it's the bottom half of the dual-limit arm pattern from the Sensor-Fused Clawbot Walkthrough.
A bump switch is a digital input, so it goes on an ADI port. Ports A through H are all interchangeable for our purposes — pick one that's empty. 'A' is conventional.
Plug the switch in. Power on the brain. Open Devices and confirm the brain sees an input on ADI port A. Press the switch by hand — the value should flip.
Add two lines to subsystems.hpp:
Save and upload. The device is now declared. Don't write any sensor logic yet.
Add one print line to opcontrol():
Upload. Look at the brain LCD. Press the switch by hand — the value should change between 0 (released) and 1 (pressed). If it doesn't change, the wiring is wrong; go fix it before moving on.
The bump switch is binary — there's nothing to calibrate. The "constant" is just the meaning of 0 and 1, which we capture in two helper functions for readability:
That's the calibration step done. For an analog sensor like a pot you'd be writing down numbers; for a binary sensor you're naming the meaning.
Pattern B from the previous section — block the lower-arm command if the bump switch says we're at the bottom:
Upload. Drive the arm down with L2 — it should stop the moment it presses the bump switch, even if you keep holding the trigger.
That's the entire workflow. One sensor, fully integrated, in five short steps.
9999 (no object detected) because of wrong port, bad cable, or wrong mounting — problems Step 3 would have surfaced in 30 seconds.The priority order — which sensor to add next, and why.
Switches, pot, GPS — sensor-by-sensor specifics.
Replace timed waits with sensor-confirmed conditions.
Sensor-Fused Clawbot — what five sensors look like working together.