From e9064e4906563a535b6d0722fad6ee3a84d6cd59 Mon Sep 17 00:00:00 2001
From: Danny Lin <75951607+dannynotsmart@users.noreply.github.com>
Date: Sat, 11 May 2024 01:21:58 -0400
Subject: [PATCH 01/14] updated Hydrogen
---
README.md | 16 +-
build.gradle | 2 +-
networktables.json | 4 +-
networktables.json.bck | 3 +
.../java/org/sciborgs1155/lib/FakePDH.java | 39 ++
.../org/sciborgs1155/lib/FaultLogger.java | 443 ++++++++++++++----
.../org/sciborgs1155/lib/InputStream.java | 61 ++-
.../java/org/sciborgs1155/lib/SparkUtils.java | 153 ++++--
.../java/org/sciborgs1155/lib/TalonUtils.java | 101 ++++
.../org/sciborgs1155/lib/TestingUtil.java | 45 +-
.../java/org/sciborgs1155/lib/Tuning.java | 74 +++
.../org/sciborgs1155/robot/Constants.java | 116 ++++-
.../java/org/sciborgs1155/robot/Robot.java | 122 +++--
.../sciborgs1155/robot/commands/Autos.java | 49 +-
.../org/sciborgs1155/robot/drive/Drive.java | 361 +++++++++-----
.../robot/drive/DriveConstants.java | 78 +--
.../sciborgs1155/robot/drive/FlexModule.java | 121 -----
.../org/sciborgs1155/robot/drive/GyroIO.java | 52 +-
.../sciborgs1155/robot/drive/ModuleIO.java | 14 +-
.../sciborgs1155/robot/drive/NavXGyro.java | 32 ++
.../org/sciborgs1155/robot/drive/NoGyro.java | 24 +
.../sciborgs1155/robot/drive/NoModule.java | 33 ++
.../sciborgs1155/robot/drive/SimModule.java | 15 +-
.../sciborgs1155/robot/drive/SparkModule.java | 143 ++++++
.../robot/drive/SwerveModule.java | 146 ++++--
.../sciborgs1155/robot/drive/TalonModule.java | 25 +-
.../org/sciborgs1155/robot/vision/Vision.java | 181 +++++++
.../robot/vision/VisionConstants.java | 64 +++
.../org/sciborgs1155/lib/InputStreamTest.java | 4 +-
.../org/sciborgs1155/robot/RobotTest.java | 4 +-
vendordeps/PathplannerLib.json | 6 +-
vendordeps/Phoenix6.json | 48 +-
vendordeps/REVLib.json | 10 +-
vendordeps/URCL.json | 12 +-
vendordeps/photonlib.json | 33 +-
35 files changed, 1984 insertions(+), 650 deletions(-)
create mode 100644 networktables.json.bck
create mode 100644 src/main/java/org/sciborgs1155/lib/FakePDH.java
create mode 100644 src/main/java/org/sciborgs1155/lib/TalonUtils.java
create mode 100644 src/main/java/org/sciborgs1155/lib/Tuning.java
delete mode 100644 src/main/java/org/sciborgs1155/robot/drive/FlexModule.java
create mode 100644 src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java
create mode 100644 src/main/java/org/sciborgs1155/robot/drive/NoGyro.java
create mode 100644 src/main/java/org/sciborgs1155/robot/drive/NoModule.java
create mode 100644 src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
create mode 100644 src/main/java/org/sciborgs1155/robot/vision/Vision.java
create mode 100644 src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
diff --git a/README.md b/README.md
index e9b0795..3a7141d 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,23 @@
# Hydrogen
-The SciBorgs' base repository.
-
-WIP
+The SciBorgs' base repository. It is a current work-in-progress.
## Structure
Our robot code is centered around [Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java)
+This project currently contains drive, vision, and autos. You are expected to add/modify code supporting existing files and new subsystems when using this template. Some of these files include but are not limited to:
+- **[Autos.java](src/main/java/org/sciborgs1155/robot/commands/Autos.java)** Add code for new subsystems in `configureAutos`, such as commands for `NamedCommands`
+- **[VisionConstants.java](src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java)** Add new `CameraConfig` objects representing cameras on the robot in the field `CAMERAS`. Also modify any camera configurations or constants if needed.
+- **[Constants.java](src/main/java/org/sciborgs1155/robot/Constants.java)** Modify the class `Field` to be updated for each year's game, and any other constants if needed.
+- **[Ports.java](src/main/java/org/sciborgs1155/robot/Ports.java)** Modify existing OI and drive ports, as well as adding new ports.
+- **[Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java)** A lot needs to be added/modified in this file, self-explanatory.
+
## Dependencies
- [WPILib](https://docs.wpilib.org/)
- [Spotless](https://github.com/diffplug/spotless/blob/main/plugin-gradle/README.md)
- [Monologue](https://github.com/shueja/Monologue)
- [REVLib](https://docs.revrobotics.com/sparkmax/software-resources/spark-max-api-information)
-- [PathPlannerLib](https://github.com/mjansen4857/pathplanner/wiki)
+- [PathPlannerLib](https://pathplanner.dev/home.html)
- [PhotonLib](https://docs.photonvision.org/en/latest/docs/programming/photonlib/adding-vendordep.html)
+- [Phoenix6](https://v6.docs.ctr-electronics.com/en/stable/)
+- [URCL](https://github.com/Mechanical-Advantage/URCL)
+- [NavX](https://pdocs.kauailabs.com/navx-mxp/software/roborio-libraries/java/)
diff --git a/build.gradle b/build.gradle
index 9eb3d2c..76471f2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
plugins {
id "java"
- id "edu.wpi.first.GradleRIO" version "2024.1.1"
+ id "edu.wpi.first.GradleRIO" version "2024.3.2"
id 'com.diffplug.spotless' version '6.23.3'
}
diff --git a/networktables.json b/networktables.json
index fe51488..41b42e6 100644
--- a/networktables.json
+++ b/networktables.json
@@ -1 +1,3 @@
-[]
+[
+
+]
diff --git a/networktables.json.bck b/networktables.json.bck
new file mode 100644
index 0000000..41b42e6
--- /dev/null
+++ b/networktables.json.bck
@@ -0,0 +1,3 @@
+[
+
+]
diff --git a/src/main/java/org/sciborgs1155/lib/FakePDH.java b/src/main/java/org/sciborgs1155/lib/FakePDH.java
new file mode 100644
index 0000000..b3a5899
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/lib/FakePDH.java
@@ -0,0 +1,39 @@
+package org.sciborgs1155.lib;
+
+import com.revrobotics.CANSparkBase;
+import edu.wpi.first.wpilibj.RobotController;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Our actual PDH can't connect to CAN, so this is a workaround to (poorly) estimate our total used
+ * current based on the stator currents of all our motors with the formula:
+ *
+ *
supply current = battery voltage * applied output * stator current
+ */
+public class FakePDH {
+ private static final List sparks = new ArrayList<>();
+ private static double statorCurrent;
+ private static double supplyCurrent;
+
+ public static void register(CANSparkBase spark) {
+ sparks.add(spark);
+ }
+
+ public static double update() {
+ double voltage = RobotController.getBatteryVoltage();
+ double output = sparks.stream().mapToDouble(CANSparkBase::getAppliedOutput).sum();
+ statorCurrent =
+ sparks.stream().mapToDouble(CANSparkBase::getOutputCurrent).map(Math::abs).sum();
+ supplyCurrent = voltage * output * statorCurrent;
+ return supplyCurrent;
+ }
+
+ public static double supplyCurrent() {
+ return supplyCurrent;
+ }
+
+ public static double statorCurrent() {
+ return statorCurrent;
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/lib/FaultLogger.java b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
index 4028180..9a26825 100644
--- a/src/main/java/org/sciborgs1155/lib/FaultLogger.java
+++ b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
@@ -1,55 +1,46 @@
package org.sciborgs1155.lib;
+import com.ctre.phoenix6.hardware.TalonFX;
+import com.kauailabs.navx.frc.AHRS;
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkBase.FaultID;
import com.revrobotics.REVLibError;
+import edu.wpi.first.networktables.NetworkTable;
+import edu.wpi.first.networktables.NetworkTableInstance;
+import edu.wpi.first.networktables.StringArrayPublisher;
+import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DutyCycleEncoder;
-import edu.wpi.first.wpilibj.Timer;
-import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
-import edu.wpi.first.wpilibj2.command.Command;
-import edu.wpi.first.wpilibj2.command.button.Trigger;
+import edu.wpi.first.wpilibj.PowerDistribution;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BooleanSupplier;
-import java.util.function.Function;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
+import org.photonvision.PhotonCamera;
+/**
+ * FaultLogger allows for faults to be logged and displayed.
+ *
+ *
+ * FaultLogger.register(spark); // registers a spark, periodically checking for hardware faults
+ * spark.set(0.5);
+ * FaultLogger.check(spark); // checks that the previous set call did not encounter an error.
+ *
+ */
public final class FaultLogger {
- /** Adds an alert widget to SmartDashboard;. */
- public static void setupLogging() {
- SmartDashboard.putData(
- "Faults",
- builder -> {
- builder.setSmartDashboardType("Alerts");
- builder.addStringArrayProperty("errors", () -> getStrings(FaultType.ERROR), null);
- builder.addStringArrayProperty("warnings", () -> getStrings(FaultType.WARNING), null);
- builder.addStringArrayProperty("infos", () -> getStrings(FaultType.INFO), null);
- });
- }
-
/** An individual fault, containing necessary information. */
- public static record Fault(String description, FaultType type, double timestamp) {
- public Fault(String description, FaultType type) {
- this(description, type, Timer.getFPGATimestamp());
- }
-
+ public static record Fault(String name, String description, FaultType type) {
@Override
- public boolean equals(Object other) {
- if (other instanceof Fault f) {
- return f.hashCode() == hashCode();
- }
- return false;
+ public String toString() {
+ return name + ": " + description;
}
+ }
- @Override
- public int hashCode() {
- return Objects.hash(description, type);
- }
+ @FunctionalInterface
+ public static interface FaultReporter {
+ void report();
}
/**
@@ -62,128 +53,386 @@ public static enum FaultType {
ERROR,
}
- private static final List>> faultSuppliers = new ArrayList<>();
- private static final Set faults = new HashSet<>();
+ /** A class to represent an alerts widget on NetworkTables */
+ public static class Alerts {
+ private final StringArrayPublisher errors;
+ private final StringArrayPublisher warnings;
+ private final StringArrayPublisher infos;
+
+ public Alerts(NetworkTable base, String name) {
+ NetworkTable table = base.getSubTable(name);
+ table.getStringTopic(".type").publish().set("Alerts");
+ errors = table.getStringArrayTopic("errors").publish();
+ warnings = table.getStringArrayTopic("warnings").publish();
+ infos = table.getStringArrayTopic("infos").publish();
+ }
+
+ public void set(Set faults) {
+ errors.set(filteredStrings(faults, FaultType.ERROR));
+ warnings.set(filteredStrings(faults, FaultType.WARNING));
+ infos.set(filteredStrings(faults, FaultType.INFO));
+ }
+ }
+
+ // DATA
+ private static final List faultReporters = new ArrayList<>();
+ private static final Set newFaults = new HashSet<>();
+ private static final Set activeFaults = new HashSet<>();
+ private static final Set totalFaults = new HashSet<>();
+
+ // NETWORK TABLES
+ private static final NetworkTable base = NetworkTableInstance.getDefault().getTable("Faults");
+ private static final Alerts activeAlerts = new Alerts(base, "Active Faults");
+ private static final Alerts totalAlerts = new Alerts(base, "Total Faults");
/** Polls registered fallibles. This method should be called periodically. */
public static void update() {
- faultSuppliers.stream()
- .map(s -> s.get())
- .flatMap(Optional::stream)
- .collect(Collectors.toCollection(() -> faults));
+ activeFaults.clear();
+
+ faultReporters.forEach(FaultReporter::report);
+ activeFaults.addAll(newFaults);
+ newFaults.clear();
+
+ totalFaults.addAll(activeFaults);
+
+ activeAlerts.set(activeFaults);
+ totalAlerts.set(totalFaults);
}
- /**
- * Returns a list of all current faults.
- *
- * @return A list of all current faults.
- */
- public static Set getFaults() {
- return faults;
+ /** Clears total faults. */
+ public static void clear() {
+ totalFaults.clear();
+ activeFaults.clear();
+ newFaults.clear();
+ }
+
+ /** Clears fault suppliers. */
+ public static void unregisterAll() {
+ faultReporters.clear();
}
/**
- * Returns a list of current faults based on the provided FaultType.
+ * Returns the set of all current faults.
*
- * @param type The type of faults to return.
- * @return A list of faults of the specified FaultType.
+ * @return The set of all current faults.
*/
- public static List getFaults(FaultType type) {
- return getFaults().stream().filter(a -> a.type() == type).toList();
+ public static Set activeFaults() {
+ return activeFaults;
}
/**
- * Returns a trigger for whether a failure has been reported.
+ * Returns the set of all total faults.
*
- * @param type The type of fault to filter for.
- * @return A trigger based on the presence of faults.
+ * @return The set of all total faults.
*/
- public static Trigger failing(FaultType type) {
- return new Trigger(() -> !getFaults(type).isEmpty());
+ public static Set totalFaults() {
+ return totalFaults;
}
/**
- * Configures a command to be ran whenever a failure is reported.
+ * Reports a fault.
*
- * @param command A function that creates a command from a set of faults.
- * @return The created trigger.
+ * @param fault The fault to report.
*/
- public static Trigger onFailing(Function, Command> command) {
- return failing(FaultType.ERROR).onTrue(command.apply(getFaults()));
+ public static void report(Fault fault) {
+ newFaults.add(fault);
+ switch (fault.type) {
+ case ERROR -> DriverStation.reportError(fault.toString(), false);
+ case WARNING -> DriverStation.reportWarning(fault.toString(), false);
+ case INFO -> System.out.println(fault.toString());
+ }
}
/**
- * Returns an array of descriptions of all faults that match the specified type.
+ * Reports a fault.
*
- * @param type The type to filter for.
- * @return An array of description strings.
+ * @param name The name of the fault.
+ * @param description The description of the fault.
+ * @param type The type of the fault.
*/
- public static String[] getStrings(FaultType type) {
- return getFaults().stream()
- .filter(a -> a.type() == type)
- .map(Fault::description)
- .toArray(String[]::new);
+ public static void report(String name, String description, FaultType type) {
+ report(new Fault(name, description, type));
}
/**
- * Registers a new fallible supplier.
+ * Registers a new fault supplier.
*
* @param supplier A supplier of an optional fault.
*/
public static void register(Supplier> supplier) {
- faultSuppliers.add(supplier);
+ faultReporters.add(() -> supplier.get().ifPresent(FaultLogger::report));
}
/**
- * Registers a new fallible supplier.
+ * Registers a new fault supplier.
*
* @param condition Whether a failure is occuring.
* @param description The failure's description.
* @param type The type of failure.
*/
- public static void register(BooleanSupplier condition, String description, FaultType type) {
- faultSuppliers.add(
- () ->
- condition.getAsBoolean()
- ? Optional.of(new Fault(description, type))
- : Optional.empty());
+ public static void register(
+ BooleanSupplier condition, String name, String description, FaultType type) {
+ faultReporters.add(
+ () -> {
+ if (condition.getAsBoolean()) {
+ report(name, description, type);
+ }
+ });
}
/**
- * Registers fallible suppliers for a CAN-based Spark motor controller.
+ * Registers fault suppliers for a CAN-based Spark motor controller.
*
* @param spark The Spark Max or Spark Flex to manage.
*/
public static void register(CANSparkBase spark) {
- int id = spark.getDeviceId();
-
- register(
+ faultReporters.add(
() -> {
- REVLibError err = spark.getLastError();
- return err == REVLibError.kOk
- ? Optional.empty()
- : Optional.of(
- new Fault(
- String.format("Spark [%d]: Error: %s", id, err.name()), FaultType.ERROR));
+ for (FaultID fault : FaultID.values()) {
+ if (spark.getFault(fault)) {
+ report(SparkUtils.name(spark), fault.name(), FaultType.ERROR);
+ }
+ }
});
-
- for (FaultID fault : FaultID.values()) {
- register(
- () -> spark.getFault(fault),
- String.format("Spark [%d]: Fault: %s", id, fault.name()),
- FaultType.WARNING);
- }
+ register(
+ () -> spark.getMotorTemperature() > 100,
+ SparkUtils.name(spark),
+ "motor above 100°C",
+ FaultType.WARNING);
+ // TODO actually fix PDH (this is cursed)
+ FakePDH.register(spark);
}
/**
- * Registers fallible suppliers for a duty cycle encoder.
+ * Registers fault suppliers for a duty cycle encoder.
*
* @param encoder The duty cycle encoder to manage.
*/
public static void register(DutyCycleEncoder encoder) {
register(
() -> !encoder.isConnected(),
- String.format("DutyCycleEncoder [%d]: Disconnected"),
+ "Duty Cycle Encoder [" + encoder.getSourceChannel() + "]",
+ "disconnected",
+ FaultType.ERROR);
+ }
+
+ /**
+ * Registers fault suppliers for a NavX.
+ *
+ * @param ahrs The NavX to manage.
+ */
+ public static void register(AHRS ahrs) {
+ register(() -> !ahrs.isConnected(), "NavX", "disconnected", FaultType.ERROR);
+ }
+
+ /**
+ * Registers fault suppliers for a power distribution hub/panel.
+ *
+ * @param powerDistribution The power distribution to manage.
+ */
+ public static void register(PowerDistribution powerDistribution) {
+ // Field[] fields = PowerDistributionFaults.class.getFields();
+ // faultReporters.add(
+ // () -> {
+ // try {
+ // PowerDistributionFaults faults = powerDistribution.getFaults();
+ // for (Field fault : fields) {
+ // if (fault.getBoolean(faults)) {
+ // report("Power Distribution", fault.getName(), FaultType.ERROR);
+ // }
+ // }
+ // } catch (Exception e) {
+ // }
+ // });
+ }
+
+ /**
+ * Registers fault suppliers for a camera.
+ *
+ * @param camera The camera to manage.
+ */
+ public static void register(PhotonCamera camera) {
+ register(
+ () -> !camera.isConnected(),
+ "Photon Camera [" + camera.getName() + "]",
+ "disconnected",
+ FaultType.ERROR);
+ }
+
+ /**
+ * Registers fault suppliers for a talon.
+ *
+ * @param talon The talon to manage.
+ */
+ public static void register(TalonFX talon) {
+ // TODO: Remove all the unnecessary faults
+ register(
+ (BooleanSupplier) talon.getFault_Hardware(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Hardware fault occurred",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_ProcTemp(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Processor temperature exceeded limit",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_DeviceTemp(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Device temperature exceeded limit",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_Undervoltage(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Device supply voltage dropped to near brownout levels",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_BootDuringEnable(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Device boot while detecting the enable signal",
FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_UnlicensedFeatureInUse(),
+ "Talon ID: " + talon.getDeviceID(),
+ "An unlicensed feature is in use, device may not behave as expected.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_BridgeBrownout(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Bridge was disabled most likely due to supply voltage dropping too low.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_RemoteSensorReset(),
+ "Talon ID: " + talon.getDeviceID(),
+ "The remote sensor has reset.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_MissingDifferentialFX(),
+ "Talon ID: " + talon.getDeviceID(),
+ "The remote Talon FX used for differential control is not present on CAN Bus.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_RemoteSensorPosOverflow(),
+ "Talon ID: " + talon.getDeviceID(),
+ "The remote sensor position has overflowed.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_OverSupplyV(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Supply Voltage has exceeded the maximum voltage rating of device.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_UnstableSupplyV(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Supply Voltage is unstable.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_ReverseHardLimit(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Reverse limit switch has been asserted. Output is set to neutral.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_ForwardHardLimit(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Forward limit switch has been asserted. Output is set to neutral.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_ReverseSoftLimit(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Reverse soft limit has been asserted. Output is set to neutral.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_ForwardSoftLimit(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Forward soft limit has been asserted. Output is set to neutral.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_RemoteSensorDataInvalid(),
+ "Talon ID: " + talon.getDeviceID(),
+ "The remote sensor's data is no longer trusted.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_FusedSensorOutOfSync(),
+ "Talon ID: " + talon.getDeviceID(),
+ "The remote sensor used for fusion has fallen out of sync to the local sensor.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_StatorCurrLimit(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Stator current limit occured.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_SupplyCurrLimit(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Supply current limit occured.",
+ FaultType.ERROR);
+
+ register(
+ (BooleanSupplier) talon.getFault_UsingFusedCANcoderWhileUnlicensed(),
+ "Talon ID: " + talon.getDeviceID(),
+ "Using Fused CANcoder feature while unlicensed. Device has fallen back to remote CANcoder.",
+ FaultType.ERROR);
+ }
+
+ /**
+ * Reports REVLibErrors from a spark.
+ *
+ * This should be called immediately after any call to the spark.
+ *
+ * @param spark The spark to report REVLibErrors from.
+ * @return If the spark is working without errors.
+ */
+ public static boolean check(CANSparkBase spark) {
+ REVLibError error = spark.getLastError();
+ return check(spark, error);
+ }
+
+ /**
+ * Reports REVLibErrors from a spark.
+ *
+ *
This should be called immediately after any call to the spark.
+ *
+ * @param spark The spark to report REVLibErrors from.
+ * @param error Any REVLibErrors that may be returned from a method for a spark.
+ * @return If the spark is working without errors.
+ */
+ public static boolean check(CANSparkBase spark, REVLibError error) {
+ if (error != REVLibError.kOk) {
+ report(SparkUtils.name(spark), error.name(), FaultType.ERROR);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns an array of descriptions of all faults that match the specified type.
+ *
+ * @param type The type to filter for.
+ * @return An array of description strings.
+ */
+ private static String[] filteredStrings(Set faults, FaultType type) {
+ return faults.stream()
+ .filter(a -> a.type() == type)
+ .map(Fault::toString)
+ .toArray(String[]::new);
}
}
diff --git a/src/main/java/org/sciborgs1155/lib/InputStream.java b/src/main/java/org/sciborgs1155/lib/InputStream.java
index 4e5c649..ede6b51 100644
--- a/src/main/java/org/sciborgs1155/lib/InputStream.java
+++ b/src/main/java/org/sciborgs1155/lib/InputStream.java
@@ -1,7 +1,10 @@
package org.sciborgs1155.lib;
import edu.wpi.first.math.MathUtil;
+import edu.wpi.first.math.filter.LinearFilter;
import edu.wpi.first.math.filter.SlewRateLimiter;
+import edu.wpi.first.networktables.DoublePublisher;
+import edu.wpi.first.networktables.NetworkTableInstance;
import java.util.function.DoubleSupplier;
import java.util.function.DoubleUnaryOperator;
@@ -23,6 +26,14 @@ public static InputStream of(DoubleSupplier base) {
return base::getAsDouble;
}
+ public static InputStream hypot(InputStream x, InputStream y) {
+ return () -> Math.hypot(x.get(), y.get());
+ }
+
+ public static InputStream atan(InputStream x, InputStream y) {
+ return () -> Math.atan2(x.get(), y.get());
+ }
+
/**
* Shorthand to return a double value.
*
@@ -71,6 +82,26 @@ public default InputStream negate() {
return scale(-1);
}
+ /**
+ * Offsets the stream by a factor.
+ *
+ * @param factor A supplier of offset values.
+ * @return An offset stream.
+ */
+ public default InputStream add(DoubleSupplier offset) {
+ return map(x -> x + offset.getAsDouble());
+ }
+
+ /**
+ * Offsets the stream by a factor.
+ *
+ * @param factor An offset.
+ * @return An offset stream.
+ */
+ public default InputStream add(double factor) {
+ return add(() -> factor);
+ }
+
/**
* Raises the stream outputs to an exponent.
*
@@ -91,6 +122,16 @@ public default InputStream signedPow(double exponent) {
return map(x -> Math.copySign(Math.pow(x, exponent), x));
}
+ /**
+ * Filters the stream's outputs by the provided {@link LinearFilter}.
+ *
+ * @param filter The linear filter to use.
+ * @return A filtered stream.
+ */
+ public default InputStream filter(LinearFilter filter) {
+ return map(filter::calculate);
+ }
+
/**
* Deadbands the stream outputs by a minimum bound and scales them from 0 to a maximum bound.
*
@@ -99,7 +140,7 @@ public default InputStream signedPow(double exponent) {
* @return A deadbanded stream.
*/
public default InputStream deadband(double deadband, double max) {
- return map(x -> MathUtil.applyDeadband(x, deadband, Double.MAX_VALUE));
+ return map(x -> MathUtil.applyDeadband(x, deadband, max));
}
/**
@@ -122,4 +163,22 @@ public default InputStream rateLimit(double rate) {
var limiter = new SlewRateLimiter(rate);
return map(x -> limiter.calculate(x));
}
+
+ /**
+ * Logs the output of this stream to networktables every time it is polled.
+ *
+ * A new stream is returned that is identical to this stream, but publishes its output to
+ * networktables every time it is polled.
+ *
+ * @param key The NetworkTables key to publish to.
+ * @return A stream with the same output as this one.
+ */
+ public default InputStream log(String key) {
+ DoublePublisher pub = NetworkTableInstance.getDefault().getDoubleTopic(key).publish();
+ return () -> {
+ double val = this.get();
+ pub.set(val);
+ return val;
+ };
+ }
}
diff --git a/src/main/java/org/sciborgs1155/lib/SparkUtils.java b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
index af845d6..dff4e30 100644
--- a/src/main/java/org/sciborgs1155/lib/SparkUtils.java
+++ b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
@@ -1,32 +1,58 @@
package org.sciborgs1155.lib;
-import static edu.wpi.first.units.Units.Rotations;
-
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkLowLevel.PeriodicFrame;
-import edu.wpi.first.units.Angle;
-import edu.wpi.first.units.Time;
-import edu.wpi.first.units.Units;
+import com.revrobotics.REVLibError;
+import com.revrobotics.SparkRelativeEncoder.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.DoubleSupplier;
/** Utility class for configuration of Spark motor controllers */
public class SparkUtils {
- public static final int FRAME_STRATEGY_DISABLED = 65535;
- public static final int FRAME_STRATEGY_SLOW = 500;
- public static final int FRAME_STRATEGY_FAST = 15;
+ private static final List runnables = new ArrayList<>();
+
+ public static void addChecker(Runnable runnable) {
+ runnables.add(runnable);
+ }
+
+ public static List getRunnables() {
+ return runnables;
+ }
+
+ // REV's docs have the size of a signed value of 65535ms for the max period
+ // https://docs.revrobotics.com/brushless/spark-max/control-interfaces#periodic-status-frames
+ // The actual max is half of this (32767ms)
+ // https://www.chiefdelphi.com/t/revlibs-documented-maximum-status-frame-period-limit-is-wrong/458845, https://www.chiefdelphi.com/t/extreme-can-utilization-but-parameters-set-ok/456613/6
+ public static final int FRAME_STRATEGY_DISABLED = 32767;
+ public static final int FRAME_STRATEGY_SLOW = 400;
+ public static final int FRAME_STRATEGY_MEDIUM = 100;
+ public static final int FRAME_STRATEGY_FAST = 20;
+ public static final int FRAME_STRATEGY_VERY_FAST = 10;
- public static final Angle ANGLE_UNIT = Units.Rotations;
- public static final Time TIME_UNIT = Units.Minutes;
- public static final Angle THROUGHBORE_PPR =
- Units.derive(Rotations).splitInto(2048).named("Pulses Per Revolution").symbol("PPR").make();
+ public static final int THROUGHBORE_CPR = 8192;
+
+ public static final int MAX_ATTEMPTS = 3;
+
+ /**
+ * Formats the name of a spark with its CAN ID.
+ *
+ * @param spark The spark to find the name of.
+ * @return The name of a spark.
+ */
+ public static String name(CANSparkBase spark) {
+ return "Spark [" + spark.getDeviceId() + "]";
+ }
/** Represents a type of sensor that can be plugged into the spark */
public static enum Sensor {
INTEGRATED,
ANALOG,
- QUADRATURE,
- DUTY_CYCLE;
+ ALTERNATE,
+ ABSOLUTE;
}
/** Represents a type of data that can be sent from the spark */
@@ -34,7 +60,9 @@ public static enum Data {
POSITION,
VELOCITY,
CURRENT,
- VOLTAGE;
+ TEMPERATURE,
+ INPUT_VOLTAGE,
+ APPLIED_OUTPUT;
}
/**
@@ -49,54 +77,88 @@ public static enum Data {
* @see Data
* @see https://docs.revrobotics.com/brushless/spark-max/control-interfaces
*/
- public static void configureFrameStrategy(
+ public static REVLibError configureFrameStrategy(
CANSparkBase spark, Set data, Set sensors, boolean withFollower) {
- int status0 = 10; // output, faults
- int status1 = FRAME_STRATEGY_SLOW; // velocity, temperature, input voltage, current
- int status2 = FRAME_STRATEGY_SLOW; // position
+ int status0 = FRAME_STRATEGY_MEDIUM; // output, faults
+ int status1 = FRAME_STRATEGY_SLOW;
+ // integrated velocity, temperature, input voltage, current | default 20
+ int status2 = FRAME_STRATEGY_SLOW; // integrated position | default 20
int status3 = FRAME_STRATEGY_DISABLED; // analog encoder | default 50
int status4 = FRAME_STRATEGY_DISABLED; // alternate quadrature encoder | default 20
int status5 = FRAME_STRATEGY_DISABLED; // duty cycle position | default 200
int status6 = FRAME_STRATEGY_DISABLED; // duty cycle velocity | default 200
+ int status7 = FRAME_STRATEGY_DISABLED;
+ // status frame 7 is cursed, the only mention i found of it in rev's docs is at
+ // https://docs.revrobotics.com/brushless/spark-flex/revlib/spark-flex-firmware-changelog#breaking-changes
+ // if it's only IAccum, there's literally no reason to enable the frame
+
+ Optional supplier1 = Optional.empty();
+ Optional supplier2 = Optional.empty();
+ Optional supplier3 = Optional.empty();
+ Optional supplier4 = Optional.empty();
+ Optional supplier5 = Optional.empty();
+ Optional supplier6 = Optional.empty();
+
+ if (withFollower || data.contains(Data.APPLIED_OUTPUT)) {
+ status0 = FRAME_STRATEGY_VERY_FAST;
+ }
- if (data.contains(Data.VELOCITY)
- || data.contains(Data.VOLTAGE)
- || data.contains(Data.CURRENT)) {
+ if (sensors.contains(Sensor.INTEGRATED) && data.contains(Data.VELOCITY)
+ || data.contains(Data.INPUT_VOLTAGE)
+ || data.contains(Data.CURRENT)
+ || data.contains(Data.TEMPERATURE)) {
status1 = FRAME_STRATEGY_FAST;
+ supplier1 = Optional.of(spark::getOutputCurrent);
}
- if (data.contains(Data.POSITION)) {
+ if (sensors.contains(Sensor.INTEGRATED) && data.contains(Data.POSITION)) {
status2 = FRAME_STRATEGY_FAST;
+ supplier2 = Optional.of(() -> spark.getEncoder().getPosition());
}
- if (sensors.contains(Sensor.ANALOG)) {
+ if (sensors.contains(Sensor.ANALOG)
+ && (data.contains(Data.VELOCITY) || data.contains(Data.POSITION))) {
status3 = FRAME_STRATEGY_FAST;
+ supplier3 = Optional.of(() -> spark.getEncoder().getVelocity());
}
- if (sensors.contains(Sensor.QUADRATURE)) {
+ if (sensors.contains(Sensor.ALTERNATE)
+ && (data.contains(Data.VELOCITY) || data.contains(Data.POSITION))) {
status4 = FRAME_STRATEGY_FAST;
+ supplier4 =
+ Optional.of(() -> spark.getEncoder(Type.kQuadrature, THROUGHBORE_CPR).getPosition());
}
- if (sensors.contains(Sensor.DUTY_CYCLE)) {
+ if (sensors.contains(Sensor.ABSOLUTE)) {
if (data.contains(Data.POSITION)) {
status5 = FRAME_STRATEGY_FAST;
+ supplier5 = Optional.of(() -> spark.getAbsoluteEncoder().getPosition());
}
if (data.contains(Data.VELOCITY)) {
status6 = FRAME_STRATEGY_FAST;
+ supplier6 = Optional.of(() -> spark.getAbsoluteEncoder().getVelocity());
}
}
- if (!withFollower) {
- status0 = FRAME_STRATEGY_SLOW;
+ int[] frames = {status0, status1, status2, status3, status4, status5, status6, status7};
+ List> suppliers =
+ List.of(
+ Optional.empty(),
+ supplier1,
+ supplier2,
+ supplier3,
+ supplier4,
+ supplier5,
+ supplier6,
+ Optional.empty());
+ REVLibError error = REVLibError.kOk;
+ for (int i = 0; i < frames.length; i++) {
+ REVLibError e = spark.setPeriodicFramePeriod(PeriodicFrame.fromId(i), frames[i]);
+ if (e != REVLibError.kOk) {
+ error = e;
+ }
}
-
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus0, status0);
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus1, status1);
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus2, status2);
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus3, status3);
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus4, status4);
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus5, status5);
- spark.setPeriodicFramePeriod(PeriodicFrame.kStatus6, status6);
+ return error;
}
/**
@@ -105,7 +167,22 @@ public static void configureFrameStrategy(
*
* @param spark The follower spark.
*/
- public static void configureFollowerFrameStrategy(CANSparkBase spark) {
- configureFrameStrategy(spark, Set.of(), Set.of(), false);
+ public static REVLibError configureNothingFrameStrategy(CANSparkBase spark) {
+ return configureFrameStrategy(spark, Set.of(), Set.of(), false);
+ }
+
+ /**
+ * Wraps the value of a call into an optional depending on the spark's indicated last error.
+ *
+ * @param The type of value.
+ * @param spark The spark to check for errors.
+ * @param value The value to wrap.
+ * @return An optional that may contain the value.
+ */
+ public static Optional wrapCall(CANSparkBase spark, T value) {
+ if (FaultLogger.check(spark)) {
+ return Optional.of(value);
+ }
+ return Optional.empty();
}
}
diff --git a/src/main/java/org/sciborgs1155/lib/TalonUtils.java b/src/main/java/org/sciborgs1155/lib/TalonUtils.java
new file mode 100644
index 0000000..518fefc
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/lib/TalonUtils.java
@@ -0,0 +1,101 @@
+package org.sciborgs1155.lib;
+
+import com.ctre.phoenix6.Orchestra;
+import com.ctre.phoenix6.configs.AudioConfigs;
+import com.ctre.phoenix6.hardware.TalonFX;
+import edu.wpi.first.wpilibj.DriverStation;
+import java.util.ArrayList;
+
+public class TalonUtils {
+ private static final Orchestra orchestra = new Orchestra();
+ private static final ArrayList talons = new ArrayList<>(4);
+ private static boolean fileLoaded = false;
+
+ /**
+ * Adds motor to the orchestra.
+ *
+ * @param talon The motor to add.
+ */
+ public static void addMotor(TalonFX talon) {
+ talons.add(talon);
+ }
+
+ /**
+ * Configure all motors to play a selected Chirp (CHRP) file in the deploy directory. Should be
+ * called once after addition of all Talons to TalonUtils.
+ *
+ * Use {@code loadOrchestraFile()} after configuration to change the played file.
+ *
+ * @param fileName The path of the file to play.
+ * @return Whether loading the file was successful.
+ */
+ public static boolean configureOrchestra(String fileName) {
+ AudioConfigs audioCfg = new AudioConfigs().withAllowMusicDurDisable(true);
+ for (TalonFX talon : talons) {
+ talon.getConfigurator().apply(audioCfg);
+ orchestra.addInstrument(talon);
+ }
+ return loadOrchestraFile(fileName);
+ }
+
+ /**
+ * Load the selected CHRP file located in the deploy directory.
+ *
+ * @param fileName The name of the file to play.
+ * @return Whether loading the file was successful.
+ */
+ public static boolean loadOrchestraFile(String fileName) {
+ fileLoaded = orchestra.loadMusic(fileName).isOK();
+ if (!fileLoaded) {
+ fileNotFound();
+ }
+
+ return fileLoaded;
+ }
+
+ /**
+ * Begin playback of the loaded file.
+ *
+ * @return Whether the operation was successful.
+ */
+ public static boolean play() {
+ if (fileLoaded) {
+ return orchestra.play().isOK();
+ }
+ fileNotFound();
+ return false;
+ }
+
+ /**
+ * Stop and restart playback of the loaded file.
+ *
+ * @return Whether the operation was successful.
+ */
+ public static boolean stop() {
+ if (fileLoaded) {
+ return orchestra.stop().isOK();
+ }
+ fileNotFound();
+ return false;
+ }
+
+ /**
+ * Pause playback of the loaded file.
+ *
+ * @return Whether the operation was successful.
+ */
+ public static boolean pause() {
+ if (fileLoaded) {
+ return orchestra.pause().isOK();
+ }
+ fileNotFound();
+ return false;
+ }
+
+ private static void fileNotFound() {
+ fileLoaded = false;
+ DriverStation.reportError(
+ "CHRP file not loaded. Check that it is in the deploy directory & includes file extension.",
+ true);
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/lib/TestingUtil.java b/src/main/java/org/sciborgs1155/lib/TestingUtil.java
index 5ea8094..e3d877e 100644
--- a/src/main/java/org/sciborgs1155/lib/TestingUtil.java
+++ b/src/main/java/org/sciborgs1155/lib/TestingUtil.java
@@ -1,9 +1,10 @@
package org.sciborgs1155.lib;
import edu.wpi.first.hal.HAL;
+import edu.wpi.first.wpilibj.simulation.DriverStationSim;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
-import edu.wpi.first.wpilibj2.command.Subsystem;
+import org.sciborgs1155.lib.FaultLogger.FaultType;
public class TestingUtil {
/**
@@ -28,7 +29,7 @@ public static void fastForward() {
* @param command The command to run.
*/
public static void run(Command command) {
- command.ignoringDisable(true).schedule();
+ command.schedule();
CommandScheduler.getInstance().run();
}
@@ -39,23 +40,43 @@ public static void run(Command command) {
* @param runs The number of times CommandScheduler is run.
*/
public static void run(Command command, int runs) {
- command.ignoringDisable(true).schedule();
+ command.schedule();
fastForward(runs);
}
+ /** Sets up DS and initializes HAL with default values and asserts that it doesn't fail. */
+ public static void setupTests() {
+ assert HAL.initialize(500, 0);
+ DriverStationSim.setEnabled(true);
+ DriverStationSim.setTest(true);
+ DriverStationSim.notifyNewData();
+ }
+
/**
- * Closes subsystem and unregisters it from CommandScheduler.
+ * Resets CommandScheduler and closes all subsystems. Please call in an @AfterEach method!
*
- * @param subsystem The subsystem to unregister.
+ * @param subsystems All subsystems that need to be closed
*/
- public static void closeSubsystem(
- TestableSubsystem subsystem) throws Exception {
- CommandScheduler.getInstance().unregisterSubsystem(subsystem);
- subsystem.close();
+ public static void reset(AutoCloseable... subsystems) throws Exception {
+ CommandScheduler.getInstance().unregisterAllSubsystems();
+ CommandScheduler.getInstance().cancelAll();
+ for (AutoCloseable subsystem : subsystems) {
+ subsystem.close();
+ }
}
- /** Initializes HAL with default values and asserts that it doesn't fail. */
- public static void setupHAL() {
- assert HAL.initialize(500, 0);
+ public static void assertEqualsReport(
+ String faultName, double expected, double actual, double delta) {
+ assertReport(
+ Math.abs(expected - actual) <= delta,
+ faultName,
+ "expected: " + expected + "; actual: " + actual);
+ }
+
+ public static void assertReport(boolean condition, String faultName, String description) {
+ FaultLogger.report(
+ faultName,
+ (condition ? "success! " : "") + description,
+ condition ? FaultType.INFO : FaultType.WARNING);
}
}
diff --git a/src/main/java/org/sciborgs1155/lib/Tuning.java b/src/main/java/org/sciborgs1155/lib/Tuning.java
new file mode 100644
index 0000000..8b820c2
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/lib/Tuning.java
@@ -0,0 +1,74 @@
+package org.sciborgs1155.lib;
+
+import edu.wpi.first.networktables.BooleanEntry;
+import edu.wpi.first.networktables.DoubleEntry;
+import edu.wpi.first.networktables.IntegerEntry;
+import edu.wpi.first.networktables.NetworkTableInstance;
+import edu.wpi.first.networktables.StringEntry;
+
+/**
+ * Tuning creates an entry with a specified topic and configurable value in Network Tables.
+ *
+ *
+ * Tuning.entry("/[FOLDER_NAME]/[TOPIC_NAME]", [CONFIGURABLE_VALUE]); //this creates a new configurable datatype value corresponding to the path given
+ *
+ */
+public final class Tuning {
+
+ /**
+ * Logs a DoubleEntry on Network Tables.
+ *
+ * @param path The file path, "/[FOLDER_NAME]/[TOPIC_NAME]", ie: "/Robot/exampleTopicName".
+ * @param value The configurable double that you would like to correspond to the topic.
+ * @return The DoubleEntry - contains all methods of DoublePublisher, DoubleSubscriber.
+ */
+ public static DoubleEntry entry(String path, double value) {
+
+ DoubleEntry entry = NetworkTableInstance.getDefault().getDoubleTopic(path).getEntry(value);
+ entry.set(value);
+
+ return entry;
+ }
+
+ /**
+ * Logs a IntegerEntry on Network Tables.
+ *
+ * @param path The file path, "/[FOLDER_NAME]/[TOPIC_NAME]", ie: "/Robot/exampleTopicName".
+ * @param value The configurable (int)long that you would like to correspond to the topic.
+ * @return The IntegerEntry - contains all methods of IntegerPublisher, IntegerSubscriber.
+ */
+ public static IntegerEntry entry(String path, long value) {
+ IntegerEntry entry = NetworkTableInstance.getDefault().getIntegerTopic(path).getEntry(value);
+ entry.set(value);
+
+ return entry;
+ }
+
+ /**
+ * Logs a StringEntry on Network Tables.
+ *
+ * @param path The file path, "/[FOLDER_NAME]/[TOPIC_NAME]", ie: "/Robot/exampleTopicName".
+ * @param value The configurable String that you would like to correspond to the topic.
+ * @return The StringEntry - contains all methods of StringPublisher, StringSubscriber.
+ */
+ public static StringEntry entry(String path, String value) {
+ StringEntry entry = NetworkTableInstance.getDefault().getStringTopic(path).getEntry(value);
+ entry.set(value);
+
+ return entry;
+ }
+
+ /**
+ * Logs a BooleanEntry on Network Tables.
+ *
+ * @param path The file path, "/[FOLDER_NAME]/[TOPIC_NAME]", ie: "/Robot/exampleTopicName".
+ * @param value The configurable boolean that you would like to correspond to the topic.
+ * @return The BooleanEntry - contains all methods of BooleanPublisher, BooleanSubscriber.
+ */
+ public static BooleanEntry entry(String path, boolean value) {
+ BooleanEntry entry = NetworkTableInstance.getDefault().getBooleanTopic(path).getEntry(value);
+ entry.set(value);
+
+ return entry;
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/robot/Constants.java b/src/main/java/org/sciborgs1155/robot/Constants.java
index 709e0b5..86e2c56 100644
--- a/src/main/java/org/sciborgs1155/robot/Constants.java
+++ b/src/main/java/org/sciborgs1155/robot/Constants.java
@@ -2,8 +2,19 @@
import static edu.wpi.first.units.Units.*;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Translation2d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.util.Units;
+import edu.wpi.first.units.Distance;
import edu.wpi.first.units.Measure;
import edu.wpi.first.units.Time;
+import edu.wpi.first.wpilibj.DriverStation;
+import edu.wpi.first.wpilibj.DriverStation.Alliance;
+import java.util.List;
import org.sciborgs1155.robot.drive.DriveConstants;
/**
@@ -16,11 +27,110 @@
* @see Units
*/
public class Constants {
+ /** Returns the robot's alliance. */
+ public static Alliance alliance() {
+ return DriverStation.getAlliance().orElse(Alliance.Blue);
+ }
+
+ /** Returns the rotation of the robot's alliance with respect to the origin. */
+ public static Rotation2d allianceRotation() {
+ return Rotation2d.fromRotations(alliance() == Alliance.Blue ? 0 : 0.5);
+ }
+
public static final Measure PERIOD = Seconds.of(0.02); // roborio tickrate (s)
- public static final double DEADBAND = 0.1;
+ public static final double DEADBAND = 0.15;
public static final double MAX_RATE =
DriveConstants.MAX_ACCEL.baseUnitMagnitude()
/ DriveConstants.MAX_ANGULAR_SPEED.baseUnitMagnitude();
- public static final double SLOW_SPEED = 0.33;
- public static final double FULL_SPEED = 1.0;
+ public static final double SLOW_SPEED_MULTIPLIER = 0.33;
+ public static final double FULL_SPEED_MULTIPLIER = 1.0;
+
+ // Origin at corner of blue alliance side of field
+ public static class Field {
+ public static final Measure LENGTH = Inches.of(651.223);
+ public static final Measure WIDTH = Inches.of(323.277);
+
+ public static final Translation3d BLUE_SPEAKER_POSE = new Translation3d(0, 5.549, 2.002);
+ public static final Translation3d RED_SPEAKER_POSE = new Translation3d(16.8167, 5.549, 2.002);
+ public static final Translation3d TARGET_OFFSET = new Translation3d(0.367, 0, 0.2);
+
+ // found from
+ // https://github.com/Mechanical-Advantage/RobotCode2024/blob/main/src/main/java/org/littletonrobotics/frc2024/FieldConstants.java
+ public static Pose3d BLUE_LEFT_NOTE =
+ new Pose3d(new Translation3d(2.9, 4.11, 0), new Rotation3d());
+ public static Pose3d BLUE_MID_NOTE =
+ new Pose3d(new Translation3d(2.9, 5.55, 0), new Rotation3d());
+ public static Pose3d BLUE_RIGHT_NOTE =
+ new Pose3d(new Translation3d(2.9, 7.0, 0), new Rotation3d());
+
+ public static Pose3d RED_LEFT_NOTE =
+ new Pose3d(new Translation3d(13.642, 4.11, 0), new Rotation3d());
+ public static Pose3d RED_MID_NOTE =
+ new Pose3d(new Translation3d(13.642, 5.55, 0), new Rotation3d());
+ public static Pose3d RED_RIGHT_NOTE =
+ new Pose3d(new Translation3d(13.642, 7.0, 0), new Rotation3d());
+
+ // left to right, facing forward towards field from origin
+ public static Pose3d CENTER_NOTE_ONE =
+ new Pose3d(new Translation3d(8.2705321, 0.7528052, 0), new Rotation3d());
+ public static Pose3d CENTER_NOTE_TWO =
+ new Pose3d(new Translation3d(8.2705321, 2.4292052, 0), new Rotation3d());
+ public static Pose3d CENTER_NOTE_THREE =
+ new Pose3d(new Translation3d(8.2705321, 4.1056052, 0), new Rotation3d());
+ public static Pose3d CENTER_NOTE_FOUR =
+ new Pose3d(new Translation3d(8.2705321, 5.78201, 0), new Rotation3d());
+ public static Pose3d CENTER_NOTE_FIVE =
+ new Pose3d(new Translation3d(8.2705321, 7.4584052, 0), new Rotation3d());
+
+ // Pose2d which contain the coordinates of the middles of the stage chains, as well as the
+ // rotation perpendicular to them.
+ public static final Pose2d BLUE_STAGE_MIDSIDE =
+ new Pose2d(Inches.of(224.125), Inches.of(162), Rotation2d.fromRadians(Math.PI));
+ public static final Pose2d BLUE_STAGE_AMPSIDE =
+ new Pose2d(Inches.of(175.5625), Inches.of(190.05), Rotation2d.fromRadians(Math.PI * 5 / 3));
+ public static final Pose2d BLUE_STAGE_SOURCESIDE =
+ new Pose2d(Inches.of(175.5625), Inches.of(133.95), Rotation2d.fromRadians(Math.PI / 3));
+ public static final Pose2d RED_STAGE_MIDSIDE =
+ new Pose2d(Inches.of(423.875), Inches.of(162), Rotation2d.fromRadians(0));
+ public static final Pose2d RED_STAGE_AMPSIDE =
+ new Pose2d(Inches.of(472.4375), Inches.of(190.05), Rotation2d.fromRadians(Math.PI * 4 / 3));
+ public static final Pose2d RED_STAGE_SOURCESIDE =
+ new Pose2d(Inches.of(472.4375), Inches.of(133.95), Rotation2d.fromRadians(Math.PI * 2 / 3));
+
+ // Pose2D which contain the coordinates ((x and y) of the AprilTag on the amp (which is directly
+ // above the amp scoring area), and the rotations.
+ public static final Translation2d BLUE_AMP =
+ new Translation2d(Inches.of(72.5), Inches.of(323.0));
+ public static final Translation2d RED_AMP =
+ new Translation2d(Inches.of(578.77), Inches.of(323.0));
+
+ // Methoeds
+
+ /** Returns the translation of the speaker for the robot's alliance. */
+ public static Translation3d speaker() {
+ return alliance() == Alliance.Red
+ ? RED_SPEAKER_POSE.plus(TARGET_OFFSET.rotateBy(new Rotation3d(0, 0, Math.PI)))
+ : BLUE_SPEAKER_POSE.plus(TARGET_OFFSET);
+ }
+
+ // ** Returns a list of 2d coordinates for the middle of the current alliance's stage chains */
+ public static List chain() {
+ return alliance() == Alliance.Blue
+ ? List.of(BLUE_STAGE_AMPSIDE, BLUE_STAGE_MIDSIDE, BLUE_STAGE_SOURCESIDE)
+ : List.of(RED_STAGE_AMPSIDE, RED_STAGE_MIDSIDE, RED_STAGE_SOURCESIDE);
+ }
+
+ // ** Returns the Pose2D of the amp on the robot's alliance. */
+ public static Translation2d amp() {
+ return alliance() == Alliance.Blue ? BLUE_AMP : RED_AMP;
+ }
+
+ /** Returns whether the provided position is within the boundaries of the field. */
+ public static boolean inField(Pose3d pose) {
+ return (pose.getX() > 0
+ && pose.getX() < Field.LENGTH.in(Meters)
+ && pose.getY() > 0
+ && pose.getY() < Field.WIDTH.in(Meters));
+ }
+ }
}
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index 3224d49..40bd571 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -1,27 +1,36 @@
package org.sciborgs1155.robot;
import static edu.wpi.first.units.Units.MetersPerSecond;
-import static edu.wpi.first.units.Units.MetersPerSecondPerSecond;
import static edu.wpi.first.units.Units.RadiansPerSecond;
import static edu.wpi.first.units.Units.Second;
+import static edu.wpi.first.units.Units.Seconds;
import static edu.wpi.first.wpilibj2.command.button.RobotModeTriggers.*;
+import static org.sciborgs1155.robot.Constants.DEADBAND;
+import static org.sciborgs1155.robot.Constants.PERIOD;
+import static org.sciborgs1155.robot.drive.DriveConstants.*;
import edu.wpi.first.wpilibj.DataLogManager;
import edu.wpi.first.wpilibj.DriverStation;
+import edu.wpi.first.wpilibj.RobotController;
+import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
+import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
+import edu.wpi.first.wpilibj2.command.Command;
+import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.Commands;
-import edu.wpi.first.wpilibj2.command.ProxyCommand;
import edu.wpi.first.wpilibj2.command.button.CommandXboxController;
import monologue.Annotations.Log;
import monologue.Logged;
import monologue.Monologue;
import org.littletonrobotics.urcl.URCL;
import org.sciborgs1155.lib.CommandRobot;
+import org.sciborgs1155.lib.FakePDH;
import org.sciborgs1155.lib.FaultLogger;
import org.sciborgs1155.lib.InputStream;
+import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.robot.Ports.OI;
import org.sciborgs1155.robot.commands.Autos;
import org.sciborgs1155.robot.drive.Drive;
-import org.sciborgs1155.robot.drive.DriveConstants;
+import org.sciborgs1155.robot.vision.Vision;
/**
* This class is where the bulk of the robot should be declared. Since Command-based is a
@@ -37,16 +46,17 @@ public class Robot extends CommandRobot implements Logged {
// SUBSYSTEMS
private final Drive drive = Drive.create();
+ private final Vision vision = Vision.create();
// COMMANDS
- @Log.NT private final Autos autos = new Autos();
+ @Log.NT private final SendableChooser autos = Autos.configureAutos(drive);
- @Log.NT private double speedMultiplier = Constants.FULL_SPEED;
+ @Log.NT private double speedMultiplier = Constants.FULL_SPEED_MULTIPLIER;
/** The robot contains subsystems, OI devices, and commands. */
public Robot() {
+ super(PERIOD.in(Seconds));
configureGameBehavior();
- configureSubsystemDefaults();
configureBindings();
}
@@ -55,58 +65,88 @@ private void configureGameBehavior() {
// Configure logging with DataLogManager, Monologue, FailureManagement, and URCL
DataLogManager.start();
Monologue.setupMonologue(this, "/Robot", false, true);
- addPeriodic(Monologue::updateAll, kDefaultPeriod);
- FaultLogger.setupLogging();
- addPeriodic(FaultLogger::update, 1);
+ addPeriodic(Monologue::updateAll, PERIOD.in(Seconds));
+ addPeriodic(FaultLogger::update, 2);
+
+ SmartDashboard.putData(CommandScheduler.getInstance());
+ // Log PDH
+ // SmartDashboard.putData("PDH", new PowerDistribution());
+ addPeriodic(() -> log("current", FakePDH.update()), PERIOD.in(Seconds));
+
+ // Configure pose estimation updates every tick
+ addPeriodic(() -> drive.updateEstimates(vision.getEstimatedGlobalPoses()), PERIOD.in(Seconds));
+
+ for (var r : SparkUtils.getRunnables()) {
+ addPeriodic(r, 5);
+ }
+
+ RobotController.setBrownoutVoltage(6.0);
if (isReal()) {
URCL.start();
} else {
DriverStation.silenceJoystickConnectionWarning(true);
+ DriverStation.silenceJoystickConnectionWarning(true);
+ addPeriodic(() -> vision.simulationPeriodic(drive.pose()), PERIOD.in(Seconds));
}
}
- /** Creates an input stream for a joystick. */
- private InputStream createJoystickStream(InputStream input, double maxSpeed, double maxRate) {
- return input
- .deadband(Constants.DEADBAND, 1)
- .negate()
- .scale(maxSpeed)
- .scale(() -> speedMultiplier)
- .signedPow(2)
- .rateLimit(maxRate);
- }
-
/**
* Configures subsystem default commands. Default commands are scheduled when no other command is
* running on a subsystem.
*/
- private void configureSubsystemDefaults() {
- drive.setDefaultCommand(
- drive.drive(
- createJoystickStream(
- driver::getLeftX,
- DriveConstants.MAX_SPEED.in(MetersPerSecond),
- DriveConstants.MAX_ACCEL.in(MetersPerSecondPerSecond)),
- createJoystickStream(
- driver::getLeftY,
- DriveConstants.MAX_SPEED.in(MetersPerSecond),
- DriveConstants.MAX_ACCEL.in(MetersPerSecondPerSecond)),
- createJoystickStream(
- driver::getRightX,
- DriveConstants.MAX_ANGULAR_SPEED.in(RadiansPerSecond),
- DriveConstants.MAX_ANGULAR_ACCEL.in(RadiansPerSecond.per(Second)))));
- }
-
/** Configures trigger -> command bindings */
private void configureBindings() {
- autonomous().whileTrue(new ProxyCommand(autos::get));
- FaultLogger.onFailing(f -> Commands.print(f.toString()));
+ InputStream x = InputStream.of(driver::getLeftX).negate();
+ InputStream y = InputStream.of(driver::getLeftY).negate();
+ InputStream r =
+ InputStream.hypot(x, y)
+ .log("Robot/raw joystick")
+ .scale(() -> speedMultiplier)
+ .clamp(1.0)
+ .deadband(Constants.DEADBAND, 1.0)
+ .signedPow(2.0)
+ .log("Robot/processed joystick")
+ .scale(MAX_SPEED.in(MetersPerSecond));
+
+ InputStream theta = InputStream.atan(x, y);
+
+ x = r.scale(theta.map(Math::cos)); // .rateLimit(MAX_ACCEL.in(MetersPerSecondPerSecond));
+ y = r.scale(theta.map(Math::sin)); // .rateLimit(MAX_ACCEL.in(MetersPerSecondPerSecond));
+
+ InputStream omega =
+ InputStream.of(driver::getRightX)
+ .negate()
+ .scale(() -> speedMultiplier)
+ .clamp(1.0)
+ .deadband(DEADBAND, 1.0)
+ .signedPow(2.0)
+ .scale(TELEOP_ANGULAR_SPEED.in(RadiansPerSecond))
+ .rateLimit(MAX_ANGULAR_ACCEL.in(RadiansPerSecond.per(Second)));
+
+ drive.setDefaultCommand(drive.drive(x, y, omega));
+
+ autonomous().whileTrue(Commands.deferredProxy(autos::getSelected));
+ test().whileTrue(systemsCheck());
+ driver.b().whileTrue(drive.zeroHeading());
driver
.leftBumper()
.or(driver.rightBumper())
- .onTrue(Commands.runOnce(() -> speedMultiplier = Constants.FULL_SPEED))
- .onFalse(Commands.run(() -> speedMultiplier = Constants.SLOW_SPEED));
+ .onTrue(Commands.runOnce(() -> speedMultiplier = Constants.SLOW_SPEED_MULTIPLIER))
+ .onFalse(Commands.runOnce(() -> speedMultiplier = Constants.FULL_SPEED_MULTIPLIER));
+ }
+
+ public Command systemsCheck() {
+ return Commands.sequence(drive.systemsCheck());
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ try {
+ drive.close();
+ } catch (Exception e) {
+ }
}
}
diff --git a/src/main/java/org/sciborgs1155/robot/commands/Autos.java b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
index 3c6d044..2cdc765 100644
--- a/src/main/java/org/sciborgs1155/robot/commands/Autos.java
+++ b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
@@ -1,21 +1,48 @@
package org.sciborgs1155.robot.commands;
-import edu.wpi.first.util.sendable.Sendable;
-import edu.wpi.first.util.sendable.SendableBuilder;
+import static edu.wpi.first.units.Units.Meters;
+import static edu.wpi.first.units.Units.MetersPerSecond;
+import static org.sciborgs1155.robot.Constants.alliance;
+
+import com.pathplanner.lib.auto.AutoBuilder;
+import com.pathplanner.lib.controllers.PPHolonomicDriveController;
+import com.pathplanner.lib.util.HolonomicPathFollowerConfig;
+import com.pathplanner.lib.util.PIDConstants;
+import com.pathplanner.lib.util.ReplanningConfig;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj2.command.Command;
-import java.util.function.Supplier;
+import edu.wpi.first.wpilibj2.command.Commands;
+import java.util.Optional;
+import org.sciborgs1155.robot.drive.Drive;
+import org.sciborgs1155.robot.drive.DriveConstants;
+import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
+import org.sciborgs1155.robot.drive.DriveConstants.Translation;
+import org.sciborgs1155.robot.drive.SwerveModule.ControlMode;
-public final class Autos implements Sendable {
+public class Autos {
+ private static Optional rotation = Optional.empty();
- private final SendableChooser> chooser = new SendableChooser<>();
+ public static SendableChooser configureAutos(Drive drive) {
+ AutoBuilder.configureHolonomic(
+ drive::pose,
+ drive::resetOdometry,
+ drive::getRobotRelativeChassisSpeeds,
+ s -> drive.setChassisSpeeds(s, ControlMode.CLOSED_LOOP_VELOCITY),
+ new HolonomicPathFollowerConfig(
+ new PIDConstants(Translation.P, Translation.I, Translation.D),
+ new PIDConstants(Rotation.P, Rotation.I, Rotation.D),
+ DriveConstants.MAX_SPEED.in(MetersPerSecond),
+ DriveConstants.RADIUS.in(Meters),
+ new ReplanningConfig()),
+ () -> alliance() == Alliance.Red,
+ drive);
- public Command get() {
- return chooser.getSelected().get();
- }
+ PPHolonomicDriveController.setRotationTargetOverride(() -> rotation);
- @Override
- public void initSendable(SendableBuilder builder) {
- chooser.initSendable(builder);
+ SendableChooser chooser = AutoBuilder.buildAutoChooser();
+ chooser.addOption("no auto", Commands.none());
+ return chooser;
}
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index 1686c49..c3a9263 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -1,28 +1,44 @@
package org.sciborgs1155.robot.drive;
-import static edu.wpi.first.units.Units.*;
+import static edu.wpi.first.units.Units.Meters;
+import static edu.wpi.first.units.Units.MetersPerSecond;
+import static edu.wpi.first.units.Units.Radians;
+import static edu.wpi.first.units.Units.Seconds;
+import static edu.wpi.first.units.Units.Volts;
+import static java.lang.Math.atan;
+import static org.sciborgs1155.lib.TestingUtil.assertEqualsReport;
+import static org.sciborgs1155.lib.TestingUtil.assertReport;
+import static org.sciborgs1155.robot.Constants.allianceRotation;
import static org.sciborgs1155.robot.Ports.Drive.*;
import static org.sciborgs1155.robot.drive.DriveConstants.*;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.Vector;
+import edu.wpi.first.math.controller.PIDController;
import edu.wpi.first.math.controller.ProfiledPIDController;
import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator;
import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Transform2d;
+import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
import edu.wpi.first.math.kinematics.SwerveDriveKinematics;
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
+import edu.wpi.first.math.numbers.N3;
import edu.wpi.first.math.trajectory.TrapezoidProfile;
+import edu.wpi.first.math.util.Units;
import edu.wpi.first.wpilibj.smartdashboard.Field2d;
import edu.wpi.first.wpilibj.smartdashboard.FieldObject2d;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj2.command.Command;
-import edu.wpi.first.wpilibj2.command.Commands;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine;
import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine.Direction;
import java.util.List;
+import java.util.Optional;
+import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import monologue.Annotations.IgnoreLogged;
import monologue.Annotations.Log;
@@ -31,6 +47,10 @@
import org.sciborgs1155.lib.InputStream;
import org.sciborgs1155.robot.Constants;
import org.sciborgs1155.robot.Robot;
+import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
+import org.sciborgs1155.robot.drive.DriveConstants.Translation;
+import org.sciborgs1155.robot.drive.SwerveModule.ControlMode;
+import org.sciborgs1155.robot.vision.Vision.PoseEstimate;
public class Drive extends SubsystemBase implements Logged, AutoCloseable {
@@ -53,71 +73,119 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
@Log.NT private final Field2d field2d = new Field2d();
private final FieldObject2d[] modules2d;
- // SysId
- private final SysIdRoutine driveRoutine;
- private final SysIdRoutine turnRoutine;
+ private final SysIdRoutine translationCharacterization;
+ private final SysIdRoutine rotationalCharacterization;
+
+ @Log.NT
+ private final ProfiledPIDController translationController =
+ new ProfiledPIDController(
+ Translation.P,
+ Translation.I,
+ Translation.D,
+ new TrapezoidProfile.Constraints(MAX_SPEED, MAX_ACCEL));
+
+ @Log.NT
+ private final PIDController rotationController =
+ new PIDController(Rotation.P, Rotation.I, Rotation.D);
/**
- * A factory to create a new drive subsystem based on whether the robot is being ran in simulation
- * or not.
+ * A factory to create a new swerve drive based on whether the robot is being ran in simulation or
+ * not.
*/
public static Drive create() {
return Robot.isReal()
? new Drive(
- new FlexModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0)),
- new FlexModule(FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1)),
- new FlexModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2)),
- new FlexModule(REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3)),
- new GyroIO.NavX())
+ new NavXGyro(),
+ new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0)),
+ new SparkModule(FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1)),
+ new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2)),
+ new SparkModule(REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3)))
: new Drive(
- new SimModule(),
- new SimModule(),
- new SimModule(),
- new SimModule(),
- new GyroIO.NoGyro());
+ new NoGyro(), new SimModule(), new SimModule(), new SimModule(), new SimModule());
+ }
+
+ /** A factory to create a nonexistent swerve drive. */
+ public static Drive none() {
+ return new Drive(new NoGyro(), new NoModule(), new NoModule(), new NoModule(), new NoModule());
}
/** A swerve drive subsystem containing four {@link ModuleIO} modules. */
public Drive(
- ModuleIO frontLeft, ModuleIO frontRight, ModuleIO rearLeft, ModuleIO rearRight, GyroIO gyro) {
+ GyroIO gyro, ModuleIO frontLeft, ModuleIO frontRight, ModuleIO rearLeft, ModuleIO rearRight) {
+ this.gyro = gyro;
this.frontLeft = new SwerveModule(frontLeft, ANGULAR_OFFSETS.get(0), " FL");
this.frontRight = new SwerveModule(frontRight, ANGULAR_OFFSETS.get(1), "FR");
this.rearLeft = new SwerveModule(rearLeft, ANGULAR_OFFSETS.get(2), "RL");
this.rearRight = new SwerveModule(rearRight, ANGULAR_OFFSETS.get(3), " RR");
- this.gyro = gyro;
modules = List.of(this.frontLeft, this.frontRight, this.rearLeft, this.rearRight);
modules2d = new FieldObject2d[modules.size()];
- driveRoutine =
+ translationCharacterization =
new SysIdRoutine(
new SysIdRoutine.Config(),
new SysIdRoutine.Mechanism(
- volts -> modules.forEach(m -> m.setDriveVoltage(volts.in(Volts))), null, this));
-
- turnRoutine =
+ volts ->
+ modules.forEach(
+ m -> m.updateDriveVoltage(Rotation2d.fromRadians(0), volts.in(Volts))),
+ null,
+ this,
+ "translation"));
+ rotationalCharacterization =
new SysIdRoutine(
new SysIdRoutine.Config(),
new SysIdRoutine.Mechanism(
- volts -> modules.forEach(m -> m.setTurnVoltage(volts.in(Volts))), null, this));
+ volts -> {
+ this.frontLeft.updateDriveVoltage(
+ Rotation2d.fromRadians(3 * Math.PI / 4), volts.in(Volts));
+ this.frontRight.updateDriveVoltage(
+ Rotation2d.fromRadians(Math.PI / 4), volts.in(Volts));
+ this.rearLeft.updateDriveVoltage(
+ Rotation2d.fromRadians(-3 * Math.PI / 4), volts.in(Volts));
+ this.rearRight.updateDriveVoltage(
+ Rotation2d.fromRadians(-Math.PI / 4), volts.in(Volts));
+ },
+ null,
+ this,
+ "rotation"));
odometry =
new SwerveDrivePoseEstimator(
- kinematics, gyro.getRotation2d(), getModulePositions(), new Pose2d());
+ kinematics,
+ gyro.getRotation2d(),
+ getModulePositions(),
+ new Pose2d(new Translation2d(), Rotation2d.fromDegrees(180)));
for (int i = 0; i < modules.size(); i++) {
var module = modules.get(i);
modules2d[i] = field2d.getObject("module-" + module.name);
}
- SmartDashboard.putData("drive quasistatic forward", driveSysIdQuasistatic(Direction.kForward));
- SmartDashboard.putData("drive dynamic forward", driveSysIdDynamic(Direction.kForward));
- SmartDashboard.putData("drive quasistatic backward", driveSysIdQuasistatic(Direction.kReverse));
- SmartDashboard.putData("drive dynamic backward", driveSysIdDynamic(Direction.kReverse));
- SmartDashboard.putData("turn quasistatic forward", turnSysIdQuasistatic(Direction.kForward));
- SmartDashboard.putData("turn dynamic forward", turnSysIdDynamic(Direction.kForward));
- SmartDashboard.putData("turn quasistatic backward", turnSysIdQuasistatic(Direction.kReverse));
- SmartDashboard.putData("turn dynamic backward", turnSysIdDynamic(Direction.kReverse));
+ gyro.reset();
+
+ translationController.setTolerance(Translation.TOLERANCE.in(Meters));
+ rotationController.enableContinuousInput(0, 2 * Math.PI);
+ rotationController.setTolerance(Rotation.TOLERANCE.in(Radians));
+
+ SmartDashboard.putData(
+ "translation quasistatic forward",
+ translationCharacterization.quasistatic(Direction.kForward));
+ SmartDashboard.putData(
+ "translation dynamic forward", translationCharacterization.dynamic(Direction.kForward));
+ SmartDashboard.putData(
+ "translation quasistatic backward",
+ translationCharacterization.quasistatic(Direction.kReverse));
+ SmartDashboard.putData(
+ "translation dynamic backward", translationCharacterization.dynamic(Direction.kReverse));
+ SmartDashboard.putData(
+ "rotation quasistatic forward", rotationalCharacterization.quasistatic(Direction.kForward));
+ SmartDashboard.putData(
+ "rotation dynamic forward", rotationalCharacterization.dynamic(Direction.kForward));
+ SmartDashboard.putData(
+ "rotation quasistatic backward",
+ rotationalCharacterization.quasistatic(Direction.kReverse));
+ SmartDashboard.putData(
+ "rotation dynamic backward", rotationalCharacterization.dynamic(Direction.kReverse));
}
/**
@@ -126,7 +194,7 @@ public Drive(
* @return The pose.
*/
@Log.NT
- public Pose2d getPose() {
+ public Pose2d pose() {
return odometry.getEstimatedPosition();
}
@@ -139,6 +207,35 @@ public void resetOdometry(Pose2d pose) {
odometry.resetPosition(gyro.getRotation2d(), getModulePositions(), pose);
}
+ public Rotation2d heading() {
+ return pose().getRotation();
+ }
+
+ /**
+ * Drives the robot while facing a target pose.
+ *
+ * @param vx A supplier for the absolute x velocity of the robot.
+ * @param vy A supplier for the absolute y velocity of the robot.
+ * @param translation A supplier for the translation2d to face on the field.
+ * @return A command to drive while facing a target.
+ */
+ public Command driveFacingTarget(
+ DoubleSupplier vx, DoubleSupplier vy, Supplier translation) {
+ return drive(vx, vy, () -> translation.get().minus(pose().getTranslation()).getAngle());
+ }
+
+ @Log.NT
+ public boolean atRotationalSetpoint() {
+ return rotationController.atSetpoint();
+ }
+
+ public boolean isFacing(Translation2d target) {
+ return Math.abs(
+ gyro.getRotation2d().getRadians()
+ - target.minus(pose().getTranslation()).getAngle().getRadians())
+ < rotationController.getPositionTolerance();
+ }
+
/**
* Drives the robot based on a {@link InputStream} for field relative x y and omega velocities.
*
@@ -149,8 +246,16 @@ public void resetOdometry(Pose2d pose) {
* @param vOmega A supplier for the angular velocity of the robot.
* @return The driving command.
*/
- public Command drive(InputStream vx, InputStream vy, InputStream vOmega) {
- return run(() -> driveFieldRelative(new ChassisSpeeds(vx.get(), vy.get(), vOmega.get())));
+ public Command drive(DoubleSupplier vx, DoubleSupplier vy, DoubleSupplier vOmega) {
+ return run(
+ () ->
+ setChassisSpeeds(
+ ChassisSpeeds.fromFieldRelativeSpeeds(
+ vx.getAsDouble(),
+ vy.getAsDouble(),
+ vOmega.getAsDouble(),
+ heading().plus(allianceRotation())),
+ ControlMode.OPEN_LOOP_VELOCITY));
}
/**
@@ -163,52 +268,36 @@ public Command drive(InputStream vx, InputStream vy, InputStream vOmega) {
* @param heading A supplier for the field relative heading of the robot.
* @return The driving command.
*/
- public Command drive(InputStream vx, InputStream vy, Supplier heading) {
- var pid =
- new ProfiledPIDController(
- Rotation.P,
- Rotation.I,
- Rotation.D,
- new TrapezoidProfile.Constraints(MAX_ANGULAR_SPEED, MAX_ANGULAR_ACCEL));
-
- return run(
- () ->
- driveFieldRelative(
- new ChassisSpeeds(
- vx.get(),
- vy.get(),
- pid.calculate(
- getPose().getRotation().getRadians(), heading.get().getRadians()))));
+ public Command drive(DoubleSupplier vx, DoubleSupplier vy, Supplier heading) {
+ return drive(
+ vx,
+ vy,
+ () -> rotationController.calculate(heading().getRadians(), heading.get().getRadians()))
+ .beforeStarting(rotationController::reset);
}
- /**
- * Drives the robot relative to field based on provided {@link ChassisSpeeds} and current heading.
- *
- * @param speeds The desired field relative chassis speeds.
- */
- public void driveFieldRelative(ChassisSpeeds speeds) {
- driveRobotRelative(ChassisSpeeds.fromFieldRelativeSpeeds(speeds, getPose().getRotation()));
- }
+ /** Robot relative chassis speeds */
+ public void setChassisSpeeds(ChassisSpeeds speeds, ControlMode mode) {
+ double speed = Math.hypot(speeds.vxMetersPerSecond, speeds.vyMetersPerSecond);
+ double angularSpeed = Math.abs(speeds.omegaRadiansPerSecond);
+ double sum = speed + angularSpeed;
+ double factor = sum == 0 ? 0 : speed / sum;
- /**
- * Drives the robot based on profided {@link ChassisSpeeds}.
- *
- * This method uses {@link ChassisSpeeds#discretize(ChassisSpeeds, double)} to reduce skew.
- *
- * @param speeds The desired robot relative chassis speeds.
- */
- public void driveRobotRelative(ChassisSpeeds speeds) {
setModuleStates(
kinematics.toSwerveModuleStates(
- ChassisSpeeds.discretize(speeds, Constants.PERIOD.in(Seconds))));
+ ChassisSpeeds.discretize(speeds, Constants.PERIOD.in(Seconds))),
+ mode,
+ factor);
}
/**
* Sets the swerve ModuleStates.
*
* @param desiredStates The desired SwerveModule states.
+ * @param mode The method to use when controlling the drive motor.
*/
- public void setModuleStates(SwerveModuleState[] desiredStates) {
+ public void setModuleStates(
+ SwerveModuleState[] desiredStates, ControlMode mode, double movementFactor) {
if (desiredStates.length != modules.size()) {
throw new IllegalArgumentException("desiredStates must have the same length as modules");
}
@@ -216,10 +305,29 @@ public void setModuleStates(SwerveModuleState[] desiredStates) {
SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, MAX_SPEED.in(MetersPerSecond));
for (int i = 0; i < modules.size(); i++) {
- modules.get(i).updateDesiredState(desiredStates[i]);
+ modules.get(i).updateSetpoint(desiredStates[i], mode, movementFactor);
}
}
+ public Command driveTo(Pose2d target) {
+ return run(() -> {
+ Transform2d transform = pose().minus(target);
+ Vector difference =
+ VecBuilder.fill(
+ transform.getX(),
+ transform.getY(),
+ transform.getRotation().getRadians() * RADIUS.in(Meters));
+ double out = translationController.calculate(difference.norm(), 0);
+ Vector velocities = difference.unit().times(out);
+ setChassisSpeeds(
+ new ChassisSpeeds(
+ velocities.get(0), velocities.get(1), velocities.get(2) / RADIUS.in(Meters)),
+ ControlMode.CLOSED_LOOP_VELOCITY);
+ })
+ .until(translationController::atGoal)
+ .withName("drive to pose");
+ }
+
/** Resets the drive encoders to currently read a position of 0. */
public void resetEncoders() {
modules.forEach(SwerveModule::resetEncoders);
@@ -232,47 +340,69 @@ public Command zeroHeading() {
/** Returns the module states. */
@Log.NT
- private SwerveModuleState[] getModuleStates() {
+ public SwerveModuleState[] getModuleStates() {
return modules.stream().map(SwerveModule::state).toArray(SwerveModuleState[]::new);
}
- /** Returns the module positions. */
+ /** Returns the module states. */
@Log.NT
- private SwerveModulePosition[] getModulePositions() {
- return modules.stream().map(SwerveModule::position).toArray(SwerveModulePosition[]::new);
+ private SwerveModuleState[] getModuleSetpoints() {
+ return modules.stream().map(SwerveModule::desiredState).toArray(SwerveModuleState[]::new);
}
- /** Returns the module setpoints. */
+ /** Returns the module positions */
@Log.NT
- public SwerveModuleState[] getModuleSetpoints() {
- return modules.stream().map(SwerveModule::desiredState).toArray(SwerveModuleState[]::new);
+ public SwerveModulePosition[] getModulePositions() {
+ return modules.stream().map(SwerveModule::position).toArray(SwerveModulePosition[]::new);
}
- /** Returns the chassis speed. */
+ /** Returns the robot relative chassis speeds. */
@Log.NT
- public ChassisSpeeds getChassisSpeed() {
+ public ChassisSpeeds getRobotRelativeChassisSpeeds() {
return kinematics.toChassisSpeeds(getModuleStates());
}
+ /** Returns the field relative chassis speeds. */
+ @Log.NT
+ public ChassisSpeeds getFieldRelativeChassisSpeeds() {
+ return ChassisSpeeds.fromRobotRelativeSpeeds(getRobotRelativeChassisSpeeds(), heading());
+ }
+
/** Updates pose estimation based on provided {@link EstimatedRobotPose} */
- public void updateEstimates(EstimatedRobotPose... poses) {
+ public void updateEstimates(PoseEstimate... poses) {
+ Pose3d[] loggedEstimates = new Pose3d[poses.length];
for (int i = 0; i < poses.length; i++) {
- odometry.addVisionMeasurement(poses[i].estimatedPose.toPose2d(), poses[i].timestampSeconds);
- field2d.getObject("Cam-" + i + " Est Pose").setPose(poses[i].estimatedPose.toPose2d());
+ loggedEstimates[i] = poses[i].estimatedPose().estimatedPose;
+ odometry.addVisionMeasurement(
+ poses[i].estimatedPose().estimatedPose.toPose2d(),
+ poses[i].estimatedPose().timestampSeconds,
+ poses[i].standardDev());
+ field2d
+ .getObject("Cam " + i + " Est Pose")
+ .setPose(poses[i].estimatedPose().estimatedPose.toPose2d());
}
+ log("estimated poses", loggedEstimates);
}
@Override
public void periodic() {
odometry.update(Robot.isReal() ? gyro.getRotation2d() : simRotation, getModulePositions());
- field2d.setRobotPose(getPose());
+ field2d.setRobotPose(pose());
for (int i = 0; i < modules2d.length; i++) {
var module = modules.get(i);
var transform = new Transform2d(MODULE_OFFSET[i], module.position().angle);
- modules2d[i].setPose(getPose().transformBy(transform));
+ modules2d[i].setPose(pose().transformBy(transform));
}
+
+ log(
+ "turning target",
+ new Pose2d(pose().getTranslation(), new Rotation2d(rotationController.getSetpoint())));
+
+ log("command", Optional.ofNullable(getCurrentCommand()).map(Command::getName).orElse("none"));
+
+ modules.forEach(SwerveModule::updatePID);
}
@Override
@@ -280,50 +410,47 @@ public void simulationPeriodic() {
simRotation =
simRotation.rotateBy(
Rotation2d.fromRadians(
- getChassisSpeed().omegaRadiansPerSecond * Constants.PERIOD.in(Seconds)));
+ getRobotRelativeChassisSpeeds().omegaRadiansPerSecond
+ * Constants.PERIOD.in(Seconds)));
}
/** Stops drivetrain */
public Command stop() {
- return runOnce(() -> driveRobotRelative(new ChassisSpeeds()));
+ return runOnce(() -> setChassisSpeeds(new ChassisSpeeds(), ControlMode.OPEN_LOOP_VELOCITY));
}
/** Sets the drivetrain to an "X" configuration, preventing movement */
public Command lock() {
var front = new SwerveModuleState(0, Rotation2d.fromDegrees(45));
var back = new SwerveModuleState(0, Rotation2d.fromDegrees(-45));
- return run(() -> setModuleStates(new SwerveModuleState[] {front, back, back, front}));
- }
-
- /** Locks the drive motors. */
- private Command lockDriveMotors() {
- return Commands.run(() -> modules.forEach(m -> m.updateDriveSpeed(0)));
- }
-
- /** Locks the turn motors. */
- private Command lockTurnMotors() {
- return Commands.run(
- () -> modules.forEach(m -> m.updateTurnRotation(Rotation2d.fromDegrees(0))));
- }
-
- /** Runs the drive quasistatic SysId while locking turn motors. */
- public Command driveSysIdQuasistatic(SysIdRoutine.Direction direction) {
- return driveRoutine.quasistatic(direction).deadlineWith(lockTurnMotors());
- }
-
- /** Runs the drive dynamic SysId while locking turn motors. */
- public Command driveSysIdDynamic(SysIdRoutine.Direction direction) {
- return driveRoutine.dynamic(direction).deadlineWith(lockTurnMotors());
- }
-
- /** Runs the turn quasistatic SysId while locking drive motors. */
- public Command turnSysIdQuasistatic(SysIdRoutine.Direction direction) {
- return turnRoutine.quasistatic(direction).deadlineWith(lockDriveMotors());
+ return run(
+ () ->
+ setModuleStates(
+ new SwerveModuleState[] {front, back, back, front},
+ ControlMode.OPEN_LOOP_VELOCITY,
+ 1));
}
- /** Runs the turn dynamic SysId while locking drive motors. */
- public Command turnSysIdDynamic(SysIdRoutine.Direction direction) {
- return turnRoutine.dynamic(direction).deadlineWith(lockDriveMotors());
+ public Command systemsCheck() {
+ ChassisSpeeds speeds = new ChassisSpeeds(1, 1, 0);
+ return run(() -> setChassisSpeeds(speeds, ControlMode.OPEN_LOOP_VELOCITY))
+ .withTimeout(0.5)
+ .finallyDo(
+ () -> {
+ modules.forEach(
+ m -> {
+ assertEqualsReport(
+ "Drive Syst Check Module Angle (degrees)",
+ 45,
+ Units.radiansToDegrees(atan(m.position().angle.getTan())),
+ 1);
+ assertReport(
+ m.state().speedMetersPerSecond * Math.signum(m.position().angle.getCos())
+ > 1,
+ "Drive Syst Check Module Speed",
+ "expected: >= 1; actual: " + m.state().speedMetersPerSecond);
+ });
+ });
}
public void close() throws Exception {
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index 22f5a26..43841f7 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -2,8 +2,8 @@
import static edu.wpi.first.units.Units.*;
-import com.pathplanner.lib.path.PathConstraints;
import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.units.Angle;
import edu.wpi.first.units.Current;
@@ -13,17 +13,27 @@
import java.util.List;
public final class DriveConstants {
- public static final Measure> MAX_SPEED = MetersPerSecond.of(5.74);
- public static final Measure> MAX_ANGULAR_SPEED = RadiansPerSecond.of(2 * Math.PI);
- public static final Measure>> MAX_ACCEL =
- MetersPerSecondPerSecond.of(8);
- public static final Measure>> MAX_ANGULAR_ACCEL =
- RadiansPerSecond.per(Second).of(2 * Math.PI);
-
// Distance between centers of right and left wheels on robot
public static final Measure TRACK_WIDTH = Meters.of(0.5715);
// Distance between front and back wheels on robot
public static final Measure WHEEL_BASE = Meters.of(0.5715);
+ // Distance from the center to any wheel of the robot
+ public static final Measure RADIUS = TRACK_WIDTH.divide(2).times(Math.sqrt(2));
+ // Robot width with bumpers
+ public static final Measure CHASSIS_WIDTH = Inches.of(32.645);
+
+ // Maximum achievable translational and rotation velocities and accelerations of the robot.
+ public static final Measure> MAX_SPEED = MetersPerSecond.of(5.74);
+ public static final Measure>> MAX_ACCEL =
+ MetersPerSecondPerSecond.of(16.0);
+ public static final Measure> MAX_ANGULAR_SPEED =
+ RadiansPerSecond.of(MAX_SPEED.in(MetersPerSecond) / RADIUS.in(Meters));
+ public static final Measure>> MAX_ANGULAR_ACCEL =
+ RadiansPerSecond.per(Second).of(MAX_ACCEL.in(MetersPerSecondPerSecond) / RADIUS.in(Meters));
+
+ // Arbitrary max rotational velocity for the driver to effectively control the robot
+ public static final Measure> TELEOP_ANGULAR_SPEED =
+ Radians.per(Second).of(2 * Math.PI);
public static final Translation2d[] MODULE_OFFSET = {
new Translation2d(WHEEL_BASE.divide(2), TRACK_WIDTH.divide(2)), // front left
@@ -42,24 +52,23 @@ public final class DriveConstants {
Rotation2d.fromRadians(Math.PI / 2) // rear right
);
+ public static final Rotation3d GYRO_OFFSET = new Rotation3d(0, 0, Math.PI);
+
public static final class Translation {
- public static final double P = 0.6;
+ public static final double P = 3.0;
public static final double I = 0.0;
- public static final double D = 0.0;
+ public static final double D = 0.05;
+
+ public static final Measure TOLERANCE = Centimeters.of(5);
}
public static final class Rotation {
- public static final double P = 0.4;
+ public static final double P = 4.5;
public static final double I = 0.0;
- public static final double D = 0.0;
- }
+ public static final double D = 0.05;
- public static final PathConstraints CONSTRAINTS =
- new PathConstraints(
- MAX_SPEED.in(MetersPerSecond),
- MAX_ACCEL.in(MetersPerSecondPerSecond),
- MAX_ANGULAR_SPEED.in(RadiansPerSecond),
- MAX_ANGULAR_ACCEL.in(RadiansPerSecond.per(Second)));
+ public static final Measure TOLERANCE = Degrees.of(3);
+ }
public static final class ModuleConstants {
public static final double COUPLING_RATIO = 0;
@@ -74,22 +83,27 @@ public static final class Driving {
// bevel pinion
public static final double GEARING = 1.0 / 45.0 / 22.0 * 15.0 * 14.0;
- public static final Measure POSITION_FACTOR =
- Rotations.of(GEARING).times(CIRCUMFERENCE.in(Meters));
- public static final Measure> VELOCITY_FACTOR = POSITION_FACTOR.per(Minute);
+ public static final Measure POSITION_FACTOR = CIRCUMFERENCE.times(GEARING);
+ public static final Measure> VELOCITY_FACTOR = POSITION_FACTOR.per(Minute);
- public static final Measure CURRENT_LIMIT = Amps.of(50);
+ public static final Measure CURRENT_LIMIT = Amps.of(60);
public static final class PID {
- public static final double P = 1;
+ public static final double P = 3.2;
public static final double I = 0.0;
public static final double D = 0.0;
}
public static final class FF {
- public static final double S = 0.3;
- public static final double V = 2.7;
- public static final double A = 0.25;
+ // s: 0.21474, 0.23963, 0.16188, 0.13714
+ // v: 2.115, 2.0681, 2.1498, 2.0948
+ // a linear: 0.17586, 0.13707, 0.23915, 0.26842
+ // a rotation: 0.37587, 0.20079
+ // 2 has 0.55 R^2
+ public static final double S = 0.23963;
+ public static final double V = 2.0681;
+ public static final double kA_linear = 0.205;
+ public static final double kA_angular = 0.376;
}
}
@@ -105,16 +119,16 @@ static final class Turning {
public static final Measure CURRENT_LIMIT = Amps.of(20);
public static final class PID {
- public static final double P = 2;
+ public static final double P = 9;
public static final double I = 0.0;
- public static final double D = 0.0;
+ public static final double D = 0.05;
}
// system constants only used in simulation
public static final class FF {
- public static final double S = 0.0;
- public static final double V = 0.25;
- public static final double A = 0.015;
+ public static final double S = 0.30817;
+ public static final double V = 0.55;
+ public static final double A = 0.03;
}
}
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/FlexModule.java b/src/main/java/org/sciborgs1155/robot/drive/FlexModule.java
deleted file mode 100644
index 78ed668..0000000
--- a/src/main/java/org/sciborgs1155/robot/drive/FlexModule.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package org.sciborgs1155.robot.drive;
-
-import static edu.wpi.first.units.Units.*;
-import static org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.COUPLING_RATIO;
-
-import com.revrobotics.CANSparkBase;
-import com.revrobotics.CANSparkBase.IdleMode;
-import com.revrobotics.CANSparkFlex;
-import com.revrobotics.CANSparkLowLevel.MotorType;
-import com.revrobotics.CANSparkMax;
-import com.revrobotics.RelativeEncoder;
-import com.revrobotics.SparkAbsoluteEncoder;
-import com.revrobotics.SparkAbsoluteEncoder.Type;
-import edu.wpi.first.math.geometry.Rotation2d;
-import java.util.Set;
-import org.sciborgs1155.lib.FaultLogger;
-import org.sciborgs1155.lib.SparkUtils;
-import org.sciborgs1155.lib.SparkUtils.Data;
-import org.sciborgs1155.lib.SparkUtils.Sensor;
-import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
-import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
-
-public class FlexModule implements ModuleIO {
-
- private final CANSparkBase driveMotor; // Neo Vortex
- private final CANSparkBase turnMotor; // Neo 550
-
- private final RelativeEncoder driveEncoder;
- private final SparkAbsoluteEncoder turningEncoder;
-
- private final Rotation2d angularOffset;
-
- /**
- * Constructs a SwerveModule for rev's MAX Swerve.
- *
- * @param drivePort drive motor port
- * @param turnPort turning motor port
- */
- public FlexModule(int drivePort, int turnPort, Rotation2d angularOffset) {
- driveMotor = new CANSparkFlex(drivePort, MotorType.kBrushless);
- driveMotor.restoreFactoryDefaults();
- driveMotor.setInverted(false);
- driveMotor.setIdleMode(IdleMode.kBrake);
- driveMotor.setSmartCurrentLimit((int) Driving.CURRENT_LIMIT.in(Amps));
-
- turnMotor = new CANSparkMax(turnPort, MotorType.kBrushless);
- turnMotor.restoreFactoryDefaults();
- turnMotor.setInverted(false);
- turnMotor.setIdleMode(IdleMode.kBrake);
- turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps));
-
- driveEncoder = driveMotor.getEncoder();
- driveEncoder.setPositionConversionFactor(Driving.POSITION_FACTOR.in(Radians));
- driveEncoder.setVelocityConversionFactor(Driving.VELOCITY_FACTOR.in(RadiansPerSecond));
-
- turningEncoder = turnMotor.getAbsoluteEncoder(Type.kDutyCycle);
- turningEncoder.setInverted(Turning.ENCODER_INVERTED);
- turningEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians));
- turningEncoder.setVelocityConversionFactor(Turning.VELOCITY_FACTOR.in(RadiansPerSecond));
-
- SparkUtils.configureFrameStrategy(
- driveMotor,
- Set.of(Data.POSITION, Data.VELOCITY, Data.VOLTAGE),
- Set.of(Sensor.INTEGRATED),
- false);
- SparkUtils.configureFrameStrategy(
- turnMotor,
- Set.of(Data.POSITION, Data.VELOCITY, Data.VOLTAGE),
- Set.of(Sensor.DUTY_CYCLE),
- false);
-
- FaultLogger.register(driveMotor);
- FaultLogger.register(turnMotor);
-
- driveMotor.burnFlash();
- turnMotor.burnFlash();
-
- resetEncoders();
-
- this.angularOffset = angularOffset;
- }
-
- @Override
- public void setDriveVoltage(double voltage) {
- driveMotor.setVoltage(voltage);
- }
-
- @Override
- public void setTurnVoltage(double voltage) {
- turnMotor.setVoltage(voltage);
- }
-
- @Override
- public double getDrivePosition() {
- double driveRot = driveEncoder.getPosition();
- // account for rotation of turn motor on rotation of drive motor
- driveRot -= turningEncoder.getPosition() * COUPLING_RATIO;
- return driveRot;
- }
-
- @Override
- public double getDriveVelocity() {
- return driveEncoder.getVelocity();
- }
-
- @Override
- public Rotation2d getRotation() {
- return Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset);
- }
-
- @Override
- public void resetEncoders() {
- driveEncoder.setPosition(0);
- }
-
- @Override
- public void close() {
- driveMotor.close();
- turnMotor.close();
- }
-}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java b/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
index d558b9c..adb8efe 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
@@ -1,66 +1,24 @@
package org.sciborgs1155.robot.drive;
-import com.kauailabs.navx.frc.AHRS;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Rotation3d;
/** Generalized gyroscope. Pigeon2, Navx, and SimGyro are to be implemented */
public interface GyroIO extends AutoCloseable {
/** Calibrates the gyroscope. Pigeon2 does not need to do anything here. */
- public default void calibrate() {}
+ default void calibrate() {}
/** Returns the rate of rotation. */
- public double getRate();
+ double getRate();
/** Returns the heading of the robot as a Rotation2d. */
- public default Rotation2d getRotation2d() {
+ default Rotation2d getRotation2d() {
return getRotation3d().toRotation2d();
}
/** Returns the heading of the robot as a Rotation3d. */
- public Rotation3d getRotation3d();
+ Rotation3d getRotation3d();
/** Resets heading to 0 */
- public void reset();
-
- /** GyroIO implementation for NavX */
- public class NavX implements GyroIO {
- private final AHRS ahrs = new AHRS();
-
- public double getRate() {
- return ahrs.getRate();
- }
-
- @Override
- public Rotation3d getRotation3d() {
- return ahrs.getRotation3d();
- }
-
- public void reset() {
- ahrs.reset();
- }
-
- @Override
- public void close() throws Exception {}
- }
-
- /** GyroIO implementation for nonexistent gyro */
- public class NoGyro implements GyroIO {
-
- @Override
- public void close() throws Exception {}
-
- @Override
- public double getRate() {
- return 0;
- }
-
- @Override
- public Rotation3d getRotation3d() {
- return new Rotation3d();
- }
-
- @Override
- public void reset() {}
- }
+ void reset();
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
index d61a9d0..6fcd2ac 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
@@ -10,39 +10,39 @@ public interface ModuleIO extends AutoCloseable, Logged {
*
* @param voltage The voltage to inputted into the drive motor.
*/
- public void setDriveVoltage(double voltage);
+ void setDriveVoltage(double voltage);
/**
* Sets the turn voltage of the module.
*
* @param voltage The voltage to inputted into the turn motor.
*/
- public void setTurnVoltage(double voltage);
+ void setTurnVoltage(double voltage);
/**
* Returns the distance the wheel traveled.
*
* @return The drive encoder position value, in radians.
*/
- public double getDrivePosition();
+ double drivePosition();
/**
* Returns the current velocity of the wheel.
*
* @return The drive encoder velocity value, in radians / seconds.
*/
- public double getDriveVelocity();
+ double driveVelocity();
/**
* Returns the angular position of the module.
*
* @return The adjusted turn encoder position value, in radians.
*/
- public Rotation2d getRotation();
+ Rotation2d rotation();
/** Resets all encoders. */
- public void resetEncoders();
+ void resetEncoders();
@Override
- public void close();
+ void close();
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java b/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java
new file mode 100644
index 0000000..e2b1fcd
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java
@@ -0,0 +1,32 @@
+package org.sciborgs1155.robot.drive;
+
+import com.kauailabs.navx.frc.AHRS;
+import edu.wpi.first.math.geometry.Rotation3d;
+import org.sciborgs1155.lib.FaultLogger;
+
+/** GyroIO implementation for NavX */
+public class NavXGyro implements GyroIO {
+ private final AHRS ahrs = new AHRS();
+
+ public NavXGyro() {
+ FaultLogger.register(ahrs);
+ }
+
+ @Override
+ public double getRate() {
+ return ahrs.getRate();
+ }
+
+ @Override
+ public Rotation3d getRotation3d() {
+ return ahrs.getRotation3d();
+ }
+
+ @Override
+ public void reset() {
+ ahrs.reset();
+ }
+
+ @Override
+ public void close() throws Exception {}
+}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java b/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java
new file mode 100644
index 0000000..e7ee93b
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java
@@ -0,0 +1,24 @@
+package org.sciborgs1155.robot.drive;
+
+import edu.wpi.first.math.geometry.Rotation3d;
+
+/** GyroIO implementation for nonexistent gyro */
+public class NoGyro implements GyroIO {
+ private final Rotation3d rotation = new Rotation3d();
+
+ @Override
+ public void close() throws Exception {}
+
+ @Override
+ public double getRate() {
+ return 0;
+ }
+
+ @Override
+ public Rotation3d getRotation3d() {
+ return rotation;
+ }
+
+ @Override
+ public void reset() {}
+}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
new file mode 100644
index 0000000..9f4d186
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
@@ -0,0 +1,33 @@
+package org.sciborgs1155.robot.drive;
+
+import edu.wpi.first.math.geometry.Rotation2d;
+
+public class NoModule implements ModuleIO {
+
+ @Override
+ public void setDriveVoltage(double voltage) {}
+
+ @Override
+ public void setTurnVoltage(double voltage) {}
+
+ @Override
+ public double drivePosition() {
+ return 0;
+ }
+
+ @Override
+ public double driveVelocity() {
+ return 0;
+ }
+
+ @Override
+ public Rotation2d rotation() {
+ return new Rotation2d();
+ }
+
+ @Override
+ public void resetEncoders() {}
+
+ @Override
+ public void close() {}
+}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index d74e723..a63df74 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -1,7 +1,6 @@
package org.sciborgs1155.robot.drive;
import static edu.wpi.first.units.Units.Seconds;
-import static org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.*;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.geometry.Rotation2d;
@@ -9,19 +8,21 @@
import edu.wpi.first.math.system.plant.LinearSystemId;
import edu.wpi.first.wpilibj.simulation.DCMotorSim;
import org.sciborgs1155.robot.Constants;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
public class SimModule implements ModuleIO {
private final DCMotorSim drive =
new DCMotorSim(
- LinearSystemId.createDCMotorSystem(Driving.FF.V, Driving.FF.A),
+ LinearSystemId.createDCMotorSystem(Driving.FF.V, Driving.FF.kA_linear),
DCMotor.getNeoVortex(1),
- Driving.GEARING);
+ 1 / Driving.GEARING);
private final DCMotorSim turn =
new DCMotorSim(
LinearSystemId.createDCMotorSystem(Turning.FF.V, Turning.FF.A),
DCMotor.getNeo550(1),
- Turning.MOTOR_GEARING);
+ 1 / Turning.MOTOR_GEARING);
@Override
public void setDriveVoltage(double voltage) {
@@ -36,17 +37,17 @@ public void setTurnVoltage(double voltage) {
}
@Override
- public double getDrivePosition() {
+ public double drivePosition() {
return drive.getAngularPositionRad();
}
@Override
- public double getDriveVelocity() {
+ public double driveVelocity() {
return drive.getAngularVelocityRadPerSec();
}
@Override
- public Rotation2d getRotation() {
+ public Rotation2d rotation() {
return Rotation2d.fromRadians(turn.getAngularPositionRad());
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
new file mode 100644
index 0000000..2908c14
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -0,0 +1,143 @@
+package org.sciborgs1155.robot.drive;
+
+import static edu.wpi.first.units.Units.*;
+import static org.sciborgs1155.lib.FaultLogger.*;
+import static org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.COUPLING_RATIO;
+
+import com.revrobotics.CANSparkBase.IdleMode;
+import com.revrobotics.CANSparkFlex;
+import com.revrobotics.CANSparkLowLevel.MotorType;
+import com.revrobotics.CANSparkMax;
+import com.revrobotics.RelativeEncoder;
+import com.revrobotics.SparkAbsoluteEncoder;
+import edu.wpi.first.math.geometry.Rotation2d;
+import java.util.Set;
+import org.sciborgs1155.lib.SparkUtils;
+import org.sciborgs1155.lib.SparkUtils.Data;
+import org.sciborgs1155.lib.SparkUtils.Sensor;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
+
+public class SparkModule implements ModuleIO {
+
+ private final CANSparkFlex driveMotor; // Neo Vortex
+ private final CANSparkMax turnMotor; // Neo 550
+
+ private final RelativeEncoder driveEncoder;
+ private final SparkAbsoluteEncoder turningEncoder;
+
+ private final Rotation2d angularOffset;
+
+ private double lastPosition;
+ private double lastVelocity;
+
+ /**
+ * Constructs a SwerveModule for rev's MAX Swerve.
+ *
+ * @param drivePort drive motor port
+ * @param turnPort turning motor port
+ */
+ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset) {
+ driveMotor = new CANSparkFlex(drivePort, MotorType.kBrushless);
+ driveEncoder = driveMotor.getEncoder();
+
+ check(driveMotor, driveMotor.restoreFactoryDefaults());
+ check(driveMotor, driveMotor.setIdleMode(IdleMode.kBrake));
+ check(driveMotor, driveMotor.setSmartCurrentLimit((int) Driving.CURRENT_LIMIT.in(Amps)));
+ check(driveMotor, driveEncoder.setPositionConversionFactor(Driving.POSITION_FACTOR.in(Meters)));
+ check(
+ driveMotor,
+ driveEncoder.setVelocityConversionFactor(Driving.VELOCITY_FACTOR.in(MetersPerSecond)));
+ check(driveMotor, driveEncoder.setAverageDepth(16));
+ check(driveMotor, driveEncoder.setMeasurementPeriod(32));
+ check(
+ driveMotor,
+ SparkUtils.configureFrameStrategy(
+ driveMotor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.INTEGRATED),
+ false));
+ check(driveMotor, driveMotor.burnFlash());
+
+ turnMotor = new CANSparkMax(turnPort, MotorType.kBrushless);
+ turningEncoder = turnMotor.getAbsoluteEncoder();
+
+ check(turnMotor, turnMotor.restoreFactoryDefaults());
+ check(turnMotor, turnMotor.setIdleMode(IdleMode.kBrake));
+ check(turnMotor, turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps)));
+ turningEncoder.setInverted(Turning.ENCODER_INVERTED);
+ check(turnMotor);
+ check(
+ turnMotor, turningEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians)));
+ check(
+ turnMotor,
+ turningEncoder.setVelocityConversionFactor(Turning.VELOCITY_FACTOR.in(RadiansPerSecond)));
+ check(turnMotor, turningEncoder.setAverageDepth(2));
+ check(
+ turnMotor,
+ SparkUtils.configureFrameStrategy(
+ turnMotor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.ABSOLUTE),
+ false));
+ SparkUtils.addChecker(
+ () ->
+ check(
+ turnMotor,
+ SparkUtils.configureFrameStrategy(
+ turnMotor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.ABSOLUTE),
+ false)));
+ check(turnMotor, turnMotor.burnFlash());
+
+ register(driveMotor);
+ register(turnMotor);
+
+ resetEncoders();
+
+ this.angularOffset = angularOffset;
+ }
+
+ @Override
+ public void setDriveVoltage(double voltage) {
+ driveMotor.setVoltage(voltage);
+ check(driveMotor);
+ log("current", driveMotor.getOutputCurrent());
+ }
+
+ @Override
+ public void setTurnVoltage(double voltage) {
+ turnMotor.setVoltage(voltage);
+ check(turnMotor);
+ }
+
+ @Override
+ public double drivePosition() {
+ lastPosition = SparkUtils.wrapCall(driveMotor, driveEncoder.getPosition()).orElse(lastPosition);
+ // account for rotation of turn motor on rotation of drive motor
+ return lastPosition - turningEncoder.getPosition() * COUPLING_RATIO;
+ }
+
+ @Override
+ public double driveVelocity() {
+ lastVelocity = SparkUtils.wrapCall(driveMotor, driveEncoder.getVelocity()).orElse(lastVelocity);
+ return lastVelocity;
+ }
+
+ @Override
+ public Rotation2d rotation() {
+ return Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset);
+ }
+
+ @Override
+ public void resetEncoders() {
+ driveEncoder.setPosition(0);
+ }
+
+ @Override
+ public void close() {
+ driveMotor.close();
+ turnMotor.close();
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java b/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java
index 464f357..19321df 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java
@@ -1,42 +1,66 @@
package org.sciborgs1155.robot.drive;
-import static org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.*;
+import static edu.wpi.first.units.Units.Seconds;
+import static org.sciborgs1155.robot.Constants.PERIOD;
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.DoubleEntry;
import monologue.Annotations.Log;
import monologue.Logged;
+import org.sciborgs1155.lib.Tuning;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
/** Class to encapsulate a REV Max Swerve module */
public class SwerveModule implements Logged, AutoCloseable {
- private final ModuleIO module;
- @Log.NT private final PIDController driveFeedback;
- @Log.NT private final PIDController turnFeedback;
+ /** The method to use when controlling the drive motor. */
+ public static enum ControlMode {
+ CLOSED_LOOP_VELOCITY,
+ OPEN_LOOP_VELOCITY;
+ }
+
+ private final ModuleIO hardware;
+
+ private final PIDController driveFeedback;
+ private final PIDController turnFeedback;
- private final SimpleMotorFeedforward driveFeedforward;
+ private final SimpleMotorFeedforward driveTranslationFeedforward;
+ private final SimpleMotorFeedforward driveRotationFeedforward;
private SwerveModuleState setpoint = new SwerveModuleState();
public final String name;
+ private final DoubleEntry drivingD = Tuning.entry("/Robot/drive/driving/D", Driving.PID.D);
+ private final DoubleEntry drivingI = Tuning.entry("/Robot/drive/driving/I", Driving.PID.I);
+ private final DoubleEntry drivingP = Tuning.entry("/Robot/drive/driving/P", Driving.PID.P);
+
+ private final DoubleEntry turningD = Tuning.entry("/Robot/drive/turning/D", Turning.PID.D);
+ private final DoubleEntry turningI = Tuning.entry("/Robot/drive/turning/I", Turning.PID.I);
+ private final DoubleEntry turningP = Tuning.entry("/Robot/drive/turning/P", Turning.PID.P);
+
/**
* Constructs a SwerveModule for rev's MAX Swerve using vortexes (flex) or krakens (talon).
*
- * @param module talon OR flex swerve module
+ * @param hardware talon OR flex swerve module
* @param angularOffset offset from drivetrain
*/
- public SwerveModule(ModuleIO module, Rotation2d angularOffset, String name) {
- this.module = module;
+ public SwerveModule(ModuleIO hardware, Rotation2d angularOffset, String name) {
+ this.hardware = hardware;
this.name = name;
driveFeedback = new PIDController(Driving.PID.P, Driving.PID.I, Driving.PID.D);
turnFeedback = new PIDController(Turning.PID.P, Turning.PID.I, Turning.PID.D);
turnFeedback.enableContinuousInput(-Math.PI, Math.PI);
- driveFeedforward = new SimpleMotorFeedforward(Driving.FF.S, Driving.FF.V, Driving.FF.A);
+ driveTranslationFeedforward =
+ new SimpleMotorFeedforward(Driving.FF.S, Driving.FF.V, Driving.FF.kA_linear);
+ driveRotationFeedforward =
+ new SimpleMotorFeedforward(Driving.FF.S, Driving.FF.V, Driving.FF.kA_angular);
setpoint = new SwerveModuleState();
}
@@ -48,7 +72,7 @@ public SwerveModule(ModuleIO module, Rotation2d angularOffset, String name) {
*/
@Log.NT
public SwerveModuleState state() {
- return new SwerveModuleState(module.getDriveVelocity(), module.getRotation());
+ return new SwerveModuleState(hardware.driveVelocity(), hardware.rotation());
}
/**
@@ -58,12 +82,7 @@ public SwerveModuleState state() {
*/
@Log.NT
public SwerveModulePosition position() {
- return new SwerveModulePosition(module.getDrivePosition(), module.getRotation());
- }
-
- @Log.NT
- public SwerveModuleState setpoint() {
- return setpoint;
+ return new SwerveModulePosition(hardware.drivePosition(), hardware.rotation());
}
/**
@@ -71,39 +90,71 @@ public SwerveModuleState setpoint() {
*
* This method should be called periodically.
*
- * @param desiredState The desired state of the module.
+ * @param setpoint The desired state of the module.
+ * @param mode The control mode to use when calculating drive voltage.
+ * @param movementRatio The ratio of translational velocity to the sum of rotational and
+ * translational velocity being requested of the entire swerve drive. 1 for only translation,
*/
- public void updateDesiredState(SwerveModuleState desiredState) {
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double movementRatio) {
// Optimize the reference state to avoid spinning further than 90 degrees
- setpoint = SwerveModuleState.optimize(desiredState, module.getRotation());
- updateDriveSpeed(setpoint.speedMetersPerSecond);
- updateTurnRotation(setpoint.angle);
+ setpoint = SwerveModuleState.optimize(setpoint, hardware.rotation());
+ // Scale setpoint by cos of turning error to reduce tread wear
+ setpoint.speedMetersPerSecond *= setpoint.angle.minus(hardware.rotation()).getCos();
+
+ // Calculate two feedforward values for using different kA depending on if the robot is rotating
+ // or translating.
+ double driveTVolts =
+ switch (mode) {
+ case CLOSED_LOOP_VELOCITY -> driveTranslationFeedforward.calculate(
+ this.setpoint.speedMetersPerSecond,
+ setpoint.speedMetersPerSecond,
+ PERIOD.in(Seconds));
+ case OPEN_LOOP_VELOCITY -> driveTranslationFeedforward.calculate(
+ setpoint.speedMetersPerSecond);
+ };
+
+ double driveRVolts =
+ switch (mode) {
+ case CLOSED_LOOP_VELOCITY -> driveRotationFeedforward.calculate(
+ this.setpoint.speedMetersPerSecond,
+ setpoint.speedMetersPerSecond,
+ PERIOD.in(Seconds));
+ case OPEN_LOOP_VELOCITY -> driveRotationFeedforward.calculate(
+ setpoint.speedMetersPerSecond);
+ };
+
+ double driveVolts = driveTVolts * movementRatio + driveRVolts * (1 - movementRatio);
+
+ if (mode == ControlMode.CLOSED_LOOP_VELOCITY) {
+ driveVolts +=
+ driveFeedback.calculate(hardware.driveVelocity(), setpoint.speedMetersPerSecond);
+ }
+
+ double turnVolts =
+ turnFeedback.calculate(hardware.rotation().getRadians(), setpoint.angle.getRadians());
+
+ hardware.setDriveVoltage(driveVolts);
+ hardware.setTurnVoltage(turnVolts);
+
+ this.setpoint = setpoint;
}
/**
- * Updates drive controller based on setpoint.
+ * Updates the drive voltage and turn angle.
*
- *
This is only used for Sysid.
+ *
This is useful for SysId characterization, but should never be run otherwise.
*
- * @param speed The desired speed of the module.
+ * @param angle The desired angle of the module.
+ * @param voltage The voltage to supply to the drive motor.
*/
- void updateDriveSpeed(double speed) {
- double driveFF = driveFeedforward.calculate(speed);
- double driveVoltage = driveFF + driveFeedback.calculate(module.getDriveVelocity(), speed);
- module.setDriveVoltage(driveVoltage);
- }
+ public void updateDriveVoltage(Rotation2d angle, double voltage) {
+ setpoint.angle = angle;
- /**
- * Updates turn controller based on setpoint.
- *
- *
This is only used for Sysid.
- *
- * @param rotation The desired rotation of the module.
- */
- void updateTurnRotation(Rotation2d rotation) {
- double turnVoltage =
- turnFeedback.calculate(module.getRotation().getRadians(), rotation.getRadians());
- module.setTurnVoltage(turnVoltage);
+ double turnVolts =
+ turnFeedback.calculate(hardware.rotation().getRadians(), setpoint.angle.getRadians());
+
+ hardware.setDriveVoltage(voltage);
+ hardware.setTurnVoltage(turnVolts);
}
@Log.NT
@@ -111,20 +162,17 @@ public SwerveModuleState desiredState() {
return setpoint;
}
- public void setDriveVoltage(double voltage) {
- module.setDriveVoltage(voltage);
- }
-
- public void setTurnVoltage(double voltage) {
- module.setTurnVoltage(voltage);
+ public void resetEncoders() {
+ hardware.resetEncoders();
}
- public void resetEncoders() {
- module.resetEncoders();
+ public void updatePID() {
+ driveFeedback.setPID(drivingP.get(), drivingI.get(), drivingD.get());
+ turnFeedback.setPID(turningP.get(), turningI.get(), turningD.get());
}
@Override
public void close() {
- module.close();
+ hardware.close();
}
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index 843369f..7a49e22 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -15,6 +15,7 @@
import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
+import org.sciborgs1155.lib.TalonUtils;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
/** Class to encapsulate a CTRE Talon Swerve module */
@@ -28,16 +29,13 @@ public TalonModule(int drivePort, int turnPort) {
driveMotor = new TalonFX(drivePort);
turnMotor = new CANSparkMax(turnPort, MotorType.kBrushless);
- resetEncoders();
-
- driveMotor.getPosition().setUpdateFrequency(100);
- driveMotor.getVelocity().setUpdateFrequency(100);
-
turnMotor.restoreFactoryDefaults();
- turnMotor.setInverted(false);
turnMotor.setIdleMode(IdleMode.kBrake);
turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps));
+ driveMotor.getPosition().setUpdateFrequency(100);
+ driveMotor.getVelocity().setUpdateFrequency(100);
+
turnEncoder = turnMotor.getAbsoluteEncoder(Type.kDutyCycle);
turnEncoder.setInverted(Turning.ENCODER_INVERTED);
turnEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians));
@@ -45,14 +43,19 @@ public TalonModule(int drivePort, int turnPort) {
SparkUtils.configureFrameStrategy(
turnMotor,
- Set.of(Data.POSITION, Data.VELOCITY, Data.VOLTAGE),
- Set.of(Sensor.DUTY_CYCLE),
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.ABSOLUTE),
false);
TalonFXConfiguration toApply = new TalonFXConfiguration();
toApply.MotorOutput.NeutralMode = NeutralModeValue.Brake;
toApply.CurrentLimits.SupplyCurrentLimit = 50;
driveMotor.getConfigurator().apply(toApply);
+
+ TalonUtils.addMotor(driveMotor);
+ resetEncoders();
+
+ turnMotor.burnFlash();
}
@Override
@@ -66,17 +69,17 @@ public void setTurnVoltage(double voltage) {
}
@Override
- public double getDrivePosition() {
+ public double drivePosition() {
return driveMotor.getPosition().getValueAsDouble();
}
@Override
- public double getDriveVelocity() {
+ public double driveVelocity() {
return driveMotor.getVelocity().getValueAsDouble();
}
@Override
- public Rotation2d getRotation() {
+ public Rotation2d rotation() {
return Rotation2d.fromRadians(turnEncoder.getPosition());
}
diff --git a/src/main/java/org/sciborgs1155/robot/vision/Vision.java b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
new file mode 100644
index 0000000..081a41a
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
@@ -0,0 +1,181 @@
+package org.sciborgs1155.robot.vision;
+
+import static org.sciborgs1155.robot.vision.VisionConstants.*;
+
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.geometry.Pose3d;
+import edu.wpi.first.math.geometry.Transform3d;
+import edu.wpi.first.math.numbers.N1;
+import edu.wpi.first.math.numbers.N3;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import monologue.Annotations.Log;
+import monologue.Logged;
+import org.photonvision.EstimatedRobotPose;
+import org.photonvision.PhotonCamera;
+import org.photonvision.PhotonPoseEstimator;
+import org.photonvision.PhotonPoseEstimator.PoseStrategy;
+import org.photonvision.simulation.PhotonCameraSim;
+import org.photonvision.simulation.SimCameraProperties;
+import org.photonvision.simulation.VisionSystemSim;
+import org.photonvision.targeting.PhotonPipelineResult;
+import org.photonvision.targeting.PhotonTrackedTarget;
+import org.sciborgs1155.lib.FaultLogger;
+import org.sciborgs1155.robot.Constants.Field;
+import org.sciborgs1155.robot.Robot;
+
+public class Vision implements Logged {
+ public static record CameraConfig(String name, Transform3d robotToCam) {}
+
+ public static record PoseEstimate(EstimatedRobotPose estimatedPose, Matrix standardDev) {}
+
+ private final PhotonCamera[] cameras;
+ private final PhotonPoseEstimator[] estimators;
+ private final PhotonCameraSim[] simCameras;
+
+ private VisionSystemSim visionSim;
+
+ /** A factory to create new vision classes with our two configured cameras */
+ public static Vision create() {
+ return new Vision(CAMERAS);
+ }
+
+ public Vision(CameraConfig... configs) {
+ cameras = new PhotonCamera[configs.length];
+ estimators = new PhotonPoseEstimator[configs.length];
+ simCameras = new PhotonCameraSim[configs.length];
+
+ for (int i = 0; i < configs.length; i++) {
+ PhotonCamera camera = new PhotonCamera(configs[i].name());
+ PhotonPoseEstimator estimator =
+ new PhotonPoseEstimator(
+ VisionConstants.TAG_LAYOUT,
+ PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR,
+ camera,
+ configs[i].robotToCam());
+
+ estimator.setMultiTagFallbackStrategy(PoseStrategy.LOWEST_AMBIGUITY);
+ cameras[i] = camera;
+ estimators[i] = estimator;
+
+ FaultLogger.register(camera);
+ }
+
+ if (Robot.isSimulation()) {
+ visionSim = new VisionSystemSim("main");
+ visionSim.addAprilTags(VisionConstants.TAG_LAYOUT);
+
+ for (int i = 0; i < cameras.length; i++) {
+ var prop = new SimCameraProperties();
+ prop.setCalibration(WIDTH, HEIGHT, FOV);
+ prop.setCalibError(0.15, 0.05);
+ prop.setFPS(45);
+ prop.setAvgLatencyMs(12);
+ prop.setLatencyStdDevMs(3.5);
+
+ PhotonCameraSim cameraSim = new PhotonCameraSim(cameras[i], prop);
+ cameraSim.setMaxSightRange(5);
+ cameraSim.enableRawStream(true);
+ cameraSim.enableProcessedStream(true);
+ cameraSim.enableDrawWireframe(true);
+
+ visionSim.addCamera(cameraSim, configs[i].robotToCam());
+ simCameras[i] = cameraSim;
+ }
+ }
+ }
+
+ /**
+ * Returns a list of all currently visible pose estimates and their standard deviation vectors.
+ *
+ * @return An {@link EstimatedRobotPose} with an estimated pose, estimate timestamp, and targets
+ * used for estimation.
+ */
+ public PoseEstimate[] getEstimatedGlobalPoses() {
+ List estimates = new ArrayList<>();
+ for (int i = 0; i < estimators.length; i++) {
+ var result = cameras[i].getLatestResult();
+ var estimate = estimators[i].update(result);
+ log("estimates present " + i, estimate.isPresent());
+ estimate
+ .filter(
+ f ->
+ Field.inField(f.estimatedPose)
+ && Math.abs(f.estimatedPose.getZ()) < MAX_HEIGHT
+ && Math.abs(f.estimatedPose.getRotation().getX()) < MAX_ANGLE
+ && Math.abs(f.estimatedPose.getRotation().getY()) < MAX_ANGLE)
+ .ifPresent(
+ e ->
+ estimates.add(
+ new PoseEstimate(
+ e, getEstimationStdDevs(e.estimatedPose.toPose2d(), result))));
+ }
+ return estimates.toArray(PoseEstimate[]::new);
+ }
+
+ /**
+ * Returns the poses of all currently visible tags.
+ *
+ * @return An array of Pose3ds.
+ */
+ @Log.NT
+ public Pose3d[] getSeenTags() {
+ return Arrays.stream(cameras)
+ .flatMap(c -> c.getLatestResult().targets.stream())
+ .map(PhotonTrackedTarget::getFiducialId)
+ .map(TAG_LAYOUT::getTagPose)
+ .map(Optional::get)
+ .toArray(Pose3d[]::new);
+ }
+
+ /**
+ * The standard deviations of the estimated pose from {@link #getEstimatedGlobalPose()}, for use
+ * with {@link edu.wpi.first.math.estimator.SwerveDrivePoseEstimator SwerveDrivePoseEstimator}.
+ * This should only be used when there are targets visible.
+ *
+ * @param estimatedPose The estimated pose to guess standard deviations for.
+ */
+ public Matrix getEstimationStdDevs(
+ Pose2d estimatedPose, PhotonPipelineResult pipelineResult) {
+ var estStdDevs = VisionConstants.SINGLE_TAG_STD_DEVS;
+ var targets = pipelineResult.getTargets();
+ int numTags = 0;
+ double avgDist = 0;
+ double avgWeight = 0;
+ for (var tgt : targets) {
+ var tagPose = TAG_LAYOUT.getTagPose(tgt.getFiducialId());
+ if (tagPose.isEmpty()) continue;
+ numTags++;
+ avgDist +=
+ tagPose.get().toPose2d().getTranslation().getDistance(estimatedPose.getTranslation());
+ avgWeight += TAG_WEIGHTS[tgt.getFiducialId() - 1];
+ }
+ if (numTags == 0) return estStdDevs;
+
+ avgDist /= numTags;
+ avgWeight /= numTags;
+
+ // Decrease std devs if multiple targets are visibleX
+ if (numTags > 1) estStdDevs = VisionConstants.MULTIPLE_TAG_STD_DEVS;
+ // Increase std devs based on (average) distance
+ if (numTags == 1 && avgDist > 4)
+ estStdDevs = VecBuilder.fill(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
+ else estStdDevs = estStdDevs.times(1 + (avgDist * avgDist / 30));
+
+ estStdDevs = estStdDevs.times(avgWeight);
+
+ return estStdDevs;
+ }
+
+ /**
+ * Updates the vision field simulation. This method should not be called when code is running on
+ * the robot.
+ */
+ public void simulationPeriodic(Pose2d robotSimPose) {
+ visionSim.update(robotSimPose);
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
new file mode 100644
index 0000000..b116640
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
@@ -0,0 +1,64 @@
+package org.sciborgs1155.robot.vision;
+
+import static edu.wpi.first.units.Units.Inches;
+
+import edu.wpi.first.apriltag.AprilTagFieldLayout;
+import edu.wpi.first.apriltag.AprilTagFields;
+import edu.wpi.first.math.Matrix;
+import edu.wpi.first.math.VecBuilder;
+import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.geometry.Rotation3d;
+import edu.wpi.first.math.geometry.Transform3d;
+import edu.wpi.first.math.geometry.Translation3d;
+import edu.wpi.first.math.numbers.N1;
+import edu.wpi.first.math.numbers.N3;
+import org.sciborgs1155.robot.vision.Vision.CameraConfig;
+
+public class VisionConstants {
+ public static final AprilTagFieldLayout TAG_LAYOUT =
+ AprilTagFieldLayout.loadField(AprilTagFields.k2024Crescendo);
+
+ /* The front of the robot is defined at the intake */
+
+ public static final CameraConfig[] CAMERAS = {
+ new CameraConfig(
+ "back left",
+ new Transform3d(
+ new Translation3d(Inches.of(-10.74), Inches.of(11.617), Inches.of(17.25)),
+ new Rotation3d(0, -Math.PI / 6, 5 * Math.PI / 6))),
+ new CameraConfig(
+ "back right",
+ new Transform3d(
+ new Translation3d(Inches.of(-10.74), Inches.of(-11.617), Inches.of(17.25)),
+ new Rotation3d(0, -Math.PI / 6, -5 * Math.PI / 6))),
+ new CameraConfig(
+ "front left",
+ new Transform3d(
+ new Translation3d(-0.089, 0.3, 0.46),
+ new Rotation3d(Math.PI, -Math.PI / 6, Math.PI / 6))),
+ new CameraConfig(
+ "front right",
+ new Transform3d(
+ new Translation3d(-0.273, -0.3, 0.485), new Rotation3d(0, -Math.PI / 6, -Math.PI / 6)))
+ };
+
+ // OV9281 constants for our configuration
+ public static final int WIDTH = 1280;
+ public static final int HEIGHT = 800;
+ public static final Rotation2d FOV = Rotation2d.fromDegrees(70);
+
+ public static final Matrix SINGLE_TAG_STD_DEVS = VecBuilder.fill(1.5, 1.5, 7);
+ public static final Matrix MULTIPLE_TAG_STD_DEVS = VecBuilder.fill(0.3, 0.3, 4);
+
+ public static final double MAX_HEIGHT = 0.305;
+ public static final double MAX_ANGLE = 0.3;
+
+ // Total of 16 AprilTags
+ // https://firstfrc.blob.core.windows.net/frc2024/Manual/2024GameManual.pdf (page 35 and more)
+ // Tag Locations (1-16) | Source: 1,2,9,10 | Speaker: 3,4,7,8 | Amp: 5,6 | Stage 11,12,13,14,15,16
+ // First half of locations are on red side, second half on blue side
+ // (ex. source: 1,2 is red, 9,10)
+
+ // Unsure if getFudicialID() returns the tags from 1-16 or 0-15 (assuming 0-15)
+ public static final double[] TAG_WEIGHTS = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+}
diff --git a/src/test/java/org/sciborgs1155/lib/InputStreamTest.java b/src/test/java/org/sciborgs1155/lib/InputStreamTest.java
index b58cb62..e8ba4ac 100644
--- a/src/test/java/org/sciborgs1155/lib/InputStreamTest.java
+++ b/src/test/java/org/sciborgs1155/lib/InputStreamTest.java
@@ -20,8 +20,8 @@ void signedPow() {
@Test
void deadband() {
var stream = InputStream.of(this::two).negate().scale(0.25);
- assert stream.deadband(0.6, 1).get() == 0.0;
- assert stream.deadband(0.1, 1).get() == -0.4;
+ assertEquals(stream.deadband(0.6, 1).get(), 0.0);
+ assertEquals(stream.deadband(0.1, 1).get(), -0.444444, 0.01);
}
@Test
diff --git a/src/test/java/org/sciborgs1155/robot/RobotTest.java b/src/test/java/org/sciborgs1155/robot/RobotTest.java
index 1043b99..96dfa3f 100644
--- a/src/test/java/org/sciborgs1155/robot/RobotTest.java
+++ b/src/test/java/org/sciborgs1155/robot/RobotTest.java
@@ -1,10 +1,12 @@
package org.sciborgs1155.robot;
import org.junit.jupiter.api.Test;
+import org.sciborgs1155.lib.TestingUtil;
public class RobotTest {
@Test
- void initialize() {
+ void initialize() throws Exception {
new Robot().close();
+ TestingUtil.reset();
}
}
diff --git a/vendordeps/PathplannerLib.json b/vendordeps/PathplannerLib.json
index f86862c..6dc648d 100644
--- a/vendordeps/PathplannerLib.json
+++ b/vendordeps/PathplannerLib.json
@@ -1,7 +1,7 @@
{
"fileName": "PathplannerLib.json",
"name": "PathplannerLib",
- "version": "2024.1.3",
+ "version": "2024.2.8",
"uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786",
"frcYear": "2024",
"mavenUrls": [
@@ -12,7 +12,7 @@
{
"groupId": "com.pathplanner.lib",
"artifactId": "PathplannerLib-java",
- "version": "2024.1.3"
+ "version": "2024.2.8"
}
],
"jniDependencies": [],
@@ -20,7 +20,7 @@
{
"groupId": "com.pathplanner.lib",
"artifactId": "PathplannerLib-cpp",
- "version": "2024.1.3",
+ "version": "2024.2.8",
"libName": "PathplannerLib",
"headerClassifier": "headers",
"sharedLibrary": false,
diff --git a/vendordeps/Phoenix6.json b/vendordeps/Phoenix6.json
index 69a4079..0322385 100644
--- a/vendordeps/Phoenix6.json
+++ b/vendordeps/Phoenix6.json
@@ -1,7 +1,7 @@
{
"fileName": "Phoenix6.json",
"name": "CTRE-Phoenix (v6)",
- "version": "24.1.0",
+ "version": "24.3.0",
"frcYear": 2024,
"uuid": "e995de00-2c64-4df5-8831-c1441420ff19",
"mavenUrls": [
@@ -19,14 +19,14 @@
{
"groupId": "com.ctre.phoenix6",
"artifactId": "wpiapi-java",
- "version": "24.1.0"
+ "version": "24.3.0"
}
],
"jniDependencies": [
{
"groupId": "com.ctre.phoenix6",
"artifactId": "tools",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -39,7 +39,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "tools-sim",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -52,7 +52,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simTalonSRX",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -65,7 +65,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simTalonFX",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -78,7 +78,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simVictorSPX",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -91,7 +91,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simPigeonIMU",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -104,7 +104,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simCANCoder",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -117,7 +117,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProTalonFX",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -130,7 +130,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANcoder",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -143,7 +143,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProPigeon2",
- "version": "24.1.0",
+ "version": "24.3.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
@@ -158,7 +158,7 @@
{
"groupId": "com.ctre.phoenix6",
"artifactId": "wpiapi-cpp",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_Phoenix6_WPI",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -173,7 +173,7 @@
{
"groupId": "com.ctre.phoenix6",
"artifactId": "tools",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_PhoenixTools",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -188,7 +188,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "wpiapi-cpp-sim",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_Phoenix6_WPISim",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -203,7 +203,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "tools-sim",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_PhoenixTools_Sim",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -218,7 +218,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simTalonSRX",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimTalonSRX",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -233,7 +233,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simTalonFX",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimTalonFX",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -248,7 +248,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simVictorSPX",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimVictorSPX",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -263,7 +263,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simPigeonIMU",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimPigeonIMU",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -278,7 +278,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simCANCoder",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimCANCoder",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -293,7 +293,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProTalonFX",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimProTalonFX",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -308,7 +308,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProCANcoder",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimProCANcoder",
"headerClassifier": "headers",
"sharedLibrary": true,
@@ -323,7 +323,7 @@
{
"groupId": "com.ctre.phoenix6.sim",
"artifactId": "simProPigeon2",
- "version": "24.1.0",
+ "version": "24.3.0",
"libName": "CTRE_SimProPigeon2",
"headerClassifier": "headers",
"sharedLibrary": true,
diff --git a/vendordeps/REVLib.json b/vendordeps/REVLib.json
index 0f3520e..f85acd4 100644
--- a/vendordeps/REVLib.json
+++ b/vendordeps/REVLib.json
@@ -1,7 +1,7 @@
{
"fileName": "REVLib.json",
"name": "REVLib",
- "version": "2024.2.0",
+ "version": "2024.2.4",
"frcYear": "2024",
"uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb",
"mavenUrls": [
@@ -12,14 +12,14 @@
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-java",
- "version": "2024.2.0"
+ "version": "2024.2.4"
}
],
"jniDependencies": [
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-driver",
- "version": "2024.2.0",
+ "version": "2024.2.4",
"skipInvalidPlatforms": true,
"isJar": false,
"validPlatforms": [
@@ -37,7 +37,7 @@
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-cpp",
- "version": "2024.2.0",
+ "version": "2024.2.4",
"libName": "REVLib",
"headerClassifier": "headers",
"sharedLibrary": false,
@@ -55,7 +55,7 @@
{
"groupId": "com.revrobotics.frc",
"artifactId": "REVLib-driver",
- "version": "2024.2.0",
+ "version": "2024.2.4",
"libName": "REVLibDriver",
"headerClassifier": "headers",
"sharedLibrary": false,
diff --git a/vendordeps/URCL.json b/vendordeps/URCL.json
index d4b7514..998c261 100644
--- a/vendordeps/URCL.json
+++ b/vendordeps/URCL.json
@@ -1,25 +1,25 @@
{
"fileName": "URCL.json",
"name": "URCL",
- "version": "2024.0.0",
+ "version": "2024.1.0",
"frcYear": "2024",
"uuid": "84246d17-a797-4d1e-bd9f-c59cd8d2477c",
"mavenUrls": [
- "https://raw.githubusercontent.com/Mechanical-Advantage/URCL/2024.0.0"
+ "https://raw.githubusercontent.com/Mechanical-Advantage/URCL/2024.1.0"
],
"jsonUrl": "https://raw.githubusercontent.com/Mechanical-Advantage/URCL/maven/URCL.json",
"javaDependencies": [
{
"groupId": "org.littletonrobotics.urcl",
"artifactId": "URCL-java",
- "version": "2024.0.0"
+ "version": "2024.1.0"
}
],
"jniDependencies": [
{
"groupId": "org.littletonrobotics.urcl",
"artifactId": "URCL-driver",
- "version": "2024.0.0",
+ "version": "2024.1.0",
"skipInvalidPlatforms": true,
"isJar": false,
"validPlatforms": [
@@ -34,7 +34,7 @@
{
"groupId": "org.littletonrobotics.urcl",
"artifactId": "URCL-cpp",
- "version": "2024.0.0",
+ "version": "2024.1.0",
"libName": "URCL",
"headerClassifier": "headers",
"sharedLibrary": false,
@@ -49,7 +49,7 @@
{
"groupId": "org.littletonrobotics.urcl",
"artifactId": "URCL-driver",
- "version": "2024.0.0",
+ "version": "2024.1.0",
"libName": "URCLDriver",
"headerClassifier": "headers",
"sharedLibrary": false,
diff --git a/vendordeps/photonlib.json b/vendordeps/photonlib.json
index c940b75..0e80a16 100644
--- a/vendordeps/photonlib.json
+++ b/vendordeps/photonlib.json
@@ -1,21 +1,36 @@
{
"fileName": "photonlib.json",
"name": "photonlib",
- "version": "v2024.1.1-beta-3.2",
+ "version": "v2024.3.1",
"uuid": "515fe07e-bfc6-11fa-b3de-0242ac130004",
"frcYear": "2024",
"mavenUrls": [
"https://maven.photonvision.org/repository/internal",
"https://maven.photonvision.org/repository/snapshots"
],
- "jsonUrl": "https://maven.photonvision.org/repository/internal/org/photonvision/PhotonLib-json/1.0/PhotonLib-json-1.0.json",
+ "jsonUrl": "https://maven.photonvision.org/repository/internal/org/photonvision/photonlib-json/1.0/photonlib-json-1.0.json",
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "org.photonvision",
- "artifactId": "PhotonLib-cpp",
- "version": "v2024.1.1-beta-3.2",
- "libName": "Photon",
+ "artifactId": "photonlib-cpp",
+ "version": "v2024.3.1",
+ "libName": "photonlib",
+ "headerClassifier": "headers",
+ "sharedLibrary": true,
+ "skipInvalidPlatforms": true,
+ "binaryPlatforms": [
+ "windowsx86-64",
+ "linuxathena",
+ "linuxx86-64",
+ "osxuniversal"
+ ]
+ },
+ {
+ "groupId": "org.photonvision",
+ "artifactId": "photontargeting-cpp",
+ "version": "v2024.3.1",
+ "libName": "photontargeting",
"headerClassifier": "headers",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
@@ -30,13 +45,13 @@
"javaDependencies": [
{
"groupId": "org.photonvision",
- "artifactId": "PhotonLib-java",
- "version": "v2024.1.1-beta-3.2"
+ "artifactId": "photonlib-java",
+ "version": "v2024.3.1"
},
{
"groupId": "org.photonvision",
- "artifactId": "PhotonTargeting-java",
- "version": "v2024.1.1-beta-3.2"
+ "artifactId": "photontargeting-java",
+ "version": "v2024.3.1"
}
]
}
\ No newline at end of file
From fa438f0c48fb8e396ca23fcdfe4d13c8c8218221 Mon Sep 17 00:00:00 2001
From: Danny Lin <75951607+dannynotsmart@users.noreply.github.com>
Date: Sat, 11 May 2024 19:32:56 -0400
Subject: [PATCH 02/14] features
---
README.md | 2 +-
.../java/org/sciborgs1155/lib/FakePDH.java | 39 ----------------
.../org/sciborgs1155/lib/FaultLogger.java | 1 -
.../java/org/sciborgs1155/robot/Robot.java | 2 -
.../org/sciborgs1155/robot/vision/Vision.java | 4 +-
.../robot/vision/VisionConstants.java | 45 ++++++++++---------
6 files changed, 27 insertions(+), 66 deletions(-)
delete mode 100644 src/main/java/org/sciborgs1155/lib/FakePDH.java
diff --git a/README.md b/README.md
index 3a7141d..78fff7d 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Our robot code is centered around [Robot.java](src/main/java/org/sciborgs1155/ro
This project currently contains drive, vision, and autos. You are expected to add/modify code supporting existing files and new subsystems when using this template. Some of these files include but are not limited to:
- **[Autos.java](src/main/java/org/sciborgs1155/robot/commands/Autos.java)** Add code for new subsystems in `configureAutos`, such as commands for `NamedCommands`
-- **[VisionConstants.java](src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java)** Add new `CameraConfig` objects representing cameras on the robot in the field `CAMERAS`. Also modify any camera configurations or constants if needed.
+- **[VisionConstants.java](src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java)** Add new `CameraConfig` fields representing cameras on the robot, and change the `create` method in [Vision](src/main/java/org/sciborgs1155/robot/vision/Vision.java). Also modify any camera configurations or constants if needed.
- **[Constants.java](src/main/java/org/sciborgs1155/robot/Constants.java)** Modify the class `Field` to be updated for each year's game, and any other constants if needed.
- **[Ports.java](src/main/java/org/sciborgs1155/robot/Ports.java)** Modify existing OI and drive ports, as well as adding new ports.
- **[Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java)** A lot needs to be added/modified in this file, self-explanatory.
diff --git a/src/main/java/org/sciborgs1155/lib/FakePDH.java b/src/main/java/org/sciborgs1155/lib/FakePDH.java
deleted file mode 100644
index b3a5899..0000000
--- a/src/main/java/org/sciborgs1155/lib/FakePDH.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.sciborgs1155.lib;
-
-import com.revrobotics.CANSparkBase;
-import edu.wpi.first.wpilibj.RobotController;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Our actual PDH can't connect to CAN, so this is a workaround to (poorly) estimate our total used
- * current based on the stator currents of all our motors with the formula:
- *
- * supply current = battery voltage * applied output * stator current
- */
-public class FakePDH {
- private static final List sparks = new ArrayList<>();
- private static double statorCurrent;
- private static double supplyCurrent;
-
- public static void register(CANSparkBase spark) {
- sparks.add(spark);
- }
-
- public static double update() {
- double voltage = RobotController.getBatteryVoltage();
- double output = sparks.stream().mapToDouble(CANSparkBase::getAppliedOutput).sum();
- statorCurrent =
- sparks.stream().mapToDouble(CANSparkBase::getOutputCurrent).map(Math::abs).sum();
- supplyCurrent = voltage * output * statorCurrent;
- return supplyCurrent;
- }
-
- public static double supplyCurrent() {
- return supplyCurrent;
- }
-
- public static double statorCurrent() {
- return statorCurrent;
- }
-}
diff --git a/src/main/java/org/sciborgs1155/lib/FaultLogger.java b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
index 9a26825..176b94c 100644
--- a/src/main/java/org/sciborgs1155/lib/FaultLogger.java
+++ b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
@@ -200,7 +200,6 @@ public static void register(CANSparkBase spark) {
"motor above 100°C",
FaultType.WARNING);
// TODO actually fix PDH (this is cursed)
- FakePDH.register(spark);
}
/**
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index 40bd571..ea09816 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -23,7 +23,6 @@
import monologue.Monologue;
import org.littletonrobotics.urcl.URCL;
import org.sciborgs1155.lib.CommandRobot;
-import org.sciborgs1155.lib.FakePDH;
import org.sciborgs1155.lib.FaultLogger;
import org.sciborgs1155.lib.InputStream;
import org.sciborgs1155.lib.SparkUtils;
@@ -71,7 +70,6 @@ private void configureGameBehavior() {
SmartDashboard.putData(CommandScheduler.getInstance());
// Log PDH
// SmartDashboard.putData("PDH", new PowerDistribution());
- addPeriodic(() -> log("current", FakePDH.update()), PERIOD.in(Seconds));
// Configure pose estimation updates every tick
addPeriodic(() -> drive.updateEstimates(vision.getEstimatedGlobalPoses()), PERIOD.in(Seconds));
diff --git a/src/main/java/org/sciborgs1155/robot/vision/Vision.java b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
index 081a41a..665f253 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/Vision.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
@@ -1,5 +1,6 @@
package org.sciborgs1155.robot.vision;
+import static org.sciborgs1155.robot.Constants.*;
import static org.sciborgs1155.robot.vision.VisionConstants.*;
import edu.wpi.first.math.Matrix;
@@ -25,7 +26,6 @@
import org.photonvision.targeting.PhotonPipelineResult;
import org.photonvision.targeting.PhotonTrackedTarget;
import org.sciborgs1155.lib.FaultLogger;
-import org.sciborgs1155.robot.Constants.Field;
import org.sciborgs1155.robot.Robot;
public class Vision implements Logged {
@@ -41,7 +41,7 @@ public static record PoseEstimate(EstimatedRobotPose estimatedPose, Matrix
Date: Sun, 12 May 2024 17:26:08 -0400
Subject: [PATCH 03/14] removed useless
---
.../java/org/sciborgs1155/lib/SparkUtils.java | 39 ---------
.../org/sciborgs1155/robot/Constants.java | 80 -------------------
.../java/org/sciborgs1155/robot/Robot.java | 5 --
.../sciborgs1155/robot/drive/SparkModule.java | 10 +--
.../robot/vision/VisionConstants.java | 28 +------
5 files changed, 5 insertions(+), 157 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/lib/SparkUtils.java b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
index dff4e30..35672c0 100644
--- a/src/main/java/org/sciborgs1155/lib/SparkUtils.java
+++ b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
@@ -3,26 +3,11 @@
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkLowLevel.PeriodicFrame;
import com.revrobotics.REVLibError;
-import com.revrobotics.SparkRelativeEncoder.Type;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.function.DoubleSupplier;
/** Utility class for configuration of Spark motor controllers */
public class SparkUtils {
-
- private static final List runnables = new ArrayList<>();
-
- public static void addChecker(Runnable runnable) {
- runnables.add(runnable);
- }
-
- public static List getRunnables() {
- return runnables;
- }
-
// REV's docs have the size of a signed value of 65535ms for the max period
// https://docs.revrobotics.com/brushless/spark-max/control-interfaces#periodic-status-frames
// The actual max is half of this (32767ms)
@@ -92,13 +77,6 @@ public static REVLibError configureFrameStrategy(
// https://docs.revrobotics.com/brushless/spark-flex/revlib/spark-flex-firmware-changelog#breaking-changes
// if it's only IAccum, there's literally no reason to enable the frame
- Optional supplier1 = Optional.empty();
- Optional supplier2 = Optional.empty();
- Optional supplier3 = Optional.empty();
- Optional supplier4 = Optional.empty();
- Optional supplier5 = Optional.empty();
- Optional supplier6 = Optional.empty();
-
if (withFollower || data.contains(Data.APPLIED_OUTPUT)) {
status0 = FRAME_STRATEGY_VERY_FAST;
}
@@ -108,49 +86,32 @@ public static REVLibError configureFrameStrategy(
|| data.contains(Data.CURRENT)
|| data.contains(Data.TEMPERATURE)) {
status1 = FRAME_STRATEGY_FAST;
- supplier1 = Optional.of(spark::getOutputCurrent);
}
if (sensors.contains(Sensor.INTEGRATED) && data.contains(Data.POSITION)) {
status2 = FRAME_STRATEGY_FAST;
- supplier2 = Optional.of(() -> spark.getEncoder().getPosition());
}
if (sensors.contains(Sensor.ANALOG)
&& (data.contains(Data.VELOCITY) || data.contains(Data.POSITION))) {
status3 = FRAME_STRATEGY_FAST;
- supplier3 = Optional.of(() -> spark.getEncoder().getVelocity());
}
if (sensors.contains(Sensor.ALTERNATE)
&& (data.contains(Data.VELOCITY) || data.contains(Data.POSITION))) {
status4 = FRAME_STRATEGY_FAST;
- supplier4 =
- Optional.of(() -> spark.getEncoder(Type.kQuadrature, THROUGHBORE_CPR).getPosition());
}
if (sensors.contains(Sensor.ABSOLUTE)) {
if (data.contains(Data.POSITION)) {
status5 = FRAME_STRATEGY_FAST;
- supplier5 = Optional.of(() -> spark.getAbsoluteEncoder().getPosition());
}
if (data.contains(Data.VELOCITY)) {
status6 = FRAME_STRATEGY_FAST;
- supplier6 = Optional.of(() -> spark.getAbsoluteEncoder().getVelocity());
}
}
int[] frames = {status0, status1, status2, status3, status4, status5, status6, status7};
- List> suppliers =
- List.of(
- Optional.empty(),
- supplier1,
- supplier2,
- supplier3,
- supplier4,
- supplier5,
- supplier6,
- Optional.empty());
REVLibError error = REVLibError.kOk;
for (int i = 0; i < frames.length; i++) {
REVLibError e = spark.setPeriodicFramePeriod(PeriodicFrame.fromId(i), frames[i]);
diff --git a/src/main/java/org/sciborgs1155/robot/Constants.java b/src/main/java/org/sciborgs1155/robot/Constants.java
index 86e2c56..48b93b2 100644
--- a/src/main/java/org/sciborgs1155/robot/Constants.java
+++ b/src/main/java/org/sciborgs1155/robot/Constants.java
@@ -2,19 +2,14 @@
import static edu.wpi.first.units.Units.*;
-import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation2d;
-import edu.wpi.first.math.geometry.Rotation3d;
-import edu.wpi.first.math.geometry.Translation2d;
-import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.util.Units;
import edu.wpi.first.units.Distance;
import edu.wpi.first.units.Measure;
import edu.wpi.first.units.Time;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
-import java.util.List;
import org.sciborgs1155.robot.drive.DriveConstants;
/**
@@ -50,81 +45,6 @@ public static class Field {
public static final Measure LENGTH = Inches.of(651.223);
public static final Measure WIDTH = Inches.of(323.277);
- public static final Translation3d BLUE_SPEAKER_POSE = new Translation3d(0, 5.549, 2.002);
- public static final Translation3d RED_SPEAKER_POSE = new Translation3d(16.8167, 5.549, 2.002);
- public static final Translation3d TARGET_OFFSET = new Translation3d(0.367, 0, 0.2);
-
- // found from
- // https://github.com/Mechanical-Advantage/RobotCode2024/blob/main/src/main/java/org/littletonrobotics/frc2024/FieldConstants.java
- public static Pose3d BLUE_LEFT_NOTE =
- new Pose3d(new Translation3d(2.9, 4.11, 0), new Rotation3d());
- public static Pose3d BLUE_MID_NOTE =
- new Pose3d(new Translation3d(2.9, 5.55, 0), new Rotation3d());
- public static Pose3d BLUE_RIGHT_NOTE =
- new Pose3d(new Translation3d(2.9, 7.0, 0), new Rotation3d());
-
- public static Pose3d RED_LEFT_NOTE =
- new Pose3d(new Translation3d(13.642, 4.11, 0), new Rotation3d());
- public static Pose3d RED_MID_NOTE =
- new Pose3d(new Translation3d(13.642, 5.55, 0), new Rotation3d());
- public static Pose3d RED_RIGHT_NOTE =
- new Pose3d(new Translation3d(13.642, 7.0, 0), new Rotation3d());
-
- // left to right, facing forward towards field from origin
- public static Pose3d CENTER_NOTE_ONE =
- new Pose3d(new Translation3d(8.2705321, 0.7528052, 0), new Rotation3d());
- public static Pose3d CENTER_NOTE_TWO =
- new Pose3d(new Translation3d(8.2705321, 2.4292052, 0), new Rotation3d());
- public static Pose3d CENTER_NOTE_THREE =
- new Pose3d(new Translation3d(8.2705321, 4.1056052, 0), new Rotation3d());
- public static Pose3d CENTER_NOTE_FOUR =
- new Pose3d(new Translation3d(8.2705321, 5.78201, 0), new Rotation3d());
- public static Pose3d CENTER_NOTE_FIVE =
- new Pose3d(new Translation3d(8.2705321, 7.4584052, 0), new Rotation3d());
-
- // Pose2d which contain the coordinates of the middles of the stage chains, as well as the
- // rotation perpendicular to them.
- public static final Pose2d BLUE_STAGE_MIDSIDE =
- new Pose2d(Inches.of(224.125), Inches.of(162), Rotation2d.fromRadians(Math.PI));
- public static final Pose2d BLUE_STAGE_AMPSIDE =
- new Pose2d(Inches.of(175.5625), Inches.of(190.05), Rotation2d.fromRadians(Math.PI * 5 / 3));
- public static final Pose2d BLUE_STAGE_SOURCESIDE =
- new Pose2d(Inches.of(175.5625), Inches.of(133.95), Rotation2d.fromRadians(Math.PI / 3));
- public static final Pose2d RED_STAGE_MIDSIDE =
- new Pose2d(Inches.of(423.875), Inches.of(162), Rotation2d.fromRadians(0));
- public static final Pose2d RED_STAGE_AMPSIDE =
- new Pose2d(Inches.of(472.4375), Inches.of(190.05), Rotation2d.fromRadians(Math.PI * 4 / 3));
- public static final Pose2d RED_STAGE_SOURCESIDE =
- new Pose2d(Inches.of(472.4375), Inches.of(133.95), Rotation2d.fromRadians(Math.PI * 2 / 3));
-
- // Pose2D which contain the coordinates ((x and y) of the AprilTag on the amp (which is directly
- // above the amp scoring area), and the rotations.
- public static final Translation2d BLUE_AMP =
- new Translation2d(Inches.of(72.5), Inches.of(323.0));
- public static final Translation2d RED_AMP =
- new Translation2d(Inches.of(578.77), Inches.of(323.0));
-
- // Methoeds
-
- /** Returns the translation of the speaker for the robot's alliance. */
- public static Translation3d speaker() {
- return alliance() == Alliance.Red
- ? RED_SPEAKER_POSE.plus(TARGET_OFFSET.rotateBy(new Rotation3d(0, 0, Math.PI)))
- : BLUE_SPEAKER_POSE.plus(TARGET_OFFSET);
- }
-
- // ** Returns a list of 2d coordinates for the middle of the current alliance's stage chains */
- public static List chain() {
- return alliance() == Alliance.Blue
- ? List.of(BLUE_STAGE_AMPSIDE, BLUE_STAGE_MIDSIDE, BLUE_STAGE_SOURCESIDE)
- : List.of(RED_STAGE_AMPSIDE, RED_STAGE_MIDSIDE, RED_STAGE_SOURCESIDE);
- }
-
- // ** Returns the Pose2D of the amp on the robot's alliance. */
- public static Translation2d amp() {
- return alliance() == Alliance.Blue ? BLUE_AMP : RED_AMP;
- }
-
/** Returns whether the provided position is within the boundaries of the field. */
public static boolean inField(Pose3d pose) {
return (pose.getX() > 0
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index ea09816..3466dbe 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -25,7 +25,6 @@
import org.sciborgs1155.lib.CommandRobot;
import org.sciborgs1155.lib.FaultLogger;
import org.sciborgs1155.lib.InputStream;
-import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.robot.Ports.OI;
import org.sciborgs1155.robot.commands.Autos;
import org.sciborgs1155.robot.drive.Drive;
@@ -74,10 +73,6 @@ private void configureGameBehavior() {
// Configure pose estimation updates every tick
addPeriodic(() -> drive.updateEstimates(vision.getEstimatedGlobalPoses()), PERIOD.in(Seconds));
- for (var r : SparkUtils.getRunnables()) {
- addPeriodic(r, 5);
- }
-
RobotController.setBrownoutVoltage(6.0);
if (isReal()) {
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 2908c14..0cb5ccc 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -80,15 +80,7 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset) {
Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
Set.of(Sensor.ABSOLUTE),
false));
- SparkUtils.addChecker(
- () ->
- check(
- turnMotor,
- SparkUtils.configureFrameStrategy(
- turnMotor,
- Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
- Set.of(Sensor.ABSOLUTE),
- false)));
+
check(turnMotor, turnMotor.burnFlash());
register(driveMotor);
diff --git a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
index 3169034..8689d2e 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
@@ -1,15 +1,11 @@
package org.sciborgs1155.robot.vision;
-import static edu.wpi.first.units.Units.Inches;
-
import edu.wpi.first.apriltag.AprilTagFieldLayout;
import edu.wpi.first.apriltag.AprilTagFields;
import edu.wpi.first.math.Matrix;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.geometry.Rotation2d;
-import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Transform3d;
-import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.numbers.N1;
import edu.wpi.first.math.numbers.N3;
import org.sciborgs1155.robot.vision.Vision.CameraConfig;
@@ -21,29 +17,13 @@ public class VisionConstants {
/* The front of the robot is defined at the intake */
public static final CameraConfig BACK_LEFT_CAMERA =
- new CameraConfig(
- "back left",
- new Transform3d(
- new Translation3d(Inches.of(-10.74), Inches.of(11.617), Inches.of(17.25)),
- new Rotation3d(0, -Math.PI / 6, 5 * Math.PI / 6)));
+ new CameraConfig("back left", new Transform3d());
public static final CameraConfig BACK_RIGHT_CAMERA =
- new CameraConfig(
- "back right",
- new Transform3d(
- new Translation3d(Inches.of(-10.74), Inches.of(-11.617), Inches.of(17.25)),
- new Rotation3d(0, -Math.PI / 6, -5 * Math.PI / 6)));
+ new CameraConfig("back right", new Transform3d());
public static final CameraConfig FRONT_LEFT_CAMERA =
- new CameraConfig(
- "front left",
- new Transform3d(
- new Translation3d(-0.089, 0.3, 0.46),
- new Rotation3d(Math.PI, -Math.PI / 6, Math.PI / 6)));
+ new CameraConfig("front left", new Transform3d());
public static final CameraConfig FRONT_RIGHT_CAMERA =
- new CameraConfig(
- "front right",
- new Transform3d(
- new Translation3d(-0.273, -0.3, 0.485),
- new Rotation3d(0, -Math.PI / 6, -Math.PI / 6)));
+ new CameraConfig("front right", new Transform3d());
// OV9281 constants for our configuration
public static final int WIDTH = 1280;
From a253ac2ae1592466775f096c9a639a429c219ca3 Mon Sep 17 00:00:00 2001
From: yxhej
Date: Tue, 30 Jul 2024 17:24:42 -0400
Subject: [PATCH 04/14] update to current commit onboarding pr
---
build.gradle | 16 +-
.../java/org/sciborgs1155/lib/Assertion.java | 90 +++++++++
.../java/org/sciborgs1155/lib/FakePDH.java | 39 ++++
.../org/sciborgs1155/lib/FaultLogger.java | 137 +-------------
.../java/org/sciborgs1155/lib/SparkUtils.java | 18 +-
src/main/java/org/sciborgs1155/lib/Test.java | 54 ++++++
...{TestingUtil.java => UnitTestingUtil.java} | 94 +++++----
.../java/org/sciborgs1155/robot/Robot.java | 26 ++-
.../sciborgs1155/robot/commands/Autos.java | 2 +-
.../org/sciborgs1155/robot/drive/Drive.java | 107 ++++++-----
.../robot/drive/DriveConstants.java | 63 +++++--
.../sciborgs1155/robot/drive/ModuleIO.java | 72 ++++++-
.../sciborgs1155/robot/drive/NoModule.java | 34 ++++
.../sciborgs1155/robot/drive/SimModule.java | 106 ++++++++++-
.../sciborgs1155/robot/drive/SparkModule.java | 110 ++++++++++-
.../robot/drive/SwerveModule.java | 178 ------------------
.../sciborgs1155/robot/drive/TalonModule.java | 166 +++++++++++++---
.../robot/vision/VisionConstants.java | 2 +-
.../org/sciborgs1155/lib/FaultLoggerTest.java | 79 ++++++++
.../org/sciborgs1155/lib/SparkUtilsTest.java | 53 ++++++
.../org/sciborgs1155/lib/TestingUtilTest.java | 124 ++++++++++++
.../java/org/sciborgs1155/lib/TuningTest.java | 41 ++++
.../org/sciborgs1155/robot/RobotTest.java | 5 +-
.../org/sciborgs1155/robot/SwerveTest.java | 122 ++++++++++--
24 files changed, 1256 insertions(+), 482 deletions(-)
create mode 100644 src/main/java/org/sciborgs1155/lib/Assertion.java
create mode 100644 src/main/java/org/sciborgs1155/lib/FakePDH.java
create mode 100644 src/main/java/org/sciborgs1155/lib/Test.java
rename src/main/java/org/sciborgs1155/lib/{TestingUtil.java => UnitTestingUtil.java} (56%)
delete mode 100644 src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java
create mode 100644 src/test/java/org/sciborgs1155/lib/FaultLoggerTest.java
create mode 100644 src/test/java/org/sciborgs1155/lib/SparkUtilsTest.java
create mode 100644 src/test/java/org/sciborgs1155/lib/TestingUtilTest.java
create mode 100644 src/test/java/org/sciborgs1155/lib/TuningTest.java
diff --git a/build.gradle b/build.gradle
index 76471f2..3661b55 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.3.2"
- id 'com.diffplug.spotless' version '6.23.3'
+ id 'com.diffplug.spotless' version '6.24.0'
}
sourceCompatibility = JavaVersion.VERSION_17
@@ -55,6 +55,7 @@ repositories {
dependencies {
implementation wpi.java.deps.wpilib()
implementation wpi.java.vendor.java()
+ implementation 'org.junit.jupiter:junit-jupiter-api:5.+'
roborioDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.roborio)
roborioDebug wpi.java.vendor.jniDebug(wpi.platforms.roborio)
@@ -71,17 +72,12 @@ dependencies {
simulationRelease wpi.sim.enableRelease()
// Junit testing
- testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
- testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
- testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.+'
+ testImplementation 'org.junit.jupiter:junit-jupiter-params:5.+'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5+'
// Monologue
- implementation 'com.github.shueja:Monologue:v1.0.0-beta1'
-}
-
-test {
- useJUnitPlatform()
- systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
+ implementation 'com.github.shueja:Monologue:+'
}
// Simulation configuration (e.g. environment variables).
diff --git a/src/main/java/org/sciborgs1155/lib/Assertion.java b/src/main/java/org/sciborgs1155/lib/Assertion.java
new file mode 100644
index 0000000..f75c15d
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/lib/Assertion.java
@@ -0,0 +1,90 @@
+package org.sciborgs1155.lib;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.function.BooleanSupplier;
+import java.util.function.DoubleSupplier;
+import org.sciborgs1155.lib.FaultLogger.FaultType;
+
+public sealed interface Assertion {
+ public void apply(boolean unitTest);
+
+ /**
+ * Asserts that a condition is true, and reports to FaultLogger
+ *
+ * @param condition
+ * @param faultName
+ * @param description
+ */
+ private static void reportTrue(boolean condition, String faultName, String description) {
+ FaultLogger.report(
+ faultName,
+ (condition ? "success! " : "") + description,
+ condition ? FaultType.INFO : FaultType.WARNING);
+ }
+
+ /**
+ * Asserts that two values are equal (with some tolerance), and reports to FaultLogger
+ *
+ * @param faultName
+ * @param expected
+ * @param actual
+ * @param delta tolerance
+ */
+ private static void reportEquals(String faultName, double expected, double actual, double delta) {
+ reportTrue(
+ Math.abs(expected - actual) <= delta,
+ faultName,
+ "expected: " + expected + "; actual: " + actual);
+ }
+
+ public static record TruthAssertion(
+ BooleanSupplier condition, String faultName, String description) implements Assertion {
+ @Override
+ public void apply(boolean unitTest) {
+ if (unitTest) {
+ assertTrue(condition, faultName + ": " + description);
+ } else {
+ reportTrue(condition.getAsBoolean(), faultName, description);
+ }
+ }
+ }
+
+ public static record EqualityAssertion(
+ String faultName, DoubleSupplier expected, DoubleSupplier actual, double delta)
+ implements Assertion {
+ @Override
+ public void apply(boolean unitTest) {
+ if (unitTest) {
+ assertEquals(expected.getAsDouble(), actual.getAsDouble(), delta, faultName);
+ } else {
+ reportEquals(faultName, expected.getAsDouble(), actual.getAsDouble(), delta);
+ }
+ }
+ }
+
+ /**
+ * @return a truth assertion
+ */
+ public static TruthAssertion tAssert(
+ BooleanSupplier condition, String faultName, String description) {
+ return new TruthAssertion(condition, faultName, description);
+ }
+
+ /**
+ * @return an equality assertion
+ */
+ public static EqualityAssertion eAssert(
+ String faultName, DoubleSupplier expected, DoubleSupplier actual, double delta) {
+ return new EqualityAssertion(faultName, expected, actual, delta);
+ }
+
+ /**
+ * @return an equality assertion
+ */
+ public static EqualityAssertion eAssert(
+ String faultName, DoubleSupplier expected, DoubleSupplier actual) {
+ return eAssert(faultName, expected, actual, 0);
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/lib/FakePDH.java b/src/main/java/org/sciborgs1155/lib/FakePDH.java
new file mode 100644
index 0000000..02e581c
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/lib/FakePDH.java
@@ -0,0 +1,39 @@
+package org.sciborgs1155.lib;
+
+import com.revrobotics.CANSparkBase;
+import edu.wpi.first.wpilibj.RobotController;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Our old PDH couldn't connect to CAN, so this was a workaround to (poorly) estimate our total used
+ * current based on the stator currents of all our motors with the formula:
+ *
+ * supply current = battery voltage * applied output * stator current
+ */
+public class FakePDH {
+ private static final List sparks = new ArrayList<>();
+ private static double statorCurrent;
+ private static double supplyCurrent;
+
+ public static void register(CANSparkBase spark) {
+ sparks.add(spark);
+ }
+
+ public static double update() {
+ double voltage = RobotController.getBatteryVoltage();
+ double output = sparks.stream().mapToDouble(CANSparkBase::getAppliedOutput).sum();
+ statorCurrent =
+ sparks.stream().mapToDouble(CANSparkBase::getOutputCurrent).map(Math::abs).sum();
+ supplyCurrent = voltage * output * statorCurrent;
+ return supplyCurrent;
+ }
+
+ public static double supplyCurrent() {
+ return supplyCurrent;
+ }
+
+ public static double statorCurrent() {
+ return statorCurrent;
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/lib/FaultLogger.java b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
index 176b94c..5a8ad73 100644
--- a/src/main/java/org/sciborgs1155/lib/FaultLogger.java
+++ b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
@@ -1,6 +1,5 @@
package org.sciborgs1155.lib;
-import com.ctre.phoenix6.hardware.TalonFX;
import com.kauailabs.navx.frc.AHRS;
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkBase.FaultID;
@@ -199,7 +198,7 @@ public static void register(CANSparkBase spark) {
SparkUtils.name(spark),
"motor above 100°C",
FaultType.WARNING);
- // TODO actually fix PDH (this is cursed)
+ // FakePDH.register(spark);
}
/**
@@ -258,140 +257,6 @@ public static void register(PhotonCamera camera) {
FaultType.ERROR);
}
- /**
- * Registers fault suppliers for a talon.
- *
- * @param talon The talon to manage.
- */
- public static void register(TalonFX talon) {
- // TODO: Remove all the unnecessary faults
- register(
- (BooleanSupplier) talon.getFault_Hardware(),
- "Talon ID: " + talon.getDeviceID(),
- "Hardware fault occurred",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_ProcTemp(),
- "Talon ID: " + talon.getDeviceID(),
- "Processor temperature exceeded limit",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_DeviceTemp(),
- "Talon ID: " + talon.getDeviceID(),
- "Device temperature exceeded limit",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_Undervoltage(),
- "Talon ID: " + talon.getDeviceID(),
- "Device supply voltage dropped to near brownout levels",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_BootDuringEnable(),
- "Talon ID: " + talon.getDeviceID(),
- "Device boot while detecting the enable signal",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_UnlicensedFeatureInUse(),
- "Talon ID: " + talon.getDeviceID(),
- "An unlicensed feature is in use, device may not behave as expected.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_BridgeBrownout(),
- "Talon ID: " + talon.getDeviceID(),
- "Bridge was disabled most likely due to supply voltage dropping too low.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_RemoteSensorReset(),
- "Talon ID: " + talon.getDeviceID(),
- "The remote sensor has reset.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_MissingDifferentialFX(),
- "Talon ID: " + talon.getDeviceID(),
- "The remote Talon FX used for differential control is not present on CAN Bus.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_RemoteSensorPosOverflow(),
- "Talon ID: " + talon.getDeviceID(),
- "The remote sensor position has overflowed.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_OverSupplyV(),
- "Talon ID: " + talon.getDeviceID(),
- "Supply Voltage has exceeded the maximum voltage rating of device.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_UnstableSupplyV(),
- "Talon ID: " + talon.getDeviceID(),
- "Supply Voltage is unstable.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_ReverseHardLimit(),
- "Talon ID: " + talon.getDeviceID(),
- "Reverse limit switch has been asserted. Output is set to neutral.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_ForwardHardLimit(),
- "Talon ID: " + talon.getDeviceID(),
- "Forward limit switch has been asserted. Output is set to neutral.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_ReverseSoftLimit(),
- "Talon ID: " + talon.getDeviceID(),
- "Reverse soft limit has been asserted. Output is set to neutral.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_ForwardSoftLimit(),
- "Talon ID: " + talon.getDeviceID(),
- "Forward soft limit has been asserted. Output is set to neutral.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_RemoteSensorDataInvalid(),
- "Talon ID: " + talon.getDeviceID(),
- "The remote sensor's data is no longer trusted.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_FusedSensorOutOfSync(),
- "Talon ID: " + talon.getDeviceID(),
- "The remote sensor used for fusion has fallen out of sync to the local sensor.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_StatorCurrLimit(),
- "Talon ID: " + talon.getDeviceID(),
- "Stator current limit occured.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_SupplyCurrLimit(),
- "Talon ID: " + talon.getDeviceID(),
- "Supply current limit occured.",
- FaultType.ERROR);
-
- register(
- (BooleanSupplier) talon.getFault_UsingFusedCANcoderWhileUnlicensed(),
- "Talon ID: " + talon.getDeviceID(),
- "Using Fused CANcoder feature while unlicensed. Device has fallen back to remote CANcoder.",
- FaultType.ERROR);
- }
-
/**
* Reports REVLibErrors from a spark.
*
diff --git a/src/main/java/org/sciborgs1155/lib/SparkUtils.java b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
index 35672c0..7d03afd 100644
--- a/src/main/java/org/sciborgs1155/lib/SparkUtils.java
+++ b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
@@ -3,11 +3,24 @@
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkLowLevel.PeriodicFrame;
import com.revrobotics.REVLibError;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
/** Utility class for configuration of Spark motor controllers */
public class SparkUtils {
+
+ private static final List runnables = new ArrayList<>();
+
+ public static void addChecker(Runnable runnable) {
+ runnables.add(runnable);
+ }
+
+ public static List getRunnables() {
+ return runnables;
+ }
+
// REV's docs have the size of a signed value of 65535ms for the max period
// https://docs.revrobotics.com/brushless/spark-max/control-interfaces#periodic-status-frames
// The actual max is half of this (32767ms)
@@ -73,9 +86,10 @@ public static REVLibError configureFrameStrategy(
int status5 = FRAME_STRATEGY_DISABLED; // duty cycle position | default 200
int status6 = FRAME_STRATEGY_DISABLED; // duty cycle velocity | default 200
int status7 = FRAME_STRATEGY_DISABLED;
- // status frame 7 is cursed, the only mention i found of it in rev's docs is at
+ // // status frame 7 is cursed, the only mention i found of it in rev's docs is at
+ // //
// https://docs.revrobotics.com/brushless/spark-flex/revlib/spark-flex-firmware-changelog#breaking-changes
- // if it's only IAccum, there's literally no reason to enable the frame
+ // // if it's only IAccum, there's literally no reason to enable the frame
if (withFollower || data.contains(Data.APPLIED_OUTPUT)) {
status0 = FRAME_STRATEGY_VERY_FAST;
diff --git a/src/main/java/org/sciborgs1155/lib/Test.java b/src/main/java/org/sciborgs1155/lib/Test.java
new file mode 100644
index 0000000..0dd2426
--- /dev/null
+++ b/src/main/java/org/sciborgs1155/lib/Test.java
@@ -0,0 +1,54 @@
+package org.sciborgs1155.lib;
+
+import static org.sciborgs1155.lib.UnitTestingUtil.runToCompletion;
+
+import edu.wpi.first.wpilibj2.command.Command;
+import edu.wpi.first.wpilibj2.command.Commands;
+import java.util.Set;
+
+/** A test, consisting of a command and assertions. Can be used for unit tests or ran on robot. */
+public record Test(Command testCommand, Set assertions) {
+ /**
+ * @param command
+ * @return a Test with no assertions
+ */
+ public static Test fromCommand(Command command) {
+ return new Test(command, Set.of());
+ }
+
+ private static Command toCommand(Test test, boolean unitTest) {
+ return test.testCommand.finallyDo(() -> test.assertions.forEach(a -> a.apply(unitTest)));
+ }
+
+ /**
+ * Creates a command from a Test
+ *
+ * @param test a Test
+ * @return a command that runs the testCommand in test and then applies all assertions
+ */
+ public static Command toCommand(Test test) {
+ return toCommand(test, false);
+ }
+
+ /**
+ * Creates a sequential command from Tests.
+ *
+ * @return a command that runs the testCommand and assertions from each test in turn
+ */
+ public static Command toCommand(Test... tests) {
+ Command c = Commands.none();
+ for (Test test : tests) {
+ c = c.andThen(toCommand(test));
+ }
+ return c;
+ }
+
+ /**
+ * Runs a unit test based on a Test.
+ *
+ * @param test
+ */
+ public static void runUnitTest(Test test) {
+ runToCompletion(toCommand(test, true));
+ }
+}
diff --git a/src/main/java/org/sciborgs1155/lib/TestingUtil.java b/src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java
similarity index 56%
rename from src/main/java/org/sciborgs1155/lib/TestingUtil.java
rename to src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java
index e3d877e..0293377 100644
--- a/src/main/java/org/sciborgs1155/lib/TestingUtil.java
+++ b/src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java
@@ -1,30 +1,72 @@
package org.sciborgs1155.lib;
+import static edu.wpi.first.units.Units.Seconds;
+
import edu.wpi.first.hal.HAL;
+import edu.wpi.first.units.Measure;
+import edu.wpi.first.units.Time;
import edu.wpi.first.wpilibj.simulation.DriverStationSim;
+import edu.wpi.first.wpilibj.simulation.SimHooks;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
-import org.sciborgs1155.lib.FaultLogger.FaultType;
-public class TestingUtil {
+public class UnitTestingUtil {
+ public static final Measure TICK_RATE = Seconds.of(0.02);
+
+ /** Sets up DS and initializes HAL with default values and asserts that it doesn't fail. */
+ public static void setupTests() {
+ assert HAL.initialize(500, 0);
+ DriverStationSim.setEnabled(true);
+ DriverStationSim.setTest(true);
+ DriverStationSim.notifyNewData();
+ FaultLogger.clear();
+ FaultLogger.unregisterAll();
+ }
+
/**
- * Runs CommandScheduler repeatedly to fast forward subsystems and run commands.
+ * Resets CommandScheduler and closes all subsystems. Please call in an @AfterEach method!
+ *
+ * @param subsystems All subsystems that need to be closed
+ */
+ public static void reset(AutoCloseable... subsystems) throws Exception {
+ CommandScheduler.getInstance().unregisterAllSubsystems();
+ CommandScheduler.getInstance().cancelAll();
+ for (AutoCloseable subsystem : subsystems) {
+ subsystem.close();
+ }
+ }
+
+ /**
+ * Runs CommandScheduler and updates timer repeatedly to fast forward subsystems and run commands.
*
* @param ticks The number of times CommandScheduler is run
*/
public static void fastForward(int ticks) {
for (int i = 0; i < ticks; i++) {
CommandScheduler.getInstance().run();
+ SimHooks.stepTiming(TICK_RATE.in(Seconds));
}
}
- /** Runs CommandScheduler 200 times to fast forward subsystems and run commands. */
+ /**
+ * Fasts forward in time by running CommandScheduler and updating timer
+ *
+ * @param time
+ */
+ public static void fastForward(Measure time) {
+ fastForward((int) (time.in(Seconds) / TICK_RATE.in(Seconds)));
+ }
+
+ /**
+ * Runs CommandScheduler and updates timer to fast forward subsystems by 4 seconds and run
+ * commands.
+ */
public static void fastForward() {
- fastForward(200);
+ fastForward(Seconds.of(4));
}
/**
- * Schedules and runs a command while disabled
+ * Schedules and runs a command
*
* @param command The command to run.
*/
@@ -34,7 +76,7 @@ public static void run(Command command) {
}
/**
- * Schedules and runs a command while disabled.
+ * Schedules and runs a command.
*
* @param command The command to run.
* @param runs The number of times CommandScheduler is run.
@@ -44,39 +86,17 @@ public static void run(Command command, int runs) {
fastForward(runs);
}
- /** Sets up DS and initializes HAL with default values and asserts that it doesn't fail. */
- public static void setupTests() {
- assert HAL.initialize(500, 0);
- DriverStationSim.setEnabled(true);
- DriverStationSim.setTest(true);
- DriverStationSim.notifyNewData();
- }
-
/**
- * Resets CommandScheduler and closes all subsystems. Please call in an @AfterEach method!
+ * Schedules a command and runs it until it ends. Be careful -- if the command you give never
+ * ends, this will be an infinate loop!
*
- * @param subsystems All subsystems that need to be closed
+ * @param command
*/
- public static void reset(AutoCloseable... subsystems) throws Exception {
- CommandScheduler.getInstance().unregisterAllSubsystems();
- CommandScheduler.getInstance().cancelAll();
- for (AutoCloseable subsystem : subsystems) {
- subsystem.close();
+ public static void runToCompletion(Command command) {
+ command.schedule();
+ fastForward(1);
+ while (command.isScheduled()) {
+ fastForward(1);
}
}
-
- public static void assertEqualsReport(
- String faultName, double expected, double actual, double delta) {
- assertReport(
- Math.abs(expected - actual) <= delta,
- faultName,
- "expected: " + expected + "; actual: " + actual);
- }
-
- public static void assertReport(boolean condition, String faultName, String description) {
- FaultLogger.report(
- faultName,
- (condition ? "success! " : "") + description,
- condition ? FaultType.INFO : FaultType.WARNING);
- }
}
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index 3466dbe..b9a9841 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -11,6 +11,8 @@
import edu.wpi.first.wpilibj.DataLogManager;
import edu.wpi.first.wpilibj.DriverStation;
+import edu.wpi.first.wpilibj.GenericHID.RumbleType;
+import edu.wpi.first.wpilibj.PowerDistribution;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
@@ -25,6 +27,7 @@
import org.sciborgs1155.lib.CommandRobot;
import org.sciborgs1155.lib.FaultLogger;
import org.sciborgs1155.lib.InputStream;
+import org.sciborgs1155.lib.Test;
import org.sciborgs1155.robot.Ports.OI;
import org.sciborgs1155.robot.commands.Autos;
import org.sciborgs1155.robot.drive.Drive;
@@ -42,6 +45,8 @@ public class Robot extends CommandRobot implements Logged {
private final CommandXboxController operator = new CommandXboxController(OI.OPERATOR);
private final CommandXboxController driver = new CommandXboxController(OI.DRIVER);
+ private final PowerDistribution pdh = new PowerDistribution();
+
// SUBSYSTEMS
private final Drive drive = Drive.create();
private final Vision vision = Vision.create();
@@ -68,7 +73,7 @@ private void configureGameBehavior() {
SmartDashboard.putData(CommandScheduler.getInstance());
// Log PDH
- // SmartDashboard.putData("PDH", new PowerDistribution());
+ SmartDashboard.putData("PDH", pdh);
// Configure pose estimation updates every tick
addPeriodic(() -> drive.updateEstimates(vision.getEstimatedGlobalPoses()), PERIOD.in(Seconds));
@@ -77,8 +82,9 @@ private void configureGameBehavior() {
if (isReal()) {
URCL.start();
+ pdh.clearStickyFaults();
+ pdh.setSwitchableChannel(true);
} else {
- DriverStation.silenceJoystickConnectionWarning(true);
DriverStation.silenceJoystickConnectionWarning(true);
addPeriodic(() -> vision.simulationPeriodic(drive.pose()), PERIOD.in(Seconds));
}
@@ -130,8 +136,22 @@ private void configureBindings() {
.onFalse(Commands.runOnce(() -> speedMultiplier = Constants.FULL_SPEED_MULTIPLIER));
}
+ public Command rumble(RumbleType rumbleType, double strength) {
+ return Commands.runOnce(
+ () -> {
+ driver.getHID().setRumble(rumbleType, strength);
+ operator.getHID().setRumble(rumbleType, strength);
+ })
+ .andThen(Commands.waitSeconds(0.3))
+ .finallyDo(
+ () -> {
+ driver.getHID().setRumble(rumbleType, 0);
+ operator.getHID().setRumble(rumbleType, 0);
+ });
+ }
+
public Command systemsCheck() {
- return Commands.sequence(drive.systemsCheck());
+ return Test.toCommand().withName("Test Mechanisms");
}
@Override
diff --git a/src/main/java/org/sciborgs1155/robot/commands/Autos.java b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
index 2cdc765..06d8355 100644
--- a/src/main/java/org/sciborgs1155/robot/commands/Autos.java
+++ b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
@@ -19,7 +19,7 @@
import org.sciborgs1155.robot.drive.DriveConstants;
import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
import org.sciborgs1155.robot.drive.DriveConstants.Translation;
-import org.sciborgs1155.robot.drive.SwerveModule.ControlMode;
+import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
public class Autos {
private static Optional rotation = Optional.empty();
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index c3a9263..e1ba8b5 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -6,8 +6,7 @@
import static edu.wpi.first.units.Units.Seconds;
import static edu.wpi.first.units.Units.Volts;
import static java.lang.Math.atan;
-import static org.sciborgs1155.lib.TestingUtil.assertEqualsReport;
-import static org.sciborgs1155.lib.TestingUtil.assertReport;
+import static org.sciborgs1155.lib.Assertion.*;
import static org.sciborgs1155.robot.Constants.allianceRotation;
import static org.sciborgs1155.robot.Ports.Drive.*;
import static org.sciborgs1155.robot.drive.DriveConstants.*;
@@ -38,29 +37,35 @@
import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine.Direction;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.function.DoubleSupplier;
+import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import monologue.Annotations.IgnoreLogged;
import monologue.Annotations.Log;
import monologue.Logged;
import org.photonvision.EstimatedRobotPose;
+import org.sciborgs1155.lib.Assertion;
import org.sciborgs1155.lib.InputStream;
+import org.sciborgs1155.lib.Test;
import org.sciborgs1155.robot.Constants;
import org.sciborgs1155.robot.Robot;
import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
import org.sciborgs1155.robot.drive.DriveConstants.Translation;
-import org.sciborgs1155.robot.drive.SwerveModule.ControlMode;
+import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
import org.sciborgs1155.robot.vision.Vision.PoseEstimate;
public class Drive extends SubsystemBase implements Logged, AutoCloseable {
// Modules
- private final SwerveModule frontLeft;
- private final SwerveModule frontRight;
- private final SwerveModule rearLeft;
- private final SwerveModule rearRight;
+ private final ModuleIO frontLeft;
+ private final ModuleIO frontRight;
+ private final ModuleIO rearLeft;
+ private final ModuleIO rearRight;
- @IgnoreLogged private final List modules;
+ @IgnoreLogged private final List modules;
private final GyroIO gyro;
private static Rotation2d simRotation = new Rotation2d();
@@ -96,12 +101,16 @@ public static Drive create() {
return Robot.isReal()
? new Drive(
new NavXGyro(),
- new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0)),
- new SparkModule(FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1)),
- new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2)),
- new SparkModule(REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3)))
+ new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
+ new SparkModule(FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
+ new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
+ new SparkModule(REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"))
: new Drive(
- new NoGyro(), new SimModule(), new SimModule(), new SimModule(), new SimModule());
+ new NoGyro(),
+ new SimModule("FL"),
+ new SimModule("FR"),
+ new SimModule("RL"),
+ new SimModule("RR"));
}
/** A factory to create a nonexistent swerve drive. */
@@ -113,10 +122,10 @@ public static Drive none() {
public Drive(
GyroIO gyro, ModuleIO frontLeft, ModuleIO frontRight, ModuleIO rearLeft, ModuleIO rearRight) {
this.gyro = gyro;
- this.frontLeft = new SwerveModule(frontLeft, ANGULAR_OFFSETS.get(0), " FL");
- this.frontRight = new SwerveModule(frontRight, ANGULAR_OFFSETS.get(1), "FR");
- this.rearLeft = new SwerveModule(rearLeft, ANGULAR_OFFSETS.get(2), "RL");
- this.rearRight = new SwerveModule(rearRight, ANGULAR_OFFSETS.get(3), " RR");
+ this.frontLeft = frontLeft;
+ this.frontRight = frontRight;
+ this.rearLeft = rearLeft;
+ this.rearRight = rearRight;
modules = List.of(this.frontLeft, this.frontRight, this.rearLeft, this.rearRight);
modules2d = new FieldObject2d[modules.size()];
@@ -127,7 +136,7 @@ public Drive(
new SysIdRoutine.Mechanism(
volts ->
modules.forEach(
- m -> m.updateDriveVoltage(Rotation2d.fromRadians(0), volts.in(Volts))),
+ m -> m.updateInputs(Rotation2d.fromRadians(0), volts.in(Volts))),
null,
this,
"translation"));
@@ -136,13 +145,13 @@ public Drive(
new SysIdRoutine.Config(),
new SysIdRoutine.Mechanism(
volts -> {
- this.frontLeft.updateDriveVoltage(
+ this.frontLeft.updateInputs(
Rotation2d.fromRadians(3 * Math.PI / 4), volts.in(Volts));
- this.frontRight.updateDriveVoltage(
+ this.frontRight.updateInputs(
Rotation2d.fromRadians(Math.PI / 4), volts.in(Volts));
- this.rearLeft.updateDriveVoltage(
+ this.rearLeft.updateInputs(
Rotation2d.fromRadians(-3 * Math.PI / 4), volts.in(Volts));
- this.rearRight.updateDriveVoltage(
+ this.rearRight.updateInputs(
Rotation2d.fromRadians(-Math.PI / 4), volts.in(Volts));
},
null,
@@ -158,7 +167,7 @@ public Drive(
for (int i = 0; i < modules.size(); i++) {
var module = modules.get(i);
- modules2d[i] = field2d.getObject("module-" + module.name);
+ modules2d[i] = field2d.getObject("module-" + module.name());
}
gyro.reset();
@@ -305,7 +314,7 @@ public void setModuleStates(
SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, MAX_SPEED.in(MetersPerSecond));
for (int i = 0; i < modules.size(); i++) {
- modules.get(i).updateSetpoint(desiredStates[i], mode, movementFactor);
+ modules.get(i).updateSetpoint(desiredStates[i], mode);
}
}
@@ -330,7 +339,7 @@ public Command driveTo(Pose2d target) {
/** Resets the drive encoders to currently read a position of 0. */
public void resetEncoders() {
- modules.forEach(SwerveModule::resetEncoders);
+ modules.forEach(ModuleIO::resetEncoders);
}
/** Zeroes the heading of the robot. */
@@ -341,19 +350,19 @@ public Command zeroHeading() {
/** Returns the module states. */
@Log.NT
public SwerveModuleState[] getModuleStates() {
- return modules.stream().map(SwerveModule::state).toArray(SwerveModuleState[]::new);
+ return modules.stream().map(ModuleIO::state).toArray(SwerveModuleState[]::new);
}
/** Returns the module states. */
@Log.NT
private SwerveModuleState[] getModuleSetpoints() {
- return modules.stream().map(SwerveModule::desiredState).toArray(SwerveModuleState[]::new);
+ return modules.stream().map(ModuleIO::desiredState).toArray(SwerveModuleState[]::new);
}
/** Returns the module positions */
@Log.NT
public SwerveModulePosition[] getModulePositions() {
- return modules.stream().map(SwerveModule::position).toArray(SwerveModulePosition[]::new);
+ return modules.stream().map(ModuleIO::position).toArray(SwerveModulePosition[]::new);
}
/** Returns the robot relative chassis speeds. */
@@ -401,8 +410,6 @@ public void periodic() {
new Pose2d(pose().getTranslation(), new Rotation2d(rotationController.getSetpoint())));
log("command", Optional.ofNullable(getCurrentCommand()).map(Command::getName).orElse("none"));
-
- modules.forEach(SwerveModule::updatePID);
}
@Override
@@ -431,26 +438,28 @@ public Command lock() {
1));
}
- public Command systemsCheck() {
+ public Test systemsCheck() {
ChassisSpeeds speeds = new ChassisSpeeds(1, 1, 0);
- return run(() -> setChassisSpeeds(speeds, ControlMode.OPEN_LOOP_VELOCITY))
- .withTimeout(0.5)
- .finallyDo(
- () -> {
- modules.forEach(
- m -> {
- assertEqualsReport(
- "Drive Syst Check Module Angle (degrees)",
- 45,
- Units.radiansToDegrees(atan(m.position().angle.getTan())),
- 1);
- assertReport(
- m.state().speedMetersPerSecond * Math.signum(m.position().angle.getCos())
- > 1,
- "Drive Syst Check Module Speed",
- "expected: >= 1; actual: " + m.state().speedMetersPerSecond);
- });
- });
+ Command testCommand =
+ run(() -> setChassisSpeeds(speeds, ControlMode.OPEN_LOOP_VELOCITY)).withTimeout(0.5);
+ Function speedCheck =
+ m ->
+ tAssert(
+ () -> m.state().speedMetersPerSecond * Math.signum(m.position().angle.getCos()) > 1,
+ "Drive Syst Check " + m.name() + " Module Speed",
+ "expected: >= 1; actual: " + m.state().speedMetersPerSecond);
+ Function atAngle =
+ m ->
+ eAssert(
+ "Drive Syst Check " + m.name() + " Module Angle (degrees)",
+ () -> 45,
+ () -> Units.radiansToDegrees(atan(m.position().angle.getTan())),
+ 1);
+ Set assertions =
+ modules.stream()
+ .flatMap(m -> Stream.of(speedCheck.apply(m), atAngle.apply(m)))
+ .collect(Collectors.toSet());
+ return new Test(testCommand, assertions);
}
public void close() throws Exception {
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index 43841f7..4e60f10 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -9,10 +9,14 @@
import edu.wpi.first.units.Current;
import edu.wpi.first.units.Distance;
import edu.wpi.first.units.Measure;
+import edu.wpi.first.units.Time;
import edu.wpi.first.units.Velocity;
import java.util.List;
public final class DriveConstants {
+ // Rate at which sensors update periodicially
+ public static final Measure SENSOR_PERIOD = Seconds.of(0.001);
+
// Distance between centers of right and left wheels on robot
public static final Measure TRACK_WIDTH = Meters.of(0.5715);
// Distance between front and back wheels on robot
@@ -86,12 +90,26 @@ public static final class Driving {
public static final Measure POSITION_FACTOR = CIRCUMFERENCE.times(GEARING);
public static final Measure> VELOCITY_FACTOR = POSITION_FACTOR.per(Minute);
- public static final Measure CURRENT_LIMIT = Amps.of(60);
+ public static final Measure CURRENT_LIMIT = Amps.of(50);
public static final class PID {
- public static final double P = 3.2;
- public static final double I = 0.0;
- public static final double D = 0.0;
+ public static final class SPARK {
+ public static final double P = 1;
+ public static final double I = 0.0;
+ public static final double D = 0.0;
+ }
+
+ public static final class TALON {
+ public static final double P = 3.2;
+ public static final double I = 0.0;
+ public static final double D = 0.0;
+ }
+
+ public static final class SIM {
+ public static final double P = 3.2;
+ public static final double I = 0.0;
+ public static final double D = 0.0;
+ }
}
public static final class FF {
@@ -100,10 +118,19 @@ public static final class FF {
// a linear: 0.17586, 0.13707, 0.23915, 0.26842
// a rotation: 0.37587, 0.20079
// 2 has 0.55 R^2
- public static final double S = 0.23963;
- public static final double V = 2.0681;
- public static final double kA_linear = 0.205;
- public static final double kA_angular = 0.376;
+ public static final class SPARK {
+ public static final double S = 0.23963;
+ public static final double V = 2.0681;
+ public static final double kA_linear = 0.205;
+ public static final double kA_angular = 0.376;
+ }
+
+ public static final class TALON {
+ public static final double S = -1; // TODO
+ public static final double V = 2.0681;
+ public static final double kA_linear = 0.205;
+ public static final double kA_angular = 0.376;
+ }
}
}
@@ -119,9 +146,23 @@ static final class Turning {
public static final Measure CURRENT_LIMIT = Amps.of(20);
public static final class PID {
- public static final double P = 9;
- public static final double I = 0.0;
- public static final double D = 0.05;
+ public static final class SPARK {
+ public static final double P = 0.3; // FAKE DOES NOT WORK
+ public static final double I = 0.0;
+ public static final double D = 0.05;
+ }
+
+ public static final class TALON {
+ public static final double P = 9;
+ public static final double I = 0.0;
+ public static final double D = 0.05;
+ }
+
+ public static final class SIM {
+ public static final double P = 9;
+ public static final double I = 0.0;
+ public static final double D = 0.05;
+ }
}
// system constants only used in simulation
diff --git a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
index 6fcd2ac..11cdf3b 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
@@ -1,10 +1,25 @@
package org.sciborgs1155.robot.drive;
import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.kinematics.SwerveModulePosition;
+import edu.wpi.first.math.kinematics.SwerveModuleState;
import monologue.Logged;
/** Generalized hardware internals for a swerve module */
-public interface ModuleIO extends AutoCloseable, Logged {
+public interface ModuleIO extends Logged, AutoCloseable {
+ /** The method to use when controlling the drive motor. */
+ public static enum ControlMode {
+ CLOSED_LOOP_VELOCITY,
+ OPEN_LOOP_VELOCITY;
+ }
+
+ /**
+ * Returns the name of the swerve module (e.g. "FR" indicating the front right swerve module.)
+ *
+ * @return The name of the swerve module.
+ */
+ String name();
+
/**
* Sets the drive voltage of the module.
*
@@ -40,9 +55,64 @@ public interface ModuleIO extends AutoCloseable, Logged {
*/
Rotation2d rotation();
+ /**
+ * Returns the current state of the module.
+ *
+ * @return The current state of the module.
+ */
+ SwerveModuleState state();
+
+ /**
+ * Returns the current position of the module.
+ *
+ * @return The current position of the module.
+ */
+ SwerveModulePosition position();
+
+ /**
+ * Returns the desired position of the module.
+ *
+ * @return The desired position of the module.
+ */
+ SwerveModuleState desiredState();
+
/** Resets all encoders. */
void resetEncoders();
+ /**
+ * Sets the setpoint value for the onboard drive motor's PID.
+ *
+ * @param velocity The velocity setpoint.
+ */
+ void setDriveSetpoint(double velocity);
+
+ /**
+ * Sets the setpoint value for the onboard turn motor's PID.
+ *
+ * @param angle The angle setpoint.
+ */
+ void setTurnSetpoint(double angle);
+
+ /**
+ * Updates controllers based on an optimized desired state and actuates the module accordingly.
+ *
+ * This method should be called periodically.
+ *
+ * @param setpoint The desired state of the module.
+ * @param mode The control mode to use when calculating drive voltage.
+ */
+ void updateSetpoint(SwerveModuleState setpoint, ControlMode mode);
+
+ /**
+ * Updates the drive voltage and turn angle.
+ *
+ *
This is useful for SysId characterization, but should never be run otherwise.
+ *
+ * @param angle The desired angle of the module.
+ * @param voltage The voltage to supply to the drive motor.
+ */
+ void updateInputs(Rotation2d angle, double voltage);
+
@Override
void close();
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
index 9f4d186..1ac7791 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
@@ -1,6 +1,8 @@
package org.sciborgs1155.robot.drive;
import edu.wpi.first.math.geometry.Rotation2d;
+import edu.wpi.first.math.kinematics.SwerveModulePosition;
+import edu.wpi.first.math.kinematics.SwerveModuleState;
public class NoModule implements ModuleIO {
@@ -10,6 +12,11 @@ public void setDriveVoltage(double voltage) {}
@Override
public void setTurnVoltage(double voltage) {}
+ @Override
+ public String name() {
+ return "NoModule";
+ }
+
@Override
public double drivePosition() {
return 0;
@@ -28,6 +35,33 @@ public Rotation2d rotation() {
@Override
public void resetEncoders() {}
+ @Override
+ public SwerveModuleState state() {
+ return new SwerveModuleState();
+ }
+
+ @Override
+ public SwerveModulePosition position() {
+ return new SwerveModulePosition();
+ }
+
+ @Override
+ public SwerveModuleState desiredState() {
+ return new SwerveModuleState();
+ }
+
+ @Override
+ public void setDriveSetpoint(double velocity) {}
+
+ @Override
+ public void setTurnSetpoint(double angle) {}
+
+ @Override
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {}
+
@Override
public void close() {}
+
+ @Override
+ public void updateInputs(Rotation2d angle, double voltage) {}
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index a63df74..8b846e7 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -1,9 +1,14 @@
package org.sciborgs1155.robot.drive;
import static edu.wpi.first.units.Units.Seconds;
+import static org.sciborgs1155.robot.Constants.PERIOD;
import edu.wpi.first.math.VecBuilder;
+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.math.system.plant.DCMotor;
import edu.wpi.first.math.system.plant.LinearSystemId;
import edu.wpi.first.wpilibj.simulation.DCMotorSim;
@@ -15,7 +20,7 @@ public class SimModule implements ModuleIO {
private final DCMotorSim drive =
new DCMotorSim(
- LinearSystemId.createDCMotorSystem(Driving.FF.V, Driving.FF.kA_linear),
+ LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear),
DCMotor.getNeoVortex(1),
1 / Driving.GEARING);
private final DCMotorSim turn =
@@ -24,6 +29,26 @@ public class SimModule implements ModuleIO {
DCMotor.getNeo550(1),
1 / Turning.MOTOR_GEARING);
+ private final PIDController driveFeedback =
+ new PIDController(Driving.PID.SIM.P, Driving.PID.SIM.I, Driving.PID.SIM.D);
+ private final PIDController turnFeedback =
+ new PIDController(Turning.PID.SIM.P, Turning.PID.SIM.I, Turning.PID.SIM.D);
+
+ private final SimpleMotorFeedforward driveTranslationFeedforward =
+ new SimpleMotorFeedforward(
+ Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear);
+ private final SimpleMotorFeedforward driveRotationFeedforward =
+ new SimpleMotorFeedforward(
+ Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_angular);
+
+ private SwerveModuleState setpoint = new SwerveModuleState();
+
+ private final String name;
+
+ public SimModule(String name) {
+ this.name = name;
+ }
+
@Override
public void setDriveVoltage(double voltage) {
drive.setInputVoltage(voltage);
@@ -51,12 +76,91 @@ public Rotation2d rotation() {
return Rotation2d.fromRadians(turn.getAngularPositionRad());
}
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public SwerveModuleState state() {
+ return new SwerveModuleState(driveVelocity(), rotation());
+ }
+
+ @Override
+ public SwerveModulePosition position() {
+ return new SwerveModulePosition(drivePosition(), rotation());
+ }
+
+ @Override
+ public SwerveModuleState desiredState() {
+ return setpoint;
+ }
+
@Override
public void resetEncoders() {
drive.setState(VecBuilder.fill(0, 0));
turn.setState(VecBuilder.fill(0, 0));
}
+ @Override
+ public void setDriveSetpoint(double velocity) {}
+
+ @Override
+ public void setTurnSetpoint(double angle) {}
+
+ @Override
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ setpoint = SwerveModuleState.optimize(setpoint, rotation());
+ setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
+
+ double driveTVolts =
+ switch (mode) {
+ case CLOSED_LOOP_VELOCITY ->
+ driveTranslationFeedforward.calculate(
+ this.setpoint.speedMetersPerSecond,
+ setpoint.speedMetersPerSecond,
+ PERIOD.in(Seconds));
+ case OPEN_LOOP_VELOCITY ->
+ driveTranslationFeedforward.calculate(setpoint.speedMetersPerSecond);
+ };
+
+ double driveRVolts =
+ switch (mode) {
+ case CLOSED_LOOP_VELOCITY ->
+ driveRotationFeedforward.calculate(
+ this.setpoint.speedMetersPerSecond,
+ setpoint.speedMetersPerSecond,
+ PERIOD.in(Seconds));
+ case OPEN_LOOP_VELOCITY ->
+ driveRotationFeedforward.calculate(setpoint.speedMetersPerSecond);
+ };
+
+ double driveVolts =
+ driveTVolts + driveRVolts; // original: double driveVolts = driveTVolts * movementRatio +
+ // driveRVolts * (1 - movementRatio);
+
+ if (mode == ControlMode.CLOSED_LOOP_VELOCITY) {
+ driveVolts += driveFeedback.calculate(driveVelocity(), setpoint.speedMetersPerSecond);
+ }
+
+ double turnVolts = turnFeedback.calculate(rotation().getRadians(), setpoint.angle.getRadians());
+
+ setDriveVoltage(driveVolts);
+ setTurnVoltage(turnVolts);
+
+ this.setpoint = setpoint;
+ }
+
+ @Override
+ public void updateInputs(Rotation2d angle, double voltage) {
+ setpoint.angle = angle;
+
+ double turnVolts = turnFeedback.calculate(rotation().getRadians(), setpoint.angle.getRadians());
+
+ setDriveVoltage(voltage);
+ setTurnVoltage(turnVolts);
+ }
+
@Override
public void close() {}
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 0cb5ccc..71a9ff4 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -4,14 +4,20 @@
import static org.sciborgs1155.lib.FaultLogger.*;
import static org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.COUPLING_RATIO;
+import com.revrobotics.CANSparkBase.ControlType;
import com.revrobotics.CANSparkBase.IdleMode;
import com.revrobotics.CANSparkFlex;
import com.revrobotics.CANSparkLowLevel.MotorType;
import com.revrobotics.CANSparkMax;
import com.revrobotics.RelativeEncoder;
import com.revrobotics.SparkAbsoluteEncoder;
+import com.revrobotics.SparkPIDController;
+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 java.util.Set;
+import monologue.Annotations.Log;
import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
@@ -19,29 +25,41 @@
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
public class SparkModule implements ModuleIO {
-
private final CANSparkFlex driveMotor; // Neo Vortex
private final CANSparkMax turnMotor; // Neo 550
private final RelativeEncoder driveEncoder;
private final SparkAbsoluteEncoder turningEncoder;
+ private final SparkPIDController drivePID;
+ private final SparkPIDController turnPID;
+
+ private final SimpleMotorFeedforward driveFF;
private final Rotation2d angularOffset;
private double lastPosition;
private double lastVelocity;
- /**
- * Constructs a SwerveModule for rev's MAX Swerve.
- *
- * @param drivePort drive motor port
- * @param turnPort turning motor port
- */
- public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset) {
+ @Log.NT private SwerveModuleState setpoint = new SwerveModuleState();
+
+ private final String name;
+
+ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset, String name) {
driveMotor = new CANSparkFlex(drivePort, MotorType.kBrushless);
driveEncoder = driveMotor.getEncoder();
+ drivePID = driveMotor.getPIDController();
+ driveFF =
+ new SimpleMotorFeedforward(
+ Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear); // TODO: Re-tune
check(driveMotor, driveMotor.restoreFactoryDefaults());
+
+ // TODO: Re-tune
+ check(driveMotor, drivePID.setP(Driving.PID.SPARK.P));
+ check(driveMotor, drivePID.setI(Driving.PID.SPARK.I));
+ check(driveMotor, drivePID.setD(Driving.PID.SPARK.D));
+ check(driveMotor, drivePID.setFeedbackDevice(driveEncoder));
+
check(driveMotor, driveMotor.setIdleMode(IdleMode.kBrake));
check(driveMotor, driveMotor.setSmartCurrentLimit((int) Driving.CURRENT_LIMIT.in(Amps)));
check(driveMotor, driveEncoder.setPositionConversionFactor(Driving.POSITION_FACTOR.in(Meters)));
@@ -61,8 +79,19 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset) {
turnMotor = new CANSparkMax(turnPort, MotorType.kBrushless);
turningEncoder = turnMotor.getAbsoluteEncoder();
+ turnPID = turnMotor.getPIDController();
check(turnMotor, turnMotor.restoreFactoryDefaults());
+
+ // TODO: Re-tune
+ check(turnMotor, turnPID.setP(Turning.PID.SPARK.P));
+ check(turnMotor, turnPID.setI(Turning.PID.SPARK.I));
+ check(turnMotor, turnPID.setD(Turning.PID.SPARK.D));
+ check(turnMotor, turnPID.setPositionPIDWrappingEnabled(true));
+ check(turnMotor, turnPID.setPositionPIDWrappingMinInput(-Math.PI));
+ check(turnMotor, turnPID.setPositionPIDWrappingMaxInput(Math.PI));
+ check(turnMotor, turnPID.setFeedbackDevice(turningEncoder));
+
check(turnMotor, turnMotor.setIdleMode(IdleMode.kBrake));
check(turnMotor, turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps)));
turningEncoder.setInverted(Turning.ENCODER_INVERTED);
@@ -80,7 +109,15 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset) {
Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
Set.of(Sensor.ABSOLUTE),
false));
-
+ SparkUtils.addChecker(
+ () ->
+ check(
+ turnMotor,
+ SparkUtils.configureFrameStrategy(
+ turnMotor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.ABSOLUTE),
+ false)));
check(turnMotor, turnMotor.burnFlash());
register(driveMotor);
@@ -89,6 +126,7 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset) {
resetEncoders();
this.angularOffset = angularOffset;
+ this.name = name;
}
@Override
@@ -104,6 +142,11 @@ public void setTurnVoltage(double voltage) {
check(turnMotor);
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public double drivePosition() {
lastPosition = SparkUtils.wrapCall(driveMotor, driveEncoder.getPosition()).orElse(lastPosition);
@@ -127,6 +170,55 @@ public void resetEncoders() {
driveEncoder.setPosition(0);
}
+ @Override
+ public SwerveModuleState state() {
+ return new SwerveModuleState(driveVelocity(), rotation());
+ }
+
+ @Override
+ public SwerveModulePosition position() {
+ return new SwerveModulePosition(drivePosition(), rotation());
+ }
+
+ @Override
+ public SwerveModuleState desiredState() {
+ return setpoint;
+ }
+
+ @Override
+ public void setDriveSetpoint(double velocity) {
+ drivePID.setReference(velocity, ControlType.kVelocity, 0, driveFF.calculate(velocity));
+ }
+
+ @Override
+ public void setTurnSetpoint(double angle) {
+ turnPID.setReference(angle, ControlType.kPosition);
+ }
+
+ @Override
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ // Optimize the reference state to avoid spinning further than 90 degrees
+ setpoint = SwerveModuleState.optimize(setpoint, rotation());
+ // Scale setpoint by cos of turning error to reduce tread wear
+ setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
+
+ if (mode == ControlMode.OPEN_LOOP_VELOCITY) {
+ setDriveVoltage(driveFF.calculate(setpoint.speedMetersPerSecond));
+ } else {
+ setDriveSetpoint(setpoint.speedMetersPerSecond);
+ }
+
+ setTurnSetpoint(setpoint.angle.getRadians());
+ this.setpoint = setpoint;
+ }
+
+ @Override
+ public void updateInputs(Rotation2d angle, double voltage) {
+ setpoint.angle = angle;
+ setDriveVoltage(voltage);
+ setTurnSetpoint(angle.getRadians());
+ }
+
@Override
public void close() {
driveMotor.close();
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java b/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java
deleted file mode 100644
index 19321df..0000000
--- a/src/main/java/org/sciborgs1155/robot/drive/SwerveModule.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package org.sciborgs1155.robot.drive;
-
-import static edu.wpi.first.units.Units.Seconds;
-import static org.sciborgs1155.robot.Constants.PERIOD;
-
-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.DoubleEntry;
-import monologue.Annotations.Log;
-import monologue.Logged;
-import org.sciborgs1155.lib.Tuning;
-import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
-import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
-
-/** Class to encapsulate a REV Max Swerve module */
-public class SwerveModule implements Logged, AutoCloseable {
-
- /** The method to use when controlling the drive motor. */
- public static enum ControlMode {
- CLOSED_LOOP_VELOCITY,
- OPEN_LOOP_VELOCITY;
- }
-
- private final ModuleIO hardware;
-
- private final PIDController driveFeedback;
- private final PIDController turnFeedback;
-
- private final SimpleMotorFeedforward driveTranslationFeedforward;
- private final SimpleMotorFeedforward driveRotationFeedforward;
-
- private SwerveModuleState setpoint = new SwerveModuleState();
-
- public final String name;
-
- private final DoubleEntry drivingD = Tuning.entry("/Robot/drive/driving/D", Driving.PID.D);
- private final DoubleEntry drivingI = Tuning.entry("/Robot/drive/driving/I", Driving.PID.I);
- private final DoubleEntry drivingP = Tuning.entry("/Robot/drive/driving/P", Driving.PID.P);
-
- private final DoubleEntry turningD = Tuning.entry("/Robot/drive/turning/D", Turning.PID.D);
- private final DoubleEntry turningI = Tuning.entry("/Robot/drive/turning/I", Turning.PID.I);
- private final DoubleEntry turningP = Tuning.entry("/Robot/drive/turning/P", Turning.PID.P);
-
- /**
- * Constructs a SwerveModule for rev's MAX Swerve using vortexes (flex) or krakens (talon).
- *
- * @param hardware talon OR flex swerve module
- * @param angularOffset offset from drivetrain
- */
- public SwerveModule(ModuleIO hardware, Rotation2d angularOffset, String name) {
- this.hardware = hardware;
- this.name = name;
- driveFeedback = new PIDController(Driving.PID.P, Driving.PID.I, Driving.PID.D);
- turnFeedback = new PIDController(Turning.PID.P, Turning.PID.I, Turning.PID.D);
- turnFeedback.enableContinuousInput(-Math.PI, Math.PI);
-
- driveTranslationFeedforward =
- new SimpleMotorFeedforward(Driving.FF.S, Driving.FF.V, Driving.FF.kA_linear);
- driveRotationFeedforward =
- new SimpleMotorFeedforward(Driving.FF.S, Driving.FF.V, Driving.FF.kA_angular);
-
- setpoint = new SwerveModuleState();
- }
-
- /**
- * Returns the current state of the module.
- *
- * @return The current state of the module.
- */
- @Log.NT
- public SwerveModuleState state() {
- return new SwerveModuleState(hardware.driveVelocity(), hardware.rotation());
- }
-
- /**
- * Returns the current position of the module.
- *
- * @return The current position of the module.
- */
- @Log.NT
- public SwerveModulePosition position() {
- return new SwerveModulePosition(hardware.drivePosition(), hardware.rotation());
- }
-
- /**
- * Updates controllers based on an optimized desired state and actuates the module accordingly.
- *
- *
This method should be called periodically.
- *
- * @param setpoint The desired state of the module.
- * @param mode The control mode to use when calculating drive voltage.
- * @param movementRatio The ratio of translational velocity to the sum of rotational and
- * translational velocity being requested of the entire swerve drive. 1 for only translation,
- */
- public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double movementRatio) {
- // Optimize the reference state to avoid spinning further than 90 degrees
- setpoint = SwerveModuleState.optimize(setpoint, hardware.rotation());
- // Scale setpoint by cos of turning error to reduce tread wear
- setpoint.speedMetersPerSecond *= setpoint.angle.minus(hardware.rotation()).getCos();
-
- // Calculate two feedforward values for using different kA depending on if the robot is rotating
- // or translating.
- double driveTVolts =
- switch (mode) {
- case CLOSED_LOOP_VELOCITY -> driveTranslationFeedforward.calculate(
- this.setpoint.speedMetersPerSecond,
- setpoint.speedMetersPerSecond,
- PERIOD.in(Seconds));
- case OPEN_LOOP_VELOCITY -> driveTranslationFeedforward.calculate(
- setpoint.speedMetersPerSecond);
- };
-
- double driveRVolts =
- switch (mode) {
- case CLOSED_LOOP_VELOCITY -> driveRotationFeedforward.calculate(
- this.setpoint.speedMetersPerSecond,
- setpoint.speedMetersPerSecond,
- PERIOD.in(Seconds));
- case OPEN_LOOP_VELOCITY -> driveRotationFeedforward.calculate(
- setpoint.speedMetersPerSecond);
- };
-
- double driveVolts = driveTVolts * movementRatio + driveRVolts * (1 - movementRatio);
-
- if (mode == ControlMode.CLOSED_LOOP_VELOCITY) {
- driveVolts +=
- driveFeedback.calculate(hardware.driveVelocity(), setpoint.speedMetersPerSecond);
- }
-
- double turnVolts =
- turnFeedback.calculate(hardware.rotation().getRadians(), setpoint.angle.getRadians());
-
- hardware.setDriveVoltage(driveVolts);
- hardware.setTurnVoltage(turnVolts);
-
- this.setpoint = setpoint;
- }
-
- /**
- * Updates the drive voltage and turn angle.
- *
- *
This is useful for SysId characterization, but should never be run otherwise.
- *
- * @param angle The desired angle of the module.
- * @param voltage The voltage to supply to the drive motor.
- */
- public void updateDriveVoltage(Rotation2d angle, double voltage) {
- setpoint.angle = angle;
-
- double turnVolts =
- turnFeedback.calculate(hardware.rotation().getRadians(), setpoint.angle.getRadians());
-
- hardware.setDriveVoltage(voltage);
- hardware.setTurnVoltage(turnVolts);
- }
-
- @Log.NT
- public SwerveModuleState desiredState() {
- return setpoint;
- }
-
- public void resetEncoders() {
- hardware.resetEncoders();
- }
-
- public void updatePID() {
- driveFeedback.setPID(drivingP.get(), drivingI.get(), drivingD.get());
- turnFeedback.setPID(turningP.get(), turningI.get(), turningD.get());
- }
-
- @Override
- public void close() {
- hardware.close();
- }
-}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index 7a49e22..104407b 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -1,61 +1,119 @@
package org.sciborgs1155.robot.drive;
import static edu.wpi.first.units.Units.*;
+import static org.sciborgs1155.lib.FaultLogger.*;
+import static org.sciborgs1155.robot.drive.DriveConstants.SENSOR_PERIOD;
+import com.ctre.phoenix6.StatusSignal;
import com.ctre.phoenix6.configs.TalonFXConfiguration;
+import com.ctre.phoenix6.controls.VelocityVoltage;
import com.ctre.phoenix6.hardware.TalonFX;
import com.ctre.phoenix6.signals.NeutralModeValue;
+import com.revrobotics.CANSparkBase.ControlType;
import com.revrobotics.CANSparkBase.IdleMode;
import com.revrobotics.CANSparkLowLevel.MotorType;
import com.revrobotics.CANSparkMax;
import com.revrobotics.SparkAbsoluteEncoder;
-import com.revrobotics.SparkAbsoluteEncoder.Type;
+import com.revrobotics.SparkPIDController;
+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 java.util.Set;
import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
import org.sciborgs1155.lib.TalonUtils;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
-/** Class to encapsulate a CTRE Talon Swerve module */
public class TalonModule implements ModuleIO {
private final TalonFX driveMotor;
private final CANSparkMax turnMotor;
-
private final SparkAbsoluteEncoder turnEncoder;
- public TalonModule(int drivePort, int turnPort) {
+ private final SparkPIDController turnPID;
+ private final SimpleMotorFeedforward driveFF;
+
+ private final StatusSignal drivePos;
+ private final StatusSignal driveVelocity;
+
+ private final VelocityVoltage velocityOut = new VelocityVoltage(0);
+
+ private SwerveModuleState setpoint = new SwerveModuleState();
+
+ private final String name;
+
+ public TalonModule(int drivePort, int turnPort, String name) {
driveMotor = new TalonFX(drivePort);
+ drivePos = driveMotor.getPosition();
+ driveVelocity = driveMotor.getVelocity();
+ driveFF =
+ new SimpleMotorFeedforward(
+ Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.kA_linear);
+
+ drivePos.setUpdateFrequency(1 / SENSOR_PERIOD.in(Seconds));
+ driveVelocity.setUpdateFrequency(1 / SENSOR_PERIOD.in(Seconds));
+
+ TalonFXConfiguration talonConfig = new TalonFXConfiguration();
+ // reset config
+ driveMotor.getConfigurator().apply(talonConfig);
+
+ talonConfig.MotorOutput.NeutralMode = NeutralModeValue.Brake;
+ talonConfig.CurrentLimits.SupplyCurrentLimit = 80;
+ talonConfig.Slot0.kP = Driving.PID.TALON.P;
+ talonConfig.Slot1.kI = Driving.PID.TALON.I;
+ talonConfig.Slot0.kD = Driving.PID.TALON.D;
+
+ driveMotor.getConfigurator().apply(talonConfig);
+
+ TalonUtils.addMotor(driveMotor);
+
turnMotor = new CANSparkMax(turnPort, MotorType.kBrushless);
+ turnEncoder = turnMotor.getAbsoluteEncoder();
+ turnPID = turnMotor.getPIDController();
- turnMotor.restoreFactoryDefaults();
- turnMotor.setIdleMode(IdleMode.kBrake);
- turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps));
+ check(turnMotor, turnMotor.restoreFactoryDefaults());
- driveMotor.getPosition().setUpdateFrequency(100);
- driveMotor.getVelocity().setUpdateFrequency(100);
+ check(turnMotor, turnPID.setP(Turning.PID.SPARK.P));
+ check(turnMotor, turnPID.setI(Turning.PID.SPARK.I));
+ check(turnMotor, turnPID.setD(Turning.PID.SPARK.D));
+ check(turnMotor, turnPID.setPositionPIDWrappingEnabled(true));
+ check(turnMotor, turnPID.setPositionPIDWrappingMinInput(-Math.PI));
+ check(turnMotor, turnPID.setPositionPIDWrappingMaxInput(Math.PI));
+ check(turnMotor, turnPID.setFeedbackDevice(turnEncoder));
- turnEncoder = turnMotor.getAbsoluteEncoder(Type.kDutyCycle);
+ check(turnMotor, turnMotor.setIdleMode(IdleMode.kBrake));
+ check(turnMotor, turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps)));
turnEncoder.setInverted(Turning.ENCODER_INVERTED);
- turnEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians));
- turnEncoder.setVelocityConversionFactor(Turning.VELOCITY_FACTOR.in(RadiansPerSecond));
-
- SparkUtils.configureFrameStrategy(
+ check(turnMotor);
+ check(turnMotor, turnEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians)));
+ check(
turnMotor,
- Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
- Set.of(Sensor.ABSOLUTE),
- false);
-
- TalonFXConfiguration toApply = new TalonFXConfiguration();
- toApply.MotorOutput.NeutralMode = NeutralModeValue.Brake;
- toApply.CurrentLimits.SupplyCurrentLimit = 50;
- driveMotor.getConfigurator().apply(toApply);
+ turnEncoder.setVelocityConversionFactor(Turning.VELOCITY_FACTOR.in(RadiansPerSecond)));
+ check(turnMotor, turnEncoder.setAverageDepth(2));
+ check(
+ turnMotor,
+ SparkUtils.configureFrameStrategy(
+ turnMotor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.ABSOLUTE),
+ false));
+ SparkUtils.addChecker(
+ () ->
+ check(
+ turnMotor,
+ SparkUtils.configureFrameStrategy(
+ turnMotor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.ABSOLUTE),
+ false)));
+ check(turnMotor, turnMotor.burnFlash());
+
+ register(turnMotor);
- TalonUtils.addMotor(driveMotor);
resetEncoders();
-
- turnMotor.burnFlash();
+ this.name = name;
}
@Override
@@ -68,14 +126,19 @@ public void setTurnVoltage(double voltage) {
turnMotor.setVoltage(voltage);
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public double drivePosition() {
- return driveMotor.getPosition().getValueAsDouble();
+ return drivePos.getValueAsDouble();
}
@Override
public double driveVelocity() {
- return driveMotor.getVelocity().getValueAsDouble();
+ return driveVelocity.getValueAsDouble();
}
@Override
@@ -83,11 +146,60 @@ public Rotation2d rotation() {
return Rotation2d.fromRadians(turnEncoder.getPosition());
}
+ @Override
+ public SwerveModuleState state() {
+ return new SwerveModuleState(driveVelocity(), rotation());
+ }
+
+ @Override
+ public SwerveModulePosition position() {
+ return new SwerveModulePosition(drivePosition(), rotation());
+ }
+
+ @Override
+ public SwerveModuleState desiredState() {
+ return setpoint;
+ }
+
@Override
public void resetEncoders() {
driveMotor.setPosition(0);
}
+ @Override
+ public void setDriveSetpoint(double velocity) {
+ driveMotor.setControl(
+ velocityOut.withVelocity(velocity).withFeedForward(driveFF.calculate(velocity)));
+ }
+
+ @Override
+ public void setTurnSetpoint(double angle) {
+ turnPID.setReference(angle, ControlType.kPosition);
+ }
+
+ @Override
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ setpoint = SwerveModuleState.optimize(setpoint, rotation());
+ // Scale setpoint by cos of turning error to reduce tread wear
+ setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
+
+ if (mode == ControlMode.OPEN_LOOP_VELOCITY) {
+ setDriveVoltage(driveFF.calculate(setpoint.speedMetersPerSecond));
+ } else {
+ setDriveSetpoint(setpoint.speedMetersPerSecond);
+ }
+
+ setTurnSetpoint(setpoint.angle.getRadians());
+ this.setpoint = setpoint;
+ }
+
+ @Override
+ public void updateInputs(Rotation2d angle, double voltage) {
+ setpoint.angle = angle;
+ setDriveVoltage(voltage);
+ setTurnSetpoint(angle.getRadians());
+ }
+
@Override
public void close() {
turnMotor.close();
diff --git a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
index 8689d2e..bc6b619 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
@@ -12,7 +12,7 @@
public class VisionConstants {
public static final AprilTagFieldLayout TAG_LAYOUT =
- AprilTagFieldLayout.loadField(AprilTagFields.k2024Crescendo);
+ AprilTagFieldLayout.loadField(AprilTagFields.kDefaultField);
/* The front of the robot is defined at the intake */
diff --git a/src/test/java/org/sciborgs1155/lib/FaultLoggerTest.java b/src/test/java/org/sciborgs1155/lib/FaultLoggerTest.java
new file mode 100644
index 0000000..a2f3bf9
--- /dev/null
+++ b/src/test/java/org/sciborgs1155/lib/FaultLoggerTest.java
@@ -0,0 +1,79 @@
+package org.sciborgs1155.lib;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.sciborgs1155.lib.UnitTestingUtil.setupTests;
+
+import edu.wpi.first.networktables.NetworkTable;
+import edu.wpi.first.networktables.NetworkTableInstance;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.sciborgs1155.lib.FaultLogger.FaultType;
+
+public class FaultLoggerTest {
+
+ @BeforeAll
+ public static void reset() {
+ FaultLogger.clear();
+ FaultLogger.unregisterAll();
+ }
+
+ @BeforeEach
+ public void setup() {
+ setupTests();
+ FaultLogger.clear();
+ }
+
+ @Test
+ void report() {
+ NetworkTable base = NetworkTableInstance.getDefault().getTable("Faults");
+ var activeInfos =
+ base.getSubTable("Active Faults").getStringArrayTopic("infos").subscribe(new String[10]);
+ var totalErrors =
+ base.getSubTable("Total Faults").getStringArrayTopic("errors").subscribe(new String[10]);
+ FaultLogger.update();
+ FaultLogger.report("Test", "Example", FaultType.INFO);
+ FaultLogger.update();
+ assertEquals(1, FaultLogger.activeFaults().size());
+ assertEquals(1, FaultLogger.totalFaults().size());
+ assertEquals(1, activeInfos.get().length);
+ assertEquals(0, totalErrors.get().length);
+
+ // duplicate
+ FaultLogger.report("Test", "Example", FaultType.INFO);
+ FaultLogger.update();
+ assertEquals(1, FaultLogger.activeFaults().size());
+ assertEquals(1, FaultLogger.totalFaults().size());
+ assertEquals(1, activeInfos.get().length);
+ assertEquals(0, totalErrors.get().length);
+
+ FaultLogger.report("Test2", "Example2", FaultType.ERROR);
+ FaultLogger.update();
+ assertEquals(1, FaultLogger.activeFaults().size());
+ assertEquals(2, FaultLogger.totalFaults().size());
+ assertEquals(0, activeInfos.get().length);
+ assertEquals(1, totalErrors.get().length);
+ }
+
+ @Test
+ void register() {
+ NetworkTable base = NetworkTableInstance.getDefault().getTable("Faults");
+ var activeErrors =
+ base.getSubTable("Active Faults").getStringArrayTopic("errors").subscribe(new String[10]);
+ var totalErrors =
+ base.getSubTable("Total Faults").getStringArrayTopic("errors").subscribe(new String[10]);
+
+ FaultLogger.update();
+ FaultLogger.register(() -> true, "Recurring Test", "Idk", FaultType.ERROR);
+ FaultLogger.update();
+ FaultLogger.update();
+
+ // System.out.println(totalErrors.get().toString());
+ for (var e : totalErrors.get()) {
+ System.out.println(e);
+ }
+
+ assertEquals(1, activeErrors.get().length);
+ assertEquals(1, totalErrors.get().length);
+ }
+}
diff --git a/src/test/java/org/sciborgs1155/lib/SparkUtilsTest.java b/src/test/java/org/sciborgs1155/lib/SparkUtilsTest.java
new file mode 100644
index 0000000..458b45c
--- /dev/null
+++ b/src/test/java/org/sciborgs1155/lib/SparkUtilsTest.java
@@ -0,0 +1,53 @@
+package org.sciborgs1155.lib;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.sciborgs1155.lib.FaultLogger.*;
+import static org.sciborgs1155.lib.UnitTestingUtil.setupTests;
+
+import com.revrobotics.CANSparkBase.IdleMode;
+import com.revrobotics.CANSparkFlex;
+import com.revrobotics.CANSparkLowLevel.MotorType;
+import com.revrobotics.RelativeEncoder;
+import java.util.Set;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.sciborgs1155.lib.SparkUtils.Data;
+import org.sciborgs1155.lib.SparkUtils.Sensor;
+
+public class SparkUtilsTest {
+
+ @BeforeEach
+ public void setup() {
+ setupTests();
+ }
+
+ @Test
+ void configure() {
+ CANSparkFlex motor = new CANSparkFlex(1, MotorType.kBrushless);
+ RelativeEncoder encoder = motor.getEncoder();
+
+ check(motor, motor.restoreFactoryDefaults());
+ check(
+ motor,
+ SparkUtils.configureFrameStrategy(
+ motor,
+ Set.of(Data.POSITION, Data.VELOCITY, Data.APPLIED_OUTPUT),
+ Set.of(Sensor.INTEGRATED),
+ false));
+ check(motor, motor.setIdleMode(IdleMode.kBrake));
+ check(motor, motor.setSmartCurrentLimit(30));
+ check(motor, encoder.setPositionConversionFactor(0.5));
+ check(motor, encoder.setVelocityConversionFactor(0.25));
+ check(motor, encoder.setMeasurementPeriod(8));
+ check(motor, encoder.setAverageDepth(2));
+ check(motor, motor.burnFlash());
+
+ assertEquals(IdleMode.kBrake, motor.getIdleMode());
+ assertEquals(0.5, encoder.getPositionConversionFactor());
+ assertEquals(0.25, encoder.getVelocityConversionFactor());
+ assertEquals(8, encoder.getMeasurementPeriod());
+ assertEquals(2, encoder.getAverageDepth());
+
+ motor.close();
+ }
+}
diff --git a/src/test/java/org/sciborgs1155/lib/TestingUtilTest.java b/src/test/java/org/sciborgs1155/lib/TestingUtilTest.java
new file mode 100644
index 0000000..7ea59d9
--- /dev/null
+++ b/src/test/java/org/sciborgs1155/lib/TestingUtilTest.java
@@ -0,0 +1,124 @@
+package org.sciborgs1155.lib;
+
+import static edu.wpi.first.wpilibj2.command.Commands.runOnce;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.sciborgs1155.lib.Assertion.eAssert;
+import static org.sciborgs1155.lib.Assertion.tAssert;
+import static org.sciborgs1155.lib.Test.runUnitTest;
+import static org.sciborgs1155.lib.Test.toCommand;
+import static org.sciborgs1155.lib.UnitTestingUtil.reset;
+import static org.sciborgs1155.lib.UnitTestingUtil.runToCompletion;
+import static org.sciborgs1155.lib.UnitTestingUtil.setupTests;
+
+import edu.wpi.first.wpilibj.DriverStation;
+import edu.wpi.first.wpilibj.Timer;
+import edu.wpi.first.wpilibj2.command.Command;
+import edu.wpi.first.wpilibj2.command.Commands;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.function.Executable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.sciborgs1155.lib.Assertion.EqualityAssertion;
+import org.sciborgs1155.lib.Assertion.TruthAssertion;
+import org.sciborgs1155.lib.FaultLogger.Fault;
+import org.sciborgs1155.lib.FaultLogger.FaultType;
+
+public class TestingUtilTest {
+ int x;
+
+ @BeforeEach
+ public void setup() {
+ setupTests();
+ x = 0;
+ }
+
+ @AfterEach
+ public void clear() throws Exception {
+ reset();
+ x = 0;
+ }
+
+ public void increment() {
+ x += 1;
+ }
+
+ public void set(int x) {
+ this.x = x;
+ }
+
+ @org.junit.jupiter.api.Test
+ public void enabled() {
+ assertTrue(DriverStation.isEnabled());
+ }
+
+ @org.junit.jupiter.api.Test
+ public void fromCommandTest() throws Exception {
+ assertEquals(0, x);
+ Test t = Test.fromCommand(Commands.runOnce(this::increment));
+ Command c = toCommand(t);
+ runToCompletion(c);
+ assertEquals(1, x);
+ }
+
+ @ParameterizedTest
+ @ValueSource(doubles = {0.4, 2, 3.2, 4.03})
+ public void runToCompletionTest(double timeout) {
+ Command c = Commands.run(() -> {}).withTimeout(timeout);
+ double startTime = Timer.getFPGATimestamp();
+ runToCompletion(c);
+ assertEquals(timeout, Timer.getFPGATimestamp() - startTime, 0.3);
+ }
+
+ public void assertFaultCount(int infoCount, int warningCount, int errorCount) {
+ FaultLogger.update();
+ Set faults = FaultLogger.totalFaults();
+ Set infos =
+ faults.stream().filter(f -> f.type() == FaultType.INFO).collect(Collectors.toSet());
+ Set warnings =
+ faults.stream().filter(f -> f.type() == FaultType.WARNING).collect(Collectors.toSet());
+ Set errors =
+ faults.stream().filter(f -> f.type() == FaultType.ERROR).collect(Collectors.toSet());
+ assertEquals(infoCount, infos.size(), infos.toString());
+ assertEquals(warningCount, warnings.size());
+ assertEquals(errorCount, errors.size());
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-4, 3, 9})
+ public void systemCheckTest(int x) {
+ EqualityAssertion goodAssertion = eAssert("x", () -> x, () -> this.x);
+ Test passes = new Test(runOnce(() -> set(x)), Set.of(goodAssertion));
+ runToCompletion(toCommand(passes));
+ assertFaultCount(1, 0, 0);
+
+ TruthAssertion badAssertion = tAssert(() -> x != this.x, "x", "fails");
+ Test fails = new Test(runOnce(() -> set(x)), Set.of(badAssertion));
+ runToCompletion(toCommand(fails));
+ assertFaultCount(1, 1, 0);
+
+ FaultLogger.clear();
+ FaultLogger.unregisterAll();
+ Test combo = new Test(runOnce(() -> set(x)), Set.of(goodAssertion, badAssertion));
+ runToCompletion(toCommand(combo));
+ assertFaultCount(1, 1, 0);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-4, 3, 9})
+ public void unitTestTest(int x) {
+ EqualityAssertion goodAssertion = eAssert("x", () -> x, () -> this.x);
+ Test passes = new Test(runOnce(() -> set(x)), Set.of(goodAssertion));
+ runUnitTest(passes);
+ assertFaultCount(0, 0, 0);
+
+ TruthAssertion badAssertion = tAssert(() -> x != this.x, "x", "fails");
+ Test fails = new Test(runOnce(() -> set(x)), Set.of(badAssertion));
+ assertThrows(AssertionError.class, (Executable) () -> runUnitTest(fails));
+ assertFaultCount(0, 0, 0);
+ }
+}
diff --git a/src/test/java/org/sciborgs1155/lib/TuningTest.java b/src/test/java/org/sciborgs1155/lib/TuningTest.java
new file mode 100644
index 0000000..6f5c28e
--- /dev/null
+++ b/src/test/java/org/sciborgs1155/lib/TuningTest.java
@@ -0,0 +1,41 @@
+package org.sciborgs1155.lib;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.sciborgs1155.lib.UnitTestingUtil.setupTests;
+
+import edu.wpi.first.networktables.BooleanEntry;
+import edu.wpi.first.networktables.DoubleEntry;
+import edu.wpi.first.networktables.IntegerEntry;
+import edu.wpi.first.networktables.StringEntry;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TuningTest {
+ private DoubleEntry dbleEnt;
+ private IntegerEntry intEnt;
+ private StringEntry strEnt;
+ private BooleanEntry boolEnt;
+
+ private double dbleVal = 2.0;
+ private long intVal = 7823; // IntgerTopic.getEntry() accepts longs for default values
+ private String strVal = "Hello, World! <3";
+ private boolean boolVal = true;
+
+ @BeforeEach
+ public void setup() {
+ setupTests();
+ }
+
+ @Test
+ void fullEntryTest() {
+ dbleEnt = Tuning.entry("/Robot/a", dbleVal);
+ intEnt = Tuning.entry("/Robot/b", intVal);
+ strEnt = Tuning.entry("/Robot/c", strVal);
+ boolEnt = Tuning.entry("/Robot/d", boolVal);
+
+ assertEquals(dbleVal, dbleEnt.get());
+ assertEquals(intVal, intEnt.get());
+ assertEquals(strVal, strEnt.get());
+ assertEquals(boolVal, boolEnt.get());
+ }
+}
diff --git a/src/test/java/org/sciborgs1155/robot/RobotTest.java b/src/test/java/org/sciborgs1155/robot/RobotTest.java
index 96dfa3f..02e750a 100644
--- a/src/test/java/org/sciborgs1155/robot/RobotTest.java
+++ b/src/test/java/org/sciborgs1155/robot/RobotTest.java
@@ -1,12 +1,13 @@
package org.sciborgs1155.robot;
+import static org.sciborgs1155.lib.UnitTestingUtil.reset;
+
import org.junit.jupiter.api.Test;
-import org.sciborgs1155.lib.TestingUtil;
public class RobotTest {
@Test
void initialize() throws Exception {
new Robot().close();
- TestingUtil.reset();
+ reset();
}
}
diff --git a/src/test/java/org/sciborgs1155/robot/SwerveTest.java b/src/test/java/org/sciborgs1155/robot/SwerveTest.java
index e96a7be..6e590d9 100644
--- a/src/test/java/org/sciborgs1155/robot/SwerveTest.java
+++ b/src/test/java/org/sciborgs1155/robot/SwerveTest.java
@@ -1,20 +1,114 @@
package org.sciborgs1155.robot;
-// import static org.sciborgs1155.lib.TestingUtil.*;
+import static edu.wpi.first.units.Units.Seconds;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.sciborgs1155.lib.Test.runUnitTest;
+import static org.sciborgs1155.lib.UnitTestingUtil.*;
+
+import edu.wpi.first.math.geometry.Pose2d;
+import edu.wpi.first.math.kinematics.ChassisSpeeds;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.sciborgs1155.robot.drive.Drive;
+import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
+import org.sciborgs1155.robot.drive.NoGyro;
+import org.sciborgs1155.robot.drive.SimModule;
/** Swerve test. Currently incomplete and does nothing. */
public class SwerveTest {
- // Drive drive;
-
- // @BeforeEach
- // public void setup() {
- // setupHAL();
- // drive = Drive.create();
- // }
-
- // @Test
- // public void reachSetpoint() {
- // run(drive.drive(() -> 1, () -> 1, () -> 1));
- // fastForward();
- // }
+ SimModule frontLeft;
+ SimModule frontRight;
+ SimModule rearLeft;
+ SimModule rearRight;
+ NoGyro gyro;
+ Drive drive;
+
+ final double DELTA = 0.15;
+
+ @BeforeEach
+ public void setup() {
+ setupTests();
+ frontLeft = new SimModule("FL");
+ frontRight = new SimModule("FR");
+ rearLeft = new SimModule("RL");
+ rearRight = new SimModule("RR");
+ gyro = new NoGyro();
+ drive = new Drive(gyro, frontLeft, frontRight, rearLeft, rearRight);
+ drive.resetEncoders();
+ }
+
+ @AfterEach
+ public void destroy() throws Exception {
+ reset(drive);
+ }
+
+ @Test
+ public void systemCheck() {
+ runUnitTest(drive.systemsCheck());
+ }
+
+ @Disabled
+ @Test
+ public void reachesRobotVelocity() {
+ double xVelocitySetpoint = -0.5;
+ double yVelocitySetpoint = 0.25;
+ run(
+ drive.run(
+ () ->
+ drive.setChassisSpeeds(
+ new ChassisSpeeds(xVelocitySetpoint, yVelocitySetpoint, 0),
+ ControlMode.CLOSED_LOOP_VELOCITY)));
+ run(drive.drive(() -> xVelocitySetpoint, () -> yVelocitySetpoint, drive::heading));
+ fastForward(500);
+
+ ChassisSpeeds chassisSpeed = drive.getFieldRelativeChassisSpeeds();
+
+ assertEquals(xVelocitySetpoint, chassisSpeed.vxMetersPerSecond, DELTA);
+ assertEquals(yVelocitySetpoint, chassisSpeed.vyMetersPerSecond, DELTA);
+ }
+
+ @Disabled
+ @ParameterizedTest
+ @ValueSource(doubles = {3, 4})
+ public void reachesAngularVelocity(double omegaRadiansPerSecond) {
+ run(
+ drive.run(
+ () ->
+ drive.setChassisSpeeds(
+ new ChassisSpeeds(0, 0, omegaRadiansPerSecond),
+ ControlMode.CLOSED_LOOP_VELOCITY)));
+ fastForward();
+
+ ChassisSpeeds chassisSpeed = drive.getRobotRelativeChassisSpeeds();
+ assertEquals(omegaRadiansPerSecond, chassisSpeed.omegaRadiansPerSecond, DELTA);
+ }
+
+ @Disabled
+ @Test
+ public void testModuleDistance() {
+ double xVelocitySetpoint = 2.265;
+ double yVelocitySetpoint = 0;
+
+ double deltaT = 4;
+ double deltaX = xVelocitySetpoint * deltaT;
+ double deltaY = yVelocitySetpoint * deltaT;
+ run(
+ drive.run(
+ () ->
+ drive.setChassisSpeeds(
+ ChassisSpeeds.fromRobotRelativeSpeeds(
+ xVelocitySetpoint, yVelocitySetpoint, 0, drive.heading()),
+ ControlMode.CLOSED_LOOP_VELOCITY)));
+
+ fastForward(Seconds.of(deltaT));
+
+ Pose2d pose = drive.pose();
+
+ assertEquals(deltaX, pose.getX(), DELTA * 2);
+ assertEquals(deltaY, pose.getY(), DELTA * 2);
+ }
}
From ce6958202f6e71303fba84a7f8e46fda1547ceca Mon Sep 17 00:00:00 2001
From: yxhej
Date: Tue, 30 Jul 2024 23:42:50 -0400
Subject: [PATCH 05/14] move feedforward to subsystem
---
.../sciborgs1155/robot/commands/Autos.java | 2 +-
.../org/sciborgs1155/robot/drive/Drive.java | 83 +++++++++++++------
.../robot/drive/DriveConstants.java | 16 ++++
.../sciborgs1155/robot/drive/ModuleIO.java | 11 +--
.../sciborgs1155/robot/drive/NoModule.java | 5 +-
.../sciborgs1155/robot/drive/SimModule.java | 61 ++++----------
.../sciborgs1155/robot/drive/SparkModule.java | 16 ++--
.../sciborgs1155/robot/drive/TalonModule.java | 24 +++---
.../org/sciborgs1155/robot/SwerveTest.java | 2 +-
9 files changed, 114 insertions(+), 106 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/robot/commands/Autos.java b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
index 06d8355..a134332 100644
--- a/src/main/java/org/sciborgs1155/robot/commands/Autos.java
+++ b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
@@ -17,9 +17,9 @@
import java.util.Optional;
import org.sciborgs1155.robot.drive.Drive;
import org.sciborgs1155.robot.drive.DriveConstants;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
import org.sciborgs1155.robot.drive.DriveConstants.Translation;
-import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
public class Autos {
private static Optional rotation = Optional.empty();
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index e1ba8b5..7e200d9 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -15,6 +15,7 @@
import edu.wpi.first.math.Vector;
import edu.wpi.first.math.controller.PIDController;
import edu.wpi.first.math.controller.ProfiledPIDController;
+import edu.wpi.first.math.controller.SimpleMotorFeedforward;
import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Pose3d;
@@ -48,17 +49,19 @@
import monologue.Logged;
import org.photonvision.EstimatedRobotPose;
import org.sciborgs1155.lib.Assertion;
+import org.sciborgs1155.lib.Assertion.EqualityAssertion;
+import org.sciborgs1155.lib.Assertion.TruthAssertion;
import org.sciborgs1155.lib.InputStream;
import org.sciborgs1155.lib.Test;
import org.sciborgs1155.robot.Constants;
import org.sciborgs1155.robot.Robot;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
+import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving.FF;
import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
import org.sciborgs1155.robot.drive.DriveConstants.Translation;
-import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
import org.sciborgs1155.robot.vision.Vision.PoseEstimate;
public class Drive extends SubsystemBase implements Logged, AutoCloseable {
-
// Modules
private final ModuleIO frontLeft;
private final ModuleIO frontRight;
@@ -81,6 +84,8 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
private final SysIdRoutine translationCharacterization;
private final SysIdRoutine rotationalCharacterization;
+ private final SimpleMotorFeedforward driveFF;
+
@Log.NT
private final ProfiledPIDController translationController =
new ProfiledPIDController(
@@ -94,23 +99,39 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
new PIDController(Rotation.P, Rotation.I, Rotation.D);
/**
- * A factory to create a new swerve drive based on whether the robot is being ran in simulation or
- * not.
+ * A factory to create a new swerve drive based on what the type of robot and whether the robot is
+ * being ran in simulation or not.
*/
public static Drive create() {
- return Robot.isReal()
- ? new Drive(
- new NavXGyro(),
- new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
- new SparkModule(FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
- new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
- new SparkModule(REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"))
- : new Drive(
- new NoGyro(),
- new SimModule("FL"),
- new SimModule("FR"),
- new SimModule("RL"),
- new SimModule("RR"));
+ if (Robot.isReal()) {
+ return switch (TYPE) {
+ case TALON ->
+ new Drive(
+ new NavXGyro(),
+ new TalonModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
+ new TalonModule(
+ FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
+ new TalonModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
+ new TalonModule(
+ REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"));
+ case SPARK ->
+ new Drive(
+ new NavXGyro(),
+ new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
+ new SparkModule(
+ FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
+ new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
+ new SparkModule(
+ REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"));
+ };
+ } else {
+ return new Drive(
+ new NoGyro(),
+ new SimModule("FL"),
+ new SimModule("FR"),
+ new SimModule("RL"),
+ new SimModule("RR"));
+ }
}
/** A factory to create a nonexistent swerve drive. */
@@ -130,6 +151,12 @@ public Drive(
modules = List.of(this.frontLeft, this.frontRight, this.rearLeft, this.rearRight);
modules2d = new FieldObject2d[modules.size()];
+ driveFF =
+ switch (TYPE) {
+ case TALON -> new SimpleMotorFeedforward(FF.TALON.S, FF.TALON.V, FF.TALON.kA_linear);
+ case SPARK -> new SimpleMotorFeedforward(FF.SPARK.S, FF.SPARK.V, FF.SPARK.kA_linear);
+ };
+
translationCharacterization =
new SysIdRoutine(
new SysIdRoutine.Config(),
@@ -207,6 +234,10 @@ public Pose2d pose() {
return odometry.getEstimatedPosition();
}
+ public Rotation2d heading() {
+ return pose().getRotation();
+ }
+
/**
* Resets the odometry to the specified pose.
*
@@ -216,10 +247,6 @@ public void resetOdometry(Pose2d pose) {
odometry.resetPosition(gyro.getRotation2d(), getModulePositions(), pose);
}
- public Rotation2d heading() {
- return pose().getRotation();
- }
-
/**
* Drives the robot while facing a target pose.
*
@@ -264,7 +291,7 @@ public Command drive(DoubleSupplier vx, DoubleSupplier vy, DoubleSupplier vOmega
vy.getAsDouble(),
vOmega.getAsDouble(),
heading().plus(allianceRotation())),
- ControlMode.OPEN_LOOP_VELOCITY));
+ DRIVE_MODE));
}
/**
@@ -314,7 +341,10 @@ public void setModuleStates(
SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, MAX_SPEED.in(MetersPerSecond));
for (int i = 0; i < modules.size(); i++) {
- modules.get(i).updateSetpoint(desiredStates[i], mode);
+ modules
+ .get(i)
+ .updateSetpoint(
+ desiredStates[i], mode, driveFF.calculate(desiredStates[i].speedMetersPerSecond));
}
}
@@ -423,7 +453,7 @@ public void simulationPeriodic() {
/** Stops drivetrain */
public Command stop() {
- return runOnce(() -> setChassisSpeeds(new ChassisSpeeds(), ControlMode.OPEN_LOOP_VELOCITY));
+ return runOnce(() -> setChassisSpeeds(new ChassisSpeeds(), ControlMode.CLOSED_LOOP_VELOCITY));
}
/** Sets the drivetrain to an "X" configuration, preventing movement */
@@ -434,14 +464,13 @@ public Command lock() {
() ->
setModuleStates(
new SwerveModuleState[] {front, back, back, front},
- ControlMode.OPEN_LOOP_VELOCITY,
+ ControlMode.CLOSED_LOOP_VELOCITY,
1));
}
public Test systemsCheck() {
ChassisSpeeds speeds = new ChassisSpeeds(1, 1, 0);
- Command testCommand =
- run(() -> setChassisSpeeds(speeds, ControlMode.OPEN_LOOP_VELOCITY)).withTimeout(0.5);
+ Command testCommand = run(() -> setChassisSpeeds(speeds, DRIVE_MODE)).withTimeout(0.5);
Function speedCheck =
m ->
tAssert(
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index 4e60f10..ab312e6 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -14,6 +14,22 @@
import java.util.List;
public final class DriveConstants {
+ public static enum ControlMode {
+ CLOSED_LOOP_VELOCITY,
+ OPEN_LOOP_VELOCITY;
+ }
+
+ public static enum ModuleType {
+ TALON,
+ SPARK;
+ }
+
+ /** The type of module on the chassis. */
+ public static final ModuleType TYPE = ModuleType.SPARK;
+
+ /** The control loop used by all of the modules when driving. */
+ public static final ControlMode DRIVE_MODE = ControlMode.OPEN_LOOP_VELOCITY;
+
// Rate at which sensors update periodicially
public static final Measure SENSOR_PERIOD = Seconds.of(0.001);
diff --git a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
index 11cdf3b..204a0e8 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
@@ -4,14 +4,10 @@
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
import monologue.Logged;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
/** Generalized hardware internals for a swerve module */
public interface ModuleIO extends Logged, AutoCloseable {
- /** The method to use when controlling the drive motor. */
- public static enum ControlMode {
- CLOSED_LOOP_VELOCITY,
- OPEN_LOOP_VELOCITY;
- }
/**
* Returns the name of the swerve module (e.g. "FR" indicating the front right swerve module.)
@@ -84,7 +80,7 @@ public static enum ControlMode {
*
* @param velocity The velocity setpoint.
*/
- void setDriveSetpoint(double velocity);
+ void setDriveSetpoint(double velocity, double feedforward);
/**
* Sets the setpoint value for the onboard turn motor's PID.
@@ -100,8 +96,9 @@ public static enum ControlMode {
*
* @param setpoint The desired state of the module.
* @param mode The control mode to use when calculating drive voltage.
+ * @param driveFF The desired feedforward voltage for the drive motor.
*/
- void updateSetpoint(SwerveModuleState setpoint, ControlMode mode);
+ void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double driveFF);
/**
* Updates the drive voltage and turn angle.
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
index 1ac7791..f655f05 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
@@ -3,6 +3,7 @@
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
public class NoModule implements ModuleIO {
@@ -51,13 +52,13 @@ public SwerveModuleState desiredState() {
}
@Override
- public void setDriveSetpoint(double velocity) {}
+ public void setDriveSetpoint(double velocity, double feedforward) {}
@Override
public void setTurnSetpoint(double angle) {}
@Override
- public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {}
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double feedforward) {}
@Override
public void close() {}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index 8b846e7..9337798 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -1,11 +1,9 @@
package org.sciborgs1155.robot.drive;
import static edu.wpi.first.units.Units.Seconds;
-import static org.sciborgs1155.robot.Constants.PERIOD;
import edu.wpi.first.math.VecBuilder;
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;
@@ -13,6 +11,7 @@
import edu.wpi.first.math.system.plant.LinearSystemId;
import edu.wpi.first.wpilibj.simulation.DCMotorSim;
import org.sciborgs1155.robot.Constants;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
@@ -34,18 +33,12 @@ public class SimModule implements ModuleIO {
private final PIDController turnFeedback =
new PIDController(Turning.PID.SIM.P, Turning.PID.SIM.I, Turning.PID.SIM.D);
- private final SimpleMotorFeedforward driveTranslationFeedforward =
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear);
- private final SimpleMotorFeedforward driveRotationFeedforward =
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_angular);
-
private SwerveModuleState setpoint = new SwerveModuleState();
private final String name;
public SimModule(String name) {
+ resetEncoders();
this.name = name;
}
@@ -103,51 +96,29 @@ public void resetEncoders() {
}
@Override
- public void setDriveSetpoint(double velocity) {}
+ public void setDriveSetpoint(double velocity, double feedforward) {
+ setDriveVoltage(driveFeedback.calculate(velocity) + feedforward);
+ }
@Override
- public void setTurnSetpoint(double angle) {}
+ public void setTurnSetpoint(double angle) {
+ setTurnVoltage(turnFeedback.calculate(angle));
+ }
@Override
- public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double driveFF) {
+ // Optimize the reference state to avoid spinning further than 90 degrees
setpoint = SwerveModuleState.optimize(setpoint, rotation());
+ // Scale setpoint by cos of turning error to reduce tread wear
setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
- double driveTVolts =
- switch (mode) {
- case CLOSED_LOOP_VELOCITY ->
- driveTranslationFeedforward.calculate(
- this.setpoint.speedMetersPerSecond,
- setpoint.speedMetersPerSecond,
- PERIOD.in(Seconds));
- case OPEN_LOOP_VELOCITY ->
- driveTranslationFeedforward.calculate(setpoint.speedMetersPerSecond);
- };
-
- double driveRVolts =
- switch (mode) {
- case CLOSED_LOOP_VELOCITY ->
- driveRotationFeedforward.calculate(
- this.setpoint.speedMetersPerSecond,
- setpoint.speedMetersPerSecond,
- PERIOD.in(Seconds));
- case OPEN_LOOP_VELOCITY ->
- driveRotationFeedforward.calculate(setpoint.speedMetersPerSecond);
- };
-
- double driveVolts =
- driveTVolts + driveRVolts; // original: double driveVolts = driveTVolts * movementRatio +
- // driveRVolts * (1 - movementRatio);
-
- if (mode == ControlMode.CLOSED_LOOP_VELOCITY) {
- driveVolts += driveFeedback.calculate(driveVelocity(), setpoint.speedMetersPerSecond);
+ if (mode == ControlMode.OPEN_LOOP_VELOCITY) {
+ setDriveVoltage(driveFF);
+ } else {
+ setDriveSetpoint(setpoint.speedMetersPerSecond, driveFF);
}
- double turnVolts = turnFeedback.calculate(rotation().getRadians(), setpoint.angle.getRadians());
-
- setDriveVoltage(driveVolts);
- setTurnVoltage(turnVolts);
-
+ setTurnSetpoint(setpoint.angle.getRadians());
this.setpoint = setpoint;
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 71a9ff4..225b9f1 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -12,7 +12,6 @@
import com.revrobotics.RelativeEncoder;
import com.revrobotics.SparkAbsoluteEncoder;
import com.revrobotics.SparkPIDController;
-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;
@@ -21,6 +20,7 @@
import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
@@ -34,7 +34,6 @@ public class SparkModule implements ModuleIO {
private final SparkPIDController drivePID;
private final SparkPIDController turnPID;
- private final SimpleMotorFeedforward driveFF;
private final Rotation2d angularOffset;
private double lastPosition;
@@ -48,9 +47,6 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset, String
driveMotor = new CANSparkFlex(drivePort, MotorType.kBrushless);
driveEncoder = driveMotor.getEncoder();
drivePID = driveMotor.getPIDController();
- driveFF =
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear); // TODO: Re-tune
check(driveMotor, driveMotor.restoreFactoryDefaults());
@@ -186,8 +182,8 @@ public SwerveModuleState desiredState() {
}
@Override
- public void setDriveSetpoint(double velocity) {
- drivePID.setReference(velocity, ControlType.kVelocity, 0, driveFF.calculate(velocity));
+ public void setDriveSetpoint(double velocity, double feedforward) {
+ drivePID.setReference(velocity, ControlType.kVelocity, 0, feedforward);
}
@Override
@@ -196,16 +192,16 @@ public void setTurnSetpoint(double angle) {
}
@Override
- public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double driveFF) {
// Optimize the reference state to avoid spinning further than 90 degrees
setpoint = SwerveModuleState.optimize(setpoint, rotation());
// Scale setpoint by cos of turning error to reduce tread wear
setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
if (mode == ControlMode.OPEN_LOOP_VELOCITY) {
- setDriveVoltage(driveFF.calculate(setpoint.speedMetersPerSecond));
+ setDriveVoltage(driveFF);
} else {
- setDriveSetpoint(setpoint.speedMetersPerSecond);
+ setDriveSetpoint(setpoint.speedMetersPerSecond, driveFF);
}
setTurnSetpoint(setpoint.angle.getRadians());
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index 104407b..e29d896 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -15,7 +15,6 @@
import com.revrobotics.CANSparkMax;
import com.revrobotics.SparkAbsoluteEncoder;
import com.revrobotics.SparkPIDController;
-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;
@@ -24,6 +23,7 @@
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
import org.sciborgs1155.lib.TalonUtils;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
@@ -33,7 +33,6 @@ public class TalonModule implements ModuleIO {
private final SparkAbsoluteEncoder turnEncoder;
private final SparkPIDController turnPID;
- private final SimpleMotorFeedforward driveFF;
private final StatusSignal drivePos;
private final StatusSignal driveVelocity;
@@ -41,16 +40,14 @@ public class TalonModule implements ModuleIO {
private final VelocityVoltage velocityOut = new VelocityVoltage(0);
private SwerveModuleState setpoint = new SwerveModuleState();
+ private final Rotation2d angularOffset;
private final String name;
- public TalonModule(int drivePort, int turnPort, String name) {
+ public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String name) {
driveMotor = new TalonFX(drivePort);
drivePos = driveMotor.getPosition();
driveVelocity = driveMotor.getVelocity();
- driveFF =
- new SimpleMotorFeedforward(
- Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.kA_linear);
drivePos.setUpdateFrequency(1 / SENSOR_PERIOD.in(Seconds));
driveVelocity.setUpdateFrequency(1 / SENSOR_PERIOD.in(Seconds));
@@ -113,6 +110,8 @@ public TalonModule(int drivePort, int turnPort, String name) {
register(turnMotor);
resetEncoders();
+
+ this.angularOffset = angularOffset;
this.name = name;
}
@@ -143,7 +142,7 @@ public double driveVelocity() {
@Override
public Rotation2d rotation() {
- return Rotation2d.fromRadians(turnEncoder.getPosition());
+ return Rotation2d.fromRadians(turnEncoder.getPosition()).minus(angularOffset);
}
@Override
@@ -167,9 +166,8 @@ public void resetEncoders() {
}
@Override
- public void setDriveSetpoint(double velocity) {
- driveMotor.setControl(
- velocityOut.withVelocity(velocity).withFeedForward(driveFF.calculate(velocity)));
+ public void setDriveSetpoint(double velocity, double feedforward) {
+ driveMotor.setControl(velocityOut.withVelocity(velocity).withFeedForward(feedforward));
}
@Override
@@ -178,15 +176,15 @@ public void setTurnSetpoint(double angle) {
}
@Override
- public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode, double driveFF) {
setpoint = SwerveModuleState.optimize(setpoint, rotation());
// Scale setpoint by cos of turning error to reduce tread wear
setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
if (mode == ControlMode.OPEN_LOOP_VELOCITY) {
- setDriveVoltage(driveFF.calculate(setpoint.speedMetersPerSecond));
+ setDriveVoltage(driveFF);
} else {
- setDriveSetpoint(setpoint.speedMetersPerSecond);
+ setDriveSetpoint(setpoint.speedMetersPerSecond, driveFF);
}
setTurnSetpoint(setpoint.angle.getRadians());
diff --git a/src/test/java/org/sciborgs1155/robot/SwerveTest.java b/src/test/java/org/sciborgs1155/robot/SwerveTest.java
index 6e590d9..5fefaeb 100644
--- a/src/test/java/org/sciborgs1155/robot/SwerveTest.java
+++ b/src/test/java/org/sciborgs1155/robot/SwerveTest.java
@@ -14,7 +14,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.sciborgs1155.robot.drive.Drive;
-import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.NoGyro;
import org.sciborgs1155.robot.drive.SimModule;
From d8b35adee98af2c4153a893d90ccb357ec778fb8 Mon Sep 17 00:00:00 2001
From: Yxhej
Date: Fri, 2 Aug 2024 09:57:57 -0400
Subject: [PATCH 06/14] revert ff changes
---
.../sciborgs1155/robot/commands/Autos.java | 2 +-
.../org/sciborgs1155/robot/drive/Drive.java | 48 ++++++++++------
.../robot/drive/DriveConstants.java | 16 ++++++
.../sciborgs1155/robot/drive/ModuleIO.java | 7 +--
.../sciborgs1155/robot/drive/NoModule.java | 1 +
.../sciborgs1155/robot/drive/SimModule.java | 55 +++++--------------
.../sciborgs1155/robot/drive/SparkModule.java | 1 +
.../sciborgs1155/robot/drive/TalonModule.java | 22 +++++---
.../org/sciborgs1155/robot/SwerveTest.java | 2 +-
9 files changed, 81 insertions(+), 73 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/robot/commands/Autos.java b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
index 06d8355..a134332 100644
--- a/src/main/java/org/sciborgs1155/robot/commands/Autos.java
+++ b/src/main/java/org/sciborgs1155/robot/commands/Autos.java
@@ -17,9 +17,9 @@
import java.util.Optional;
import org.sciborgs1155.robot.drive.Drive;
import org.sciborgs1155.robot.drive.DriveConstants;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
import org.sciborgs1155.robot.drive.DriveConstants.Translation;
-import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
public class Autos {
private static Optional rotation = Optional.empty();
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index e1ba8b5..f422ba8 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -52,9 +52,9 @@
import org.sciborgs1155.lib.Test;
import org.sciborgs1155.robot.Constants;
import org.sciborgs1155.robot.Robot;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.Rotation;
import org.sciborgs1155.robot.drive.DriveConstants.Translation;
-import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
import org.sciborgs1155.robot.vision.Vision.PoseEstimate;
public class Drive extends SubsystemBase implements Logged, AutoCloseable {
@@ -94,23 +94,39 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
new PIDController(Rotation.P, Rotation.I, Rotation.D);
/**
- * A factory to create a new swerve drive based on whether the robot is being ran in simulation or
- * not.
+ * A factory to create a new swerve drive based on what the type of robot and whether the robot is
+ * being ran in simulation or not.
*/
public static Drive create() {
- return Robot.isReal()
- ? new Drive(
- new NavXGyro(),
- new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
- new SparkModule(FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
- new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
- new SparkModule(REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"))
- : new Drive(
- new NoGyro(),
- new SimModule("FL"),
- new SimModule("FR"),
- new SimModule("RL"),
- new SimModule("RR"));
+ if (Robot.isReal()) {
+ return switch (TYPE) {
+ case TALON ->
+ new Drive(
+ new NavXGyro(),
+ new TalonModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
+ new TalonModule(
+ FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
+ new TalonModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
+ new TalonModule(
+ REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"));
+ case SPARK ->
+ new Drive(
+ new NavXGyro(),
+ new SparkModule(FRONT_LEFT_DRIVE, FRONT_LEFT_TURNING, ANGULAR_OFFSETS.get(0), "FL"),
+ new SparkModule(
+ FRONT_RIGHT_DRIVE, FRONT_RIGHT_TURNING, ANGULAR_OFFSETS.get(1), "FR"),
+ new SparkModule(REAR_LEFT_DRIVE, REAR_LEFT_TURNING, ANGULAR_OFFSETS.get(2), "RL"),
+ new SparkModule(
+ REAR_RIGHT_DRIVE, REAR_RIGHT_TURNING, ANGULAR_OFFSETS.get(3), "RR"));
+ };
+ } else {
+ return new Drive(
+ new NoGyro(),
+ new SimModule("FL"),
+ new SimModule("FR"),
+ new SimModule("RL"),
+ new SimModule("RR"));
+ }
}
/** A factory to create a nonexistent swerve drive. */
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index 4e60f10..ab312e6 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -14,6 +14,22 @@
import java.util.List;
public final class DriveConstants {
+ public static enum ControlMode {
+ CLOSED_LOOP_VELOCITY,
+ OPEN_LOOP_VELOCITY;
+ }
+
+ public static enum ModuleType {
+ TALON,
+ SPARK;
+ }
+
+ /** The type of module on the chassis. */
+ public static final ModuleType TYPE = ModuleType.SPARK;
+
+ /** The control loop used by all of the modules when driving. */
+ public static final ControlMode DRIVE_MODE = ControlMode.OPEN_LOOP_VELOCITY;
+
// Rate at which sensors update periodicially
public static final Measure SENSOR_PERIOD = Seconds.of(0.001);
diff --git a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
index 11cdf3b..5f290bb 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
@@ -4,15 +4,10 @@
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
import monologue.Logged;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
/** Generalized hardware internals for a swerve module */
public interface ModuleIO extends Logged, AutoCloseable {
- /** The method to use when controlling the drive motor. */
- public static enum ControlMode {
- CLOSED_LOOP_VELOCITY,
- OPEN_LOOP_VELOCITY;
- }
-
/**
* Returns the name of the swerve module (e.g. "FR" indicating the front right swerve module.)
*
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
index 1ac7791..b58222c 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
@@ -3,6 +3,7 @@
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
public class NoModule implements ModuleIO {
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index 8b846e7..694fafb 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -1,7 +1,6 @@
package org.sciborgs1155.robot.drive;
import static edu.wpi.first.units.Units.Seconds;
-import static org.sciborgs1155.robot.Constants.PERIOD;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.controller.PIDController;
@@ -13,6 +12,7 @@
import edu.wpi.first.math.system.plant.LinearSystemId;
import edu.wpi.first.wpilibj.simulation.DCMotorSim;
import org.sciborgs1155.robot.Constants;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
@@ -34,10 +34,7 @@ public class SimModule implements ModuleIO {
private final PIDController turnFeedback =
new PIDController(Turning.PID.SIM.P, Turning.PID.SIM.I, Turning.PID.SIM.D);
- private final SimpleMotorFeedforward driveTranslationFeedforward =
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear);
- private final SimpleMotorFeedforward driveRotationFeedforward =
+ private final SimpleMotorFeedforward driveFF =
new SimpleMotorFeedforward(
Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_angular);
@@ -103,51 +100,29 @@ public void resetEncoders() {
}
@Override
- public void setDriveSetpoint(double velocity) {}
+ public void setDriveSetpoint(double velocity) {
+ setDriveVoltage(driveFeedback.calculate(velocity) + driveFF.calculate(velocity));
+ }
@Override
- public void setTurnSetpoint(double angle) {}
+ public void setTurnSetpoint(double angle) {
+ setTurnVoltage(turnFeedback.calculate(angle));
+ }
@Override
public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {
+ // Optimize the reference state to avoid spinning further than 90 degrees
setpoint = SwerveModuleState.optimize(setpoint, rotation());
+ // Scale setpoint by cos of turning error to reduce tread wear
setpoint.speedMetersPerSecond *= setpoint.angle.minus(rotation()).getCos();
- double driveTVolts =
- switch (mode) {
- case CLOSED_LOOP_VELOCITY ->
- driveTranslationFeedforward.calculate(
- this.setpoint.speedMetersPerSecond,
- setpoint.speedMetersPerSecond,
- PERIOD.in(Seconds));
- case OPEN_LOOP_VELOCITY ->
- driveTranslationFeedforward.calculate(setpoint.speedMetersPerSecond);
- };
-
- double driveRVolts =
- switch (mode) {
- case CLOSED_LOOP_VELOCITY ->
- driveRotationFeedforward.calculate(
- this.setpoint.speedMetersPerSecond,
- setpoint.speedMetersPerSecond,
- PERIOD.in(Seconds));
- case OPEN_LOOP_VELOCITY ->
- driveRotationFeedforward.calculate(setpoint.speedMetersPerSecond);
- };
-
- double driveVolts =
- driveTVolts + driveRVolts; // original: double driveVolts = driveTVolts * movementRatio +
- // driveRVolts * (1 - movementRatio);
-
- if (mode == ControlMode.CLOSED_LOOP_VELOCITY) {
- driveVolts += driveFeedback.calculate(driveVelocity(), setpoint.speedMetersPerSecond);
+ if (mode == ControlMode.OPEN_LOOP_VELOCITY) {
+ setDriveVoltage(driveFF.calculate(setpoint.speedMetersPerSecond));
+ } else {
+ setDriveSetpoint(setpoint.speedMetersPerSecond);
}
- double turnVolts = turnFeedback.calculate(rotation().getRadians(), setpoint.angle.getRadians());
-
- setDriveVoltage(driveVolts);
- setTurnVoltage(turnVolts);
-
+ setTurnSetpoint(setpoint.angle.getRadians());
this.setpoint = setpoint;
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 71a9ff4..305c944 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -21,6 +21,7 @@
import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index 104407b..e4697b0 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -24,13 +24,14 @@
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
import org.sciborgs1155.lib.TalonUtils;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Driving;
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
public class TalonModule implements ModuleIO {
private final TalonFX driveMotor;
private final CANSparkMax turnMotor;
- private final SparkAbsoluteEncoder turnEncoder;
+ private final SparkAbsoluteEncoder turningEncoder;
private final SparkPIDController turnPID;
private final SimpleMotorFeedforward driveFF;
@@ -43,8 +44,9 @@ public class TalonModule implements ModuleIO {
private SwerveModuleState setpoint = new SwerveModuleState();
private final String name;
+ private final Rotation2d angularOffset;
- public TalonModule(int drivePort, int turnPort, String name) {
+ public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String name) {
driveMotor = new TalonFX(drivePort);
drivePos = driveMotor.getPosition();
driveVelocity = driveMotor.getVelocity();
@@ -70,7 +72,7 @@ public TalonModule(int drivePort, int turnPort, String name) {
TalonUtils.addMotor(driveMotor);
turnMotor = new CANSparkMax(turnPort, MotorType.kBrushless);
- turnEncoder = turnMotor.getAbsoluteEncoder();
+ turningEncoder = turnMotor.getAbsoluteEncoder();
turnPID = turnMotor.getPIDController();
check(turnMotor, turnMotor.restoreFactoryDefaults());
@@ -81,17 +83,18 @@ public TalonModule(int drivePort, int turnPort, String name) {
check(turnMotor, turnPID.setPositionPIDWrappingEnabled(true));
check(turnMotor, turnPID.setPositionPIDWrappingMinInput(-Math.PI));
check(turnMotor, turnPID.setPositionPIDWrappingMaxInput(Math.PI));
- check(turnMotor, turnPID.setFeedbackDevice(turnEncoder));
+ check(turnMotor, turnPID.setFeedbackDevice(turningEncoder));
check(turnMotor, turnMotor.setIdleMode(IdleMode.kBrake));
check(turnMotor, turnMotor.setSmartCurrentLimit((int) Turning.CURRENT_LIMIT.in(Amps)));
- turnEncoder.setInverted(Turning.ENCODER_INVERTED);
+ turningEncoder.setInverted(Turning.ENCODER_INVERTED);
check(turnMotor);
- check(turnMotor, turnEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians)));
+ check(
+ turnMotor, turningEncoder.setPositionConversionFactor(Turning.POSITION_FACTOR.in(Radians)));
check(
turnMotor,
- turnEncoder.setVelocityConversionFactor(Turning.VELOCITY_FACTOR.in(RadiansPerSecond)));
- check(turnMotor, turnEncoder.setAverageDepth(2));
+ turningEncoder.setVelocityConversionFactor(Turning.VELOCITY_FACTOR.in(RadiansPerSecond)));
+ check(turnMotor, turningEncoder.setAverageDepth(2));
check(
turnMotor,
SparkUtils.configureFrameStrategy(
@@ -114,6 +117,7 @@ public TalonModule(int drivePort, int turnPort, String name) {
resetEncoders();
this.name = name;
+ this.angularOffset = angularOffset;
}
@Override
@@ -143,7 +147,7 @@ public double driveVelocity() {
@Override
public Rotation2d rotation() {
- return Rotation2d.fromRadians(turnEncoder.getPosition());
+ return Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset);
}
@Override
diff --git a/src/test/java/org/sciborgs1155/robot/SwerveTest.java b/src/test/java/org/sciborgs1155/robot/SwerveTest.java
index 6e590d9..5fefaeb 100644
--- a/src/test/java/org/sciborgs1155/robot/SwerveTest.java
+++ b/src/test/java/org/sciborgs1155/robot/SwerveTest.java
@@ -14,7 +14,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.sciborgs1155.robot.drive.Drive;
-import org.sciborgs1155.robot.drive.ModuleIO.ControlMode;
+import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
import org.sciborgs1155.robot.drive.NoGyro;
import org.sciborgs1155.robot.drive.SimModule;
From a28d127544f1a87505b37922f534ac620b23f0aa Mon Sep 17 00:00:00 2001
From: Yxhej
Date: Fri, 2 Aug 2024 12:17:46 -0400
Subject: [PATCH 07/14] documentation!!!!!!!
---
.../java/org/sciborgs1155/robot/Robot.java | 21 +--
.../org/sciborgs1155/robot/drive/Drive.java | 132 +++++++++++-------
.../robot/drive/DriveConstants.java | 10 +-
.../sciborgs1155/robot/drive/ModuleIO.java | 10 +-
.../sciborgs1155/robot/drive/NoModule.java | 19 ++-
.../sciborgs1155/robot/drive/SimModule.java | 10 +-
.../sciborgs1155/robot/drive/SparkModule.java | 25 ++--
.../sciborgs1155/robot/drive/TalonModule.java | 25 ++--
.../robot/vision/VisionConstants.java | 1 -
9 files changed, 146 insertions(+), 107 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index b9a9841..ca35622 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -40,7 +40,6 @@
* subsystems, commands, and trigger mappings) should be declared here.
*/
public class Robot extends CommandRobot implements Logged {
-
// INPUT DEVICES
private final CommandXboxController operator = new CommandXboxController(OI.OPERATOR);
private final CommandXboxController driver = new CommandXboxController(OI.DRIVER);
@@ -65,7 +64,7 @@ public Robot() {
/** Configures basic behavior during different parts of the game. */
private void configureGameBehavior() {
- // Configure logging with DataLogManager, Monologue, FailureManagement, and URCL
+ // Configure logging with DataLogManager, Monologue, URCL, and FaultLogger
DataLogManager.start();
Monologue.setupMonologue(this, "/Robot", false, true);
addPeriodic(Monologue::updateAll, PERIOD.in(Seconds));
@@ -90,15 +89,12 @@ private void configureGameBehavior() {
}
}
- /**
- * Configures subsystem default commands. Default commands are scheduled when no other command is
- * running on a subsystem.
- */
- /** Configures trigger -> command bindings */
+ /** Configures trigger -> command bindings. */
private void configureBindings() {
InputStream x = InputStream.of(driver::getLeftX).negate();
InputStream y = InputStream.of(driver::getLeftY).negate();
+ // Apply speed multiplier, deadband, square inputs, and scale translation to max speed
InputStream r =
InputStream.hypot(x, y)
.log("Robot/raw joystick")
@@ -111,9 +107,11 @@ private void configureBindings() {
InputStream theta = InputStream.atan(x, y);
+ // Split x and y components of translation input
x = r.scale(theta.map(Math::cos)); // .rateLimit(MAX_ACCEL.in(MetersPerSecondPerSecond));
y = r.scale(theta.map(Math::sin)); // .rateLimit(MAX_ACCEL.in(MetersPerSecondPerSecond));
+ // Apply speed multiplier, deadband, square inputs, and scale rotation to max teleop speed
InputStream omega =
InputStream.of(driver::getRightX)
.negate()
@@ -136,6 +134,13 @@ private void configureBindings() {
.onFalse(Commands.runOnce(() -> speedMultiplier = Constants.FULL_SPEED_MULTIPLIER));
}
+ /**
+ * Command factory to make both controllers rumble.
+ *
+ * @param rumbleType The area of the controller to rumble.
+ * @param strength The intensity of the rumble.
+ * @return The command to rumble both controllers.
+ */
public Command rumble(RumbleType rumbleType, double strength) {
return Commands.runOnce(
() -> {
@@ -151,7 +156,7 @@ public Command rumble(RumbleType rumbleType, double strength) {
}
public Command systemsCheck() {
- return Test.toCommand().withName("Test Mechanisms");
+ return Test.toCommand(drive.systemsCheck()).withName("Test Mechanisms");
}
@Override
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index f422ba8..f9a0a14 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -58,7 +58,6 @@
import org.sciborgs1155.robot.vision.Vision.PoseEstimate;
public class Drive extends SubsystemBase implements Logged, AutoCloseable {
-
// Modules
private final ModuleIO frontLeft;
private final ModuleIO frontRight;
@@ -67,7 +66,8 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
@IgnoreLogged private final List modules;
- private final GyroIO gyro;
+ // Gyro
+ private final GyroIO gyro; // navX2-MXP
private static Rotation2d simRotation = new Rotation2d();
public final SwerveDriveKinematics kinematics = new SwerveDriveKinematics(MODULE_OFFSET);
@@ -78,9 +78,11 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
@Log.NT private final Field2d field2d = new Field2d();
private final FieldObject2d[] modules2d;
+ // Characterization routines
private final SysIdRoutine translationCharacterization;
private final SysIdRoutine rotationalCharacterization;
+ // Path-following
@Log.NT
private final ProfiledPIDController translationController =
new ProfiledPIDController(
@@ -94,8 +96,7 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
new PIDController(Rotation.P, Rotation.I, Rotation.D);
/**
- * A factory to create a new swerve drive based on what the type of robot and whether the robot is
- * being ran in simulation or not.
+ * A factory to create a new swerve drive based on the type of module used / real or simulation.
*/
public static Drive create() {
if (Robot.isReal()) {
@@ -134,7 +135,7 @@ public static Drive none() {
return new Drive(new NoGyro(), new NoModule(), new NoModule(), new NoModule(), new NoModule());
}
- /** A swerve drive subsystem containing four {@link ModuleIO} modules. */
+ /** A swerve drive subsystem containing four {@link ModuleIO} modules and a gyroscope. */
public Drive(
GyroIO gyro, ModuleIO frontLeft, ModuleIO frontRight, ModuleIO rearLeft, ModuleIO rearRight) {
this.gyro = gyro;
@@ -224,41 +225,21 @@ public Pose2d pose() {
}
/**
- * Resets the odometry to the specified pose.
+ * Returns the currently-estimated field-relative yaw of the robot.
*
- * @param pose The pose to which to set the odometry.
+ * @return The rotation.
*/
- public void resetOdometry(Pose2d pose) {
- odometry.resetPosition(gyro.getRotation2d(), getModulePositions(), pose);
- }
-
public Rotation2d heading() {
return pose().getRotation();
}
/**
- * Drives the robot while facing a target pose.
+ * Resets the odometry to the specified pose.
*
- * @param vx A supplier for the absolute x velocity of the robot.
- * @param vy A supplier for the absolute y velocity of the robot.
- * @param translation A supplier for the translation2d to face on the field.
- * @return A command to drive while facing a target.
+ * @param pose The pose to which to set the odometry.
*/
- public Command driveFacingTarget(
- DoubleSupplier vx, DoubleSupplier vy, Supplier translation) {
- return drive(vx, vy, () -> translation.get().minus(pose().getTranslation()).getAngle());
- }
-
- @Log.NT
- public boolean atRotationalSetpoint() {
- return rotationController.atSetpoint();
- }
-
- public boolean isFacing(Translation2d target) {
- return Math.abs(
- gyro.getRotation2d().getRadians()
- - target.minus(pose().getTranslation()).getAngle().getRadians())
- < rotationController.getPositionTolerance();
+ public void resetOdometry(Pose2d pose) {
+ odometry.resetPosition(gyro.getRotation2d(), getModulePositions(), pose);
}
/**
@@ -301,28 +282,58 @@ public Command drive(DoubleSupplier vx, DoubleSupplier vy, Supplier
.beforeStarting(rotationController::reset);
}
- /** Robot relative chassis speeds */
- public void setChassisSpeeds(ChassisSpeeds speeds, ControlMode mode) {
- double speed = Math.hypot(speeds.vxMetersPerSecond, speeds.vyMetersPerSecond);
- double angularSpeed = Math.abs(speeds.omegaRadiansPerSecond);
- double sum = speed + angularSpeed;
- double factor = sum == 0 ? 0 : speed / sum;
+ /**
+ * Drives the robot while facing a target pose.
+ *
+ * @param vx A supplier for the absolute x velocity of the robot.
+ * @param vy A supplier for the absolute y velocity of the robot.
+ * @param translation A supplier for the translation2d to face on the field.
+ * @return A command to drive while facing a target.
+ */
+ public Command driveFacingTarget(
+ DoubleSupplier vx, DoubleSupplier vy, Supplier translation) {
+ return drive(vx, vy, () -> translation.get().minus(pose().getTranslation()).getAngle());
+ }
+ @Log.NT
+ public boolean atRotationalSetpoint() {
+ return rotationController.atSetpoint();
+ }
+
+ /**
+ * Checks whether the robot is facing towards a point on the field.
+ *
+ * @param target The field-relative point to check.
+ * @return Whether the robot is facing the target closely enough.
+ */
+ public boolean isFacing(Translation2d target) {
+ return Math.abs(
+ gyro.getRotation2d().getRadians()
+ - target.minus(pose().getTranslation()).getAngle().getRadians())
+ < rotationController.getPositionTolerance();
+ }
+
+ /**
+ * Sets the states of each of the swerve modules using target speeds that the drivetrain will work
+ * to reach.
+ *
+ * @param speeds The speeds the drivetrain will run at.
+ * @param mode The control loop used to achieve those speeds.
+ */
+ public void setChassisSpeeds(ChassisSpeeds speeds, ControlMode mode) {
setModuleStates(
kinematics.toSwerveModuleStates(
ChassisSpeeds.discretize(speeds, Constants.PERIOD.in(Seconds))),
- mode,
- factor);
+ mode);
}
/**
- * Sets the swerve ModuleStates.
+ * Sets the states of each of the swerve modules.
*
* @param desiredStates The desired SwerveModule states.
* @param mode The method to use when controlling the drive motor.
*/
- public void setModuleStates(
- SwerveModuleState[] desiredStates, ControlMode mode, double movementFactor) {
+ public void setModuleStates(SwerveModuleState[] desiredStates, ControlMode mode) {
if (desiredStates.length != modules.size()) {
throw new IllegalArgumentException("desiredStates must have the same length as modules");
}
@@ -334,6 +345,13 @@ public void setModuleStates(
}
}
+ /**
+ * Command factory that automatically path-follows, in a straight line, to a position on the
+ * field.
+ *
+ * @param target The pose to reach.
+ * @return The command to run the control loop until the pose is reached.
+ */
public Command driveTo(Pose2d target) {
return run(() -> {
Transform2d transform = pose().minus(target);
@@ -353,7 +371,7 @@ public Command driveTo(Pose2d target) {
.withName("drive to pose");
}
- /** Resets the drive encoders to currently read a position of 0. */
+ /** Resets all drive encoders to read a position of 0. */
public void resetEncoders() {
modules.forEach(ModuleIO::resetEncoders);
}
@@ -375,25 +393,29 @@ private SwerveModuleState[] getModuleSetpoints() {
return modules.stream().map(ModuleIO::desiredState).toArray(SwerveModuleState[]::new);
}
- /** Returns the module positions */
+ /** Returns the module positions. */
@Log.NT
public SwerveModulePosition[] getModulePositions() {
return modules.stream().map(ModuleIO::position).toArray(SwerveModulePosition[]::new);
}
- /** Returns the robot relative chassis speeds. */
+ /** Returns the robot-relative chassis speeds. */
@Log.NT
public ChassisSpeeds getRobotRelativeChassisSpeeds() {
return kinematics.toChassisSpeeds(getModuleStates());
}
- /** Returns the field relative chassis speeds. */
+ /** Returns the field-relative chassis speeds. */
@Log.NT
public ChassisSpeeds getFieldRelativeChassisSpeeds() {
return ChassisSpeeds.fromRobotRelativeSpeeds(getRobotRelativeChassisSpeeds(), heading());
}
- /** Updates pose estimation based on provided {@link EstimatedRobotPose} */
+ /**
+ * Updates pose estimate based on vision-provided {@link EstimatedRobotPose}s.
+ *
+ * @param poses The pose estimates based on vision data.
+ */
public void updateEstimates(PoseEstimate... poses) {
Pose3d[] loggedEstimates = new Pose3d[poses.length];
for (int i = 0; i < poses.length; i++) {
@@ -411,8 +433,10 @@ public void updateEstimates(PoseEstimate... poses) {
@Override
public void periodic() {
+ // update our heading in reality / sim
odometry.update(Robot.isReal() ? gyro.getRotation2d() : simRotation, getModulePositions());
+ // update our simulated field poses
field2d.setRobotPose(pose());
for (int i = 0; i < modules2d.length; i++) {
@@ -437,12 +461,12 @@ public void simulationPeriodic() {
* Constants.PERIOD.in(Seconds)));
}
- /** Stops drivetrain */
+ /** Stops the drivetrain. */
public Command stop() {
return runOnce(() -> setChassisSpeeds(new ChassisSpeeds(), ControlMode.OPEN_LOOP_VELOCITY));
}
- /** Sets the drivetrain to an "X" configuration, preventing movement */
+ /** Sets the drivetrain to an "X" configuration, preventing movement. */
public Command lock() {
var front = new SwerveModuleState(0, Rotation2d.fromDegrees(45));
var back = new SwerveModuleState(0, Rotation2d.fromDegrees(-45));
@@ -450,10 +474,16 @@ public Command lock() {
() ->
setModuleStates(
new SwerveModuleState[] {front, back, back, front},
- ControlMode.OPEN_LOOP_VELOCITY,
- 1));
+ ControlMode.OPEN_LOOP_VELOCITY));
}
+ /**
+ * Factory for our drive systems check.
+ *
+ * Checks for properly functioning movement and speed / heading measurements.
+ *
+ * @return The test to run.
+ */
public Test systemsCheck() {
ChassisSpeeds speeds = new ChassisSpeeds(1, 1, 0);
Command testCommand =
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index ab312e6..048dab3 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -14,20 +14,22 @@
import java.util.List;
public final class DriveConstants {
+ /** The type of control loop to use when controlling the drive motor. */
public static enum ControlMode {
CLOSED_LOOP_VELOCITY,
OPEN_LOOP_VELOCITY;
}
+ /** The type of modules being used. */
public static enum ModuleType {
- TALON,
- SPARK;
+ TALON, // Kraken X60 Drive, NEO 550 Turn
+ SPARK; // NEO Vortex Drive, NEO 550 Turn
}
- /** The type of module on the chassis. */
+ // The type of module on the chassis
public static final ModuleType TYPE = ModuleType.SPARK;
- /** The control loop used by all of the modules when driving. */
+ // The control loop used by all of the modules when driving
public static final ControlMode DRIVE_MODE = ControlMode.OPEN_LOOP_VELOCITY;
// Rate at which sensors update periodicially
diff --git a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
index 5f290bb..56629c6 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
@@ -18,14 +18,14 @@ public interface ModuleIO extends Logged, AutoCloseable {
/**
* Sets the drive voltage of the module.
*
- * @param voltage The voltage to inputted into the drive motor.
+ * @param voltage The voltage to input into the drive motor.
*/
void setDriveVoltage(double voltage);
/**
* Sets the turn voltage of the module.
*
- * @param voltage The voltage to inputted into the turn motor.
+ * @param voltage The voltage to input into the turn motor.
*/
void setTurnVoltage(double voltage);
@@ -39,12 +39,12 @@ public interface ModuleIO extends Logged, AutoCloseable {
/**
* Returns the current velocity of the wheel.
*
- * @return The drive encoder velocity value, in radians / seconds.
+ * @return The drive encoder velocity value, in radians / second.
*/
double driveVelocity();
/**
- * Returns the angular position of the module.
+ * Returns the current angular position of the module.
*
* @return The adjusted turn encoder position value, in radians.
*/
@@ -101,7 +101,7 @@ public interface ModuleIO extends Logged, AutoCloseable {
/**
* Updates the drive voltage and turn angle.
*
- *
This is useful for SysId characterization, but should never be run otherwise.
+ *
This is useful for SysId characterization and should not be used otherwise.
*
* @param angle The desired angle of the module.
* @param voltage The voltage to supply to the drive motor.
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
index b58222c..dc69457 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoModule.java
@@ -6,6 +6,10 @@
import org.sciborgs1155.robot.drive.DriveConstants.ControlMode;
public class NoModule implements ModuleIO {
+ @Override
+ public String name() {
+ return "NoModule";
+ }
@Override
public void setDriveVoltage(double voltage) {}
@@ -13,11 +17,6 @@ public void setDriveVoltage(double voltage) {}
@Override
public void setTurnVoltage(double voltage) {}
- @Override
- public String name() {
- return "NoModule";
- }
-
@Override
public double drivePosition() {
return 0;
@@ -33,9 +32,6 @@ public Rotation2d rotation() {
return new Rotation2d();
}
- @Override
- public void resetEncoders() {}
-
@Override
public SwerveModuleState state() {
return new SwerveModuleState();
@@ -51,6 +47,9 @@ public SwerveModuleState desiredState() {
return new SwerveModuleState();
}
+ @Override
+ public void resetEncoders() {}
+
@Override
public void setDriveSetpoint(double velocity) {}
@@ -61,8 +60,8 @@ public void setTurnSetpoint(double angle) {}
public void updateSetpoint(SwerveModuleState setpoint, ControlMode mode) {}
@Override
- public void close() {}
+ public void updateInputs(Rotation2d angle, double voltage) {}
@Override
- public void updateInputs(Rotation2d angle, double voltage) {}
+ public void close() {}
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index 694fafb..7d4919b 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -46,6 +46,11 @@ public SimModule(String name) {
this.name = name;
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public void setDriveVoltage(double voltage) {
drive.setInputVoltage(voltage);
@@ -73,11 +78,6 @@ public Rotation2d rotation() {
return Rotation2d.fromRadians(turn.getAngularPositionRad());
}
- @Override
- public String name() {
- return name;
- }
-
@Override
public SwerveModuleState state() {
return new SwerveModuleState(driveVelocity(), rotation());
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 305c944..348f9b3 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -26,8 +26,8 @@
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
public class SparkModule implements ModuleIO {
- private final CANSparkFlex driveMotor; // Neo Vortex
- private final CANSparkMax turnMotor; // Neo 550
+ private final CANSparkFlex driveMotor; // NEO Vortex
+ private final CANSparkMax turnMotor; // NEO 550
private final RelativeEncoder driveEncoder;
private final SparkAbsoluteEncoder turningEncoder;
@@ -36,6 +36,7 @@ public class SparkModule implements ModuleIO {
private final SparkPIDController turnPID;
private final SimpleMotorFeedforward driveFF;
+
private final Rotation2d angularOffset;
private double lastPosition;
@@ -130,6 +131,11 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset, String
this.name = name;
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public void setDriveVoltage(double voltage) {
driveMotor.setVoltage(voltage);
@@ -143,11 +149,6 @@ public void setTurnVoltage(double voltage) {
check(turnMotor);
}
- @Override
- public String name() {
- return name;
- }
-
@Override
public double drivePosition() {
lastPosition = SparkUtils.wrapCall(driveMotor, driveEncoder.getPosition()).orElse(lastPosition);
@@ -166,11 +167,6 @@ public Rotation2d rotation() {
return Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset);
}
- @Override
- public void resetEncoders() {
- driveEncoder.setPosition(0);
- }
-
@Override
public SwerveModuleState state() {
return new SwerveModuleState(driveVelocity(), rotation());
@@ -186,6 +182,11 @@ public SwerveModuleState desiredState() {
return setpoint;
}
+ @Override
+ public void resetEncoders() {
+ driveEncoder.setPosition(0);
+ }
+
@Override
public void setDriveSetpoint(double velocity) {
drivePID.setReference(velocity, ControlType.kVelocity, 0, driveFF.calculate(velocity));
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index e4697b0..44fa53c 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -20,6 +20,7 @@
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
import java.util.Set;
+import monologue.Annotations.Log;
import org.sciborgs1155.lib.SparkUtils;
import org.sciborgs1155.lib.SparkUtils.Data;
import org.sciborgs1155.lib.SparkUtils.Sensor;
@@ -29,22 +30,23 @@
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
public class TalonModule implements ModuleIO {
- private final TalonFX driveMotor;
- private final CANSparkMax turnMotor;
+ private final TalonFX driveMotor; // Kraken X60
+ private final CANSparkMax turnMotor; // NEO 550
+
+ private final StatusSignal drivePos;
+ private final StatusSignal driveVelocity;
private final SparkAbsoluteEncoder turningEncoder;
private final SparkPIDController turnPID;
private final SimpleMotorFeedforward driveFF;
- private final StatusSignal drivePos;
- private final StatusSignal driveVelocity;
+ private final Rotation2d angularOffset;
private final VelocityVoltage velocityOut = new VelocityVoltage(0);
- private SwerveModuleState setpoint = new SwerveModuleState();
+ @Log.NT private SwerveModuleState setpoint = new SwerveModuleState();
private final String name;
- private final Rotation2d angularOffset;
public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String name) {
driveMotor = new TalonFX(drivePort);
@@ -116,10 +118,16 @@ public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String
register(turnMotor);
resetEncoders();
+
this.name = name;
this.angularOffset = angularOffset;
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public void setDriveVoltage(double voltage) {
driveMotor.setVoltage(voltage);
@@ -130,11 +138,6 @@ public void setTurnVoltage(double voltage) {
turnMotor.setVoltage(voltage);
}
- @Override
- public String name() {
- return name;
- }
-
@Override
public double drivePosition() {
return drivePos.getValueAsDouble();
diff --git a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
index bc6b619..b0d06d6 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
@@ -15,7 +15,6 @@ public class VisionConstants {
AprilTagFieldLayout.loadField(AprilTagFields.kDefaultField);
/* The front of the robot is defined at the intake */
-
public static final CameraConfig BACK_LEFT_CAMERA =
new CameraConfig("back left", new Transform3d());
public static final CameraConfig BACK_RIGHT_CAMERA =
From 8306957285c57959eb60479ad8dd9a173c70aef6 Mon Sep 17 00:00:00 2001
From: yxhej
Date: Thu, 8 Aug 2024 00:30:35 -0400
Subject: [PATCH 08/14] cleanup + module rotation safety
---
.../org/sciborgs1155/lib/FaultLogger.java | 1 -
.../org/sciborgs1155/robot/drive/Drive.java | 10 ++++-----
.../robot/drive/DriveConstants.java | 5 +++--
.../sciborgs1155/robot/drive/ModuleIO.java | 4 ++--
.../sciborgs1155/robot/drive/SparkModule.java | 8 ++++++-
.../sciborgs1155/robot/drive/TalonModule.java | 21 +++++++++++++------
6 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/lib/FaultLogger.java b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
index 5a8ad73..1786143 100644
--- a/src/main/java/org/sciborgs1155/lib/FaultLogger.java
+++ b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
@@ -198,7 +198,6 @@ public static void register(CANSparkBase spark) {
SparkUtils.name(spark),
"motor above 100°C",
FaultType.WARNING);
- // FakePDH.register(spark);
}
/**
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index f9a0a14..8ab64f4 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -66,8 +66,8 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
@IgnoreLogged private final List modules;
- // Gyro
- private final GyroIO gyro; // navX2-MXP
+ // Gyro, navX2-MXP
+ private final GyroIO gyro;
private static Rotation2d simRotation = new Rotation2d();
public final SwerveDriveKinematics kinematics = new SwerveDriveKinematics(MODULE_OFFSET);
@@ -82,7 +82,7 @@ public class Drive extends SubsystemBase implements Logged, AutoCloseable {
private final SysIdRoutine translationCharacterization;
private final SysIdRoutine rotationalCharacterization;
- // Path-following
+ // Movement automation
@Log.NT
private final ProfiledPIDController translationController =
new ProfiledPIDController(
@@ -314,8 +314,8 @@ public boolean isFacing(Translation2d target) {
}
/**
- * Sets the states of each of the swerve modules using target speeds that the drivetrain will work
- * to reach.
+ * Sets the states of each swerve module using target speeds that the drivetrain will work to
+ * reach.
*
* @param speeds The speeds the drivetrain will run at.
* @param mode The control loop used to achieve those speeds.
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index 048dab3..ec5ce16 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -13,6 +13,7 @@
import edu.wpi.first.units.Velocity;
import java.util.List;
+/** Constants for our 2024 MAXSwerve drivetrain. */
public final class DriveConstants {
/** The type of control loop to use when controlling the drive motor. */
public static enum ControlMode {
@@ -33,7 +34,7 @@ public static enum ModuleType {
public static final ControlMode DRIVE_MODE = ControlMode.OPEN_LOOP_VELOCITY;
// Rate at which sensors update periodicially
- public static final Measure SENSOR_PERIOD = Seconds.of(0.001);
+ public static final Measure SENSOR_PERIOD = Seconds.of(0.02);
// Distance between centers of right and left wheels on robot
public static final Measure TRACK_WIDTH = Meters.of(0.5715);
@@ -144,7 +145,7 @@ public static final class SPARK {
}
public static final class TALON {
- public static final double S = -1; // TODO
+ public static final double S = 0; // TODO
public static final double V = 2.0681;
public static final double kA_linear = 0.205;
public static final double kA_angular = 0.376;
diff --git a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
index 56629c6..13c33a3 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/ModuleIO.java
@@ -32,14 +32,14 @@ public interface ModuleIO extends Logged, AutoCloseable {
/**
* Returns the distance the wheel traveled.
*
- * @return The drive encoder position value, in radians.
+ * @return The drive encoder position value, in meters.
*/
double drivePosition();
/**
* Returns the current velocity of the wheel.
*
- * @return The drive encoder velocity value, in radians / second.
+ * @return The drive encoder velocity value, in meters / second.
*/
double driveVelocity();
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 348f9b3..3094edc 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -41,6 +41,7 @@ public class SparkModule implements ModuleIO {
private double lastPosition;
private double lastVelocity;
+ private Rotation2d lastRotation;
@Log.NT private SwerveModuleState setpoint = new SwerveModuleState();
@@ -164,7 +165,12 @@ public double driveVelocity() {
@Override
public Rotation2d rotation() {
- return Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset);
+ lastRotation =
+ SparkUtils.wrapCall(
+ turnMotor,
+ Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset))
+ .orElse(lastRotation);
+ return lastRotation;
}
@Override
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index 44fa53c..b515a8b 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -2,7 +2,7 @@
import static edu.wpi.first.units.Units.*;
import static org.sciborgs1155.lib.FaultLogger.*;
-import static org.sciborgs1155.robot.drive.DriveConstants.SENSOR_PERIOD;
+import static org.sciborgs1155.robot.drive.DriveConstants.*;
import com.ctre.phoenix6.StatusSignal;
import com.ctre.phoenix6.configs.TalonFXConfiguration;
@@ -37,15 +37,17 @@ public class TalonModule implements ModuleIO {
private final StatusSignal driveVelocity;
private final SparkAbsoluteEncoder turningEncoder;
+ private final VelocityVoltage velocityOut = new VelocityVoltage(0);
+
private final SparkPIDController turnPID;
private final SimpleMotorFeedforward driveFF;
private final Rotation2d angularOffset;
- private final VelocityVoltage velocityOut = new VelocityVoltage(0);
-
@Log.NT private SwerveModuleState setpoint = new SwerveModuleState();
+ private Rotation2d lastRotation;
+
private final String name;
public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String name) {
@@ -64,9 +66,11 @@ public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String
driveMotor.getConfigurator().apply(talonConfig);
talonConfig.MotorOutput.NeutralMode = NeutralModeValue.Brake;
- talonConfig.CurrentLimits.SupplyCurrentLimit = 80;
+ talonConfig.Feedback.SensorToMechanismRatio = Driving.POSITION_FACTOR.in(Meters);
+ talonConfig.CurrentLimits.SupplyCurrentLimit = Driving.CURRENT_LIMIT.in(Amps);
+
talonConfig.Slot0.kP = Driving.PID.TALON.P;
- talonConfig.Slot1.kI = Driving.PID.TALON.I;
+ talonConfig.Slot0.kI = Driving.PID.TALON.I;
talonConfig.Slot0.kD = Driving.PID.TALON.D;
driveMotor.getConfigurator().apply(talonConfig);
@@ -150,7 +154,12 @@ public double driveVelocity() {
@Override
public Rotation2d rotation() {
- return Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset);
+ lastRotation =
+ SparkUtils.wrapCall(
+ turnMotor,
+ Rotation2d.fromRadians(turningEncoder.getPosition()).minus(angularOffset))
+ .orElse(lastRotation);
+ return lastRotation;
}
@Override
From e4f86ee73514881e7af7b3530cdbce3587f3215b Mon Sep 17 00:00:00 2001
From: yxhej
Date: Thu, 15 Aug 2024 21:26:21 -0400
Subject: [PATCH 09/14] add switch statements for mod sim based on motor type
:(
---
.../sciborgs1155/robot/drive/SimModule.java | 28 +++++++++++++++----
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index 7d4919b..cef09c8 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -1,6 +1,7 @@
package org.sciborgs1155.robot.drive;
import static edu.wpi.first.units.Units.Seconds;
+import static org.sciborgs1155.robot.drive.DriveConstants.TYPE;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.controller.PIDController;
@@ -19,10 +20,19 @@
public class SimModule implements ModuleIO {
private final DCMotorSim drive =
- new DCMotorSim(
- LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear),
- DCMotor.getNeoVortex(1),
- 1 / Driving.GEARING);
+ switch (TYPE) {
+ case SPARK ->
+ new DCMotorSim(
+ LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear),
+ DCMotor.getNeoVortex(1),
+ 1 / Driving.GEARING);
+ case TALON ->
+ new DCMotorSim(
+ LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear),
+ DCMotor.getKrakenX60(1),
+ 1 / Driving.GEARING);
+ };
+
private final DCMotorSim turn =
new DCMotorSim(
LinearSystemId.createDCMotorSystem(Turning.FF.V, Turning.FF.A),
@@ -35,8 +45,14 @@ public class SimModule implements ModuleIO {
new PIDController(Turning.PID.SIM.P, Turning.PID.SIM.I, Turning.PID.SIM.D);
private final SimpleMotorFeedforward driveFF =
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_angular);
+ switch (TYPE) {
+ case SPARK ->
+ new SimpleMotorFeedforward(
+ Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear);
+ case TALON ->
+ new SimpleMotorFeedforward(
+ Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.kA_linear);
+ };
private SwerveModuleState setpoint = new SwerveModuleState();
From 3feca2eb61fea579396d7d148b6fbad22696b376 Mon Sep 17 00:00:00 2001
From: yxhej
Date: Thu, 15 Aug 2024 21:27:56 -0400
Subject: [PATCH 10/14] trolling
---
src/main/java/org/sciborgs1155/robot/drive/SimModule.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index cef09c8..5bb4275 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -28,7 +28,7 @@ public class SimModule implements ModuleIO {
1 / Driving.GEARING);
case TALON ->
new DCMotorSim(
- LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear),
+ LinearSystemId.createDCMotorSystem(Driving.FF.TALON.V, Driving.FF.TALON.kA_linear),
DCMotor.getKrakenX60(1),
1 / Driving.GEARING);
};
From bf43c303df860554e2ce6f0f0ff0e9163132cc05 Mon Sep 17 00:00:00 2001
From: yxhej
Date: Sun, 18 Aug 2024 16:08:28 -0400
Subject: [PATCH 11/14] specify needed changes, fix drive sim, remove
unnecessary 'get's
---
README.md | 11 +--
simgui-ds.json | 19 +++--
simgui.json | 74 ++++++++++++++++++-
.../java/org/sciborgs1155/lib/FakePDH.java | 39 ----------
.../org/sciborgs1155/lib/FaultLogger.java | 28 +++----
.../org/sciborgs1155/robot/Constants.java | 1 +
.../java/org/sciborgs1155/robot/Ports.java | 1 +
.../java/org/sciborgs1155/robot/Robot.java | 8 +-
.../org/sciborgs1155/robot/drive/Drive.java | 18 ++---
.../robot/drive/DriveConstants.java | 57 +++++---------
.../org/sciborgs1155/robot/drive/GyroIO.java | 8 +-
.../sciborgs1155/robot/drive/NavXGyro.java | 4 +-
.../org/sciborgs1155/robot/drive/NoGyro.java | 4 +-
.../sciborgs1155/robot/drive/SimModule.java | 42 ++++++-----
.../sciborgs1155/robot/drive/SparkModule.java | 11 +--
.../sciborgs1155/robot/drive/TalonModule.java | 9 +--
.../org/sciborgs1155/robot/vision/Vision.java | 11 ++-
.../robot/vision/VisionConstants.java | 4 +-
18 files changed, 186 insertions(+), 163 deletions(-)
delete mode 100644 src/main/java/org/sciborgs1155/lib/FakePDH.java
diff --git a/README.md b/README.md
index 78fff7d..d02ddfa 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,24 @@
# Hydrogen
-The SciBorgs' base repository. It is a current work-in-progress.
+The SciBorgs' base repository. It is a living document that should be updated yearly for new libraries and other changes.
## Structure
-Our robot code is centered around [Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java)
+Our robot code is centered around [Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java).
This project currently contains drive, vision, and autos. You are expected to add/modify code supporting existing files and new subsystems when using this template. Some of these files include but are not limited to:
- **[Autos.java](src/main/java/org/sciborgs1155/robot/commands/Autos.java)** Add code for new subsystems in `configureAutos`, such as commands for `NamedCommands`
+- **[DriveConstants.java](src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java)** Modify control constants yearly for each new robot, and all drivetrain constants for each new drivetrain as needed.
- **[VisionConstants.java](src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java)** Add new `CameraConfig` fields representing cameras on the robot, and change the `create` method in [Vision](src/main/java/org/sciborgs1155/robot/vision/Vision.java). Also modify any camera configurations or constants if needed.
- **[Constants.java](src/main/java/org/sciborgs1155/robot/Constants.java)** Modify the class `Field` to be updated for each year's game, and any other constants if needed.
- **[Ports.java](src/main/java/org/sciborgs1155/robot/Ports.java)** Modify existing OI and drive ports, as well as adding new ports.
-- **[Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java)** A lot needs to be added/modified in this file, self-explanatory.
+- **[Robot.java](src/main/java/org/sciborgs1155/robot/Robot.java)** A lot: subsystems, command files, related triggers & bindings, interactions with other subsystems & files, library configurations/starting, etc..
## Dependencies
- [WPILib](https://docs.wpilib.org/)
-- [Spotless](https://github.com/diffplug/spotless/blob/main/plugin-gradle/README.md)
-- [Monologue](https://github.com/shueja/Monologue)
- [REVLib](https://docs.revrobotics.com/sparkmax/software-resources/spark-max-api-information)
- [PathPlannerLib](https://pathplanner.dev/home.html)
- [PhotonLib](https://docs.photonvision.org/en/latest/docs/programming/photonlib/adding-vendordep.html)
- [Phoenix6](https://v6.docs.ctr-electronics.com/en/stable/)
- [URCL](https://github.com/Mechanical-Advantage/URCL)
- [NavX](https://pdocs.kauailabs.com/navx-mxp/software/roborio-libraries/java/)
+- [Spotless](https://github.com/diffplug/spotless/blob/main/plugin-gradle/README.md)
+- [Monologue](https://github.com/shueja/Monologue)
diff --git a/simgui-ds.json b/simgui-ds.json
index a945a65..e3207aa 100644
--- a/simgui-ds.json
+++ b/simgui-ds.json
@@ -4,9 +4,9 @@
"visible": false
}
},
- "Joysticks": {
+ "Keyboard 0 Settings": {
"window": {
- "visible": false
+ "visible": true
}
},
"System Joysticks": {
@@ -18,12 +18,12 @@
{
"axisConfig": [
{
- "decKey": 68,
- "incKey": 65
+ "decKey": 83,
+ "incKey": 87
},
{
- "decKey": 87,
- "incKey": 83
+ "decKey": 65,
+ "incKey": 68
},
{
"decKey": 69,
@@ -33,8 +33,8 @@
},
{},
{
- "decKey": 76,
- "incKey": 74
+ "decKey": 74,
+ "incKey": 75
}
],
"axisCount": 6,
@@ -63,8 +63,7 @@
"axisConfig": [
{},
{
- "decKey": 73,
- "incKey": 75
+ "decKey": 73
}
],
"axisCount": 4,
diff --git a/simgui.json b/simgui.json
index 0199a96..10b540b 100644
--- a/simgui.json
+++ b/simgui.json
@@ -19,10 +19,22 @@
"/Robot/drive/rearLeft/turnFeedback": "PIDController",
"/Robot/drive/rearRight/driveFeedback": "PIDController",
"/Robot/drive/rearRight/turnFeedback": "PIDController",
+ "/Robot/drive/rotationController": "PIDController",
+ "/Robot/drive/translationController": "ProfiledPIDController",
+ "/SmartDashboard/Scheduler": "Scheduler",
+ "/SmartDashboard/VisionSystemSim-main/Sim Field": "Field2d",
"/SmartDashboard/drive dynamic backward": "Command",
"/SmartDashboard/drive dynamic forward": "Command",
"/SmartDashboard/drive quasistatic backward": "Command",
"/SmartDashboard/drive quasistatic forward": "Command",
+ "/SmartDashboard/rotation dynamic backward": "Command",
+ "/SmartDashboard/rotation dynamic forward": "Command",
+ "/SmartDashboard/rotation quasistatic backward": "Command",
+ "/SmartDashboard/rotation quasistatic forward": "Command",
+ "/SmartDashboard/translation dynamic backward": "Command",
+ "/SmartDashboard/translation dynamic forward": "Command",
+ "/SmartDashboard/translation quasistatic backward": "Command",
+ "/SmartDashboard/translation quasistatic forward": "Command",
"/SmartDashboard/turn dynamic backward": "Command",
"/SmartDashboard/turn dynamic forward": "Command",
"/SmartDashboard/turn quasistatic backward": "Command",
@@ -30,6 +42,9 @@
},
"windows": {
"/Robot/drive/field2d": {
+ "bottom": 1476,
+ "height": 8.210550308227539,
+ "left": 150,
"module- FL": {
"style": "Line"
},
@@ -42,6 +57,9 @@
"module-RL": {
"style": "Line"
},
+ "right": 2961,
+ "top": 79,
+ "width": 16.541748046875,
"window": {
"visible": true
}
@@ -59,6 +77,9 @@
}
},
"NetworkTables": {
+ "Retained Values": {
+ "open": false
+ },
"transitory": {
"Robot": {
"drive": {
@@ -68,6 +89,54 @@
},
"open": true
},
+ "Pose2d##v_/Robot/drive/pose": {
+ "Rotation2d##v_rotation": {
+ "open": true
+ },
+ "Translation2d##v_translation": {
+ "open": true
+ },
+ "open": true
+ },
+ "SwerveModulePosition[]##v_/Robot/drive/getModulePositions": {
+ "SwerveModulePosition##v_[1]": {
+ "Rotation2d##v_angle": {
+ "open": true
+ }
+ }
+ },
+ "SwerveModuleState[]##v_/Robot/drive/getModuleSetpoints": {
+ "SwerveModuleState##v_[0]": {
+ "Rotation2d##v_angle": {
+ "open": true
+ }
+ },
+ "SwerveModuleState##v_[1]": {
+ "open": true
+ },
+ "SwerveModuleState##v_[2]": {
+ "Rotation2d##v_angle": {
+ "open": true
+ },
+ "open": true
+ },
+ "SwerveModuleState##v_[3]": {
+ "Rotation2d##v_angle": {
+ "open": true
+ },
+ "open": true
+ },
+ "open": true
+ },
+ "SwerveModuleState[]##v_/Robot/drive/getModuleStates": {
+ "SwerveModuleState##v_[0]": {
+ "Rotation2d##v_angle": {
+ "open": true
+ },
+ "open": true
+ },
+ "open": true
+ },
"frontRight": {
"SwerveModulePosition##v_/Robot/drive/frontRight/position": {
"Rotation2d##v_angle": {
@@ -85,7 +154,10 @@
},
"open": true
},
- "open": true
+ "open": true,
+ "vision": {
+ "open": true
+ }
},
"Shuffleboard": {
"open": true
diff --git a/src/main/java/org/sciborgs1155/lib/FakePDH.java b/src/main/java/org/sciborgs1155/lib/FakePDH.java
deleted file mode 100644
index 02e581c..0000000
--- a/src/main/java/org/sciborgs1155/lib/FakePDH.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.sciborgs1155.lib;
-
-import com.revrobotics.CANSparkBase;
-import edu.wpi.first.wpilibj.RobotController;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Our old PDH couldn't connect to CAN, so this was a workaround to (poorly) estimate our total used
- * current based on the stator currents of all our motors with the formula:
- *
- * supply current = battery voltage * applied output * stator current
- */
-public class FakePDH {
- private static final List sparks = new ArrayList<>();
- private static double statorCurrent;
- private static double supplyCurrent;
-
- public static void register(CANSparkBase spark) {
- sparks.add(spark);
- }
-
- public static double update() {
- double voltage = RobotController.getBatteryVoltage();
- double output = sparks.stream().mapToDouble(CANSparkBase::getAppliedOutput).sum();
- statorCurrent =
- sparks.stream().mapToDouble(CANSparkBase::getOutputCurrent).map(Math::abs).sum();
- supplyCurrent = voltage * output * statorCurrent;
- return supplyCurrent;
- }
-
- public static double supplyCurrent() {
- return supplyCurrent;
- }
-
- public static double statorCurrent() {
- return statorCurrent;
- }
-}
diff --git a/src/main/java/org/sciborgs1155/lib/FaultLogger.java b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
index 1786143..5982ac6 100644
--- a/src/main/java/org/sciborgs1155/lib/FaultLogger.java
+++ b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
@@ -4,12 +4,14 @@
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkBase.FaultID;
import com.revrobotics.REVLibError;
+import edu.wpi.first.hal.PowerDistributionFaults;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringArrayPublisher;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DutyCycleEncoder;
import edu.wpi.first.wpilibj.PowerDistribution;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -228,19 +230,19 @@ public static void register(AHRS ahrs) {
* @param powerDistribution The power distribution to manage.
*/
public static void register(PowerDistribution powerDistribution) {
- // Field[] fields = PowerDistributionFaults.class.getFields();
- // faultReporters.add(
- // () -> {
- // try {
- // PowerDistributionFaults faults = powerDistribution.getFaults();
- // for (Field fault : fields) {
- // if (fault.getBoolean(faults)) {
- // report("Power Distribution", fault.getName(), FaultType.ERROR);
- // }
- // }
- // } catch (Exception e) {
- // }
- // });
+ var fields = PowerDistributionFaults.class.getFields();
+ faultReporters.add(
+ () -> {
+ try {
+ PowerDistributionFaults faults = powerDistribution.getFaults();
+ for (Field fault : fields) {
+ if (fault.getBoolean(faults)) {
+ report("Power Distribution", fault.getName(), FaultType.ERROR);
+ }
+ }
+ } catch (Exception e) {
+ }
+ });
}
/**
diff --git a/src/main/java/org/sciborgs1155/robot/Constants.java b/src/main/java/org/sciborgs1155/robot/Constants.java
index 48b93b2..787e2cb 100644
--- a/src/main/java/org/sciborgs1155/robot/Constants.java
+++ b/src/main/java/org/sciborgs1155/robot/Constants.java
@@ -22,6 +22,7 @@
* @see Units
*/
public class Constants {
+ // TODO: Modify as needed.
/** Returns the robot's alliance. */
public static Alliance alliance() {
return DriverStation.getAlliance().orElse(Alliance.Blue);
diff --git a/src/main/java/org/sciborgs1155/robot/Ports.java b/src/main/java/org/sciborgs1155/robot/Ports.java
index fee0217..7e3add8 100644
--- a/src/main/java/org/sciborgs1155/robot/Ports.java
+++ b/src/main/java/org/sciborgs1155/robot/Ports.java
@@ -1,6 +1,7 @@
package org.sciborgs1155.robot;
public final class Ports {
+ // TODO: Add and change all ports as needed.
public static final class OI {
public static final int OPERATOR = 0;
public static final int DRIVER = 1;
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index ca35622..dd616a0 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -64,6 +64,7 @@ public Robot() {
/** Configures basic behavior during different parts of the game. */
private void configureGameBehavior() {
+ // TODO: Add configs for all additional libraries, components, intersubsystem interaction
// Configure logging with DataLogManager, Monologue, URCL, and FaultLogger
DataLogManager.start();
Monologue.setupMonologue(this, "/Robot", false, true);
@@ -73,9 +74,10 @@ private void configureGameBehavior() {
SmartDashboard.putData(CommandScheduler.getInstance());
// Log PDH
SmartDashboard.putData("PDH", pdh);
+ FaultLogger.register(pdh);
// Configure pose estimation updates every tick
- addPeriodic(() -> drive.updateEstimates(vision.getEstimatedGlobalPoses()), PERIOD.in(Seconds));
+ addPeriodic(() -> drive.updateEstimates(vision.estimatedGlobalPoses()), PERIOD.in(Seconds));
RobotController.setBrownoutVoltage(6.0);
@@ -125,13 +127,17 @@ private void configureBindings() {
drive.setDefaultCommand(drive.drive(x, y, omega));
autonomous().whileTrue(Commands.deferredProxy(autos::getSelected));
+
test().whileTrue(systemsCheck());
+
driver.b().whileTrue(drive.zeroHeading());
driver
.leftBumper()
.or(driver.rightBumper())
.onTrue(Commands.runOnce(() -> speedMultiplier = Constants.SLOW_SPEED_MULTIPLIER))
.onFalse(Commands.runOnce(() -> speedMultiplier = Constants.FULL_SPEED_MULTIPLIER));
+
+ // TODO: Add any additional bindings.
}
/**
diff --git a/src/main/java/org/sciborgs1155/robot/drive/Drive.java b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
index 8ab64f4..a0357e2 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/Drive.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/Drive.java
@@ -178,8 +178,8 @@ public Drive(
odometry =
new SwerveDrivePoseEstimator(
kinematics,
- gyro.getRotation2d(),
- getModulePositions(),
+ gyro.rotation2d(),
+ modulePositions(),
new Pose2d(new Translation2d(), Rotation2d.fromDegrees(180)));
for (int i = 0; i < modules.size(); i++) {
@@ -239,7 +239,7 @@ public Rotation2d heading() {
* @param pose The pose to which to set the odometry.
*/
public void resetOdometry(Pose2d pose) {
- odometry.resetPosition(gyro.getRotation2d(), getModulePositions(), pose);
+ odometry.resetPosition(gyro.rotation2d(), modulePositions(), pose);
}
/**
@@ -308,7 +308,7 @@ public boolean atRotationalSetpoint() {
*/
public boolean isFacing(Translation2d target) {
return Math.abs(
- gyro.getRotation2d().getRadians()
+ gyro.rotation2d().getRadians()
- target.minus(pose().getTranslation()).getAngle().getRadians())
< rotationController.getPositionTolerance();
}
@@ -383,26 +383,26 @@ public Command zeroHeading() {
/** Returns the module states. */
@Log.NT
- public SwerveModuleState[] getModuleStates() {
+ public SwerveModuleState[] moduleStates() {
return modules.stream().map(ModuleIO::state).toArray(SwerveModuleState[]::new);
}
/** Returns the module states. */
@Log.NT
- private SwerveModuleState[] getModuleSetpoints() {
+ private SwerveModuleState[] moduleSetpoints() {
return modules.stream().map(ModuleIO::desiredState).toArray(SwerveModuleState[]::new);
}
/** Returns the module positions. */
@Log.NT
- public SwerveModulePosition[] getModulePositions() {
+ public SwerveModulePosition[] modulePositions() {
return modules.stream().map(ModuleIO::position).toArray(SwerveModulePosition[]::new);
}
/** Returns the robot-relative chassis speeds. */
@Log.NT
public ChassisSpeeds getRobotRelativeChassisSpeeds() {
- return kinematics.toChassisSpeeds(getModuleStates());
+ return kinematics.toChassisSpeeds(moduleStates());
}
/** Returns the field-relative chassis speeds. */
@@ -434,7 +434,7 @@ public void updateEstimates(PoseEstimate... poses) {
@Override
public void periodic() {
// update our heading in reality / sim
- odometry.update(Robot.isReal() ? gyro.getRotation2d() : simRotation, getModulePositions());
+ odometry.update(Robot.isReal() ? gyro.rotation2d() : simRotation, modulePositions());
// update our simulated field poses
field2d.setRobotPose(pose());
diff --git a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
index ec5ce16..d50a726 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/DriveConstants.java
@@ -13,9 +13,12 @@
import edu.wpi.first.units.Velocity;
import java.util.List;
-/** Constants for our 2024 MAXSwerve drivetrain. */
+/**
+ * Constants for our 2024 MAXSwerve drivetrain. All fields in this file should be updated for the
+ * current robot configuration!
+ */
public final class DriveConstants {
- /** The type of control loop to use when controlling the drive motor. */
+ /** The type of control loop to use when controlling a module's drive motor. */
public static enum ControlMode {
CLOSED_LOOP_VELOCITY,
OPEN_LOOP_VELOCITY;
@@ -27,6 +30,8 @@ public static enum ModuleType {
SPARK; // NEO Vortex Drive, NEO 550 Turn
}
+ // TODO: Change central drivetrain constants as needed.
+
// The type of module on the chassis
public static final ModuleType TYPE = ModuleType.SPARK;
@@ -77,6 +82,7 @@ public static enum ModuleType {
public static final Rotation3d GYRO_OFFSET = new Rotation3d(0, 0, Math.PI);
+ // TODO: Change ALL characterization constants for each unique robot as needed.
public static final class Translation {
public static final double P = 3.0;
public static final double I = 0.0;
@@ -113,18 +119,12 @@ public static final class Driving {
public static final class PID {
public static final class SPARK {
- public static final double P = 1;
- public static final double I = 0.0;
- public static final double D = 0.0;
- }
-
- public static final class TALON {
public static final double P = 3.2;
public static final double I = 0.0;
public static final double D = 0.0;
}
- public static final class SIM {
+ public static final class TALON {
public static final double P = 3.2;
public static final double I = 0.0;
public static final double D = 0.0;
@@ -132,23 +132,16 @@ public static final class SIM {
}
public static final class FF {
- // s: 0.21474, 0.23963, 0.16188, 0.13714
- // v: 2.115, 2.0681, 2.1498, 2.0948
- // a linear: 0.17586, 0.13707, 0.23915, 0.26842
- // a rotation: 0.37587, 0.20079
- // 2 has 0.55 R^2
public static final class SPARK {
- public static final double S = 0.23963;
- public static final double V = 2.0681;
- public static final double kA_linear = 0.205;
- public static final double kA_angular = 0.376;
+ public static final double S = 0.088468;
+ public static final double V = 2.1314;
+ public static final double A = 0.33291;
}
public static final class TALON {
- public static final double S = 0; // TODO
- public static final double V = 2.0681;
- public static final double kA_linear = 0.205;
- public static final double kA_angular = 0.376;
+ public static final double S = 0.088468;
+ public static final double V = 2.1314;
+ public static final double A = 0.33291;
}
}
}
@@ -165,23 +158,9 @@ static final class Turning {
public static final Measure CURRENT_LIMIT = Amps.of(20);
public static final class PID {
- public static final class SPARK {
- public static final double P = 0.3; // FAKE DOES NOT WORK
- public static final double I = 0.0;
- public static final double D = 0.05;
- }
-
- public static final class TALON {
- public static final double P = 9;
- public static final double I = 0.0;
- public static final double D = 0.05;
- }
-
- public static final class SIM {
- public static final double P = 9;
- public static final double I = 0.0;
- public static final double D = 0.05;
- }
+ public static final double P = 9;
+ public static final double I = 0.0;
+ public static final double D = 0.05;
}
// system constants only used in simulation
diff --git a/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java b/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
index adb8efe..5bb816e 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
@@ -9,15 +9,15 @@ public interface GyroIO extends AutoCloseable {
default void calibrate() {}
/** Returns the rate of rotation. */
- double getRate();
+ double rate();
/** Returns the heading of the robot as a Rotation2d. */
- default Rotation2d getRotation2d() {
- return getRotation3d().toRotation2d();
+ default Rotation2d rotation2d() {
+ return rotation3d().toRotation2d();
}
/** Returns the heading of the robot as a Rotation3d. */
- Rotation3d getRotation3d();
+ Rotation3d rotation3d();
/** Resets heading to 0 */
void reset();
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java b/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java
index e2b1fcd..18c4ff6 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/NavXGyro.java
@@ -13,12 +13,12 @@ public NavXGyro() {
}
@Override
- public double getRate() {
+ public double rate() {
return ahrs.getRate();
}
@Override
- public Rotation3d getRotation3d() {
+ public Rotation3d rotation3d() {
return ahrs.getRotation3d();
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java b/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java
index e7ee93b..66968e0 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/NoGyro.java
@@ -10,12 +10,12 @@ public class NoGyro implements GyroIO {
public void close() throws Exception {}
@Override
- public double getRate() {
+ public double rate() {
return 0;
}
@Override
- public Rotation3d getRotation3d() {
+ public Rotation3d rotation3d() {
return rotation;
}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index 5bb4275..d8a393b 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -23,36 +23,39 @@ public class SimModule implements ModuleIO {
switch (TYPE) {
case SPARK ->
new DCMotorSim(
- LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear),
+ LinearSystemId.createDCMotorSystem(Driving.FF.SPARK.V, Driving.FF.SPARK.A),
DCMotor.getNeoVortex(1),
1 / Driving.GEARING);
case TALON ->
new DCMotorSim(
- LinearSystemId.createDCMotorSystem(Driving.FF.TALON.V, Driving.FF.TALON.kA_linear),
+ LinearSystemId.createDCMotorSystem(Driving.FF.TALON.V, Driving.FF.TALON.A),
DCMotor.getKrakenX60(1),
1 / Driving.GEARING);
};
+ private final PIDController driveFeedback =
+ switch (TYPE) {
+ case SPARK ->
+ new PIDController(Driving.PID.SPARK.P, Driving.PID.SPARK.I, Driving.PID.SPARK.D);
+ case TALON ->
+ new PIDController(Driving.PID.TALON.P, Driving.PID.TALON.I, Driving.PID.TALON.D);
+ };
+ private final SimpleMotorFeedforward driveFF =
+ switch (TYPE) {
+ case SPARK ->
+ new SimpleMotorFeedforward(Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.A);
+ case TALON ->
+ new SimpleMotorFeedforward(Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.A);
+ };
+
private final DCMotorSim turn =
new DCMotorSim(
LinearSystemId.createDCMotorSystem(Turning.FF.V, Turning.FF.A),
DCMotor.getNeo550(1),
1 / Turning.MOTOR_GEARING);
- private final PIDController driveFeedback =
- new PIDController(Driving.PID.SIM.P, Driving.PID.SIM.I, Driving.PID.SIM.D);
private final PIDController turnFeedback =
- new PIDController(Turning.PID.SIM.P, Turning.PID.SIM.I, Turning.PID.SIM.D);
-
- private final SimpleMotorFeedforward driveFF =
- switch (TYPE) {
- case SPARK ->
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear);
- case TALON ->
- new SimpleMotorFeedforward(
- Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.kA_linear);
- };
+ new PIDController(Turning.PID.P, Turning.PID.I, Turning.PID.D);
private SwerveModuleState setpoint = new SwerveModuleState();
@@ -60,6 +63,8 @@ public class SimModule implements ModuleIO {
public SimModule(String name) {
this.name = name;
+
+ turnFeedback.enableContinuousInput(-Math.PI, Math.PI);
}
@Override
@@ -117,12 +122,13 @@ public void resetEncoders() {
@Override
public void setDriveSetpoint(double velocity) {
- setDriveVoltage(driveFeedback.calculate(velocity) + driveFF.calculate(velocity));
+ setDriveVoltage(
+ driveFeedback.calculate(driveVelocity(), velocity) + driveFF.calculate(velocity));
}
@Override
- public void setTurnSetpoint(double angle) {
- setTurnVoltage(turnFeedback.calculate(angle));
+ public void setTurnSetpoint(double setpoint) {
+ setTurnVoltage(turnFeedback.calculate(rotation().getRadians(), setpoint));
}
@Override
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
index 3094edc..536057e 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SparkModule.java
@@ -52,12 +52,10 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset, String
driveEncoder = driveMotor.getEncoder();
drivePID = driveMotor.getPIDController();
driveFF =
- new SimpleMotorFeedforward(
- Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.kA_linear); // TODO: Re-tune
+ new SimpleMotorFeedforward(Driving.FF.SPARK.S, Driving.FF.SPARK.V, Driving.FF.SPARK.A);
check(driveMotor, driveMotor.restoreFactoryDefaults());
- // TODO: Re-tune
check(driveMotor, drivePID.setP(Driving.PID.SPARK.P));
check(driveMotor, drivePID.setI(Driving.PID.SPARK.I));
check(driveMotor, drivePID.setD(Driving.PID.SPARK.D));
@@ -86,10 +84,9 @@ public SparkModule(int drivePort, int turnPort, Rotation2d angularOffset, String
check(turnMotor, turnMotor.restoreFactoryDefaults());
- // TODO: Re-tune
- check(turnMotor, turnPID.setP(Turning.PID.SPARK.P));
- check(turnMotor, turnPID.setI(Turning.PID.SPARK.I));
- check(turnMotor, turnPID.setD(Turning.PID.SPARK.D));
+ check(turnMotor, turnPID.setP(Turning.PID.P));
+ check(turnMotor, turnPID.setI(Turning.PID.I));
+ check(turnMotor, turnPID.setD(Turning.PID.D));
check(turnMotor, turnPID.setPositionPIDWrappingEnabled(true));
check(turnMotor, turnPID.setPositionPIDWrappingMinInput(-Math.PI));
check(turnMotor, turnPID.setPositionPIDWrappingMaxInput(Math.PI));
diff --git a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
index b515a8b..e211354 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/TalonModule.java
@@ -55,8 +55,7 @@ public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String
drivePos = driveMotor.getPosition();
driveVelocity = driveMotor.getVelocity();
driveFF =
- new SimpleMotorFeedforward(
- Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.kA_linear);
+ new SimpleMotorFeedforward(Driving.FF.TALON.S, Driving.FF.TALON.V, Driving.FF.TALON.A);
drivePos.setUpdateFrequency(1 / SENSOR_PERIOD.in(Seconds));
driveVelocity.setUpdateFrequency(1 / SENSOR_PERIOD.in(Seconds));
@@ -83,9 +82,9 @@ public TalonModule(int drivePort, int turnPort, Rotation2d angularOffset, String
check(turnMotor, turnMotor.restoreFactoryDefaults());
- check(turnMotor, turnPID.setP(Turning.PID.SPARK.P));
- check(turnMotor, turnPID.setI(Turning.PID.SPARK.I));
- check(turnMotor, turnPID.setD(Turning.PID.SPARK.D));
+ check(turnMotor, turnPID.setP(Turning.PID.P));
+ check(turnMotor, turnPID.setI(Turning.PID.I));
+ check(turnMotor, turnPID.setD(Turning.PID.D));
check(turnMotor, turnPID.setPositionPIDWrappingEnabled(true));
check(turnMotor, turnPID.setPositionPIDWrappingMinInput(-Math.PI));
check(turnMotor, turnPID.setPositionPIDWrappingMaxInput(Math.PI));
diff --git a/src/main/java/org/sciborgs1155/robot/vision/Vision.java b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
index 665f253..4009a20 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/Vision.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
@@ -39,9 +39,9 @@ public static record PoseEstimate(EstimatedRobotPose estimatedPose, Matrix estimates = new ArrayList<>();
for (int i = 0; i < estimators.length; i++) {
var result = cameras[i].getLatestResult();
@@ -111,8 +111,7 @@ public PoseEstimate[] getEstimatedGlobalPoses() {
.ifPresent(
e ->
estimates.add(
- new PoseEstimate(
- e, getEstimationStdDevs(e.estimatedPose.toPose2d(), result))));
+ new PoseEstimate(e, estimationStdDevs(e.estimatedPose.toPose2d(), result))));
}
return estimates.toArray(PoseEstimate[]::new);
}
@@ -139,7 +138,7 @@ public Pose3d[] getSeenTags() {
*
* @param estimatedPose The estimated pose to guess standard deviations for.
*/
- public Matrix getEstimationStdDevs(
+ public Matrix estimationStdDevs(
Pose2d estimatedPose, PhotonPipelineResult pipelineResult) {
var estStdDevs = VisionConstants.SINGLE_TAG_STD_DEVS;
var targets = pipelineResult.getTargets();
diff --git a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
index b0d06d6..1ae0380 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
@@ -14,9 +14,10 @@ public class VisionConstants {
public static final AprilTagFieldLayout TAG_LAYOUT =
AprilTagFieldLayout.loadField(AprilTagFields.kDefaultField);
- /* The front of the robot is defined at the intake */
+ /** TODO: Create cameras with updated constants; be sure to add in {@link Vision#create} */
public static final CameraConfig BACK_LEFT_CAMERA =
new CameraConfig("back left", new Transform3d());
+
public static final CameraConfig BACK_RIGHT_CAMERA =
new CameraConfig("back right", new Transform3d());
public static final CameraConfig FRONT_LEFT_CAMERA =
@@ -41,6 +42,5 @@ public class VisionConstants {
// First half of locations are on red side, second half on blue side
// (ex. source: 1,2 is red, 9,10)
- // Unsure if getFudicialID() returns the tags from 1-16 or 0-15 (assuming 0-15)
public static final double[] TAG_WEIGHTS = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
}
From dff9391b9e221273f5c73b8098b64c40da36ebae Mon Sep 17 00:00:00 2001
From: sigalrmp
Date: Tue, 3 Sep 2024 00:24:28 -0400
Subject: [PATCH 12/14] added back talon fault registration
---
.../org/sciborgs1155/lib/FaultLogger.java | 63 +++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/src/main/java/org/sciborgs1155/lib/FaultLogger.java b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
index 5982ac6..29c690c 100644
--- a/src/main/java/org/sciborgs1155/lib/FaultLogger.java
+++ b/src/main/java/org/sciborgs1155/lib/FaultLogger.java
@@ -1,5 +1,7 @@
package org.sciborgs1155.lib;
+import com.ctre.phoenix6.StatusSignal;
+import com.ctre.phoenix6.hardware.TalonFX;
import com.kauailabs.navx.frc.AHRS;
import com.revrobotics.CANSparkBase;
import com.revrobotics.CANSparkBase.FaultID;
@@ -17,6 +19,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import org.photonvision.PhotonCamera;
@@ -258,6 +261,66 @@ public static void register(PhotonCamera camera) {
FaultType.ERROR);
}
+ /**
+ * Registers fault suppliers for a talon.
+ *
+ * @param talon The talon to manage.
+ */
+ public static void register(TalonFX talon) {
+ int id = talon.getDeviceID();
+ BiConsumer, String> regFault =
+ (f, d) -> register((BooleanSupplier) f, "Talon ID: " + id, d, FaultType.ERROR);
+
+ // TODO: Remove all the unnecessary faults
+ regFault.accept(talon.getFault_Hardware(), "Hardware fault occurred");
+ regFault.accept(talon.getFault_ProcTemp(), "Processor temperature exceeded limit");
+ regFault.accept(talon.getFault_Hardware(), "Hardware fault occurred");
+ regFault.accept(talon.getFault_ProcTemp(), "Processor temperature exceeded limit");
+ regFault.accept(talon.getFault_DeviceTemp(), "Device temperature exceeded limit");
+ regFault.accept(
+ talon.getFault_Undervoltage(), "Device supply voltage dropped to near brownout levels");
+ regFault.accept(
+ talon.getFault_BootDuringEnable(), "Device boot while detecting the enable signal");
+ regFault.accept(
+ talon.getFault_UnlicensedFeatureInUse(),
+ "An unlicensed feature is in use, device may not behave as expected.");
+ regFault.accept(
+ talon.getFault_BridgeBrownout(),
+ "Bridge was disabled most likely due to supply voltage dropping too low.");
+ regFault.accept(talon.getFault_RemoteSensorReset(), "The remote sensor has reset.");
+ regFault.accept(
+ talon.getFault_MissingDifferentialFX(),
+ "The remote Talon FX used for differential control is not present on CAN Bus.");
+ regFault.accept(
+ talon.getFault_RemoteSensorPosOverflow(), "The remote sensor position has overflowed.");
+ regFault.accept(
+ talon.getFault_OverSupplyV(),
+ "Supply Voltage has exceeded the maximum voltage rating of device.");
+ regFault.accept(talon.getFault_UnstableSupplyV(), "Supply Voltage is unstable.");
+ regFault.accept(
+ talon.getFault_ReverseHardLimit(),
+ "Reverse limit switch has been asserted. Output is set to neutral.");
+ regFault.accept(
+ talon.getFault_ForwardHardLimit(),
+ "Forward limit switch has been asserted. Output is set to neutral.");
+ regFault.accept(
+ talon.getFault_ReverseSoftLimit(),
+ "Reverse soft limit has been asserted. Output is set to neutral.");
+ regFault.accept(
+ talon.getFault_ForwardSoftLimit(),
+ "Forward soft limit has been asserted. Output is set to neutral.");
+ regFault.accept(
+ talon.getFault_RemoteSensorDataInvalid(), "The remote sensor's data is no longer trusted.");
+ regFault.accept(
+ talon.getFault_FusedSensorOutOfSync(),
+ "The remote sensor used for fusion has fallen out of sync to the local sensor.");
+ regFault.accept(talon.getFault_StatorCurrLimit(), "Stator current limit occured.");
+ regFault.accept(talon.getFault_SupplyCurrLimit(), "Supply current limit occured.");
+ regFault.accept(
+ talon.getFault_UsingFusedCANcoderWhileUnlicensed(),
+ "Using Fused CANcoder feature while unlicensed. Device has fallen back to remote CANcoder.");
+ }
+
/**
* Reports REVLibErrors from a spark.
*
From 6609665ff46c41a4a0479e446629e82ed3d9ad73 Mon Sep 17 00:00:00 2001
From: sigalrmp
Date: Wed, 4 Sep 2024 13:13:18 -0400
Subject: [PATCH 13/14] changed cam configs so sim doesn't crash
---
src/main/java/org/sciborgs1155/robot/vision/Vision.java | 2 +-
.../org/sciborgs1155/robot/vision/VisionConstants.java | 9 +++++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/robot/vision/Vision.java b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
index 4009a20..0919e47 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/Vision.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/Vision.java
@@ -41,7 +41,7 @@ public static record PoseEstimate(EstimatedRobotPose estimatedPose, Matrix
Date: Wed, 4 Sep 2024 13:40:33 -0400
Subject: [PATCH 14/14] make small comment changes & add cam transform warning
---
src/main/java/org/sciborgs1155/lib/Assertion.java | 4 ++--
src/main/java/org/sciborgs1155/lib/InputStream.java | 1 -
src/main/java/org/sciborgs1155/lib/SparkUtils.java | 1 -
src/main/java/org/sciborgs1155/lib/Tuning.java | 1 -
src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java | 6 +++---
src/main/java/org/sciborgs1155/robot/Robot.java | 2 +-
src/main/java/org/sciborgs1155/robot/drive/GyroIO.java | 2 +-
src/main/java/org/sciborgs1155/robot/drive/SimModule.java | 1 -
.../java/org/sciborgs1155/robot/vision/VisionConstants.java | 1 +
9 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/sciborgs1155/lib/Assertion.java b/src/main/java/org/sciborgs1155/lib/Assertion.java
index f75c15d..8e8ef8d 100644
--- a/src/main/java/org/sciborgs1155/lib/Assertion.java
+++ b/src/main/java/org/sciborgs1155/lib/Assertion.java
@@ -11,7 +11,7 @@ public sealed interface Assertion {
public void apply(boolean unitTest);
/**
- * Asserts that a condition is true, and reports to FaultLogger
+ * Asserts that a condition is true and reports to FaultLogger.
*
* @param condition
* @param faultName
@@ -25,7 +25,7 @@ private static void reportTrue(boolean condition, String faultName, String descr
}
/**
- * Asserts that two values are equal (with some tolerance), and reports to FaultLogger
+ * Asserts that two values are equal (with some tolerance) and reports to FaultLogger.
*
* @param faultName
* @param expected
diff --git a/src/main/java/org/sciborgs1155/lib/InputStream.java b/src/main/java/org/sciborgs1155/lib/InputStream.java
index ede6b51..3ac9b44 100644
--- a/src/main/java/org/sciborgs1155/lib/InputStream.java
+++ b/src/main/java/org/sciborgs1155/lib/InputStream.java
@@ -15,7 +15,6 @@
*/
@FunctionalInterface
public interface InputStream extends DoubleSupplier {
-
/**
* Creates an input stream from another.
*
diff --git a/src/main/java/org/sciborgs1155/lib/SparkUtils.java b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
index 7d03afd..10b5224 100644
--- a/src/main/java/org/sciborgs1155/lib/SparkUtils.java
+++ b/src/main/java/org/sciborgs1155/lib/SparkUtils.java
@@ -10,7 +10,6 @@
/** Utility class for configuration of Spark motor controllers */
public class SparkUtils {
-
private static final List runnables = new ArrayList<>();
public static void addChecker(Runnable runnable) {
diff --git a/src/main/java/org/sciborgs1155/lib/Tuning.java b/src/main/java/org/sciborgs1155/lib/Tuning.java
index 8b820c2..04bae02 100644
--- a/src/main/java/org/sciborgs1155/lib/Tuning.java
+++ b/src/main/java/org/sciborgs1155/lib/Tuning.java
@@ -14,7 +14,6 @@
*
*/
public final class Tuning {
-
/**
* Logs a DoubleEntry on Network Tables.
*
diff --git a/src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java b/src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java
index 0293377..e2b4706 100644
--- a/src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java
+++ b/src/main/java/org/sciborgs1155/lib/UnitTestingUtil.java
@@ -49,7 +49,7 @@ public static void fastForward(int ticks) {
}
/**
- * Fasts forward in time by running CommandScheduler and updating timer
+ * Fasts forward in time by running CommandScheduler and updating timer.
*
* @param time
*/
@@ -66,7 +66,7 @@ public static void fastForward() {
}
/**
- * Schedules and runs a command
+ * Schedules and runs a command.
*
* @param command The command to run.
*/
@@ -88,7 +88,7 @@ public static void run(Command command, int runs) {
/**
* Schedules a command and runs it until it ends. Be careful -- if the command you give never
- * ends, this will be an infinate loop!
+ * ends, this will be an infinite loop!
*
* @param command
*/
diff --git a/src/main/java/org/sciborgs1155/robot/Robot.java b/src/main/java/org/sciborgs1155/robot/Robot.java
index dd616a0..a4baf31 100644
--- a/src/main/java/org/sciborgs1155/robot/Robot.java
+++ b/src/main/java/org/sciborgs1155/robot/Robot.java
@@ -62,7 +62,7 @@ public Robot() {
configureBindings();
}
- /** Configures basic behavior during different parts of the game. */
+ /** Configures basic behavior for different periods during the game. */
private void configureGameBehavior() {
// TODO: Add configs for all additional libraries, components, intersubsystem interaction
// Configure logging with DataLogManager, Monologue, URCL, and FaultLogger
diff --git a/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java b/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
index 5bb816e..b9ba5eb 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/GyroIO.java
@@ -3,7 +3,7 @@
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Rotation3d;
-/** Generalized gyroscope. Pigeon2, Navx, and SimGyro are to be implemented */
+/** Generalized gyroscope. Pigeon2, Navx, and SimGyro are to be implemented. */
public interface GyroIO extends AutoCloseable {
/** Calibrates the gyroscope. Pigeon2 does not need to do anything here. */
default void calibrate() {}
diff --git a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
index d8a393b..e898367 100644
--- a/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
+++ b/src/main/java/org/sciborgs1155/robot/drive/SimModule.java
@@ -18,7 +18,6 @@
import org.sciborgs1155.robot.drive.DriveConstants.ModuleConstants.Turning;
public class SimModule implements ModuleIO {
-
private final DCMotorSim drive =
switch (TYPE) {
case SPARK ->
diff --git a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
index bd2039b..133a5bf 100644
--- a/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
+++ b/src/main/java/org/sciborgs1155/robot/vision/VisionConstants.java
@@ -16,6 +16,7 @@ public class VisionConstants {
AprilTagFieldLayout.loadField(AprilTagFields.kDefaultField);
/** TODO: Create cameras with updated constants; be sure to add in {@link Vision#create} */
+ // WARNING: EMPTY TRANSFORMS WILL CRASH SIMULATION UPON TAG DETECTION
public static final CameraConfig BACK_LEFT_CAMERA =
new CameraConfig("back left", new Transform3d(1, 1, 1, new Rotation3d()));