Tutorial

IntelliJ with GitHub

Programming as a team introduces special challenges. You’ll need extra communication to keep everyone productive, and additional tools to keep from losing work.

Professional environments use Revision Control Systems to store the code, communicate the changes, and keep people from overwriting each other’s work.  FRC programming teams should also use Revision Control.

Probably the most popular revision control system right now is git, a distributed version control system created by Linus Torvalds, the same guy who created Linux. You can use git from the command line, or from within development environments such as Eclipse, IntelliJ, and Visual Studio Code.  GitHub is a web-based hosting service for git, and the GitHub corporation is a FIRST sponsor.  All programming mentors and students can get free GitHub accounts, and FIRST teams can get upgraded Team Accounts.

To say that git is “distributed” means that every programmer will have a copy of the code, along with the history of the changes.  There is also one remote repository of the code out on the internet. Programmers will occasionally pull changes from the remote repository to their local copy and occasionally push their own changes up to the remote repository.  In this way, everyone eventually has the same code and the same history.

Combinations of the code files are called “commits“, and the word “commit” here is both a noun and a verb.  Committing your changes creates a commit that you can retrieve later.

OK, I know that that was a lot of information.  Honestly, git is a really deep subject, and companies that use it develop really complicated methodologies for its use.  I do not recommend that FRC teams try to use everything in the git toolkit, or try to emulate commercial practices.  Especially at first.  Instead, let’s lay out the minimal functions.

Installing and configuring git

IntelliJ provides an excellent user interface to git, much better than the UI in Eclipse or VS Code.  However, the git package is separate from IntelliJ.  You must install it on your laptop.  Instructions are at:  https://git-scm.com/ .

You should configure git to know who you are.  This information will be added to the repository very time you commit.  Open up a terminal window and execute the following:

git config --global user.email "myEmailAddress@whereever.com"
git config --global user.name "My real name"

Cloning an existing repository

Suppose that there is a repository on GitHub that you’d like to download to your laptop.  Making a local copy is called making a clone.

Consider the code repository at:  https://github.com/firebears-frc/testrobot0.  Go ahead and visit that page in a browser.  Press the green button labeled “Clone or Download” and then press the little clipboard button.  This will copy the repository’s formal URL into your clipboard.

vsc_git_clone

Now go to IntelliJ:

  1. From the main menu select File > New > Project from version control > git
  2. Paste your git URL into the “Clone Repository” dialog.  Click “OK” and open the new project in the current window.
  3. A dialog may appear asking if you want to import a Gradle project.  You do.
    If the dialog gets lost, open your project in the Project tool window.  Right-click on the build.gradle file and select “Import Gradle Project”.

At this point you now have a clone of the repository on your machine.  You won’t be able to push changes up to the remote repository unless the owner has granted you permission, but you can read, edit, and deploy this code to the robot.

Creating a new repository

Suppose you have a robot project on your local machine that has never been under git control, but you’d like to upload it into GitHub.   Open your project in IntelliJ and then:

  1. From the menu select VSC > Import into Version Control > Share Project on GitHub.
  2. Log on with your GitHub account:
    intellij_github_login.png
  3. Specify the repository name and description:
    intellij_github_share
  4. Make the initial commit of all your files:
    intellij_github_commit_1
  5. Now go to your browser and visit the page on https://github.com for your new repository.  You should now see all the files listed on the web page.

Note that this process has initialized your local project to be tracked by git.    You’ll notice that there is now a “Version Control” tab at the bottom of the window that will open the Version Control tool window.  The “Local Changes” tab will show files that have been added or modified since your last commit.

intellij_github_vc.png

Also notice the branch indicator in the lower right corner of the screen.  This will be used when you start managing multiple branches in your repository.

Committing changes to the code

From now on, when you add files, delete files, or modify files, git and IntelliJ will keep track of how your code differs from the most recent commit.  The Local Changes tab keeps track of what has changed.  When you are ready to make a commitment, select the files, right-click, and select “Commit”.

intellij_github_commit_2

Type in a commit message.  Try to write informative messages, since you and others will be reading this later.  Good messages say things like “Updated autonomous commands for new encoder” or “Code changes after first regional”.   Bad messages contain jokes or gibberish or say things like “changed stuff”.

