Skip to content

Commit

Permalink
back port from 2024-frc-software (#97)
Browse files Browse the repository at this point in the history
* update to WPILib 2024.3.1 and latests vendor deps
* changed default to field relative
* fix bug where driveFacingAngle translation was reversed on red alliance
* add ArmSystemSim class and VelocitySystemSim class to make it easier to incorporate system models with Phoenix 6 devices
* incorporate pose ambiguity into standard deviation calcualtion for estimated pose from vision
* ignore vision pose estimate if too far from current estimated position (this requires use of a command to reset the pose to the estiamted pose from vision)
* update FieldConstants and Field2d classes for Crescendo
* add lock to speaker button which keeps the robot pointed at its alliance speaker while driving
  • Loading branch information
gcschmit authored Feb 28, 2024
1 parent 79d4d2c commit da566af
Show file tree
Hide file tree
Showing 23 changed files with 499 additions and 91 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id 'com.diffplug.spotless' version '6.11.0'
id "com.peterabeles.gversion" version "1.10"
}
Expand Down
7 changes: 7 additions & 0 deletions simgui.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
"/SmartDashboard/SystemStatus/SwerveModule2/SystemCheck": "Command",
"/SmartDashboard/SystemStatus/SwerveModule3/SystemCheck": "Command",
"/SmartDashboard/VisionSystemSim-simCamera/Sim Field": "Field2d"
},
"windows": {
"/Shuffleboard/MAIN/SendableChooser[0]": {
"window": {
"visible": true
}
}
}
},
"NetworkTables Info": {
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/frc/lib/team3061/drivetrain/Drivetrain.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public Drivetrain(DrivetrainIO io) {

this.autoThetaController.enableContinuousInput(-Math.PI, Math.PI);

this.isFieldRelative = false;
this.isFieldRelative = true;

// based on testing we can drive in turbo mode all the time
this.isTurbo = true;
Expand Down Expand Up @@ -400,8 +400,12 @@ public void driveFacingAngle(
xVelocity *= slowModeMultiplier;
yVelocity *= slowModeMultiplier;
}

this.io.driveFieldRelativeFacingAngle(xVelocity, yVelocity, targetDirection, isOpenLoop);
int allianceMultiplier = this.alliance == Alliance.Blue ? 1 : -1;
this.io.driveFieldRelativeFacingAngle(
xVelocity * allianceMultiplier,
yVelocity * allianceMultiplier,
targetDirection,
isOpenLoop);
}

/**
Expand Down Expand Up @@ -737,7 +741,7 @@ private String getSwerveLocation(int swerveModuleNumber) {
}

/*
* Checks the swerve module to see if its velocity and rotation are moving as expected
* Checks the swerve module to see if its velocity and rotation are moving as expected
* @param swerveModuleNumber the swerve module number to check
*/

Expand All @@ -748,7 +752,7 @@ private void checkSwerveModule(
double velocityTarget,
double velocityTolerance) {

Boolean isOffset = false;
Boolean isOffset = false;

// Check to see if the direction is rotated properly
// steerAbsolutePositionDeg is a value that is between (-180, 180]
Expand All @@ -758,7 +762,7 @@ private void checkSwerveModule(
&& this.inputs.swerve[swerveModuleNumber].steerAbsolutePositionDeg
< angleTarget + angleTolerance) {
}

// check if angle is in threshold +- 180
else if (this.inputs.swerve[swerveModuleNumber].steerAbsolutePositionDeg
> angleTarget - angleTolerance - 180
Expand All @@ -783,8 +787,8 @@ else if (this.inputs.swerve[swerveModuleNumber].steerAbsolutePositionDeg
+ " is: "
+ inputs.swerve[swerveModuleNumber].steerAbsolutePositionDeg);
}
// Checks the velocity of the swerve module depending on if there is an offset

// Checks the velocity of the swerve module depending on if there is an offset

if (!isOffset) {
if (inputs.swerve[swerveModuleNumber].driveVelocityMetersPerSec
Expand All @@ -802,7 +806,7 @@ else if (this.inputs.swerve[swerveModuleNumber].steerAbsolutePositionDeg
+ " is: "
+ inputs.swerve[swerveModuleNumber].driveVelocityMetersPerSec);
}
} else { // if there is an offset, check the velocity in the opposite direction
} else { // if there is an offset, check the velocity in the opposite direction
if (inputs.swerve[swerveModuleNumber].driveVelocityMetersPerSec
< -(velocityTarget - velocityTolerance)
&& inputs.swerve[swerveModuleNumber].driveVelocityMetersPerSec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ public DrivetrainIOCTRE() {
driveFacingAngleThetaKi.get(),
driveFacingAngleThetaKd.get());
this.driveFacingAngleRequest.HeadingController.enableContinuousInput(0, Math.PI * 2);

// always define 0° (towards the red alliance) as "forward"; the Drivetrain subsystem handles
// the definition of forward based on the current alliance
this.driveFacingAngleRequest.ForwardReference = SwerveRequest.ForwardReference.RedAlliance;
this.driveFieldCentricRequest.ForwardReference = SwerveRequest.ForwardReference.RedAlliance;
}

@Override
Expand Down
125 changes: 125 additions & 0 deletions src/main/java/frc/lib/team3061/sim/ArmSystemSim.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package frc.lib.team3061.sim;

import com.ctre.phoenix6.hardware.CANcoder;
import com.ctre.phoenix6.hardware.TalonFX;
import com.ctre.phoenix6.sim.CANcoderSimState;
import com.ctre.phoenix6.sim.ChassisReference;
import com.ctre.phoenix6.sim.TalonFXSimState;
import edu.wpi.first.math.system.plant.DCMotor;
import edu.wpi.first.math.util.Units;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.simulation.SingleJointedArmSim;
import edu.wpi.first.wpilibj.smartdashboard.Mechanism2d;
import edu.wpi.first.wpilibj.smartdashboard.MechanismLigament2d;
import edu.wpi.first.wpilibj.smartdashboard.MechanismRoot2d;
import edu.wpi.first.wpilibj.util.Color;
import edu.wpi.first.wpilibj.util.Color8Bit;
import frc.robot.Constants;
import org.littletonrobotics.junction.Logger;

public class ArmSystemSim {

private TalonFX motor;
private CANcoder encoder;
private TalonFXSimState motorSimState;
private CANcoderSimState encoderSimState;
private SingleJointedArmSim systemSim;
private double sensorToMechanismRatio;
private double rotorToSensorRatio;
private String subsystemName;

// Create a Mechanism2d display of an Arm with a fixed ArmTower and moving Arm.
private Mechanism2d mech2d;
private MechanismRoot2d armPivot;
private MechanismLigament2d armTower;
private MechanismLigament2d arm;

public ArmSystemSim(
TalonFX motor,
CANcoder encoder,
boolean motorInverted,
double sensorToMechanismRatio,
double rotorToSensorRatio,
double length,
double mass,
double minAngle,
double maxAngle,
double startingAngle,
String subsystemName) {
if (Constants.getMode() != Constants.Mode.SIM) {
return;
}

this.motor = motor;
this.encoder = encoder;
this.sensorToMechanismRatio = sensorToMechanismRatio;
this.rotorToSensorRatio = rotorToSensorRatio;

this.motorSimState = this.motor.getSimState();
this.motorSimState.Orientation =
motorInverted
? ChassisReference.Clockwise_Positive
: ChassisReference.CounterClockwise_Positive;
this.encoderSimState = this.encoder.getSimState();
this.encoderSimState.Orientation =
motorInverted
? ChassisReference.Clockwise_Positive
: ChassisReference.CounterClockwise_Positive;

this.systemSim =
new SingleJointedArmSim(
DCMotor.getFalcon500Foc(1),
sensorToMechanismRatio * rotorToSensorRatio,
SingleJointedArmSim.estimateMOI(length, mass),
length,
minAngle,
maxAngle,
true,
startingAngle);

this.mech2d = new Mechanism2d(1, 1);
this.armPivot = mech2d.getRoot("ArmPivot", 0.5, 0.5);
this.armTower = armPivot.append(new MechanismLigament2d("ArmTower", .3, -90));
this.arm =
armPivot.append(
new MechanismLigament2d("Arm", length, startingAngle, 6, new Color8Bit(Color.kYellow)));
this.armTower.setColor(new Color8Bit(Color.kBlue));

this.subsystemName = subsystemName;
}

public void updateSim() {
if (Constants.getMode() != Constants.Mode.SIM) {
return;
}

// update the sim states supply voltage based on the simulated battery
this.motorSimState.setSupplyVoltage(RobotController.getBatteryVoltage());
this.encoderSimState.setSupplyVoltage(RobotController.getBatteryVoltage());

// update the input voltages of the models based on the outputs of the simulated TalonFXs
double motorVoltage = this.motorSimState.getMotorVoltage();
this.systemSim.setInput(motorVoltage);

// update the models
this.systemSim.update(Constants.LOOP_PERIOD_SECS);

// update the simulated TalonFX based on the model outputs
double mechanismRadians = this.systemSim.getAngleRads();
double mechanismRotations = mechanismRadians / (2 * Math.PI);
double sensorRotations = mechanismRotations * this.sensorToMechanismRatio;
double motorRotations = sensorRotations * this.rotorToSensorRatio;
double mechanismRadiansPerSec = this.systemSim.getVelocityRadPerSec();
double mechanismRPS = mechanismRadiansPerSec / (2 * Math.PI);
double encoderRPS = mechanismRPS * this.sensorToMechanismRatio;
double motorRPS = encoderRPS * this.rotorToSensorRatio;
this.motorSimState.setRawRotorPosition(motorRotations);
this.motorSimState.setRotorVelocity(motorRPS);
this.encoderSimState.setRawPosition(sensorRotations);
this.encoderSimState.setVelocity(encoderRPS);

// Update the Mechanism Arm angle based on the simulated arm angle
arm.setAngle(Units.radiansToDegrees(mechanismRadians));
Logger.recordOutput(subsystemName + "/ArmSim", mech2d);
}
}
58 changes: 58 additions & 0 deletions src/main/java/frc/lib/team3061/sim/VelocitySystemSim.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package frc.lib.team3061.sim;

import com.ctre.phoenix6.hardware.TalonFX;
import com.ctre.phoenix6.sim.ChassisReference;
import com.ctre.phoenix6.sim.TalonFXSimState;
import edu.wpi.first.math.numbers.N1;
import edu.wpi.first.math.system.plant.LinearSystemId;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.simulation.LinearSystemSim;
import frc.robot.Constants;

public class VelocitySystemSim {

private TalonFX motor;
private TalonFXSimState motorSimState;
private LinearSystemSim<N1, N1, N1> systemSim;
private double gearRatio;

public VelocitySystemSim(
TalonFX motor, boolean motorInverted, double kV, double kA, double gearRatio) {
this.motor = motor;
this.gearRatio = gearRatio;

if (Constants.getMode() != Constants.Mode.SIM) {
return;
}

this.motorSimState = this.motor.getSimState();
this.motorSimState.Orientation =
motorInverted
? ChassisReference.Clockwise_Positive
: ChassisReference.CounterClockwise_Positive;

this.systemSim = new LinearSystemSim<>(LinearSystemId.identifyVelocitySystem(kV, kA));
}

public void updateSim() {
if (Constants.getMode() != Constants.Mode.SIM) {
return;
}

// update the sim states supply voltage based on the simulated battery
this.motorSimState.setSupplyVoltage(RobotController.getBatteryVoltage());

// update the input voltages of the models based on the outputs of the simulated TalonFXs
this.systemSim.setInput(this.motorSimState.getMotorVoltage());

// update the models
this.systemSim.update(Constants.LOOP_PERIOD_SECS);

// update the simulated TalonFX based on the model outputs
double mechanismRadiansPerSec = this.systemSim.getOutput(0);
double motorRPS = mechanismRadiansPerSec * this.gearRatio / (2 * Math.PI);
double motorRotations = motorRPS * Constants.LOOP_PERIOD_SECS;
this.motorSimState.addRotorPosition(motorRotations);
this.motorSimState.setRotorVelocity(motorRPS);
}
}
36 changes: 26 additions & 10 deletions src/main/java/frc/lib/team3061/vision/Vision.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@ public class Vision extends SubsystemBase {
private RobotOdometry odometry;
private final TunableNumber poseDifferenceThreshold =
new TunableNumber("Vision/VisionPoseThreshold", POSE_DIFFERENCE_THRESHOLD_METERS);
private final TunableNumber stdDevSlope = new TunableNumber("Vision/stdDevSlope", 0.10);
private final TunableNumber stdDevPower = new TunableNumber("Vision/stdDevPower", 2.0);
private final TunableNumber stdDevSlopeDistance =
new TunableNumber("Vision/StdDevSlopeDistance", 0.10);
private final TunableNumber stdDevPowerDistance =
new TunableNumber("Vision/stdDevPowerDistance", 2.0);
private final TunableNumber stdDevMultiTagFactor =
new TunableNumber("Vision/stdDevMultiTagFactor", 0.2);
private final TunableNumber stdDevFactorAmbiguity =
new TunableNumber("Vision/StdDevSlopeFactorAmbiguity", 1.0);

/**
* Create a new Vision subsystem. The number of VisionIO objects passed to the constructor must
Expand Down Expand Up @@ -148,15 +152,19 @@ private void processNewVisionData(int i) {
RobotConfig.getInstance().getRobotToCameraTransforms()[i].inverse());
Pose2d estimatedRobotPose2d = estimatedRobotPose3d.toPose2d();

// only update the pose estimator if the vision subsystem is enabled
if (isEnabled) {
// only update the pose estimator if the vision subsystem is enabled and vision's estimated
// pose is within the specified tolerance of the current pose
if (isEnabled
&& estimatedRobotPose2d
.getTranslation()
.getDistance(odometry.getEstimatedPosition().getTranslation())
< MAX_POSE_DIFFERENCE_METERS) {
// when updating the pose estimator, specify standard deviations based on the distance
// from the robot to the AprilTag (the greater the distance, the less confident we are
// in the measurement)
Matrix<N3, N1> stdDev = getStandardDeviations(i, estimatedRobotPose2d, ios[i].minAmbiguity);
odometry.addVisionMeasurement(
estimatedRobotPose2d,
ios[i].estimatedCameraPoseTimestamp,
getStandardDeviations(i, estimatedRobotPose2d));
estimatedRobotPose2d, ios[i].estimatedCameraPoseTimestamp, stdDev);
isVisionUpdating = true;
}

Expand Down Expand Up @@ -241,8 +249,11 @@ public boolean posesHaveConverged() {
*
* @param estimatedPose The estimated pose to guess standard deviations for.
*/
private Matrix<N3, N1> getStandardDeviations(int index, Pose2d estimatedPose) {
Matrix<N3, N1> estStdDevs = VecBuilder.fill(1, 1, 2);
private Matrix<N3, N1> getStandardDeviations(
int index, Pose2d estimatedPose, double minAmbiguity) {
// The gyro is very accurate; so, rely on the vision pose estimation primarily for x and y
// position and not for rotation.
Matrix<N3, N1> estStdDevs = VecBuilder.fill(1, 1, 10);
int numTags = 0;
double avgDist = 0;
for (int tagID = 0; tagID < ios[index].tagsSeen.length; tagID++) {
Expand All @@ -264,9 +275,14 @@ private Matrix<N3, N1> getStandardDeviations(int index, Pose2d estimatedPose) {
if (numTags == 1 && avgDist > MAX_DISTANCE_TO_TARGET_METERS) {
estStdDevs = VecBuilder.fill(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
} else {
estStdDevs = estStdDevs.times(stdDevSlope.get() * (Math.pow(avgDist, stdDevPower.get())));
estStdDevs =
estStdDevs.times(
stdDevSlopeDistance.get() * (Math.pow(avgDist, stdDevPowerDistance.get())));
}

// Adjust standard deviations based on the ambiguity of the pose
estStdDevs = estStdDevs.times(stdDevFactorAmbiguity.get() * minAmbiguity / MAXIMUM_AMBIGUITY);

return estStdDevs;
}
}
2 changes: 1 addition & 1 deletion src/main/java/frc/lib/team3061/vision/VisionConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ private VisionConstants() {
// the maximum distance between the robot's pose derived from the target and the current robot's
// estimated pose for the target to be used to update the robot's pose (essentially, always use a
// valid target to update the robot's pose)
public static final double MAX_POSE_DIFFERENCE_METERS = 1000.0;
public static final double MAX_POSE_DIFFERENCE_METERS = 2.0;

// the maximum distance between the robot and the target, for the target to be used to update the
// robot's pose
Expand Down
1 change: 1 addition & 0 deletions src/main/java/frc/lib/team3061/vision/VisionIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static class VisionIOInputs {
double estimatedCameraPoseTimestamp = 0.0;
boolean[] tagsSeen = new boolean[] {};
double lastCameraTimestamp = 0.0;
double minAmbiguity = 0.0;
}

/**
Expand Down
Loading

0 comments on commit da566af

Please sign in to comment.