Skip to content

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.java with 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:

WhatWhy It’s SafeExample
Constants (speeds, delays, thresholds)They’re just numbers — the code structure stays the sameChange DEPLOY_SPEED from 0.15 to 0.12
Dashboard/log outputsAdding SmartDashboard.putNumber() or System.out.println() only adds visibility — it doesn’t change behaviorLog the turret angle every loop
Simple new commandsA new command that calls existing subsystem methods doesn’t change how those methods workA command that runs the intake at a different speed
Print statements for debuggingSystem.out.println() writes to the console and nothing elsePrint “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:

WhatWhy It’s DangerousWhat Could Go Wrong
Subsystem hardware config (motor initialization, inversion, current limits)These are tuned to the physical robotMotors spin the wrong way, gears strip, breakers trip
Generated files (generated/TunerConstants.java)Auto-generated by CTRE Tuner X for the swerve driveSwerve modules lose calibration, robot drives erratically
PID gains and control loopsTuned through careful testing on the actual robotRobot oscillates, overshoots, or becomes uncontrollable
CAN ID assignmentsMust match the physical wiringCode talks to the wrong motor — or no motor at all
Motor inversion settingsMatched to how motors are physically mountedTwo motors on the same mechanism fight each other

Checkpoint: Safe vs. Unsafe
For each change below, decide if it's SAFE or UNSAFE for a new team member to make without mentor approval: (1) Change the intake roller speed from 0.5 to 0.4, (2) Change a motor's CAN ID from 31 to 35, (3) Add System.out.println() inside a command's execute() method, (4) Change the PID gains on the turret.
  1. 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.

  2. 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.

  3. SAFE ✅ — Print statements only write to the console. They don’t affect motor behavior at all. Great for debugging!

  4. 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 Constants.java in your IDE. This is the central place where all the robot’s “magic numbers” live — CAN IDs, speeds, field measurements, and configuration values.

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:

  1. Giving numbers meaningful namesSHOOTER_LEFT_MOTOR is clearer than 21
  2. Centralizing changes — change a value in one place, and every file that uses it gets the update
  3. 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:

Constants.java — Structure
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 RangeSystemMotors
1–20Drivetrain (swerve)Configured in TunerConstants (don’t edit!)
21–25Shooting systemLeft flywheel, right flywheel, turret, feeder, spinner
31–34Intake systemLeft roller, right roller, left deploy, right deploy
41–43Climber systemLeft 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:

Adding a dashboard output
// Put a number on the dashboard
SmartDashboard.putNumber("Turret Angle", turret.getAngle());
// Put a boolean (true/false) on the dashboard
SmartDashboard.putBoolean("Intake Deployed", isDeployed);
// Put a string on the dashboard
SmartDashboard.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:

Example: Adding dashboard output to a subsystem
// Inside ShooterSubsystem.java, in the periodic() method:
@Override
public 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:

RobotContainer.java — Adding a slow intake named command
// 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?

  1. You’re using an existing methodrunIntake() is already tested and working
  2. You’re only changing the input0.25 instead of 0.5
  3. The subsystem handles the hardware — your command doesn’t touch motors directly
  4. It’s easy to test — bind it to a button, press it, see if the intake runs slower
  5. 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:

ButtonCurrent Binding
XtoggleOnTrue — Intake toggle
YtoggleOnTrue — Auto shoot
AwhileTrue — Jostle
BonTrue — Elevator

If there’s a free button (like the bumpers or triggers), you could add:

RobotContainer.java — Binding to a button
// Example: Left bumper runs slow intake while held
controller.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:

Terminal window
# From the robot project root directory:
./gradlew build

If 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 need import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;

Step 2: Deploy to the Robot

Once the build succeeds, deploy to the robot:

Terminal window
./gradlew deploy

This 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:

Terminal window
# See what you changed
git diff
# Undo all changes and go back to the last commit
git checkout .

This is why we learned Git in Lesson 0.1 — it’s your safety net.


Checkpoint: Testing Workflow
You added a SmartDashboard.putNumber() call to ShooterSubsystem's periodic() method, but after deploying, the value doesn't appear on the dashboard. List three things you would check to debug this.

Here are the most common reasons a dashboard value doesn’t show up:

  1. 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 deploy again and watch for errors.

  2. 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.

  3. Check that periodic() is actually running — if the subsystem isn’t registered properly (not created in RobotContainer, or not passed to a command), its periodic() 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 SeeWhat It Means
Your println() messagesYour code is running and reaching that point
ERROR or red textSomething went wrong — read the message carefully
CAN: ... not foundA motor controller isn’t responding at the expected CAN ID
CommandScheduler loop overrunThe 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:

Smart printing — only when state changes
// BAD: prints 50 times per second
@Override
public void execute() {
System.out.println("Turret angle: " + turret.getAngle());
}
// GOOD: prints only when the state changes
@Override
public 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:

  1. Does it build? Run ./gradlew build one more time. Never commit code that doesn’t compile.

  2. Did I only change what I intended? Run git diff to see exactly what changed. Look for accidental edits — a stray character, a deleted line you didn’t mean to remove.

  3. 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?

  4. 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.

  5. 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:

Terminal window
# 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 review

This 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.