intellij_github_commit_3

After entering a useful message, hit the “Commit” button.  This will commit your changes locally, but will not yet push those changes upstream to Github.

Pulling changes back from the repository

Suppose someone else has made changes to the code and pushed them up to the remote repository.  You’d like to fetch those changes and merge them into our code.  This is called doing a pull from the remote.

You can pull at any time, but it is usually best to commit your code locally before pulling.  That is to say, commit the code but don’t push it up yet.  Follow the directions in the previous section to do the commit.

To pull down changes, select from the main menu:  VCS > Git > Pull.   The best case scenario (which is usually what happens) is that upstream changes will be seamlessly added to your code and everything will work perfectly.

One thing that might go wrong is that the changes pulled in will invalidate or undo something you are doing.  You should always look over the incoming changes.

The worst case scenario is that someone else will have changed files that you are working on, and you will need to “merge” changes.  The Conflicts dialog will show which files are in conflict:

intellij_github_merge_1

For each conflicting file, you will have three options

  • Accept Yours : ignore all changes in the remote repository and stick with your changes.
  • Accept Theirs : overwrite this file with the file from the remote repository.
  • Merge: Manually decide how the conflict will be resolved.  This involves picking out individual changes will be copied from your branch or from the remote branch.  If neither change looks right, you can edit text in the middle panel any way you like:intellij_github_merge_2.png

After you’ve modified a conflicted file and saved the changes, go back to the Local Changes view.   After merging all the conflicts, perform another “Commit”.

Pushing your changes up to the repository

If you are ready to release your changes to the rest of the group, you can push your commits up to the remote repository.  Use the menu options VCS > Git > Push.

Synching with the remote repository

There’s an important discipline that everyone must develop with respect to the remote repository, which is that you should always pull in remote changes before pushing up your own.  If there are incoming changes, then you must recheck the merged code to verify that it is OK.

With multiple programmers, you should perform the following steps manually:

  1. Commit your changes locally.
  2. Pull remote changes, and deal with any merge conflicts.
  3. Verify that the merged code compiles correctly and that the code works correctly.  If there are any problems, fix them and then go back to step 1.
  4. Push all commits to the remote repository.

Further Reading:

Tutorial

Creating Java Programs with IntelliJ

Creating GradleRIO FRC robot programs in IntelliJ is quite easy.

Step 0: The prerequisites

At this point you should already have installed Java and set up IntelliJ.

You must be connected to the internet the first time you build the project.  After the first build, you can build in offline mode.

Also, you should obtain and run the WPILib one-step installer (available at the beginning of the 2019 season).  Even if you’ll be developing with InteliJ,  you’re likely to need the tools and project templates included in this package.

Step 1: Create a new Robot Project

There are currently several ways to create a basic WPILib robot project:

  • Create a new robot project with VS Code using the WPILib extension.
  • Create a project with RobotBuilder.  You will need a recent version of RobotBuilder that generates GradleRIO projects.  The 2019 software release will contain all new tools, including RobotBuilder.  Or, you can build the latest version from GitHub.
    RobotBuilder creates a full command-based project, so it may be more complicated than what is described in Step 3 below here.
  • Uncompress a copy of the Quickstart.zip file and make a copy of the “java” directory.  Rename the directory to whatever you like.

Now, whichever method you use, add an empty directory called “vendordeps”.   Your project should now look like this:

intellij_program_files

Inside IntelliJ, open the File menu and select File > Open.  Select your project root directory and hit the “OK” button. The “Import Gradle” dialog will appear. Hit “OK” again and open the project in the current window.   The “Gradle” tool window on the right side of the window should populate with all the GradleRIO tasks.

Third party dependencies

If you intend to use any third-party software, you will need to add some JSON files to the vendordeps directory.  The best way to get these files is to get the official installers.  Examples of third party packages include:

Picking a JDK

By default, your IntelliJ project will use your JAVA_HOME environment variable to determine where Java is stored on your laptop.

However, it is possible to have multiple Java Development Kits (JDKs) installed on your laptop.  If you need pick a specific JDK (which IntelliJ will refer to as an SDK), you should select File > Project Structure > Project  from the main menu.  From the Project Structure dialog you can configure a new SDK or configure a previously defined SDK.

