Logging
Telemetry publishes a number under a name. DataLogManager copies every published value and every console line into one file on disk. You start the recorder in Robot.java, log three signals from your mechanism, then open the file and read them back.
- The project from Coroutines 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 failure that lasts a tenth of a second is gone before anyone sees it. The file on disk is the only record. Two lines start the recorder. The rest of the lesson gives it something to record and then reads the file back.
Start the log once
Both calls go at the top of the Robot constructor, which is empty on the branch. Logging then runs from the first loop to the last.
import org.wpilib.driverstation.DriverStation;import org.wpilib.system.DataLogManager; public Robot() { DataLogManager.start(); DriverStation.startDataLog(DataLogManager.getLog()); // Always-on bindings, if you add any, go below.}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.
Those two lines are only for the file. Watching numbers live needs nothing: RobotBase registers a NetworkTables backend at /Telemetry before your Robot runs, so anything you log reaches the dashboard either way.
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 game piece goes through.
import static org.wpilib.units.Units.Rotations; import org.wpilib.telemetry.Telemetry;import org.wpilib.telemetry.TelemetryTable; private void record() { TelemetryTable table = Telemetry.getTable(getName()); table.log("PositionRot", getPosition().in(Rotations)); table.log("TargetRot", getTargetPosition().in(Rotations)); table.log("AppliedVolts", motor.getMotorVoltage().getValueAsDouble());}import static org.wpilib.units.Units.RotationsPerSecond; import org.wpilib.telemetry.Telemetry;import org.wpilib.telemetry.TelemetryTable; private void record() { TelemetryTable table = Telemetry.getTable(getName()); table.log("VelocityRPS", getVelocity().in(RotationsPerSecond)); table.log("TargetRPS", getTargetVelocity().in(RotationsPerSecond)); table.log("AppliedVolts", motor.getMotorVoltage().getValueAsDouble());}Telemetry.getTable(...) hands back the table for a name, making it on the first call and returning the same one after that. So there is nothing to build in the constructor and nothing to keep in a field. Calling it every loop is the intended use.
getName() is the armflywheel's own name, which Mechanism takes from the class unless you override it. That is what puts all three signals under ArmFlywheel/ without you spelling the prefix into three strings.
.in(Rotations) is where the unit gets decided. Both getters return a WPILib unit type rather than a bare number, and logging one means naming the unit you want it in. Say it here and say it again in the signal name, so the file and the code agree.
Nothing calls record yet. Register it once, as the last line of the ArmFlywheel constructor. The scheduler then runs it every loop while the robot has power. Add import org.wpilib.command3.Scheduler; with it.
public Arm() { // ... the pasted config, unchanged motor.getConfigurator().apply(talonFXCfg); Scheduler.getDefault().addPeriodic(() -> record());}public Flywheel() { // ... the pasted config, unchanged motor.getConfigurator().apply(talonFXCfg); Scheduler.getDefault().addPeriodic(() -> record());}Do not call record from inside a command instead. A command logs only while it runs, so the trace stops the moment a button comes up.
Signal names
Whoever opens the log at an event may not have written the code. The name in the tree is all 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. - Let the table do the grouping. Everything logged to the
Armtable arrives together in the viewer, next toFlywheelandDrivetrain. Do not write the prefix into the signal name as well. - One writer per fact. Two classes logging
PositionRotto the same table give you a trace that flickers between them, and no way to tell which is which. - 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 now, on a run whose answer you already know. Then the first log you open is not one you need in a hurry.
- 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 and expand
NT:/Telemetry/Arm. PutPositionRotandTargetRoton one graph, andAppliedVoltson a second. - Open it in AdvantageScope and expand
NT:/Telemetry/Flywheel. PutVelocityRPSandTargetRPSon one graph, andAppliedVoltson 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.
A trace that holds one value is not always a bug. Telemetry writes an entry only when the value changes, so an arm that is genuinely still records one sample and then nothing until it moves. The rest of the file tells you which you have. Every signal stopping at the same instant means the logging stopped. One flat signal among live ones means the thing it measures was flat.
Three things go wrong the first time, and they look like this.
- Nothing published
- The file exists and holds no
Telemetry/Armtable. Either the two constructor lines never ran, or theaddPeriodicline is missing. - Stale signal
- The trace freezes partway through and holds one value.
recordis called from a command that finished, not fromaddPeriodic. - 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
Telemetry/Flywheeltable. Either the two constructor lines never ran, or theaddPeriodicline is missing. - Stale signal
- The trace freezes partway through and holds one value.
recordis called from a command that finished, not fromaddPeriodic. - 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
- A
Telemetry/ArmFlywheeltable in the tree, with all three entries under it. TargetRotstepping to your target, andPositionRotcatching up to meet it.TargetRPSstepping to your target, andVelocityRPSclimbing to meet it.AppliedVoltslarge while the arm moves, small while it holds.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?