Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into sysid-tuning
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/frc/robot/Constants.java
  • Loading branch information
admlvntv committed Sep 15, 2024
2 parents be9c57b + 1758e2a commit 6f5856e
Show file tree
Hide file tree
Showing 33 changed files with 819 additions and 511 deletions.
36 changes: 17 additions & 19 deletions Operator_Controls_Map_README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
| RPi Pico Pin | GP2040-CE | Button | Used For |
| ------------ | --------- | ------ | --------------------- |
| RPi Pico Pin | GP2040-CE | Button | Used For |
|-----------------|-----------|-----------|---------------------------------|
| **MODE SWITCH** |
| GP16 | S1 | BACK | Want cube(T)/cone(F)) |
| **MANUAL** |
| GP2 | UP | POV UP | Arm manual up |
| GP3 | DOWN | POV DOWN | Arm manual down |
| GP4 | RIGHT | POV RIGHT | Arm manual extend |
| GP5 | LEFT | POV LEFT | Arm manual retrack |
| **SCORE** |
| GP6 | B1 | A | Arm score low node |
| GP7 | B2 | B | Arm score mid-node |
| GP11 | B4 | Y | Arm score high node |
| **UTILITY** |
| GP10 | B3 | X | Arm home |
| GP18 | L3 | LS | Arm stop |
| GP19 | R3 | RS | Arm stop |
| **ACQUIRE** |
| GP13 | L1 | LB | Arm to shelf |
| GP12 | R1 | RB | Arm floor |
| GP16 | S1 | BACK | Solenoid Locked(f)/Unlocked(t) |
| **ARM** |
| GP2 | UP | POV UP | Arm manual up |
| GP3 | DOWN | POV DOWN | Arm manual down |
| **CLIMBER** |
| GP4 | RIGHT | POV RIGHT | Climber manual up |
| GP5 | LEFT | POV LEFT | Climber manual down |
| **SCORE** |
| GP6 | B1 | A | Spin Up Flywheel |
| GP7 | B2 | B | Expel intake and indexer |
| GP11 | B4 | Y | Spin Flywheel Backwards |
| **UTILITY** |
| GP10 | B3 | X | Arm home |
| GP13 | L1 | LB | Arm align to podium (hopefully) |
| GP12 | R1 | RB | Duck |
27 changes: 16 additions & 11 deletions src/main/java/frc/lib/io/vision/Vision.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.numbers.N1;
import edu.wpi.first.math.numbers.N3;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import frc.lib.utils.RobotOdometry;
import frc.lib.utils.TunableNumber;
import frc.robot.FieldConstants;
import java.util.List;
import java.util.Optional;
import org.littletonrobotics.junction.Logger;

