Skip to content
Bradley Chumanov edited this page Jan 13, 2026 · 16 revisions

Contents


Basic Usage

BadgerLog is designed to use annotations to generate custom NetworkTables wrappers that support some additional functionality. Annotations are the preferred method of using BadgerLog.

Some Notes before starting.

  • Fields need to be initialized after the constructor
  • Fields can not be final
  • Fields can have any access modifier (public, private, protected)
  • Fields may be either instance, or static fields
  • BadgerLog.update must be called in a periodic method. (usually Robot.robotPeriodic)

Given this, it is relatively easy to start creating entries.

The base annotation needed to mark a field to have an entry created is the @Entry annotation

Example base usage

@Entry(EntryType.PUBLISHER)
public int basicInteger = 1;

Without specifying the key using the @Key annotation, the default is Enclosing Class/Field Name.

In this example, if the class the field was defined in was named ArmSubsystem then the final NetworkTables key would be ArmSubsystem/basicInteger

The / in the key for NetworkTables specifies that it should use the table within the BadgerLog table named ArmSubsystem and the entry named basicInteger.

It is also possible to annotate a publisher method with the following requirements.

  • The method returns a valid type
  • The method has no parameters

Example

@Entry(EntryType.PUBLISHER)
public int getSensorReading(){
    return 1; // the sensor value
}

This has the same key generation as the field annotation.

In this example, the publisher method is called every update loop and then the value is published to NetworkTables.


Types of Entries

There are 3 options to generate entries for NetworkTables. Publishers, Subscribers, and Sendables. They all have different specifications and do different things.

Publishers

Publisher entries take the value on the field and put it to NetworkTables. Changing the field's value in code, changes it on NetworkTables.
This can be used with any type of field except those with the value of Sendable

Subscribers

Subscriber entries take the value from NetworkTables and set the field's value to it. Changing the value on NetworkTables changes the field's value.

This can be used with any type of field except those with the value of Sendable and additionally, unlike publishers, cannot be used with the STRUCT StructType option.

Sendables

Sendable entries use the defined Sendable.init method to create associated entries and tables. Natively supports using both publisher and subscriber options.

This can be used with only the Sendable (or subclasses of it) field type.



Entry Configuration

BadgerLog allows for a high level of customization of each entry. This is done also through annotations, but are always optional and have a global default if missing.

Every (except @Key) annotation can be placed at the class or field level. The class level creates a 'default' configuration for the class. Field level annotations take priority over class level ones for configuration.

(@Key may not be used at the class level because it results in duplicate NetworkTables entries)

@Key

The @Key annotation allows for customization of the key used on NetworkTables.

Parameters: String - the key to use. (In the key portion of table/key on NetworkTables)
Changes: Key on NetworkTables

Usage Example

@Entry(EntryType.SUBSCRIBER)
@Key("AutoWaitTime")
public double waitTime = 0;

This creates a double entry named AutoWaitTime under the containing classes name.

Using @Key removes the standard generation of the key from the field name

The @Key annotation additionally allows for instance specific values, referencing an instance variable. It is in the format of {Field Name}.

Usage Example

@Entry(EntryType.SUBSCRIBER)
@Key("{descriptor}")
public double moduleP = 0;

private String descriptor;

public SwerveModule(/*Other parameters */, String descriptor) {
    // Other initialization code
    this.descriptor = descriptor;
}

@Table

The @Table annotation allows for customization of the table above the key used on NetworkTables.

Parameters: String - the table to use above the key. (In the table portion of table/key on NetworkTables)
Changes: Table on NetworkTables

Usage Example

@Entry(EntryType.SUBSCRIBER)
@Table("Auto")
public double waitTime = 0;

This creates a double entry under the Auto table named waitTime.

Using @Table removes the standard generation of the table from the containing class

Similar to @Key, the @Table annotation allows for instance specific keys. It is in the format of {Field Name}.

Usage Example

