Advantage Code and Java

We have our first attempt at swerve working but want to get the tuning better by using advantage scope. We however dont see the module states appearing on the left to drag and only see published values from our smart dashboard for each motor separately. We did notice the code listed on the advantage scope page
SwerveModuleState states = new SwerveModuleState {
new SwerveModuleState(),
new SwerveModuleState(),
new SwerveModuleState(),
new SwerveModuleState()
}

StructArrayPublisher publisher = NetworkTableInstance.getDefault()
.getStructArrayTopic(“MyStates”, SwerveModuleState.struct).publish();

periodic() {
publisher.set(states);
}

but are unsure where to put it in our code we have tried a few times where it seems logical (ie our void Log values and void periodic in our swerve module subsystem) but always get an error at the end of the first } that it wants a ; and if we put the publisher in the void periodic in our code we get an error that publisher cannot be resolved are we missing a library that has publisher in it or are we putting this in the wrong place?

can I see what the advantage scope looks like?

Here is what we see in advantage scope

I’m not sure why it is like that. Can I have your code? Is there a reason you do not use Logger.RecordOutput or @AutoLogOutput?

Not sure what that is. We are just starting out with a more advanced system as this is the first time we have moved beyond a very simple programming system with the bot. It is our first attempt with swerve and we have never needed loggers before.

This explains it. Try and read the whole AdvantageKit and Scope doc to get a full understanding. Can I have your code so that I can play around with it?

Trying to upload what my student has done to github but for some reason when we signed in to an account in vs code and tried it wpilib got deleted from the vs code and we have to reinstall it.

ok just going to paste what she has in the swerve module drive subsystem
package frc.robot.subsystems.Drive;

//import com.ctre.phoenix6.controls.DutyCycleOut;
//import com.ctre.phoenix6.controls.PositionVoltage;
//import com.ctre.phoenix6.controls.VelocityVoltage;
import com.revrobotics.RelativeEncoder;
import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.config.ClosedLoopConfig.FeedbackSensor;
import com.revrobotics.spark.config.SparkBaseConfig.IdleMode;
import com.revrobotics.spark.config.SparkMaxConfig;
import com.revrobotics.spark.SparkBase.ControlType;
import com.revrobotics.spark.SparkBase.PersistMode;
import com.revrobotics.spark.SparkBase.ResetMode;
import com.revrobotics.spark.SparkClosedLoopController;
import com.revrobotics.spark.SparkLowLevel.MotorType;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.controller.PIDController;
import edu.wpi.first.math.controller.SimpleMotorFeedforward;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
import edu.wpi.first.networktables.DoubleArrayPublisher;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.Publisher;
import edu.wpi.first.networktables.StructArrayPublisher;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import frc.lib.math.OnboardModuleState;
import frc.lib.util.LoggedTunableNumber;
import frc.lib.util.SwerveModuleConstants;
import frc.robot.Constants;
//import frc.robot.subsystems.Encoders.ModuleEncoder;
import frc.robot.subsystems.Encoders.ModuleEncoderThrifty;

