Lesson 3.1: Constants & Safe Edits
🎯 What You’ll Learn
By the end of this lesson you will be able to:
- Identify which parts of the codebase are safe to edit and which are off-limits
- Modify constants in
Constants.javawith confidence - Add SmartDashboard outputs to see live robot data
- Create a simple command using existing subsystem methods
- Build, deploy, and verify your changes safely
- Read console output and driver station logs
- Apply a basic code review mindset before committing changes
The Safe Editing Mindset
You’ve spent the last five lessons reading and understanding the robot code. Now it’s time to start making changes. But not all changes are created equal — some are perfectly safe, and others can break the robot in ways that are hard to debug.
The golden rule:
If you’re not sure whether a change is safe, ask before you save.
Let’s draw a clear line between what’s safe and what’s not.
✅ Safe to Edit
These are changes that won’t break hardware, won’t mess up calibration, and are easy to verify:
| What | Why It’s Safe | Example |
|---|---|---|
| Constants (speeds, delays, thresholds) | They’re just numbers — the code structure stays the same | Change DEPLOY_SPEED from 0.15 to 0.12 |
| Dashboard/log outputs | Adding SmartDashboard.putNumber() or System.out.println() only adds visibility — it doesn’t change behavior | Log the turret angle every loop |
| Simple new commands | A new command that calls existing subsystem methods doesn’t change how those methods work | A command that runs the intake at a different speed |
| Print statements for debugging | System.out.println() writes to the console and nothing else | Print “Intake deployed!” when deploy runs |
🚫 Do NOT Edit (Without Mentor Approval)
These changes can cause real problems — motors fighting each other, calibration lost, or code that won’t compile:
| What | Why It’s Dangerous | What Could Go Wrong |
|---|---|---|
| Subsystem hardware config (motor initialization, inversion, current limits) | These are tuned to the physical robot | Motors spin the wrong way, gears strip, breakers trip |
Generated files (generated/TunerConstants.java) | Auto-generated by CTRE Tuner X for the swerve drive | Swerve modules lose calibration, robot drives erratically |
| PID gains and control loops | Tuned through careful testing on the actual robot | Robot oscillates, overshoots, or becomes uncontrollable |
| CAN ID assignments | Must match the physical wiring | Code talks to the wrong motor — or no motor at all |
| Motor inversion settings | Matched to how motors are physically mounted | Two motors on the same mechanism fight each other |
-
SAFE ✅ — Changing a roller speed constant is a simple number change. The code structure stays the same, and you can easily test it and revert if the speed is wrong.
-
UNSAFE 🚫 — CAN IDs must match the physical wiring. If you change the ID in code but the motor is still wired to ID 31, the code will talk to nothing (or the wrong motor). Only change CAN IDs when you’ve physically re-wired and confirmed the new ID with the electrical team.
-
SAFE ✅ — Print statements only write to the console. They don’t affect motor behavior at all. Great for debugging!
-
UNSAFE 🚫 — PID gains are carefully tuned through testing on the real robot. Wrong gains can make the turret oscillate wildly, overshoot its target, or become completely unresponsive. This is a Programming 2+ topic.
Understanding Constants.java
Open
Why Constants Exist
Without a constants file, you’d see numbers like 21, 0.15, and 4.03 scattered throughout the code with no explanation. Constants solve this by:
- Giving numbers meaningful names —
SHOOTER_LEFT_MOTORis clearer than21 - Centralizing changes — change a value in one place, and every file that uses it gets the update
- Documenting the robot — the constants file is basically a wiring diagram in code
The Structure of Constants.java
Constants.java is organized into inner classes that group related values:
public class Constants { // The CANivore bus name public static final CANBus kCANivoreBus = new CANBus("Carnivore");
// Shooting system constants public static final class ShootingConstants { public static final int SHOOTER_LEFT_MOTOR = 21; public static final int SHOOTER_RIGHT_MOTOR = 22; public static final int TURRET_MOTOR = 23; public static final int FEEDER_MOTOR = 24; public static final int SPINNER_MOTOR = 25; public static final double HOOD_MIN = 0.0; public static final double HOOD_MAX = 0.85; }
// Intake system constants public static final class IntakeConstants { public static final int INTAKE_LEFT_MOTOR = 31; public static final int INTAKE_RIGHT_MOTOR = 32; public static final int DEPLOY_LEFT_MOTOR = 33; public static final int DEPLOY_RIGHT_MOTOR = 34; }
// Climber system constants public static final class ClimberConstants { public static final int CLIMB_LEFT_MOTOR = 41; public static final int CLIMB_RIGHT_MOTOR = 42; public static final int ELEVATOR_MOTOR = 43; }
// Field positions and zones public static final class FieldConstants { // ... field measurements, hub positions, trench zones }}Notice the CAN ID ranges — they’re organized so you can tell what system a motor belongs to just by its ID:
| CAN ID Range | System | Motors |
|---|---|---|
| 1–20 | Drivetrain (swerve) | Configured in TunerConstants (don’t edit!) |
| 21–25 | Shooting system | Left flywheel, right flywheel, turret, feeder, spinner |
| 31–34 | Intake system | Left roller, right roller, left deploy, right deploy |
| 41–43 | Climber system | Left climb, right climb, elevator |
What’s Safe to Change in Constants
Even though Constants.java contains values you shouldn’t touch (CAN IDs), it also contains values that are perfectly safe to experiment with:
- Speed values like
HOOD_MIN,HOOD_MAX— these control how far the hood servo travels - Timing values like
FEED_DELAY(in AutoShootCommand) — how long to wait before feeding - Threshold values like
TRENCH_HOOD_THRESHOLD— when to flatten the hood near trenches - Feature flags like
USE_TRAJECTORY_PASSING_DEFAULT— toggle between calculation methods
The key question to ask: “Does changing this number affect which hardware the code talks to, or just how it behaves?” If it’s just behavior (speed, timing, threshold), it’s generally safe. If it’s identity (CAN ID, bus name, motor inversion), leave it alone.
Adding Dashboard Outputs
One of the most useful safe edits you can make is adding SmartDashboard outputs. These let you see live robot data on the driver station dashboard while the robot is running.
What is SmartDashboard?
SmartDashboard is a WPILib tool that displays key-value pairs sent from the robot code. You put a value in your code, and it shows up on the dashboard in real time. It’s like adding a speedometer to your car — it doesn’t change how the car drives, it just lets you see what’s happening.
How to Add a Dashboard Output
The pattern is simple — one line of code:
// Put a number on the dashboardSmartDashboard.putNumber("Turret Angle", turret.getAngle());
// Put a boolean (true/false) on the dashboardSmartDashboard.putBoolean("Intake Deployed", isDeployed);
// Put a string on the dashboardSmartDashboard.putString("Current State", "AIMING");The first argument is the key (the label that shows up on the dashboard), and the second is the value. You typically add these inside a subsystem’s periodic() method so they update every loop:
// Inside ShooterSubsystem.java, in the periodic() method:@Overridepublic void periodic() { // ... existing code ...
// Add these lines to see shooter data on the dashboard: SmartDashboard.putNumber("Shooter/Left RPM", leftMotor.getVelocity().getValueAsDouble() * 60); SmartDashboard.putNumber("Shooter/Right RPM", rightMotor.getVelocity().getValueAsDouble() * 60); SmartDashboard.putBoolean("Shooter/Flywheels Ready", areFlywheelsAtSpeed());}Why Dashboard Outputs Are Safe
Adding SmartDashboard.putNumber() or SmartDashboard.putBoolean():
- Does not change any motor behavior — it only sends data to the dashboard
- Has negligible performance impact — the robot loop handles thousands of these per second
- Is easy to remove — just delete the line if you don’t need it anymore
- Helps everyone — the drive team can see what the robot is doing in real time
Where is the best place to add a SmartDashboard.putNumber() call that shows a subsystem's sensor reading every loop?
Creating a Simple Command
In Lesson 2.2 you learned how commands work. Now let’s create one. The safest kind of new command is one that only calls existing subsystem methods — you’re not changing how the subsystem works, just creating a new way to trigger it.
Example: A “Slow Intake” Command
Say you want an intake command that runs the rollers at a slower speed for delicate game pieces. Here’s how you’d add it as a named command in RobotContainer:
// Add this near the other NamedCommands.registerCommand() calls:NamedCommands.registerCommand("SlowIntake", Commands.run(() -> { intakeSubsystem.runIntake(0.25); // 25% speed instead of 50%}, intakeSubsystem));That’s it. One line. You’re calling runIntake() — a method that already exists in IntakeSubsystem — just with a different speed value. The subsystem code doesn’t change at all.
What Makes This Safe?
- You’re using an existing method —
runIntake()is already tested and working - You’re only changing the input —
0.25instead of0.5 - The subsystem handles the hardware — your command doesn’t touch motors directly
- It’s easy to test — bind it to a button, press it, see if the intake runs slower
- It’s easy to revert — delete the one line and you’re back to where you started
Adding a Button Binding for Your Command
To test your new command, you can bind it to an unused button. Check which buttons are already taken:
| Button | Current Binding |
|---|---|
| X | toggleOnTrue — Intake toggle |
| Y | toggleOnTrue — Auto shoot |
| A | whileTrue — Jostle |
| B | onTrue — Elevator |
If there’s a free button (like the bumpers or triggers), you could add:
// Example: Left bumper runs slow intake while heldcontroller.leftBumper() .whileTrue(Commands.run(() -> intakeSubsystem.runIntake(0.25), intakeSubsystem));Testing Your Changes Safely
You’ve made a change. Now what? Here’s the testing workflow every FRC programmer should follow:
Step 1: Build the Project
Before anything else, make sure your code compiles:
# From the robot project root directory:./gradlew buildIf the build fails, read the error message carefully. The most common issues:
- Missing semicolon or bracket — Java is picky about syntax
- Wrong method name — check the subsystem for the exact method spelling
- Missing import — if you used
SmartDashboard, you needimport edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
Step 2: Deploy to the Robot
Once the build succeeds, deploy to the robot:
./gradlew deployThis sends your compiled code to the roboRIO. The robot must be connected (USB, Ethernet, or Wi-Fi) and the roboRIO must be on.
Step 3: Check the Dashboard
After deploying, open the driver station and your dashboard (Shuffleboard, Glass, or SmartDashboard). Look for:
- Your new dashboard values — are they showing up? Are the numbers reasonable?
- No new errors — check the driver station console for red error messages
- Normal behavior — does everything that worked before still work?
Step 4: Test Your Specific Change
If you changed a constant, test the behavior it affects:
- Changed intake speed? Run the intake and see if it feels different.
- Added a dashboard output? Check that the value updates in real time.
- Created a new command? Press the button and verify it does what you expect.
Step 5: Revert If Something’s Wrong
If anything seems off, don’t panic. Git makes it easy to undo:
# See what you changedgit diff
# Undo all changes and go back to the last commitgit checkout .This is why we learned Git in Lesson 0.1 — it’s your safety net.
Here are the most common reasons a dashboard value doesn’t show up:
-
Check that the code compiled and deployed successfully — if the build failed or you forgot to deploy, the robot is still running the old code. Run
./gradlew deployagain and watch for errors. -
Check the dashboard key name — make sure you’re looking for the exact key you used in
putNumber(). If you used"Shooter/Left RPM", look for that exact string. Dashboard keys are case-sensitive. -
Check that
periodic()is actually running — if the subsystem isn’t registered properly (not created in RobotContainer, or not passed to a command), itsperiodic()method might not be called. Verify the subsystem is instantiated in RobotContainer.
Other things to check: Is the robot enabled? (Dashboard values from periodic() update even when disabled, but some subsystem logic might be gated.) Is the dashboard connected to the robot? (Check the connection indicator in the driver station.)
Reading Console Output and Logs
When you add System.out.println() statements or something goes wrong, you need to know where to look.
The Driver Station Console
The FRC Driver Station has a console panel that shows:
System.out.println()output — your debug messages appear here- Error messages — stack traces, CAN errors, watchdog warnings
- WPILib messages — framework status updates
To see it: open the Driver Station, look for the console/log area (usually at the bottom or in a separate tab).
What to Look For
When reading console output, focus on:
| What You See | What It Means |
|---|---|
Your println() messages | Your code is running and reaching that point |
ERROR or red text | Something went wrong — read the message carefully |
CAN: ... not found | A motor controller isn’t responding at the expected CAN ID |
CommandScheduler loop overrun | The code is taking too long to run each loop — usually means too many print statements or expensive calculations |
| Stack traces (long error with file names and line numbers) | An exception was thrown — the first line tells you what went wrong, and the file/line numbers tell you where |
Using Print Statements Strategically
Instead of printing every loop (50 times per second!), print only when something interesting happens:
// BAD: prints 50 times per second@Overridepublic void execute() { System.out.println("Turret angle: " + turret.getAngle());}
// GOOD: prints only when the state changes@Overridepublic void execute() { boolean ready = areFlywheelsAtSpeed(); if (ready && !wasReady) { System.out.println(">>> Flywheels reached target speed! <<<"); } wasReady = ready;}The second approach gives you the information you need without flooding the console.
The Code Review Mindset
Before you commit and push your changes, take a moment to review your own code. This is a habit that separates good programmers from great ones.
The Self-Review Checklist
Ask yourself these five questions before every commit:
-
Does it build? Run
./gradlew buildone more time. Never commit code that doesn’t compile. -
Did I only change what I intended? Run
git diffto see exactly what changed. Look for accidental edits — a stray character, a deleted line you didn’t mean to remove. -
Is it safe? Review the safe/unsafe table from earlier in this lesson. Did you accidentally touch a CAN ID, motor config, or generated file?
-
Can I explain it? If a teammate asked “what does this change do?”, could you explain it in one sentence? If not, the change might be too complex or unclear.
-
Is it easy to revert? If this change causes problems at competition, can you quickly undo it? Small, focused changes are easier to revert than big ones.
Writing Good Commit Messages
When you commit, write a message that explains what you changed and why:
# Good commit messages:git commit -m "Add dashboard output for shooter flywheel RPM"git commit -m "Reduce intake deploy speed from 0.15 to 0.12 for smoother deploy"git commit -m "Create SlowIntake named command for delicate game pieces"
# Bad commit messages:git commit -m "fixed stuff"git commit -m "changes"git commit -m "asdf"A good commit message helps your future self (and your teammates) understand why the change was made. When something breaks at 2 AM during competition, you’ll be glad you wrote clear messages.
You're about to commit a change that adds a SmartDashboard output to IntakeSubsystem. Which commit message is best?
Putting It All Together
Let’s recap the safe contribution workflow you’ve learned:
1. Identify what to change └── Is it safe? (constants, dashboard, simple commands) ├── YES → proceed └── NO → ask a mentor first
2. Make the change └── Small, focused edits only
3. Build └── ./gradlew build ├── Success → continue └── Failure → read error, fix, rebuild
4. Deploy and test └── ./gradlew deploy → check dashboard → test behavior
5. Review your own code └── git diff → check the self-review checklist
6. Commit and push └── git checkout -b my-feature git add . git commit -m "Clear description of what and why" git push origin my-feature
7. Open a pull request └── Ask a teammate or mentor to reviewThis workflow applies to every change you make, whether it’s a one-line constant tweak or a new command. Build the habit now, and it’ll serve you well through Programming 2, 3, and 4.
What’s Next?
Congratulations — you’ve completed all six robot code lessons! 🎉
You can now:
- Navigate the robot project and find any file
- Explain what each core file does
- Trace a button press from controller to motor output
- Read and understand subsystems and commands
- Make safe edits: constants, dashboard outputs, simple commands
- Test, review, and commit your changes properly
Head to Activity 3.2: Make a Small Change to put this lesson into practice with a guided hands-on exercise. Then try Activity 3.3: Add a Simple Feature to create your own command from scratch.
After that, check out the Reference Sheets section to complete your Code Map, Trace Worksheet, Glossary, and Code Reading Checklist — the tangible proof of everything you’ve learned.