@Entry(EntryType.SUBSCRIBER)
@Table("Modules:{descriptor}")
public double moduleP = 0;

private String descriptor;

public SwerveModule(/*Other parameters */, String descriptor) {
    // Other initialization code
    this.descriptor = descriptor;
}

This would create a new entry for every instance of the SwerveModule class. The entry would be under the Modules: with the value of descriptor.

The {Field Name} must match the field name exactly as defined in the class.


@StructType

Structs are a NetworkTables format for publishing structured data.

The @StructType annotation allows for customization of how a Struct type entry is created. See Wrappers for an explanation of each type.

Parameters: StructType - the type of struct entry to use
Changes: How a struct entry is created

Usage Example

@Entry(EntryType.PUBLISHER)
@Struct(StructType.STRUCT)
public Rotation2d rotation2d = Rotation2d.fromDegrees(1306);

This would publish the struct as a value-struct, using the default NetworkTables implementation of the StructEntry.

This could be changed to StructOptions.SUB_TABLE for subtable publishing or StructOptions.MAPPING for mapping publishing.


@UnitConversion

The @UnitConversion annotation allows units to be converted to a different form of the same unit.

Parameters:

  • String - the unit to convert to
  • String converterId - (Optional) the id of the converter to use with a mapping. Defaults empty

Usage Example

@Entry(EntryType.PUBLISHER)
@UnitConversion("meters")
public Distance height = Inches.of(10);

This publishes a double entry of the height converted to meters. Subscribing works the same way but in reverse. The unit String can be any form of the unit, case-insensitive.

// These all work
@UnitConversion("m")
@UnitConversion("meter")
@UnitConversion("METERS")
@UnitConversion("M")

The @UnitConversion annotation also allows for itself to be placed multiple times on a field to specify the converters to use for a more complicated mapping. Examples include Pose2d, Pose3d, Twist2d, and Twist3d.

The converterId is always translation for distance types and rotation for rotation types. It is only ever used when there are more than one unit to convert at a time.

Usage Example

@Entry(EntryType.SUBSCRIBER)
@UnitConversion(value = "in", converterId = "translation")
@UnitConversion(value = "radian", converterId = "rotation")
public Pose2d robotPose = Pose2d.kZero;

This converts any distance to inches and any rotation to radians in the Pose2d


@AutoGenerateStruct

The @AutoGenerateStruct annotation allows a struct to be generated from a record or enum class. It is used as a marker for BadgerLog to use the generated struct instead of a mapping.

Parameters: boolean (Optional) whether to generate the struct or not. Defaults true Changes: Marks the field to have a struct auto generated for it

Usage Example

@Entry(EntryType.SUBSCRIBER)
@AutoGenerateStruct
public CustomRecord record = new CustomRecord("value", 3);

public record CustomRecord(String value, int count) {
}

This will automatically generate a struct for this, allowing it to be used with BadgerLog's struct publishing options.



BadgerLog Methods

BadgerLog provides a bunch of utility methods to allow for some unique interactions with NetworkTables.

putValue methods

The putValue method acts similarly to the EntryType.PUBLISHER for annotations. The only difference is that it doesn't automatically generate the configuration from annotations. Useful for one time logging of specific fields, but not recommended.

Usage Example

public void execute() {
    BadgerLog.putValue("MoveArmCommand/Current Draw", /* the current draw */);
    BadgerLog.putValue("MoveArmCommand/Arm Angle", /* the current angle */,
            new Configuration().withConverter("", UnitConversion.createConverter("degrees")));
}

getValue methods

The getValue method acts similarly to the EntryType.SUBSCRIBER for annotations. The only difference is that it doesn't automatically generate the configuration from annotations. Useful for one time retrieving of specific keys, but not recommended.

Usage Example

public void periodic() {
    int kG = BadgerLog.getValue("Arm/kG", /* the initial value */);
    int targetRotation = BadgerLog.getValue("Arm/Target Rotation", /* initial rotation */,
            new Configuration().withStructType(StructType.SUBTABLE));
}