Set your team number

Open the build.gradle file and modify the team number setting, probably around 17:

intellij_program_gradle.png

First Build

Open the Gradle tools window.  You should see your new project listed.  Open your project’s icon and select the “build” folder and then double-click on the “build” task.  This should successfully build your new FRC Java project.

Step 3: Program to control one motor

If you started your program in RobotBuilder, you may already have mostly complete program.  If you started with the Quickstart example, your program is mostly just in one Robot.java file.  We can augment the Quickstart example to control a motor.

Back in the Project tools window, double-click on that Robot.java file.  This is your main Java program for controlling the robot.

All of the code in Robot.java is useful, but for simple programs it is optional.  To keep this tutorial really simple, we’re going to delete everything except robotInit() and teleopPeriodic().   Also we’re going to tweak the “import” statements a bit:

intellij_program_edit

For this example, assume we have a joystick connected to drivers’ station and a motor controller connect to the roboRIO.  We’ll create two variables around line 7 to represent them:

Joystick stick;
SpeedController motor;

Now we’ll instantiate the objects inside the robotInit() method:

public void robotInit() {
    stick = new Joystick(0);
    motor = new WPI_TalonSRX(2);
}

For my example, I’m using a Talon SRX connected to CAN ID 2.  If you’re using any other motor controller, just change the line to reflect your hardware.

Next, change your teleop mode so it reads the joystick, and sets the speed of the motor:

public void teleopPeriodic() {
    double speed=stick.getY();
    motor.set(speed);
}

The getY() function tells us how for forward or backwards the joystick has been pushed.  The speed value will be a number between -1.0 and 1.0.

From the Gradle tools window you can now build or deploy the program.

 

Step 4:  Drive your robot

Start up the FRC Driver Station software.  You should see green bars next to “Communications”, “Robot Code”, and “Joysticks”.  Also, you should see your correct team number.  If the team number is wrong, click the Gear icon on the left side to get to the setup panel.

vsc_program_driver

Click the “Enable” button to initialize teleoperated mode.  You should now be able to drive the motor with the joystick.

Consider for a moment what’s going on with the code.  The robotInit() method was called once, and then teleopPeriodic() is being called 50 times a second.  Each call of teleopPeriodic() reads the joystick and passes that value into the motor.

Further Reading:

Tutorial

Installing IntelliJ

IntelliJ is a sophisticated professional development environment for Java.  It is produced by the Jetbrains company, which also produces Android Studio, CLion for C++ development, and PyCharm for Python.  Although IntelliJ is a commercial product, there is Community Edition that is free to use.

FRC robot programs will be built using GradleRIO, which can execute from inside any development environment, including IntelliJ.

Step 0:  Install prerequisites

You must install Java.  Even if you will be developing in C++, you’ll need Java installed to run IntelliJ and GradleRIO.  You should define your JAVA_HOME environment variable to point to your JDK installation.  Often setting JAVA_HOME is considered optional, but I have seen many strange situations resolved after this variable has been properly set.

You should obtain and run the WPILib one-step installer (available at the beginning of the 2019 season).  Even if you’ll be developing with IntelliJ,  you’re likely to need the tools and project templates included in this package.

It is highly recommended (though not strictly required) that you also install git, instructions for which are at: https://git-scm.com/ .

Step 1: Download and install IntelliJ

IntelliJ is available for Windows, Macintosh, and Linux.  Download the installer from https://www.jetbrains.com/idea/download.  Select download on the “Community” version.

intelij_install_0

Run the installer, if possible as the Administrator.  After installation, you’ll probably have to reboot your machine.

intelij_install_1

The first time you start IntelliJ, it will go through setup dialogs.  You can safely take all the default options.

IntelliJ is a big, sophisticated program, but it’s pretty user friendly.  Text editors will appear in the center of the window.  The tabs on the edges of the windows open up “tool windows” on the sides.

intellij_install_windowTo get you started:

  1. The Project tool window lists all the files in your project.  Double-click on them to pop up an editor.
  2. The Structure tool window summarizes the contents of the file you are editing. Double-click on anything to navigate to that item.
  3. The Gradle tool window lists all the tasks that can be executed when building your project.  Double-click to execute.

Step 2: Build a simple project

