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 your mechanism, then open the file and read them back.
- The project from Deploy and Run running on the bench.
- Robot.java and one mechanism class from the previous lessons.
- AdvantageScope installed from Prerequisites.
What mechanism are you working on?
The lesson below is written for the one you pick. Switch back any time to read it for the other.
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.
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.
Three signals are enough for a first log, and they are read in pairs. Velocity against target says whether the wheel is up to speed. Voltage next to either one says what the spin-up cost, and what it takes to hold that speed once a note goes through.
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);}import org.wpilib.networktables.DoublePublisher;import org.wpilib.networktables.NetworkTableInstance; private final DoublePublisher velocityLog = NetworkTableInstance.getDefault().getDoubleTopic("Flywheel/VelocityRPS").publish();private final DoublePublisher targetLog = NetworkTableInstance.getDefault().getDoubleTopic("Flywheel/TargetRPS").publish();private final DoublePublisher voltageLog = NetworkTableInstance.getDefault().getDoubleTopic("Flywheel/AppliedVolts").publish(); private void record(double velocity, double target, double volts) { velocityLog.set(velocity); targetLog.set(target); voltageLog.set(volts);}The three publishers are fields, built once when the armflywheel 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.
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/Positionmakes the reader guess.Arm/PositionRotcan share a project with degrees and radians without a collision. - Group with a slash. Everything under
Arm/arrives together in the viewer, next toFlywheel/andDrivetrain/. - One publisher per fact. Two classes publishing
Arm/PositionRotgive 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.
- Start the program with WPILib: Hardware Sim Robot Code and enable the OpMode that moves the arm. Send it to a target, let it settle, then send it back.
- Start the program with WPILib: Hardware Sim Robot Code and enable the OpMode that spins the flywheel. Take it to full, hold it there long enough to settle, then let it coast down.
- Disable, then stop the program, so the end of the file gets written out.
- Find the newest
.wpilog. The program ran on your laptop, so the file is in the project'slogsfolder. - Open it in AdvantageScope. Put
Arm/PositionRotandArm/TargetRoton one graph, andArm/AppliedVoltson a second. - Open it in AdvantageScope. Put
Flywheel/VelocityRPSandFlywheel/TargetRPSon one graph, andFlywheel/AppliedVoltson a second. - Line the enabled interval up against the motion. Position should move only while enabled, and voltage should drop off once the arm arrives.
- Line the enabled interval up against the motion. Velocity should climb only while enabled, and voltage should settle to a smaller steady number once the wheel is at speed.
Three things go wrong the first time, and they look like this.
- Nothing published
- The file exists and holds no
Arm/entries. Either the two constructor lines never ran, orrecordis never called from a loop. - Stale signal
- The trace freezes partway through and holds one value. The publishing code sits inside a command that finished, so nothing has called
setsince. - Bad units
- The shape looks right and the numbers are off by the gear ratio. Fix
SensorToMechanismRatioon the motor, then log the run again.
- Nothing published
- The file exists and holds no
Flywheel/entries. Either the two constructor lines never ran, orrecordis never called from a loop. - Stale signal
- The trace freezes partway through and holds one value. The publishing code sits inside a command that finished, so nothing has called
setsince. - Bad units
- The shape looks right and the numbers are off by the gear ratio. Fix
SensorToMechanismRatioon 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 armflywheel did, with nobody in the room narrating it.
You should see
- An
ArmFlywheel/group in the tree, with all three entries under it. Arm/TargetRotstepping to your target, andArm/PositionRotcatching up to meet it.Flywheel/TargetRPSstepping to your target, andFlywheel/VelocityRPSclimbing to meet it.Arm/AppliedVoltslarge while the arm moves, small while it holds.Flywheel/AppliedVoltslarge through the spin-up, smaller once the wheel is at speed.- The enabled interval covering every part that moves.
Check yourself
What does DriverStation.startDataLog(DataLogManager.getLog()) add that DataLogManager.start() does not?
The arm knows its position. How does that number reach the .wpilog?
The flywheel knows its speed. How does that number reach the .wpilog?
You ran the program with WPILib: Hardware Sim Robot Code. Where is the .wpilog?
Arm/PositionRot climbs, then freezes partway through the run and holds one value. What happened?
Flywheel/VelocityRPS climbs, then freezes partway through the run and holds one value. What happened?