public class SwerveModule {
/* Module details */
public int moduleNumber;

/* Motors */
private SparkMax angleMotor;
private SparkMax driveMotor;

/* Encoders and their values /
private RelativeEncoder driveEncoder;
private RelativeEncoder integratedAngleEncoder;
private ModuleEncoderThrifty angleEncoder;
private double lastAngle;
private double states;
/
Controllers */
public final SparkClosedLoopController driveController;
public final SparkClosedLoopController angleController;
public final double anglePID;
public final double driveSVA;
public final double drivePID;
public final PIDController drivePIDController;
public SimpleMotorFeedforward feedforward;

//private VelocityVoltage drVelocityVoltage;
//private DutyCycleOut drDutyCycleOut;
//private PositionVoltage angPositionVoltage;

public LoggedTunableNumber driveSpeed;

// For logging
private double driveSetpoint = 0f;
private double angleSetpoint = 0f;

public SwerveModule(int moduleNumber, SwerveModuleConstants moduleConstants) {
this.moduleNumber = moduleNumber;

this.anglePID = moduleConstants.anglePID;
this.driveSVA = moduleConstants.driveSVA;
this.drivePID = moduleConstants.drivePID;
this.drivePIDController = new PIDController(drivePID[0], drivePID[1], drivePID[2]);

/* Angle Encoder Config */
angleEncoder = new ModuleEncoderThrifty(moduleConstants.cancoderID);
angleEncoder.setOffset(moduleConstants.angleOffset);

/* Angle Motor Config */
angleMotor = new SparkMax(moduleConstants.angleMotorID, MotorType.kBrushless);
integratedAngleEncoder = angleMotor.getEncoder();
angleController = angleMotor.getClosedLoopController();
configAngleMotor();

/* Drive Motor Config */
driveMotor = new SparkMax(moduleConstants.driveMotorID, MotorType.kBrushless);
driveEncoder = driveMotor.getEncoder();
driveController = driveMotor.getClosedLoopController();
configDriveMotor();

lastAngle = getState().angle.getDegrees();

}

public void setDesiredState(SwerveModuleState desiredState) {

desiredState = OnboardModuleState.optimize(
    desiredState,
    getState().angle); // Custom optimize command, since default WPILib optimize assumes
// continuous controller which REV and CTRE are not

this.setSpeed(desiredState);
this.setAngle(desiredState);

}

public void resetToAbsolute() {
double integratedAngleEncoderPosition = this.integratedAngleEncoder.getPosition();
double absolutePosition = integratedAngleEncoderPosition - integratedAngleEncoderPosition % 360
+ angleEncoder.getAbsolutePosition().getDegrees();
integratedAngleEncoder.setPosition(absolutePosition);

// double absolutePosition = getAngle().getRotations() - angleOffset.getRotations();
//angleMotor.set(absolutePosition);
}

private void configAngleMotor() {
SparkMaxConfig config = new SparkMaxConfig();

// Set motor configurations
config
    .inverted(true)
    .idleMode(IdleMode.kBrake);

// Set encoder configurations
config.encoder
    .positionConversionFactor(Constants.SwerveConstants.angleConversionFactor) // Conversion factor for angle encoder
    .velocityConversionFactor(1.0); // Adjust as needed if velocity conversion is relevant

// Set PID configurations
config.closedLoop
    .feedbackSensor(FeedbackSensor.kPrimaryEncoder)
    .pid(anglePID[0], anglePID[1], anglePID[2]);

// Apply the configuration
angleMotor.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);

this.resetToAbsolute(); // Reset to absolute position as part of configuration

}

private void configDriveMotor() {
SparkMaxConfig config = new SparkMaxConfig();

// Set motor configurations
config
.idleMode(IdleMode.kCoast)
.inverted(moduleNumber == 1 || moduleNumber == 3); // Invert for specific modules

// Set encoder configurations
config.encoder
.positionConversionFactor(Constants.SwerveConstants.driveConversionPositionFactor)
.velocityConversionFactor(Constants.SwerveConstants.driveConversionVelocityFactor);

// Set PID configurations
config.closedLoop
.feedbackSensor(FeedbackSensor.kPrimaryEncoder)
.pid(1.0, 0.0, 0.0); // Example PID values

// Apply the configuration
driveMotor.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);

// Additional configurations and initializations
this.feedforward = new SimpleMotorFeedforward(driveSVA[0], driveSVA[1], driveSVA[2]);
driveEncoder.setPosition(0.0); // Reset encoder position
}

private void setSpeed(SwerveModuleState desiredState) {
driveSetpoint = desiredState.speedMetersPerSecond;
}

private void setAngle(SwerveModuleState desiredState) {
// Prevent rotating module if speed is less then 1%. Prevents jittering.
double angle = (Math.abs(desiredState.speedMetersPerSecond) <= (Constants.SwerveConstants.maxSpeed * 0.02))
? lastAngle
: desiredState.angle
.getDegrees();
angleSetpoint = angle;
angleController.setReference(angle, ControlType.kPosition);
lastAngle = angle;
}