Expand All @@ -38,7 +38,7 @@ 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 stdDevSlope = new TunableNumber("Vision/stdDevSlope", 0.50);
private final TunableNumber stdDevPower = new TunableNumber("Vision/stdDevPower", 2.0);
private final TunableNumber stdDevMultiTagFactor =
new TunableNumber("Vision/stdDevMultiTagFactor", 0.2);
Expand Down Expand Up @@ -98,16 +98,16 @@ public void periodic() {
Pose2d estimatedRobotPose2d = ios[i].estimatedRobotPose.toPose2d();

// only update the pose estimator if the vision subsystem is enabled
if (isEnabled) {
if (isEnabled && !DriverStation.isAutonomous()) {
// 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)
odometry.addVisionData(
List.of(
new RobotOdometry.TimestampedVisionUpdate(
ios[i].estimatedRobotPoseTimestamp,
estimatedRobotPose2d,
getStandardDeviations(i, estimatedRobotPose2d))));
odometry
.getPoseEstimator()
.addVisionMeasurement(
estimatedRobotPose2d,
ios[i].estimatedRobotPoseTimestamp,
getStandardDeviations(i, estimatedRobotPose2d));
isVisionUpdating = true;
}

Expand Down Expand Up @@ -171,7 +171,12 @@ public void enable(boolean enable) {
public boolean posesHaveConverged() {
for (int i = 0; i < visionIOs.length; i++) {
Pose3d robotPose = ios[i].estimatedRobotPose;
if (odometry.getLatestPose().minus(robotPose.toPose2d()).getTranslation().getNorm()
if (odometry
.getPoseEstimator()
.getEstimatedPosition()
.minus(robotPose.toPose2d())
.getTranslation()
.getNorm()
< poseDifferenceThreshold.get()) {
Logger.recordOutput("Vision/posesInLine", true);
return true;
Expand All @@ -188,7 +193,7 @@ 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(0.1, 0.1, 1);
Matrix<N3, N1> estStdDevs = VecBuilder.fill(1, 1, 0.5);
int[] tags = ios[index].estimatedRobotPoseTags;
int numTags = 0;
double avgDist = 0;
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/frc/lib/utils/AllianceFlipUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ private AllianceFlipUtil() {
throw new IllegalStateException("Utility class");
}

/** Negates a distance based on current alliance color. */
public static double applyRelative(double distance) {
if (shouldFlip()) {
return -distance;
} else {
return distance;
}
}

/** Flips an x coordinate to the correct side of the field based on the current alliance color. */
public static double apply(double xCoordinate) {
if (shouldFlip()) {
Expand Down
195 changes: 25 additions & 170 deletions src/main/java/frc/lib/utils/RobotOdometry.java
Original file line number Diff line number Diff line change
@@ -1,182 +1,37 @@
package frc.lib.utils;

import edu.wpi.first.math.Matrix;
import edu.wpi.first.math.Nat;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Twist2d;
import edu.wpi.first.math.numbers.N1;
import edu.wpi.first.math.numbers.N3;
import edu.wpi.first.wpilibj.Timer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.SwerveDriveKinematics;
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import frc.robot.subsystems.drive.Drive;

public class RobotOdometry {
private static final double historyLengthSecs = 0.3;

private Pose2d basePose = new Pose2d();
private Pose2d latestPose = new Pose2d();
private final NavigableMap<Double, PoseUpdate> updates = new TreeMap<>();
private final Matrix<N3, N1> q = new Matrix<>(Nat.N3(), Nat.N1());

private static RobotOdometry instance = null;

/**
* Initializes the RobotOdometry instance with the provided state standard deviations.
*
* @param stateStdDevs a Matrix representing the standard deviations of the robot state in terms
* of the x, y, and theta coordinates
*/
public RobotOdometry(Matrix<N3, N1> stateStdDevs) {
for (int i = 0; i < 3; ++i) {
q.set(i, 0, stateStdDevs.get(i, 0) * stateStdDevs.get(i, 0));
}
private static final RobotOdometry robotOdometry = new RobotOdometry();
private SwerveDrivePoseEstimator estimator;
private SwerveModulePosition[] defaultPositions =
new SwerveModulePosition[] {
new SwerveModulePosition(),
new SwerveModulePosition(),
new SwerveModulePosition(),
new SwerveModulePosition()
};

private RobotOdometry() {
estimator =
new SwerveDrivePoseEstimator(
new SwerveDriveKinematics(Drive.getModuleTranslations()),
new Rotation2d(),
defaultPositions,
new Pose2d());
}

public static RobotOdometry getInstance() {
if (instance == null) {
instance = new RobotOdometry(VecBuilder.fill(0.003, 0.003, 0.0002));
}
return instance;
}

/** Returns the latest robot pose based on drive and vision data. */
public Pose2d getLatestPose() {
return latestPose;
}

/** Resets the odometry to a known pose. */
public void resetPose(Pose2d pose) {
basePose = pose;
updates.clear();
update();
return robotOdometry;
}

/** Records a new drive movement. */
public void addDriveData(double timestamp, Twist2d twist) {
updates.put(timestamp, new PoseUpdate(twist, new ArrayList<>()));
update();
}

/** Records a new set of vision updates. */
public void addVisionData(List<TimestampedVisionUpdate> visionData) {
for (var timestampedVisionUpdate : visionData) {
var timestamp = timestampedVisionUpdate.timestamp();
var visionUpdate =
new VisionUpdate(timestampedVisionUpdate.pose(), timestampedVisionUpdate.stdDevs());

if (updates.containsKey(timestamp)) {
// There was already an update at this timestamp, add to it
var oldVisionUpdates = updates.get(timestamp).visionUpdates();
oldVisionUpdates.add(visionUpdate);
oldVisionUpdates.sort(VisionUpdate.compareDescStdDev);

} else {
// Insert a new update
var prevUpdate = updates.floorEntry(timestamp);
var nextUpdate = updates.ceilingEntry(timestamp);
if (prevUpdate == null || nextUpdate == null) {
// Outside the range of existing data
return;
}

// Create partial twists (prev -> vision, vision -> next)
var twist0 =
GeomUtils.multiplyTwist(
nextUpdate.getValue().twist(),
(timestamp - prevUpdate.getKey()) / (nextUpdate.getKey() - prevUpdate.getKey()));
var twist1 =
GeomUtils.multiplyTwist(
nextUpdate.getValue().twist(),
(nextUpdate.getKey() - timestamp) / (nextUpdate.getKey() - prevUpdate.getKey()));

// Add new pose updates
var newVisionUpdates = new ArrayList<VisionUpdate>();
newVisionUpdates.add(visionUpdate);
newVisionUpdates.sort(VisionUpdate.compareDescStdDev);
updates.put(timestamp, new PoseUpdate(twist0, newVisionUpdates));
updates.put(
nextUpdate.getKey(), new PoseUpdate(twist1, nextUpdate.getValue().visionUpdates()));
}
}

// Recalculate latest pose once
update();
public SwerveDrivePoseEstimator getPoseEstimator() {
return estimator;
}

/** Clears old data and calculates the latest pose. */
private void update() {
// Clear old data and update base pose
while (updates.size() > 1
&& updates.firstKey() < Timer.getFPGATimestamp() - historyLengthSecs) {
var update = updates.pollFirstEntry();
basePose = update.getValue().apply(basePose, q);
}

// Update latest pose
latestPose = basePose;
for (var updateEntry : updates.entrySet()) {
latestPose = updateEntry.getValue().apply(latestPose, q);
}
}

/**
* Represents a sequential update to a pose estimate, with a twist (drive movement) and list of
* vision updates.
*/
private record PoseUpdate(Twist2d twist, ArrayList<VisionUpdate> visionUpdates) {
public Pose2d apply(Pose2d lastPose, Matrix<N3, N1> q) {
// Apply drive twist
var pose = lastPose.exp(twist);

// Apply vision updates
for (var visionUpdate : visionUpdates) {
// Calculate Kalman gains based on std devs
// (https://github.com/wpilibsuite/allwpilib/blob/main/wpimath/src/main/java/edu/wpi/first/math/estimator/)
Matrix<N3, N3> visionK = new Matrix<>(Nat.N3(), Nat.N3());
var r = new double[3];
for (int i = 0; i < 3; ++i) {
r[i] = visionUpdate.stdDevs().get(i, 0) * visionUpdate.stdDevs().get(i, 0);
}
for (int row = 0; row < 3; ++row) {
if (q.get(row, 0) == 0.0) {
visionK.set(row, row, 0.0);
} else {
visionK.set(
row, row, q.get(row, 0) / (q.get(row, 0) + Math.sqrt(q.get(row, 0) * r[row])));
}
}

// Calculate twist between current and vision pose
var visionTwist = pose.log(visionUpdate.pose());

// Multiply by Kalman gain matrix
var twistMatrix =
visionK.times(VecBuilder.fill(visionTwist.dx, visionTwist.dy, visionTwist.dtheta));

// Apply twist
pose =
pose.exp(
new Twist2d(twistMatrix.get(0, 0), twistMatrix.get(1, 0), twistMatrix.get(2, 0)));
}

return pose;
}
}

/** Represents a single vision pose with associated standard deviations. */
public record VisionUpdate(Pose2d pose, Matrix<N3, N1> stdDevs) {
public static final Comparator<VisionUpdate> compareDescStdDev =
(VisionUpdate a, VisionUpdate b) -> {
return -Double.compare(
a.stdDevs().get(0, 0) + a.stdDevs().get(1, 0),
b.stdDevs().get(0, 0) + b.stdDevs().get(1, 0));
};
}

/** Represents a single vision pose with a timestamp and associated standard deviations. */
public record TimestampedVisionUpdate(double timestamp, Pose2d pose, Matrix<N3, N1> stdDevs) {}
}
4 changes: 3 additions & 1 deletion src/main/java/frc/robot/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ public static Mode getMode() {
}

public static final Map<RobotType, String> logFolders =
Map.of(RobotType.ROBOT_2023, "/media/sda1", RobotType.ROBOT_2024_COMP, "/media/sda1");
Map.of(
RobotType.ROBOT_2023, "/media/sda1",
RobotType.ROBOT_2024_COMP, "/media/sda1");

public enum RobotType {
ROBOT_2023,
Expand Down
13 changes: 3 additions & 10 deletions src/main/java/frc/robot/FieldConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,14 @@ public static final class Speaker {
public static final class Subwoofer {
public static final Pose2d ampFaceCorner =
new Pose2d(
Units.inchesToMeters(35.775),
Units.inchesToMeters(239.366),
Rotation2d.fromDegrees(-120));
Units.inchesToMeters(26.9), Units.inchesToMeters(260.15), Rotation2d.fromDegrees(64.6));

public static final Pose2d sourceFaceCorner =
new Pose2d(
Units.inchesToMeters(35.775),
Units.inchesToMeters(197.466),
Rotation2d.fromDegrees(120));
Units.inchesToMeters(24.2), Units.inchesToMeters(174.0), Rotation2d.fromDegrees(-61.4));

public static final Pose2d centerFace =
new Pose2d(
Units.inchesToMeters(35.775),
Units.inchesToMeters(218.416),
Rotation2d.fromDegrees(180));
new Pose2d(Units.inchesToMeters(49.775), Units.inchesToMeters(218.416), new Rotation2d());
}

public static final class Stage {
Expand Down
Loading

0 comments on commit 6f5856e

Please sign in to comment.