Creating  complete robot programs in IntelliJ is a lesson I’ll defer to another tutorial.  For now, you can download an existing project and verify that IntelliJ can build robot programs.

  1. If you start with the startup dialog, Select “Check out from Version Control” .  If you are already in the IntelliJ window, select File > New > Project from Version Control > Git.   Give the URL value of: https://github.com/firebears-frc/testrobot0.git and then hit the Clone button.
  2. Open the “Project” tool window on the left of the screen.  Expand the testrobot0 project to see all the files.  Right-click on the build.gradle file to get a popup menu.  Select the “Import Gradle project” item, which will likely be the last item on the list.
  3. Importing will take another minute.  After this “Gradle” tool window will become available on the right side of the window.
  4. When importing is done, you should also see testrobot0 in your Gradle tool window.  Open this item and then open “build”.  Double-click on the “assemble” task.  This should successfully compile the program.
  5. Under embeddedtools, double-click on the “deploy” task to deploy the program to your robot.  This will fail if you aren’t connected to a roboRIO.  But, no harm will have been done.

Further Reading:

Tutorial

Debugging: Shuffleboard

Shuffleboard is a customizable dashboard that provides amazing visibility into your robot. You can set up many graphical widgets on the Shuffleboard window, each of which displays information from the robot.  For instance you can create widgets for current motor speeds, air pressure, or the output of sensors.  The Shufflebord window can have multiple tabs to organize the widgets.

You can use Shuffleboard to provide real-time information while driving in a competition, but it’s also very useful while developing and testing your hardware and software.  It can definitely help you out when you’re trying to answer questions like “Are the actuators and sensors working correctly?” or “Why doesn’t it behave the same as it did yesterday?” or generally “What’s really going on inside the robot?”

There are at least three ways to start up this tool.  Within the driver station software, you can specify Shuffleboard as your “Dashboard Type”, which will cause shuffleboard to start up when you start the driver station.  You can also start it from the Visual Studio Code Command Palette with the command “WPILib: Start Tool”.  A third way to start it up is to start the program directly from the “tools” directory in your FRC install. (e.g. C:\Users\Public\frc2019\tools).

The default configuration for Shuffleboard has two tabs listed at the top:  SmartDashboard and Livewindow.

The SmartDashboard Tab

First, a bit of history:  SmartDashboard is a dashboard similar to Shuffleboard, but it is older and has fewer features.  Shuffleboard expands on the programming interface for this older program.   Many of the programming examples that come with WPILib will contain code that sets up widgets on SmartDashboard.  In the same way, these calls will create widgets in Shuffleboard.

The SmartDashboard tab is the default destination for custom widgets created within your robot program.

debug_shuffleboard_smartdashboard

The widgets you can add to this tab fall into two categories:  raw data and Sendable objects.

Raw Data Widgets

You can display numeric, boolean, or text values directly to Shuffleboard.

SmartDashboard.putNumber("Rangefinder dist", rangeFinder.getRangeInches());
SmartDashboard.putBoolean("Shooter", shooter.readyToFire());
SmartDashboard.putBoolean("Target visible", visionSystem.onTarget());
SmartDashboard.putString("Intake status", intake.getStatus());

The four widgets created above will not update automatically. You must put out the values again when you want them to change. You can update them in a periodic function, such as by putting the rangefinder line into your robot’s robotPeriodic() method or a subsystem’s periodic() method.  Or, you can change them as needed.  For instance, you might use a periodic method up update the rangefinder distance every 20 milliseconds.  On the other hand your Intake subsystem might contain code that updates its dashboard status when the status actually changes.

Once the widgets show up on the Shuffleboard you can modify their format type.  For instance, a boolean widget can be text or a colored box.  A number widget can be just text or it could be a dial or graph.

The graph format can be especially when debugging. Imagine two graphs representing two drivetrain motors; you could compare the two graphs when considering if the motors are getting correct signals.

debug_shuffleboard_graphs.png

In general, raw data lets you answer basic debugging questions about the status of robot components.

Sendable Data Widgets

Many WPILib objects implement the “Sendable” interface, which allow those objects to communicate over the Shuffleboard’s network table interface.  Most motors, actuators, and sensors are sendable.