public void logValues() {
SmartDashboard.putNumber(Constants.SwerveConstants.moduleNames[moduleNumber] + " Desired Speed", driveSetpoint);
SmartDashboard.putNumber(Constants.SwerveConstants.moduleNames[moduleNumber] + " Actual Speed", this.getSpeed());

SmartDashboard.putNumber(Constants.SwerveConstants.moduleNames[moduleNumber] + " Desired Angle",
    angleSetpoint % 360);
SmartDashboard.putNumber(Constants.SwerveConstants.moduleNames[moduleNumber] + " Actual Angle",
    angleEncoder.getAbsolutePosition().getDegrees());

SwerveModuleState states = new SwerveModuleState {
new SwerveModuleState(),
new SwerveModuleState(),
new SwerveModuleState(),
new SwerveModuleState()
};

StructArrayPublisher publisher = NetworkTableInstance.getDefault()
.getStructArrayTopic(“MyStates”, SwerveModuleState.struct).publish();

}

public void goToHome() {
Rotation2d angle = getAngle();
angleController.setReference(angle.getDegrees() - angle.getDegrees() % 360,
ControlType.kPosition);
lastAngle = angle.getDegrees() - angle.getDegrees() % 360;
}
private Rotation2d getAngle() {
return Rotation2d.fromDegrees(this.integratedAngleEncoder.getPosition());
}

public SwerveModuleState getState() {
return new SwerveModuleState(this.getSpeed(), this.getAngle());
}

public double getSpeed() {
return this.driveEncoder.getVelocity();
}

public double getDistance() {
return this.driveEncoder.getPosition();
}

public SwerveModulePosition getPosition() {
return new SwerveModulePosition(this.getDistance(), this.getAngle());
}

public SwerveModulePosition getRedPosition() {
return new SwerveModulePosition(this.getDistance(), Rotation2d.fromDegrees(-this.getAngle().getDegrees()));
}

public SparkMax getDriveMotor() {
return this.driveMotor;
}

public void periodic() {
var pidOutput = MathUtil.clamp(drivePIDController.calculate(getSpeed(),
driveSetpoint),
-Constants.SwerveConstants.maxSpeed,
Constants.SwerveConstants.maxSpeed);
var ffOutput = feedforward.calculate(driveSetpoint);

SmartDashboard.putNumber(Constants.SwerveConstants.moduleNames[moduleNumber] + " PID output", pidOutput);
SmartDashboard.putNumber(Constants.SwerveConstants.moduleNames[moduleNumber] + " FF output", ffOutput);

driveController.setReference((pidOutput / Constants.SwerveConstants.maxSpeed)
    * 12 + ffOutput,
    ControlType.kVoltage);
     Publisher.set(states);

}
}

what publisher is being used in periodic and who calls logValues()?