putSendable method

The putSendable method acts similarly to the EntryType.SENDABLE for annotations. There is almost no difference in its use, except that it allows for publishing a Sendable within a method. putSendable should not be called for the same key twice.

Usage Example

private Field2d field2d = new Field2d();

public void initializeOdometry() {
    field2d.setRobotPose(/* pose data */);

    BadgerLog.putSendable("Field2d", field2d);
}

createSelectorFromEnum methods

The createSelectorFromEnum method allows an enum's constants to be published as a SendableChooser. It takes the name of each constant, and publishes it as a string.

By default, it will use the first constant defined in the Enum class, but can be specified with the method overload.

public Robot() {
    BadgerLog.createSelectorFromEnum("Robot/RobotModeSelector", RobotMode.class, RobotMode.AUTOMATIC,
            (value) -> /* on changed */
    );
}

public enum RobotMode {
    MANUAL,
    AUTOMATIC,
    DISABLED
}

createNetworkTablesButton methods

The createNetworkTablesButton method allows a Trigger to be bound to a boolean on NetworkTables. The createAutoResettingButton is similar, but resets back to false after being true for 0.25 seconds.

A special requirement of both methods: it cannot be called more than once with the same key

public Autos() {
    BadgerLog.createNetworkTablesButton("Auto/IgnoreWaitTime", CommandScheduler.getInstance().getDefaultButtonLoop())
            .onTrue(/* some command */);

    BadgerLog.createAutoResettingButton("Auto/ResetOdometry", CommandScheduler.getInstance().getDefaultButtonLoop())
            .onTrue(/* some command */);
}

removeNetworkTablesEntry method

The removeNetworkTablesEntry method allows for the unpublishing of an entry. It removes the entry completely from updating and NetworkTables.



Wrappers

BadgerLog contains wrappers around NetworkTables entries to enable more customization and features.

Normal Value

Defined by ValueEntry. Enables the use of the mapping system, allows the conversion of any type into a valid NetworkTables type.

Struct Value

Defined by StructValueEntry. Publishes the struct as is, using the NetworkTables struct implementation.

Does not work with Elastic (Support for structs doesn't exist).

Subtable

Defined by SubtableEntry. Generates every entry needed to create a struct-like view. Creates relative tables for each field defined in the struct.

Sendable

Defined by SendableEntry. Registers the Sendable with the SendableRegistry and updates it.



Common Issues

Entries not appearing on NetworkTables

Likely is a cause of some other issue, check the warnings in the console.

Configuration Invalidated

The configuration was invalidated. Caused by some other issue.

Key specified wrong

A key referencing an instance field was wrong. Check that the key matches the field's name exactly.

Final field

A field was final when it cannot be. Remove the final modifier

Null field

A field was not initialized after startup. Make sure the field is either initialized in the constructor or when the field is defined.

Struct generated wrong

The struct references an invalid type. Check the struct's schema.

Invalid schema

The schema referenced something that was not expected. Check the schema

Known issue with autogenerated structs for enums. They can only be published using the STRUCT publishing option

Converter has default id with multiple converters

The default converter should not be used with multiple converters. Make sure each converter is defined.

Converter has invalid unit type

The unit specified in the converter doesn't exist in any form. Change it to a known form of the unit.

Converter has wrong id

The unit converter doesn't match the id defined in the mapping. Only 2 used are translation and rotation.

Record references itself

Recursive records are not supported.

Mapping doesn't exist for a type

The type doesn't have an associated mapping with it. Either change the type, or create a mapping for it.

Struct value type doesn't allow subscribing

Known issue with the STRUCT type. Switch to the SUB_TABLE type for it to work.

Class cannot be cast to a specific type

Make sure the type of the field/method value matches the type you want.

Common mistake

// wrong (long type)
double value = BadgerLog.getValue("key", 0);

// correct (double type)
double value = BadgerLog.getValue("key", 0.0);
double value = BadgerLog.getValue("key", 0D);