Skip to content

Commit

Permalink
Merge pull request #32 from TitaniumTitans/Steggo-PhotonVision
Browse files Browse the repository at this point in the history
Implement subsystem for PhotonVision
  • Loading branch information
GearBoxFox authored Feb 9, 2024
2 parents e21835d + 09424c3 commit b3a74cd
Show file tree
Hide file tree
Showing 6 changed files with 608 additions and 2 deletions.
49 changes: 47 additions & 2 deletions src/main/java/frc/robot/subsystems/drive/DriveSubsystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.pathplanner.lib.util.HolonomicPathFollowerConfig;
import com.pathplanner.lib.util.PathPlannerLogging;
import com.pathplanner.lib.util.ReplanningConfig;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
Expand All @@ -29,14 +30,21 @@
import edu.wpi.first.math.util.Units;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import frc.robot.subsystems.drive.module.Module;
import frc.robot.subsystems.drive.module.ModuleIO;
import frc.robot.subsystems.vision.VisionSubsystem;
import frc.robot.util.LocalADStarAK;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;

import frc.robot.util.PoseEstimator;
import org.littletonrobotics.junction.AutoLogOutput;
import org.littletonrobotics.junction.Logger;

Expand All @@ -54,24 +62,48 @@ public class DriveSubsystem extends SubsystemBase {
private final Module[] modules = new Module[4]; // FL, FR, BL, BR

private final SwerveDriveKinematics kinematics = new SwerveDriveKinematics(getModuleTranslations());
private final PoseEstimator m_poseEstimator;
private Pose2d pose = new Pose2d();
private Rotation2d lastGyroRotation = new Rotation2d();

public DriveSubsystem(
private final VisionSubsystem[] m_cameras;

public DriveSubsystem (
GyroIO gyroIO,
ModuleIO flModuleIO,
ModuleIO frModuleIO,
ModuleIO blModuleIO,
ModuleIO brModuleIO) {
this (gyroIO,
flModuleIO,
frModuleIO,
blModuleIO,
brModuleIO,
new VisionSubsystem[]{});
}

public DriveSubsystem(
GyroIO gyroIO,
ModuleIO flModuleIO,
ModuleIO frModuleIO,
ModuleIO blModuleIO,
ModuleIO brModuleIO,
VisionSubsystem[] cameras) {
this.gyroIO = gyroIO;
modules[0] = new Module(flModuleIO);
modules[1] = new Module(frModuleIO);
modules[2] = new Module(blModuleIO);
modules[3] = new Module(brModuleIO);

m_cameras = cameras;
m_poseEstimator =
new PoseEstimator(VecBuilder.fill(Units.inchesToMeters(0.5),
Units.inchesToMeters(0.5),
Units.degreesToRadians(15)));

// Configure AutoBuilder for PathPlanner
AutoBuilder.configureHolonomic(
this::getPose,
this::getVisionPose,
this::setPose,
() -> kinematics.toChassisSpeeds(getModuleStates()),
this::runVelocity,
Expand Down Expand Up @@ -141,8 +173,15 @@ public void periodic() {
}
Logger.recordOutput("Odometry/Twist", twist);
// Apply the twist (change since last sample) to the current pose
m_poseEstimator.addDriveData(Timer.getFPGATimestamp(), twist);
pose = pose.exp(twist);
}
m_poseEstimator.addVisionData(
Arrays.stream(m_cameras)
.flatMap((VisionSubsystem camera) -> Stream.of(camera.getPose(m_poseEstimator.getLatestPose())))
.filter(Optional::isPresent)
.flatMap(update -> Stream.of(update.get()))
.toList());
}

/**
Expand Down Expand Up @@ -218,6 +257,12 @@ public Pose2d getPose() {
return pose;
}

/** Returns the robot pose with vision updates */
@AutoLogOutput(key = "Odometry/RobotVision")
public Pose2d getVisionPose() {
return m_poseEstimator.getLatestPose();
}

/** Returns the current odometry rotation. */
public Rotation2d getRotation() {
return pose.getRotation();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package frc.robot.subsystems.vision;

import org.littletonrobotics.junction.AutoLog;

@AutoLog
public class PhotonVisionIOInputs {
protected boolean camConnected = false;
protected double camLatency = 0.0;
protected boolean tagsFound = false;
protected int[] tagIds = new int[] {};
protected int numTagsFound = 0;

public boolean isCamConnected() {
return camConnected;
}

public void setCamConnected(boolean camConnected) {
this.camConnected = camConnected;
}

public double getCamLatency() {
return camLatency;
}

public void setCamLatency(double camLatency) {
this.camLatency = camLatency;
}

public int[] getTagIds() {
return tagIds;
}

public void setTagIds(int[] tagIds) {
this.tagIds = tagIds;
}

public int getNumTagsFound() {
return numTagsFound;
}

public void setNumTagsFound(int numTagsFound) {
this.numTagsFound = numTagsFound;
}

public boolean isTagsFound() {
return tagsFound;
}

public void setTagsFound(boolean tagsFound) {
this.tagsFound = tagsFound;
}
}
132 changes: 132 additions & 0 deletions src/main/java/frc/robot/subsystems/vision/VisionSubsystem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package frc.robot.subsystems.vision;


import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.math.Matrix;
import edu.wpi.first.math.Nat;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Transform3d;
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.math.util.Units;
import frc.robot.util.PoseEstimator;
import org.littletonrobotics.junction.Logger;
import org.photonvision.EstimatedRobotPose;
import org.photonvision.PhotonCamera;
import org.photonvision.PhotonPoseEstimator;
import org.photonvision.targeting.PhotonPipelineResult;

import java.io.IOException;
import java.util.Optional;

public class VisionSubsystem {

private final PhotonCamera m_camera;
private final Transform3d robotToCam;
private PhotonPoseEstimator m_photonPoseEstimator;
private AprilTagFieldLayout m_aprilTagFieldLayout;
private final String m_name;

private final PhotonVisionIOInputsAutoLogged inputs = new PhotonVisionIOInputsAutoLogged();

public void updateInputs(){
inputs.setCamConnected(m_camera.isConnected());
inputs.setCamLatency(m_camera.getLatestResult().getLatencyMillis());
inputs.setTagIds(getTargetIDs());
inputs.setNumTagsFound(m_camera.getLatestResult().targets.size());
inputs.setTagsFound(m_camera.getLatestResult().hasTargets());

Logger.processInputs("PhotonVision/" + m_name, inputs);
}

public VisionSubsystem(String camName, Transform3d camPose) {
m_camera = new PhotonCamera(camName);
m_name = camName;
robotToCam = camPose;
try {
m_aprilTagFieldLayout =AprilTagFieldLayout.loadFromResource(AprilTagFields.k2024Crescendo.m_resourceFile);
m_photonPoseEstimator = new PhotonPoseEstimator(
m_aprilTagFieldLayout,
PhotonPoseEstimator.PoseStrategy.CLOSEST_TO_REFERENCE_POSE,
m_camera,
robotToCam);
} catch (IOException e){
throw new IllegalStateException(e);
}
}

public Optional<PoseEstimator.TimestampedVisionUpdate> getPose(Pose2d prevEstimatedRobotPose) {

m_photonPoseEstimator.setReferencePose(prevEstimatedRobotPose);

PhotonPipelineResult camResult = m_camera.getLatestResult();
Optional<EstimatedRobotPose> opPose = m_photonPoseEstimator.update(camResult);

if (opPose.isEmpty()) {
return Optional.empty();
} else {
EstimatedRobotPose estPose = opPose.get();
return Optional.of(new PoseEstimator.TimestampedVisionUpdate(estPose.timestampSeconds,
estPose.estimatedPose.toPose2d(),
VecBuilder.fill(Units.inchesToMeters(0.5), Units.inchesToMeters(0.5), Units.degreesToRadians(15))));
}
}

public int[] getTargetIDs () {
PhotonPipelineResult targets = m_camera.getLatestResult();
int[] results;

if(targets.hasTargets()) {
results = new int[targets.getTargets().size()];

for (int i = 0; i < results.length; i++) {
results[i] = targets.getTargets().get(i).getFiducialId();
}

}
else {
results = new int[0];
}

return results;
}

public record VisionUpdate(Pose2d pose, Matrix<N3, N1> stdDevs, double timestamp) {
public Pose2d apply(Pose2d lastPose, Matrix<N3, N1> q) {
// Apply vision updates
// 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] = this.stdDevs().get(i, 0) * this.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 = lastPose.log(this.pose());

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

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

return lastPose;
}
}
}

Loading

0 comments on commit b3a74cd

Please sign in to comment.