SmartDashboard.putData(leftMotor);
SmartDashboard.putData(rangeFinder1);
SmartDashboard.putData(rangeFinder2);
SmartDashboard.putData(grabberSolenoid);

The magic of sendable objects is that they update their data automatically.  You only need to call the above lines once, such as in your robot’s robotInit() method or in a subsystem’s constructor.

The visual format of each sendable widget is specific to its object.  For instance, the motor’s widget is a number slider that tells the current output of the motor.  For some debugging scenarios, you might be better off sending the motor output as raw data, so you can view it as a graph.

Note that you can optionally add a name value to the widget:

SmartDashboard.putData("Forward rangefinder", rangeFinder1);
SmartDashboard.putData("Backwards rangefinder", rangeFinder2);

More objects are sendable than you might expect. For instance all Commands are sendable and create a button widget that lets you trigger the command.  Subsystems are sendable and create a widget that tells you which Commands are currently running on them.

SmartDashboard.putData(Robot.driveTrain);
SmartDashboard.putData("Fire Shooter", new FireShooterCommand());
SmartDashboard.putData("Turn 90 Degrees", new TurnCommand(90));
SmartDashboard.putData(Scheduler.getInstance());

 

The LiveWindow Tab

LiveWindow shows each subsystem and the child components within them.  In teleop and autonomous modes the LiveWindow shows what the components are doing. However, if you enable Test mode on the driver station, this tab will come alive and allow you to manipulate the components.  This allows you to test (and debug) robot components without writing any special code.  Does your newly installed motor really work?  Enable test mode and you can run it at any speed.

debug_shuffleboard_livewindow

LiveWindow comes for free; no code changes are necessary to create it.  However, to get the best value out of this tool, you should let the dashboard know which components are children of which subsystems.

If you are creating your components within the subsystem code,  you can designate the child status with addChild() calls.  Note that the first argument to addChild is the text name for the component.  If you do not give a name, the LiveWindow will give you default names like “Spark[3]”.

public DriveTrain() {
    leftMotor = new Spark(0);
    addChild("Left", leftMotor);
    leftMotor.setInverted(false);
    rightMotor = new Spark(1);
    addChild("Right", rightMotor);
    rightMotor.setInverted(false);
    rangeFinder = new Ultrasonic(2, 3);
    addChild("rangeFinder", rangeFinder);
}

If you are creating your components elsewhere, such as in a RobotMap class, you can still designate the child-relationship by giving the component a name and subsystem name:

intakeMotor=new Spark(3);
intakeMotor.setName("Intake", "intakeMotor");

Another absolutely genius feature of LiveWindow is that it lets you configure PID subsystems in real time.  Configuring PID without this feature involves a lot of trial and error, mixed with constant recompile cycles.  With LiveWindow you can dial it in in real time, and then copy the chosen parameters back into your code.

Setting up a Custom Debug Tab

The primary users for Shuffleboard are the robot drivers.  At the beginning of a match, the drivers will fire up their driver station and they will want to see only the widgets that assist them.  Programmers are secondary users of Shuffleboard, so we shouldn’t clutter up the main Shuffleboard screen with our diagnostic widgets.  For this reason, we may shift our widgets off to secondary tabs, or we may configure them to go away when we aren’t debugging.

You can write code that sets up new tabs and positions widgets within them.

Consider that the widgets described above look like long-term decisions.  You set them up and assume that you will always need them.  When debugging, we often create temporary code just for the purpose of answering certain questions.  You could create temporary code for widgets, but they may pop up and get in the way of your permanent widgets.  It would be nice to have a designated spot to put the temporary stuff.

A neat way to address this need is to programmatically create a “Debug” tab where all your temporary widgets.  Custom tabs can be created in your code:

ShuffleboardTab debugTab = Shuffleboard.getTab("Debug");

Widgets can then be added to the tab with a name (e.g. “Vision Dist”) and a default value.  The “withWidget” method declares the widget’s format type.  Number widgets can be of type “Number Bar”, “Number Slider”, “Graph”, “Voltage View” or “Text View”. Boolean widgets can be of type “Boolean Box”, “Toggle Button”, “Toggle Switch”, or “Text View”.  String widgets can only be “Text View”.

 NetworkTableEntry visionDistWidget = debugTab
        .add("Vision Dist", 0.0)
        .withWidget("Graph")
        .getEntry();

