Gray Matter
WorkshopLogging
WPILib 2027 is still in alpha: these pages change as the APIs settle.
LESSON 16

Logging

DataLogManager copies every NetworkTables value and every console line into one file on disk. You start it in Robot.java, publish three signals from the arm, then open the file and read them back. Drivetrain pose and swerve telemetry wait for Workshop 3.

13 minutes
You’ll need
  • The project from Deploy and Run running on the bench.
  • Robot.java and one mechanism class from the previous lessons.
  • AdvantageScope installed from Prerequisites.

A log is the only witness to a failure that lasted a tenth of a second. The robot stops, ten people offer a theory, and the file on disk is the one account anybody can check.

Two lines start the recorder. The rest of this lesson is about giving it something worth recording, and then proving you can get the file back and read it.

Start the log once

Both calls go at the top of the Robot constructor, ahead of the mechanisms. Anything that happens during startup then lands in the same file as the rest of the run.

Robot.java: logging starts once
import org.wpilib.driverstation.DriverStation;
import org.wpilib.system.DataLogManager;
 
public Robot() {
DataLogManager.start();
DriverStation.startDataLog(DataLogManager.getLog());
 
// Construct mechanisms and global bindings after logging is active.
}

DataLogManager.start() opens the file and captures NetworkTables values and console output. DriverStation.startDataLog adds what NetworkTables never sees: enabled state, robot mode, which OpMode is running, and joystick positions. Skip the second call and you get numbers with no way to tell whether the robot was enabled when they happened.

Leave logging on in every mode and every build. A special logging build, deployed after the match that went wrong, records the next failure instead of the one you are trying to explain.

Publish three signals

Three signals are enough for a first log, and they are read in pairs. Position against target says whether the arm arrived. Voltage next to either one says what the trip cost, and whether the motor was loaded the whole way.

Arm.java: three numbers worth keeping
import org.wpilib.networktables.DoublePublisher;
import org.wpilib.networktables.NetworkTableInstance;
 
private final DoublePublisher positionLog =
NetworkTableInstance.getDefault().getDoubleTopic("Arm/PositionRot").publish();
private final DoublePublisher targetLog =
NetworkTableInstance.getDefault().getDoubleTopic("Arm/TargetRot").publish();
private final DoublePublisher voltageLog =
NetworkTableInstance.getDefault().getDoubleTopic("Arm/AppliedVolts").publish();
 
private void record(double position, double target, double volts) {
positionLog.set(position);
targetLog.set(target);
voltageLog.set(volts);
}

The three publishers are fields, built once when the arm is built. Build one inside a loop and the code opens a fresh handle fifty times a second, closing none of them.

Call record from whatever already refreshes those values: the runRepeatedly(...) command that holds the target, or a background task added with Scheduler.getDefault().addPeriodic(...). The command publishes only while it runs. The background task publishes for as long as the robot has power, and neither one is a new loop of yours.

SignalUnitsQuestion it answers
Arm/PositionRotMechanism rotationsWhere the arm really is.
Arm/TargetRotMechanism rotationsWhere it was told to go.
Arm/AppliedVoltsVoltsHow hard it pushed to get there.

Signal names

The name is the whole interface to a log. Six weeks from now, at an event, someone who did not write this code will be reading it. The name in the tree is all the documentation they get.

  • Put the unit in the name. Arm/Position makes the reader guess. Arm/PositionRot can share a project with degrees and radians without a collision.
  • Group with a slash. Everything under Arm/ arrives together in the viewer, next to Flywheel/ and Drivetrain/.
  • One publisher per fact. Two classes publishing Arm/PositionRot give you a trace that flickers between two sources, with nothing to say which one you are reading.
  • Add a signal when you can name the question it answers. A hundred signals nobody plots is slower to search than twelve that get used.

Rename a signal later and the code still compiles. Every saved layout and every script that read the old name stops working. Spend the extra minute now.

Read the file back

Do this once, here, on a run whose answer you already know. The first log you ever open should not be one you need at eleven at night on an event floor.

  1. Start the program with ./gradlew simulateJava and enable the OpMode that moves the arm. Send it to a target, let it settle, then send it back.
  2. Disable, then stop the program, so the end of the file gets written out.
  3. Find the newest .wpilog. The program ran on your laptop, so the file is in the project's logs folder.
  4. Open it in AdvantageScope. Put Arm/PositionRot and Arm/TargetRot on one graph, and Arm/AppliedVolts on a second.
  5. Line the enabled interval up against the motion. Position should move only while enabled, and voltage should drop off once the arm arrives.
Watch out
Entries reach disk in batches, not one at a time. Kill the program while it is still enabled and the last second or two never gets written, which is usually the part you wanted. Disable, stop the program, and only then cut power.

Three things go wrong the first time, and they look like this.

Empty tree
Nothing published
The file exists and holds no Arm/ entries. Either the two constructor lines never ran, or record is never called from a loop.
Flat line
Stale signal
The trace freezes partway through and holds one value. The publishing code sits inside a command that finished, so nothing has called set since.
Wrong scale
Bad units
The shape looks right and the numbers are off by the gear ratio. Fix SensorToMechanismRatio on the motor, then log the run again.

Check your work

You are finished when a file on your own laptop can tell you what the arm did, with nobody in the room narrating it.

Check

You should see

  • An Arm/ group in the tree, with all three entries under it.
  • Arm/TargetRot stepping to your target, and Arm/PositionRot catching up to meet it.
  • Arm/AppliedVolts large while the arm moves, small while it holds.
  • The enabled interval covering every part that moves.

Keep the file. Swerve calibration in Workshop 3 pulls a wheel radius and four module angles out of logs that look like this one.

WPILib: On-robot telemetry recording

Check yourself

01

Where do the two logging calls belong?

02

What does DriverStation.startDataLog(DataLogManager.getLog()) add that DataLogManager.start() does not?

03

The arm knows its position. How does that number reach the .wpilog?

04

Why name the entry Arm/PositionRot rather than Arm/Position?

05

You ran the program with ./gradlew simulateJava. Where is the .wpilog?

06

Arm/PositionRot climbs, then freezes partway through the run and holds one value. What happened?

Pick an answer for each.