If you enclose your code in ``` it won’t be mangled by discourse and will be easier to read.

There are a few things worth clarifying here. First, AdvantageScope is compatible with several different libraries for logging data (such as swerve states). The AdvantageScope docs that you referenced in the original post suggest one of the simplest ways of logging data, which is exactly what you want in this case. @krish_parmar is referencing a different logging library called AdvantageKit, which is a much more advanced library for highly specialized use cases. It is not the right solution to this problem.

The example code in the AdvantageScope docs is condensed, but here are the pieces that you’ll want to implement in your specific case:

  1. The “publisher” is the object that defines the name you want to use for publishing the states to NetworkTables. This should be declared and instantiated as a member of a class, as shown below. It looks like the code you sent is a class for a single swerve module. Since you’re trying to publish the states of all of the classes, the publisher should be defined in a higher-level class (probably the same place you’ve defined your swerve kinematics).
public class Drive extends SubsystemBase { // High-level class controlling multiple modules
  private StructArrayPublisher<SwerveModuleState> publisher =
      NetworkTableInstance.getDefault().getStructArrayTopic("MyStates", SwerveModuleState.struct).publish();
}
  1. In some type of periodic method (possibly periodic for a WPILib subsystem or a custom method), you’ll use the publisher to send the current swerve states. Note that since you’re logging from a high-level class controlling multiple modules, you’ll need to read the module states from each module object. It looks like your SwerveModule class already includes a getState() method, which is exactly what you need. Here’s an example of what that code might look like:
public class Drive extends SubsystemBase {
  private StructArrayPublisher<SwerveModuleState> publisher = ...; // Same as above

  // This is an example, you might have this defined differently in your drive class
  private SwerveModule frontLeft = ...;
  private SwerveModule frontRight = ...;
  private SwerveModule backLeft = ...;
  private SwerveModule backRight = ...;

  public void periodic() {
    // Create an array of the states from each of the four module
    SwerveModuleState[] states = new SwerveModuleState[] {
      frontLeft.getState(), // "getState()" is already defined on your SwerveModule class
      frontRight.getState(),
      backLeft.getState(),
      backRight.getState()
    };

    // Send the states to NetworkTables using the publisher object
    publisher.set(states);
  }
}

Let me know if you have additional questions about how to set this up.

2 Likes

We have tried starting from a different direction by using the spark swerve template on the advantage kit site. We have gotten everything ok so far except that it uses encoders plugged into the sparks not the thrifty encoders we have. We have gotten it down to only a few errors but are having troubles switching it all over to the new encoders.
we followed the instructiosn at Spark Swerve Template | AdvantageKit but are stll getting errors. Here is the current state of the relevant code.
ModuleIOSpark.java
"// Copyright 2021-2025 FRC 6328
// Mechanical Advantage · GitHub
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// version 3 as published by the Free Software Foundation or
// available in the root directory of this project.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

package frc.robot.subsystems.drive;

import static frc.robot.subsystems.drive.DriveConstants.;
import static frc.robot.util.SparkUtil.
;

import com.revrobotics.RelativeEncoder;
import com.revrobotics.spark.ClosedLoopSlot;
import com.revrobotics.spark.SparkBase;
import com.revrobotics.spark.SparkBase.ControlType;
import com.revrobotics.spark.SparkBase.PersistMode;
import com.revrobotics.spark.SparkBase.ResetMode;
import com.revrobotics.spark.SparkClosedLoopController;
import com.revrobotics.spark.SparkClosedLoopController.ArbFFUnits;
import com.revrobotics.spark.SparkLowLevel.MotorType;
import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.config.ClosedLoopConfig.FeedbackSensor;
import com.revrobotics.spark.config.SparkBaseConfig.IdleMode;
import com.revrobotics.spark.config.SparkMaxConfig;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.filter.Debouncer;
import edu.wpi.first.math.geometry.Rotation2d;
import frc.robot.subsystems.Encoders.ModuleEncoderThrifty;
import java.util.Queue;
import java.util.function.DoubleSupplier;

/**

  • Module IO implementation for Spark Flex drive motor controller, Spark Max turn motor controller,
  • and duty cycle absolute encoder.
    */
    public class ModuleIOSpark implements ModuleIO {
    private final Rotation2d zeroRotation;

// Hardware objects
private final SparkBase driveSpark;
private final SparkBase turnSpark;
private final RelativeEncoder driveEncoder;
private final ModuleEncoderThrifty turnEncoder;

// Closed loop controllers
private final SparkClosedLoopController driveController;
private final SparkClosedLoopController turnController;

// Queue inputs from odometry thread
private final Queue timestampQueue;
private final Queue drivePositionQueue;
private final Queue turnPositionQueue;

// Connection debouncers
private final Debouncer driveConnectedDebounce = new Debouncer(0.5);
private final Debouncer turnConnectedDebounce = new Debouncer(0.5);

public ModuleIOSpark(int module) {
zeroRotation =
switch (module) {
case 0 → frontLeftZeroRotation;
case 1 → frontRightZeroRotation;
case 2 → backLeftZeroRotation;
case 3 → backRightZeroRotation;
default → new Rotation2d();
};
driveSpark =
new SparkMax(
switch (module) {
case 0 → frontLeftDriveCanId;
case 1 → frontRightDriveCanId;
case 2 → backLeftDriveCanId;
case 3 → backRightDriveCanId;
default → 0;
},
MotorType.kBrushless);
turnSpark =
new SparkMax(
switch (module) {
case 0 → frontLeftTurnCanId;
case 1 → frontRightTurnCanId;
case 2 → backLeftTurnCanId;
case 3 → backRightTurnCanId;
default → 0;
},
MotorType.kBrushless);
driveEncoder = driveSpark.getEncoder();
turnEncoder = turnSpark.getEncoder();
driveController = driveSpark.getClosedLoopController();
turnController = turnSpark.getClosedLoopController();

// Configure drive motor
var driveConfig = new SparkMaxConfig();
driveConfig
    .idleMode(IdleMode.kBrake)
    .smartCurrentLimit(driveMotorCurrentLimit)
    .voltageCompensation(12.0);
driveConfig
    .encoder
    .positionConversionFactor(driveEncoderPositionFactor)
    .velocityConversionFactor(driveEncoderVelocityFactor)
    .uvwMeasurementPeriod(10)
    .uvwAverageDepth(2);
driveConfig
    .closedLoop
    .feedbackSensor(FeedbackSensor.kPrimaryEncoder)
    .pidf(
        driveKp, 0.0,
        driveKd, 0.0);
driveConfig
    .signals
    .primaryEncoderPositionAlwaysOn(true)
    .primaryEncoderPositionPeriodMs((int) (1000.0 / odometryFrequency))
    .primaryEncoderVelocityAlwaysOn(true)
    .primaryEncoderVelocityPeriodMs(20)
    .appliedOutputPeriodMs(20)
    .busVoltagePeriodMs(20)
    .outputCurrentPeriodMs(20);
tryUntilOk(
    driveSpark,
    5,
    () ->
        driveSpark.configure(
            driveConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters));
tryUntilOk(driveSpark, 5, () -> driveEncoder.setPosition(0.0));

// Configure turn motor
var turnConfig = new SparkMaxConfig();
turnConfig
    .inverted(turnInverted)
    .idleMode(IdleMode.kBrake)
    .smartCurrentLimit(turnMotorCurrentLimit)
    .voltageCompensation(12.0);
turnConfig
    .encoder
    .positionConversionFactor(turnEncoderPositionFactor)
    .velocityConversionFactor(turnEncoderVelocityFactor)
    .uvwAverageDepth(2);
turnConfig
    .closedLoop
    .feedbackSensor(FeedbackSensor.kAbsoluteEncoder)
    .positionWrappingEnabled(true)
    .positionWrappingInputRange(turnPIDMinInput, turnPIDMaxInput)
    .pidf(turnKp, 0.0, turnKd, 0.0);
turnConfig
    .signals
    .primaryEncoderPositionAlwaysOn(true)
    .primaryEncoderPositionPeriodMs((int) (1000.0 / odometryFrequency))
    .primaryEncoderVelocityAlwaysOn(true)
    .primaryEncoderVelocityPeriodMs(20)
    .appliedOutputPeriodMs(20)
    .busVoltagePeriodMs(20)
    .outputCurrentPeriodMs(20);
tryUntilOk(
    turnSpark, 5, () -> turnEncoder.setPosition(turnSpark.getAbsolutePosition()));

// Create odometry queues
timestampQueue = SparkOdometryThread.getInstance().makeTimestampQueue();
drivePositionQueue =
    SparkOdometryThread.getInstance().registerSignal(driveSpark, driveEncoder::getPosition);
turnPositionQueue =
    SparkOdometryThread.getInstance().registerSignal(turnSpark, turnEncoder::getPosition);

}

@Override
public void updateInputs(ModuleIOInputs inputs) {
// Update drive inputs
sparkStickyFault = false;
ifOk(driveSpark, driveEncoder::getPosition, (value) → inputs.drivePositionRad = value);
ifOk(driveSpark, driveEncoder::getVelocity, (value) → inputs.driveVelocityRadPerSec = value);
ifOk(
driveSpark,
new DoubleSupplier {driveSpark::getAppliedOutput, driveSpark::getBusVoltage},
(values) → inputs.driveAppliedVolts = values[0] * values[1]);
ifOk(driveSpark, driveSpark::getOutputCurrent, (value) → inputs.driveCurrentAmps = value);
inputs.driveConnected = driveConnectedDebounce.calculate(!sparkStickyFault);

// Update turn inputs
sparkStickyFault = false;
ifOk(
    turnSpark,
    turnEncoder::getPosition,
    (value) -> inputs.turnPosition = new Rotation2d(value).minus(zeroRotation));
ifOk(turnSpark, turnEncoder::getVelocity, (value) -> inputs.turnVelocityRadPerSec = value);
ifOk(
    turnSpark,
    new DoubleSupplier[] {turnSpark::getAppliedOutput, turnSpark::getBusVoltage},
    (values) -> inputs.turnAppliedVolts = values[0] * values[1]);
ifOk(turnSpark, turnSpark::getOutputCurrent, (value) -> inputs.turnCurrentAmps = value);
inputs.turnConnected = turnConnectedDebounce.calculate(!sparkStickyFault);

// Update odometry inputs
inputs.odometryTimestamps =
    timestampQueue.stream().mapToDouble((Double value) -> value).toArray();
inputs.odometryDrivePositionsRad =
    drivePositionQueue.stream().mapToDouble((Double value) -> value).toArray();
inputs.odometryTurnPositions =
    turnPositionQueue.stream()
        .map((Double value) -> new Rotation2d(value).minus(zeroRotation))
        .toArray(Rotation2d[]::new);
timestampQueue.clear();
drivePositionQueue.clear();
turnPositionQueue.clear();

}

@Override
public void setDriveOpenLoop(double output) {
driveSpark.setVoltage(output);
}

@Override
public void setTurnOpenLoop(double output) {
turnSpark.setVoltage(output);
}

@Override
public void setDriveVelocity(double velocityRadPerSec) {
double ffVolts = driveKs * Math.signum(velocityRadPerSec) + driveKv * velocityRadPerSec;
driveController.setReference(
velocityRadPerSec,
ControlType.kVelocity,
ClosedLoopSlot.kSlot0,
ffVolts,
ArbFFUnits.kVoltage);
}

@Override
public void setTurnPosition(Rotation2d rotation) {
double setpoint =
MathUtil.inputModulus(
rotation.plus(zeroRotation).getRadians(), turnPIDMinInput, turnPIDMaxInput);
turnController.setReference(setpoint, ControlType.kPosition);
}
}
"

ModuleEncoderThrifty.java
"package frc.robot.subsystems.Encoders;

import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.wpilibj.AnalogInput;
import edu.wpi.first.wpilibj.RobotController;

public class ModuleEncoderThrifty implements ModuleEncoder {
private AnalogInput encoder;
private Rotation2d offset = new Rotation2d();

public ModuleEncoderThrifty(int id) {
encoder = new AnalogInput(id);
}

@Override
public void setOffset(Rotation2d offset) {
this.offset = offset;
}

private double getRawAbsolutePosition() {
return encoder.getAverageVoltage() / RobotController.getVoltage5V();
}

@Override
public Rotation2d getAbsolutePosition() {
double angle = Math.toRadians(360.0 * getRawAbsolutePosition() - offset.getDegrees());
if (angle < 0) {
angle = Math.PI * 2 + angle;
}
return Rotation2d.fromRadians(angle);
}
} "

Figured out the first part how to define
turnEncoder = turnSpark.getEncoder();
as
" turnEncoder =
new ModuleEncoderThrifty(
switch (module) {
case 0 → frontLeftTurnCanId;
case 1 → frontRightTurnCanId;
case 2 → backLeftTurnCanId;
case 3 → backRightTurnCanId;
default → 0;
}); "

Actually going to close this thread and start one specific to new problem