Values can then be set into the widgets like this:

visionDistWidget.setNumber(vision.getTargetDistance());

Values can be set throughout your code:

debug_shuffleboard_custom_code.png

Note that in this example, we set up all our widgets in the Robot class.  A better pattern might be to create the debugTab variable in the Robot class, but then create the debug widgets inside the subsystems and commands.

The above code will generate widgets on a custom tab on the Shuffleboard window:

debug_shuffleboard_custom_tab

 

Further Reading:

Tutorial

Debugging: print statements and logging

Debugging is the process of figuring out why software isn’t doing what it should, and then fixing it so it behaves better.

Computer programmers always spend more time debugging code than they do writing it in first place. It is important to build up your skills in debugging code, whether the problem is in your own software or in code written by others.

Strangely, there isn’t a lot of literature available on this subject, and practically no formal education on debugging software. There should be more study and more formal methodologies. At least, all programmers should know the general techniques used by others and should learn the tools that are available.

Formalizing the questions

Sometimes debugging is easy. You see the problem immediately, or after a minute’s thought. However, if the problem has taken more than a couple minute’s consideration, you should start to specify the questions you need answers to. Often it helps to actually write these questions.  Really.  Write down the questions as if you you’re posing them to some third party.

Typical questions are:

  • Exactly how do I reproduce this problem? What is the negative scenario (where the problem occurs) and what is a positive scenario (where there is no problem)
  • Where was the program executing when things went wrong? How far into the program did we get? In what routine did the problem occur, and what was the path to get to that routine?  Did the Command I’m working on even execute?
  • What is the state of the data at the time of the problem? What does the data look like in positive scenarios? Why isn’t my Command ever finished?  What are the input and output values on my PID controller?

Debugging with print statements

The oldest and most common tool for debugging is to put temporary “print” statements into the code. You can print out variable values so you know the state of the data. Sometimes you just print little messages telling you where the program was executing, so you get a better idea of where the problem occurred.

A print statement in Java looks like this:

System.out.println("motor speed is " + motor.get());

When this statement executes, the text will print out on VS Code’s RioLog window and also on the Driver Station’s console window.  The printed text will also be available in the Driver Station’s Log File Viewer.  For basic debugging, the console may prove more useful than the Log File Viewer.

Print statements help answer questions like “Did a specific routine even execute” or “How did the motor speed vary during autonomous”.

Exceptions and Stack traces

When something goes seriously wrong in a Java program, the program may communicate this to other parts of the program by “throwing an exception”.  When an exception occurs, the program breaks out of the routine it is running and passes the exception to the routine that called it.  The exception is then propagated up the calling stack until one of the routines can handle it.  Handling the exception is called “catching” the exception.

When an exception is thrown, the program often prints out a “stack trace” to the console, which will show where the error occurred.  There is an example stack trace:

ERROR 1 Unhandled exception: java.lang.NullPointerException org.firebears.betaTestRobot2.subsystems.Board.setMotor2(Board.java:82) 
Error at org.firebears.betaTestRobot2.subsystems.Board.setMotor2(Board.java:82): 
Unhandled exception: java.lang.NullPointerException 
  at org.firebears.betaTestRobot2.subsystems.Board.setMotor2(Board.java:82) 
  at org.firebears.betaTestRobot2.commands.AutonomousCommand.execute(AutonomousCommand.java:29) 
  at edu.wpi.first.wpilibj.command.Command.run(Command.java:292) 
  at edu.wpi.first.wpilibj.command.Scheduler.run(Scheduler.java:224) 
  at org.firebears.betaTestRobot2.Robot.autonomousPeriodic(Robot.java:118) 
  at edu.wpi.first.wpilibj.IterativeRobotBase.loopFunc(IterativeRobotBase.java:225) 
  at edu.wpi.first.wpilibj.TimedRobot.startCompetition(TimedRobot.java:81) 
  at edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:261) 
  at org.firebears.betaTestRobot2.Main.main(Main.java:20) 
Warning 1 Robots should not quit, but yours did! edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:272) 
Warning at edu.wpi.first.wpilibj.RobotBase.startRobot(RobotBase.java:272): Robots should not quit, but yours did! 
ERROR 1 The startCompetition() method (or methods called by it) should have handled the exception above.

