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

Conditions and Logging

Every mechanism command so far uses runRepeatedly, so it never ends. A routine needs another way to know when that command has finished, or should be considered finished. Then we log that answer, so you can see it come true.

Branchmech-4-ReadingState11 minutes
You’ll need
  • An arm or flywheel on Motion Magic, from Motion Magic in Code.
  • 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.

The arrival question

First, decide what tolerance counts as good enough. The arm is measured as an angle, so its tolerance is an angle too. WPILib's Angle type converts between rotations, degrees and radians for you. Degrees are the easiest of the three to picture, so we will make the tolerance one degree.

First, decide what tolerance counts as good enough. The flywheel is measured as a speed, so its tolerance is a speed too. WPILib's AngularVelocity type converts between rotations a second, RPM and radians a second for you. We will make the tolerance half a rotation a second.

This may take some tuning. Too tight and it never comes true, because the sensor jitters. Too loose and it comes true before the armflywheel is anywhere useful. For the same reason, never check that the reading equals the target exactly: it almost never will.

Next, add three getters. One says where the armflywheel is. One says where it is headed, which we read off the current request. The last says whether it has arrived.

Arm.java: the arrival check
public class Arm implements Mechanism {
// ... motor, encoder and positionOut, unchanged
 
// How close counts as "at target".
private final Angle tolerance = Degrees.of(1.0);
 
// ... constructor and commands, unchanged
 
/** True when the arm has reached its target angle. */
public boolean isAtTarget() {
return getPosition().isNear(getTargetPosition(), tolerance);
}
 
/** Current measured arm angle. */
public Angle getPosition() {
return encoder.getPosition().getValue();
}
 
/** Angle the arm is currently driving toward. */
public Angle getTargetPosition() {
return positionOut.getPositionMeasure();
}
 
// ... setPosition and stopMotor, unchanged
}
Flywheel.java: the arrival check
public class Flywheel implements Mechanism {
// ... motor and velocityOut, unchanged
 
// How close counts as "at target".
private final AngularVelocity tolerance = RotationsPerSecond.of(0.5);
 
// ... constructor and commands, unchanged
 
/** True when the flywheel has reached its target speed. */
public boolean isAtTarget() {
return getVelocity().isNear(getTargetVelocity(), tolerance);
}
 
/** Current measured flywheel speed. */
public AngularVelocity getVelocity() {
return motor.getVelocity().getValue();
}
 
/** Speed the flywheel is currently driving toward. */
public AngularVelocity getTargetVelocity() {
return velocityOut.getVelocityMeasure();
}
 
// ... setVelocity and stopMotor, unchanged
}

Start the log

Recording starts once, at the top of the Robot constructor, and runs from the first loop to the last.

Robot.java: start recording
import org.wpilib.driverstation.DriverStation;
import org.wpilib.system.DataLogManager;
 
public Robot() {
// Record every logged value, plus the driver station, to a .wpilog file.
DataLogManager.start();
DriverStation.startDataLog(DataLogManager.getLog());
}

DataLogManager.start() creates the log file and records every NetworkTables value into it. On the robot it records console output too. DataLogManager.getLog() hands back that same file, and DriverStation.startDataLog writes the driver station's values into it. Those are values that never appear in NetworkTables: whether the robot is enabled, the robot mode, which OpMode is running, and the joystick positions.

Log the mechanism

The armflywheel writes its own signals. Add TelemetryLoggable to the implements line, then write a logTo method that logs the three getters and the motor voltage.

Arm.java: log the arrival check
import org.wpilib.telemetry.TelemetryLoggable;
import org.wpilib.telemetry.TelemetryTable;
 
public class Arm implements Mechanism, TelemetryLoggable {
// ... everything above, unchanged
 
/** Write the arm's signals to the log. Robot calls this every loop. */
@Override
public void logTo(TelemetryTable table) {
table.log("Position", getPosition());
table.log("TargetPosition", getTargetPosition());
table.log("AtTarget", isAtTarget());
table.log("AppliedVoltage", motor.getMotorVoltage().getValue());
}
 
// ... setPosition and stopMotor, unchanged
}
Flywheel.java: log the arrival check
import org.wpilib.telemetry.TelemetryLoggable;
import org.wpilib.telemetry.TelemetryTable;
 
public class Flywheel implements Mechanism, TelemetryLoggable {
// ... everything above, unchanged
 
/** Write the flywheel's signals to the log. Robot calls this every loop. */
@Override
public void logTo(TelemetryTable table) {
table.log("Velocity", getVelocity());
table.log("TargetVelocity", getTargetVelocity());
table.log("AtTarget", isAtTarget());
table.log("AppliedVoltage", motor.getMotorVoltage().getValue());
}
 
// ... setVelocity and stopMotor, unchanged
}

A unit value logs in its SI unit, with the unit attached: radians for an angleradians per second for a speed, and volts for the voltage. You do not convert it here. AdvantageScope can convert it to rotations or degreesrotations per second or RPM when you graph it.

Nothing calls logTo yet. Robot does, once per mechanism, every loop, right after the scheduler runs.

Robot.java: log every mechanism every loop
import org.wpilib.telemetry.Telemetry;
 
@Override
public void robotPeriodic() {
Scheduler.getDefault().run();
Telemetry.log("Arm", arm);
Telemetry.log("Flywheel", flywheel);
}

Telemetry.log sees a TelemetryLoggable, makes a table with the name you gave it, and hands that table to logTo. Everything the armflywheel logs lands under Telemetry/ArmFlywheel. robotPeriodic runs in every mode, so the log keeps going whether or not a command is running.

Read the file back

  1. Start WPILib: Hardware Sim Robot Code, enable Teleoperated, and hold the left triggerright trigger until the armflywheel settles. Let go, then do it once more.
  2. Disable, then stop the program.
  3. Open the newest .wpilog from the project's logs folder in AdvantageScope. Do it before you start the program again: a file still named WPILIB_TBD_ is deleted on the next start.
  4. Expand NT:/Telemetry/Arm. Put Position and TargetPosition on one graph, and AtTarget and AppliedVoltage on a second.
  5. Expand NT:/Telemetry/Flywheel. Put Velocity and TargetVelocity on one graph, and AtTarget and AppliedVoltage on a second.
Check

You should see

  • TargetPosition stepping to your target, and Position catching up to meet it.
  • TargetVelocity stepping to your target, and Velocity climbing to meet it.
  • AtTarget turning true once the two meet, and false while the armflywheel is on its way.

A signal that holds one value is not always a bug. Telemetry writes an entry only when the value changes, so a armflywheel that is still records one sample and then nothing until it moves.

Watch out
Entries reach disk in batches. Stop the program while it is still enabled and the last of them never get written. Disable first.
WPILib: On-robot telemetry recording

Check yourself

Why does isAtTarget() compare against a tolerance instead of checking whether the reading equals the target?

Why does isAtTarget() live in the mechanism's own class instead of being written out wherever a routine needs it?

The log shows the arm settling 2 degrees short of its target, with a 1 degree tolerance. What do you change?

The log shows the flywheel settling at 73 rotations per second against a target of 75, with a 0.5 tolerance. What do you change?

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

You wrote logTo in your mechanism, but the .wpilog has no table for it. What is missing?

You ran the program with WPILib: Hardware Sim Robot Code. Where is the .wpilog?

Pick an answer for each.