Skip to content

Tunable values

DogLog's tunable values feature leverages NetworkTables to stream strings, numbers, and booleans to your robot code, making it easy to tweak robot behavior without needing to redeploy code.

You can create a tunable value by calling DogLog.tunable() and providing a key for the value in NetworkTables, and a default value.

// Create a tunable double with a key of "Intake/Voltage" and a default value of 4.5
private final TunableDouble voltage = DogLog.tunable("Intake/Voltage", 4.5);

Creating a tunable value returns a WPILib tunable.

You can provide a unit when creating tunable numbers, which allows apps like AdvantageScope to better display the logged value. Pass a third argument containing a unit string, a Unit, or a Measure:

import static org.wpilib.units.Units.Inches;
import static org.wpilib.units.Units.RPM;
import org.wpilib.units.measure.AngularVelocity;
private final TunableDouble intakeVoltage =
DogLog.tunable("Intake/Voltage", 4.5, "V");
private final TunableDouble elevatorHeightOverride = DogLog.tunable("Elevator/HeightOverride", 0.0, Inches);
private final Tunable<AngularVelocity> shooterVelocity =
DogLog.tunable("Shooter/Velocity", RPM.of(2000));

DogLog writes unit metadata for tools that understand WPILib units. A measure tunable retains its specific measure type and is published in its base unit.

Once you've created a tunable value, you can read it from the returned WPILib tunable:

tunable.get();

Tunable values are great for speeding up development at home, but at competitions it's best to avoid relying on NetworkTables for core robot functionality. By default, DogLog will ignore tunable values from NetworkTables when connected to the FMS on a competition field.

You can customize this behavior with the ntTunables option in DogLogOptions.

When ntTunables is disabled, reads return the original default value and dashboard edits are discarded. Re-enabling tuning restores the most recent value accepted while tuning was enabled.

In addition to periodically polling the latest value of a tunable, you can also register a consumer function to be called whenever the value changes.

private final TunableDouble voltage = DogLog.tunable("Intake/Voltage", 4, newVoltage -> {
motor.setVoltage(newVoltage);
});

One callback can be registered per tunable value. When the value is changed, the provided function will be called with the updated value of the tunable.

Once a tunable value is created in code, you can change the value via NetworkTables.

Tunable values are published under the /Tunables/ table in NetworkTables. So, a tunable value with a key of Intake/Voltage is published at /Tunables/Intake/Voltage.

Here are some examples of how to change a tunable value using various popular FRC dashboards:

Here's a full example of how you can use tunable values to configure PID for a TalonFX without needing to redeploy code:

src/main/java/frc/robot/TunablePid.java
package frc.robot;
import com.ctre.phoenix6.configs.TalonFXConfiguration;
import com.ctre.phoenix6.hardware.TalonFX;
import dev.doglog.DogLog;
public class TunablePid {
public static void create(String key, TalonFX motor, TalonFXConfiguration defaultConfig) {
DogLog.tunable(key + "/kP", defaultConfig.Slot0.kP, newP ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKP(newP))
);
DogLog.tunable(key + "/kI", defaultConfig.Slot0.kI, newI ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKI(newI))
);
DogLog.tunable(key + "/kD", defaultConfig.Slot0.kD, newD ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKD(newD))
);
DogLog.tunable(key + "/kS", defaultConfig.Slot0.kS, newS ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKS(newS))
);
DogLog.tunable(key + "/kV", defaultConfig.Slot0.kV, newV ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKV(newV))
);
DogLog.tunable(key + "/kA", defaultConfig.Slot0.kA, newA ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKA(newA))
);
DogLog.tunable(key + "/kG", defaultConfig.Slot0.kG, newG ->
motor.getConfigurator().apply(defaultConfig.Slot0.withKG(newG))
);
}
private TunablePid() {}
}

You can use this TunablePid class like so:

TunablePid.create("Elevator", elevatorMotor, new TalonFXConfiguration());