The above stack trace shows that an unexpected null value was encountered when the AutonomousCommand tried to set a motor value.

A stack trace is kind of a good news / bad news situation.  On one hand, you have a serious problem that shuts down processing.  On the other hand, you know generally what went wrong and exactly where it happened.

Learn to read stack traces and use them in your debugging.  Understand what the exception types are and how to interpret the calling stack.

Logging

The print statements described above are temporary changes to the program.  You should delete them after they have served their purpose.

Logging is a more formal process of printing out program state and execution.  If you identify things you want to monitor, you can leave the logging statements in your code, and then selectively turn on the ones you want to print.  Logs let you answer questions like “How often did our pneumatics fire?” or “What command was running just before we experienced a brownout?” or “Which autonomous program executed and what happened during that command?”

You can log items of different levels of importance.  Java supports seven levels of log severity, in this order:

  • SEVERE – serious failures
  • WARNING – potential problems
  • INFO – informational messages
  • CONFIG – configuration change messages
  • FINE – detailed debugging and tracing messages
  • FINER – more detailed debugging messages
  • FINEST – highly detailed debugging messages

Before you can do any logging, you must first create a Logger variable in each Java class:

private final Logger logger = Logger.getLogger(this.getClass().getName());

To actually create logs, call methods on the logger variable:

logger.fine("vision target acquired: angle=" + a + " : dist=" + d);

logger.config("PID controller values: " + p + "," + i+ "," + d);

logger.info("starting AutonomousCommand3 : gameData=" + gameData);

logger.warning("Air pressure is only " + pressure + " psi");

If you catch and handle an exception, you may want to log the problem, even if you’ve taken care of it.  The proper form is to add the exception to the log statement.  The following code will log a warning message, accompanied by the stack trace:

} catch (IOException e) {
    logger.log(Level.WARNING, "Failed to open connection", e);
}

Make good decisions about what to log. Don’t get carried way. Too many logs might create performance problems. Too many logs make it harder to find what you’re interested in.

Logged data will show up on the driver station Console and also within the Log Viewer.  For long term monitoring, the Log Viewer becomes extremely useful.   The Log Viewer can be used to reconstruct what happened during match.  Having some well chosen log statements may help you reconstruct what commands executed at what times, and what were the critical values of air pressure, elevator height, or motor speed.

Configuring your Loggers

Logging can be reconfigured to print out different things for different scenarios. For instance, you may decide that only messages of level INFO or higher get printed.  Later you can easily switch the level down to FINE, which will cause all the CONFIG and FINE messages to also print.  You can also specify different levels for different Java packages.  For instance, you may want the default log level to be CONFIG, but the autonomous commands log at the FINE level.  These levels are easy to set and easy to change later.

To configure your logging, create a file called “logging.properties” in your “deploy” directory:

debug_log_config.png

Here’s the content of our sample file:

frc.robot.handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=ALL

frc.robot.level=CONFIG
frc.robot.commands.auto=FINE
frc.robot.subsystems.DriveTrain=FINEST

The first two lines just cause all messages in your project to be printed out to the console.  Line 4 sets the default logging level to CONFIG or higher for all loggers under the frc.robot package.  Line 5 causes all logging in the frc.robot.commands.auto package to log at the FINE level.  Line 6 sets the logging level of the DriveTrain subsystem to be FINEST.

To tell Java where your logging config file is, you must add one line to your build.gradle file.   You must add one jvmArg to the deploy / artifacts / frcJavaArtifact section:

frcJavaArtifact('frcJava') {
    targets << "roborio"
    jvmArgs = [ '-Djava.util.logging.config.file=/home/lvuser/deploy/logging.properties' ]
    // Debug can be overridden by command line, for use with VSCode
    debug = getDebugOrDefault(false)
}

Configuration specific behavior

Occasionally, you may need to do a little special processing that depends on the logging configuration.  Most of the time this is a bad idea, because logging should not change the behavior of your code.   On the rare occasions when this is necessary, you can detect the logging level as follows:

if (logger.isLoggable(Level.FINE)) {
    doubled=lidar.getDistance();
    logger.fine("Lidar distance = "+ d);
}

Further Reading: