From 517ae758a1dd72144052e5772345fba5b6d4b43b Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 12 Jan 2025 15:16:07 -0800 Subject: [PATCH 01/63] initial sobbing --- WPILib-License.md | 2 +- build.gradle | 34 +++-- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +- gradlew.bat | 22 +-- settings.gradle | 6 +- .../lib199/CachedSparkMax.java | 12 +- .../lib199/DummySparkMaxAnswer.java | 27 ++-- .../lib199/MotorControllerFactory.java | 136 ++++++++++------- .../carlmontrobotics/lib199/MotorErrors.java | 49 +++--- .../lib199/SparkVelocityPIDController.java | 33 ++-- .../lib199/sim/MockSparkBase.java | 35 +++-- .../lib199/sim/MockSparkFlex.java | 8 +- .../lib199/sim/MockSparkMax.java | 8 +- .../sim/MockedSparkMaxPIDController.java | 43 +++--- .../lib199/swerve/SwerveModule.java | 35 +++-- .../lib199/swerve/SwerveModuleSim.java | 6 +- vendordeps/NavX.json | 40 ----- ...rLib.json => PathplannerLib-2025.2.1.json} | 10 +- .../{Phoenix5.json => Phoenix5-5.35.1.json} | 52 +++++-- .../{Phoenix6.json => Phoenix6-25.1.0.json} | 144 ++++++++++++------ ...json => PlayingWithFusion-2025.01.04.json} | 39 +++-- .../{REVLib.json => REVLib-2025.0.0.json} | 16 +- vendordeps/Studica-2025.0.0.json | 71 +++++++++ vendordeps/WPILibNewCommands.json | 2 +- 26 files changed, 497 insertions(+), 342 deletions(-) delete mode 100644 vendordeps/NavX.json rename vendordeps/{PathplannerLib.json => PathplannerLib-2025.2.1.json} (85%) rename vendordeps/{Phoenix5.json => Phoenix5-5.35.1.json} (75%) rename vendordeps/{Phoenix6.json => Phoenix6-25.1.0.json} (77%) rename vendordeps/{playingwithfusion2024.json => PlayingWithFusion-2025.01.04.json} (75%) rename vendordeps/{REVLib.json => REVLib-2025.0.0.json} (88%) create mode 100644 vendordeps/Studica-2025.0.0.json diff --git a/WPILib-License.md b/WPILib-License.md index 43b62ec2..645e5425 100644 --- a/WPILib-License.md +++ b/WPILib-License.md @@ -1,4 +1,4 @@ -Copyright (c) 2009-2023 FIRST and other WPILib contributors +Copyright (c) 2009-2024 FIRST and other WPILib contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/build.gradle b/build.gradle index 1481bab4..058d7b3d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "edu.wpi.first.GradleRIO" version "2024.3.1" + id "edu.wpi.first.GradleRIO" version "2025.2.1" id "maven-publish" id "eclipse" } @@ -11,7 +11,6 @@ java { } group 'org.carlmontrobotics' - def ROBOT_MAIN_CLASS = "" // Define my targets (RoboRIO) and artifacts (deployable files) @@ -37,6 +36,8 @@ deploy { frcStaticFileDeploy(getArtifactTypeClass('FileTreeArtifact')) { files = project.fileTree('src/main/deploy') directory = '/home/lvuser/deploy' + deleteOldFiles = false // Change to true to delete files on roboRIO that no + // longer exist in deploy directory of this project } } } @@ -54,6 +55,7 @@ def includeDesktopSupport = true // Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. // Also defines JUnit 5. dependencies { + annotationProcessor wpi.java.deps.wpilibAnnotations() implementation wpi.java.deps.wpilib() implementation wpi.java.vendor.java() @@ -71,26 +73,29 @@ dependencies { nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) simulationRelease wpi.sim.enableRelease() - testImplementation 'junit:junit:4.13.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.mockito:mockito-core:5.11.0' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.6' } +test { + useJUnitPlatform() + systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' +} + // Simulation configuration (e.g. environment variables). wpi.sim.addGui().defaultEnabled = true wpi.sim.addDriverstation() -// Setting up my Jar File. +// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar') +// in order to make them all available at runtime. Also adding the manifest so WPILib +// knows where to look for our Robot Class. jar { - // Note: Do NOT add all the libraries to the jar. Doing so will cause robot programs to have - // 2 copies of each of the libraries classes, one from lib199 and one from the robot program. + from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } from sourceSets.main.allSource - - // Add the manifest so WPILib knows where to look for our Robot Class. manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) - - // There shouldn't be any duplicate classes. duplicatesStrategy = DuplicatesStrategy.FAIL } @@ -111,11 +116,6 @@ deployArtifact.jarTask = jar wpi.java.configureExecutableTasks(jar) wpi.java.configureTestTasks(test) -// Configure string concat to always inline compile -tasks.withType(JavaCompile) { - options.compilerArgs.add '-XDstringConcat=inline' -} - // Allow the generated javadocs to link to the documentation of WPILib and vendor libraries javadoc { options.with { @@ -129,3 +129,7 @@ javadoc { } } +// Configure string concat to always inline compile +tasks.withType(JavaCompile) { + options.compilerArgs.add '-XDstringConcat=inline' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 34592 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 delta 34555 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5e82d67b..34bd9ce9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=permwrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..9d21a218 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index d94f73c6..c493958a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { repositories { mavenLocal() gradlePluginPortal() - String frcYear = '2024' + String frcYear = '2025' File frcHome if (OperatingSystem.current().isWindows()) { String publicFolder = System.getenv('PUBLIC') @@ -20,8 +20,8 @@ pluginManagement { } def frcHomeMaven = new File(frcHome, 'maven') maven { - name 'frcHome' - url frcHomeMaven + name = 'frcHome' + url = frcHomeMaven } } } diff --git a/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java b/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java index 34a090ae..4696302d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java +++ b/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java @@ -1,14 +1,14 @@ package org.carlmontrobotics.lib199; -import com.revrobotics.CANSparkMax; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkPIDController; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkMax; @Deprecated -public class CachedSparkMax extends CANSparkMax { +public class CachedSparkMax extends SparkMax { private RelativeEncoder encoder; - private SparkPIDController pidController; + private SparkClosedLoopController pidController; public CachedSparkMax(int deviceId, MotorType type) { super(deviceId, type); @@ -22,8 +22,8 @@ public RelativeEncoder getEncoder() { } @Override - public SparkPIDController getPIDController() { - return pidController == null ? (pidController = super.getPIDController()) : pidController; + public SparkClosedLoopController getClosedLoopController() { + return pidController == null ? (pidController = super.getClosedLoopController()) : pidController; } } diff --git a/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java b/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java index d3fd177a..016ef4b3 100644 --- a/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java +++ b/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java @@ -1,14 +1,15 @@ package org.carlmontrobotics.lib199; -import com.revrobotics.CANSparkMax; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkAbsoluteEncoder; -import com.revrobotics.SparkAnalogSensor; -import com.revrobotics.SparkLimitSwitch; -import com.revrobotics.SparkPIDController; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.SparkPIDController.AccelStrategy; +import com.revrobotics.spark.SparkAbsoluteEncoder; +import com.revrobotics.spark.SparkAnalogSensor; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkLimitSwitch; +import com.revrobotics.spark.SparkLowLevel; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.config.MAXMotionConfig; +import com.revrobotics.spark.SparkMax; import org.mockito.invocation.InvocationOnMock; @@ -18,12 +19,12 @@ public class DummySparkMaxAnswer extends REVLibErrorAnswer { public static final DummySparkMaxAnswer ANSWER = new DummySparkMaxAnswer(); - public static final CANSparkMax DUMMY_SPARK_MAX = Mocks.mock(CANSparkMax.class, ANSWER); + public static final SparkMax DUMMY_SPARK_MAX = Mocks.mock(SparkMax.class, ANSWER); public static final RelativeEncoder DUMMY_ENCODER = Mocks.mock(RelativeEncoder.class, REVLibErrorAnswer.ANSWER); public static final SparkAnalogSensor DUMMY_ANALOG_SENSOR = Mocks.mock(SparkAnalogSensor.class, REVLibErrorAnswer.ANSWER); public static final SparkLimitSwitch DUMMY_LIMIT_SWITCH = Mocks.mock(SparkLimitSwitch.class, REVLibErrorAnswer.ANSWER); - public static final SparkPIDController DUMMY_PID_CONTROLLER = Mocks.mock(SparkPIDController.class, ANSWER); + public static final SparkClosedLoopController DUMMY_PID_CONTROLLER = Mocks.mock(SparkClosedLoopController.class, ANSWER); public static final SparkAbsoluteEncoder DUMMY_ABSOLUTE_ENCODER = Mocks.mock(SparkAbsoluteEncoder.class, ANSWER); @@ -36,14 +37,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return DUMMY_ANALOG_SENSOR; } else if(returnType == SparkLimitSwitch.class) { return DUMMY_LIMIT_SWITCH; - } else if(returnType == SparkPIDController.class) { + } else if(returnType == SparkClosedLoopController.class) { return DUMMY_PID_CONTROLLER; } else if(returnType == MotorType.class) { return MotorType.kBrushless; } else if(returnType == IdleMode.class) { return IdleMode.kBrake; - } else if(returnType == AccelStrategy.class) { - return AccelStrategy.kTrapezoidal; + } else if(returnType == MAXMotionConfig.MAXMotionPositionMode.class) { + return MAXMotionConfig.MAXMotionPositionMode.kMAXMotionTrapezoidal; } else if(returnType == SparkAbsoluteEncoder.class) { return DUMMY_ABSOLUTE_ENCODER; } diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 0cd62ee7..cd0245dd 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -11,13 +11,17 @@ import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; import com.ctre.phoenix.motorcontrol.can.WPI_VictorSPX; import com.ctre.phoenix6.hardware.CANcoder; -import com.revrobotics.CANSparkMax; -import com.revrobotics.CANSparkBase.ExternalFollower; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkBase; -import com.revrobotics.CANSparkFlex; -import com.revrobotics.CANSparkLowLevel; -import com.revrobotics.SparkPIDController; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.config.SparkBaseConfig; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.config.SparkMaxConfig; +import com.revrobotics.servohub.ServoHub.ResetMode; +import com.revrobotics.spark.ClosedLoopSlot; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel; +import com.revrobotics.spark.SparkClosedLoopController; import org.carlmontrobotics.lib199.sim.MockSparkFlex; import org.carlmontrobotics.lib199.sim.MockSparkMax; @@ -81,83 +85,107 @@ public static WPI_TalonSRX createTalon(int id) { //checks for spark max errors @Deprecated - public static CANSparkMax createSparkMax(int id, MotorErrors.TemperatureLimit temperatureLimit) { + public static SparkMax createSparkMax(int id, MotorErrors.TemperatureLimit temperatureLimit) { return createSparkMax(id, temperatureLimit.limit); } @Deprecated - public static CANSparkMax createSparkMax(int id, int temperatureLimit) { - CANSparkMax spark; + public static SparkMax createSparkMax(int id, int temperatureLimit) { + SparkMax spark; if (RobotBase.isReal()) { - spark = new CANSparkMax(id, CANSparkLowLevel.MotorType.kBrushless); + spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkMax.createMockSparkMax(id, CANSparkLowLevel.MotorType.kBrushless); + spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); } - spark.setPeriodicFramePeriod(CANSparkLowLevel.PeriodicFrame.kStatus0, 1); + SparkMaxConfig config = new SparkMaxConfig(); + // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); + //FIXME: What is kStatus0 + // config.signals. MotorErrors.reportSparkMaxTemp(spark, temperatureLimit); - MotorErrors.reportError(spark.restoreFactoryDefaults()); - MotorErrors.reportError(spark.follow(ExternalFollower.kFollowerDisabled, 0)); - MotorErrors.reportError(spark.setIdleMode(IdleMode.kBrake)); - MotorErrors.reportError(spark.enableVoltageCompensation(12)); - MotorErrors.reportError(spark.setSmartCurrentLimit(50)); - + // MotorErrors.reportError(config.follow(ExternalFollower.kFollowerDisabled, 0)); + // config.follow(null, false); dont follow nothing because thats the norm + // MotorErrors.reportError(config.setIdleMode(IdleMode.kBrake)); + config.idleMode(IdleMode.kBrake); + // MotorErrors.reportError(config.enableVoltageCompensation(12)); + config.voltageCompensation(12); + // MotorErrors.reportError(config.smartCurrentLimit(50)); + config.smartCurrentLimit(50); + MotorErrors.checkSparkMaxErrors(spark); - - SparkPIDController controller = spark.getPIDController(); - MotorErrors.reportError(controller.setOutputRange(-1, 1)); - MotorErrors.reportError(controller.setP(0)); - MotorErrors.reportError(controller.setI(0)); - MotorErrors.reportError(controller.setD(0)); - MotorErrors.reportError(controller.setFF(0)); + SparkClosedLoopController controller = spark.getClosedLoopController(); + // MotorErrors.reportError(controller.setOutputRange(-1, 1)); + config.closedLoop.minOutput(-1); + config.closedLoop.maxOutput(1); + // MotorErrors.reportError(controller.setP(0)); + // MotorErrors.reportError(controller.setI(0)); + // MotorErrors.reportError(controller.setD(0)); + config.closedLoop.p(0, ClosedLoopSlot.kSlot0); + config.closedLoop.i(0, ClosedLoopSlot.kSlot0); + config.closedLoop.d(0, ClosedLoopSlot.kSlot0); + // MotorErrors.reportError(controller.setFF(0)); + config.closedLoop.velocityFF(0); return spark; } - public static CANSparkMax createSparkMax(int id, MotorConfig config) { - CANSparkMax spark; + public static SparkMax createSparkMax(int id, SparkBaseConfig config) { + SparkMax spark; if (RobotBase.isReal()) { - spark = new CANSparkMax(id, CANSparkLowLevel.MotorType.kBrushless); + spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkMax.createMockSparkMax(id, CANSparkLowLevel.MotorType.kBrushless); + spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); } - - configureSpark(spark, config); + + spark.configure( + config, + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters + ); return spark; } - public static CANSparkFlex createSparkFlex(int id, MotorConfig config) { - CANSparkFlex spark; + public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { + SparkFlex spark; if (RobotBase.isReal()) { - spark = new CANSparkFlex(id, CANSparkLowLevel.MotorType.kBrushless); + spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkFlex.createMockSparkFlex(id, CANSparkLowLevel.MotorType.kBrushless); + spark = MockSparkFlex.createMockSparkFlex(id, SparkLowLevel.MotorType.kBrushless); } - configureSpark(spark, config); + spark.configure( + config, + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters + ); return spark; } - private static void configureSpark(CANSparkBase spark, MotorConfig config) { - MotorErrors.reportSparkTemp(spark, config.temperatureLimitCelsius); - - MotorErrors.reportError(spark.restoreFactoryDefaults()); - //MotorErrors.reportError(spark.follow(ExternalFollower.kFollowerDisabled, 0)); - MotorErrors.reportError(spark.setIdleMode(IdleMode.kBrake)); - MotorErrors.reportError(spark.enableVoltageCompensation(12)); - MotorErrors.reportError(spark.setSmartCurrentLimit(config.currentLimitAmps)); - - MotorErrors.checkSparkErrors(spark); - - SparkPIDController controller = spark.getPIDController(); - MotorErrors.reportError(controller.setOutputRange(-1, 1)); - MotorErrors.reportError(controller.setP(0)); - MotorErrors.reportError(controller.setI(0)); - MotorErrors.reportError(controller.setD(0)); - MotorErrors.reportError(controller.setFF(0)); + private static void configureSpark(SparkBase spark, SparkMaxConfig config) { + MotorErrors.reportSparkTemp(spark, (int) spark.getMotorTemperature()); + + SparkMaxConfig newConfig = new SparkMaxConfig(); + + config.idleMode(IdleMode.kBrake); + + config.voltageCompensation(12); + config.smartCurrentLimit(50); + + config.closedLoop.minOutput(-1); + config.closedLoop.maxOutput(1); + config.closedLoop.p(0, ClosedLoopSlot.kSlot0); + config.closedLoop.i(0, ClosedLoopSlot.kSlot0); + config.closedLoop.d(0, ClosedLoopSlot.kSlot0); + config.closedLoop.velocityFF(0); + + spark.configure( + config, + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters + ); } /** diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 5d5a0655..e331d30b 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -6,22 +6,24 @@ import java.util.concurrent.ConcurrentSkipListMap; import com.ctre.phoenix.ErrorCode; -import com.revrobotics.CANSparkBase; -import com.revrobotics.CANSparkFlex; -import com.revrobotics.CANSparkMax; -import com.revrobotics.CANSparkBase.FaultID; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkBase.Faults; +import com.revrobotics.spark.config.SparkBaseConfig; +import com.revrobotics.spark.config.SparkMaxConfig; import com.revrobotics.REVLibError; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; public final class MotorErrors { - private static final Map temperatureSparks = new ConcurrentSkipListMap<>(); + private static final Map temperatureSparks = new ConcurrentSkipListMap<>(); private static final Map sparkTemperatureLimits = new ConcurrentHashMap<>(); private static final Map overheatedSparks = new ConcurrentHashMap<>(); - private static final Map flags = new ConcurrentSkipListMap<>( + private static final Map flags = new ConcurrentSkipListMap<>( (spark1, spark2) -> (spark1.getDeviceId() - spark2.getDeviceId())); - private static final Map stickyFlags = new ConcurrentSkipListMap<>( + private static final Map stickyFlags = new ConcurrentSkipListMap<>( (spark1, spark2) -> (spark1.getDeviceId() - spark2.getDeviceId())); public static final int kOverheatTripCount = 5; @@ -65,7 +67,7 @@ private static > void reportError(String vendor, T error, T ok System.err.println(Arrays.toString(stack)); } - public static void checkSparkErrors(CANSparkBase spark) { + public static void checkSparkErrors(SparkBase spark) { //Purposely obviously impersonal to differentiate from actual computer generated errors short faults = spark.getFaults(); short stickyFaults = spark.getStickyFaults(); @@ -84,11 +86,11 @@ public static void checkSparkErrors(CANSparkBase spark) { } @Deprecated - public static void checkSparkMaxErrors(CANSparkMax spark) { - checkSparkErrors((CANSparkBase)spark); + public static void checkSparkMaxErrors(SparkMax spark) { + checkSparkErrors((SparkBase)spark); } - private static String formatFaults(CANSparkBase spark) { + private static String formatFaults(SparkBase spark) { String out = ""; for(FaultID fault: FaultID.values()) { if(spark.getFault(fault)) { @@ -98,7 +100,7 @@ private static String formatFaults(CANSparkBase spark) { return out; } - private static String formatStickyFaults(CANSparkBase spark) { + private static String formatStickyFaults(SparkBase spark) { String out = ""; for(FaultID fault: FaultID.values()) { if(spark.getStickyFault(fault)) { @@ -125,27 +127,27 @@ static void reportNextNSparkErrors(int n) { lastSparkErrorIndexReported = (lastSparkErrorIndexReported + n) % flags.size(); } - public static CANSparkMax createDummySparkMax() { + public static SparkMax createDummySparkMax() { return DummySparkMaxAnswer.DUMMY_SPARK_MAX; } @Deprecated - public static void reportSparkMaxTemp(CANSparkMax spark, TemperatureLimit temperatureLimit) { + public static void reportSparkMaxTemp(SparkMax spark, TemperatureLimit temperatureLimit) { reportSparkMaxTemp(spark, temperatureLimit.limit); } - public static boolean isSparkMaxOverheated(CANSparkMax spark){ + public static boolean isSparkMaxOverheated(SparkMax spark){ int id = spark.getDeviceId(); int motorMaxTemp = sparkTemperatureLimits.get(id); return ( spark.getMotorTemperature() >= motorMaxTemp ); } @Deprecated - public static void reportSparkMaxTemp(CANSparkMax spark, int temperatureLimit) { - reportSparkTemp((CANSparkBase) spark, temperatureLimit); + public static void reportSparkMaxTemp(SparkMax spark, int temperatureLimit) { + reportSparkTemp((SparkBase) spark, temperatureLimit); } - public static void reportSparkTemp(CANSparkBase spark, int temperatureLimit) { + public static void reportSparkTemp(SparkBase spark, int temperatureLimit) { int id = spark.getDeviceId(); temperatureSparks.put(id, spark); sparkTemperatureLimits.put(id, temperatureLimit); @@ -169,14 +171,14 @@ static void reportNextNSparkTemps(int n) { lastSparkTempIndexReported = (lastSparkTempIndexReported + n) % temperatureSparks.size(); } - private static void reportSparkTemp(int port, CANSparkBase spark) { + private static void reportSparkTemp(int port, SparkBase spark) { double temp = spark.getMotorTemperature(); double limit = sparkTemperatureLimits.get(port); int numTrips = overheatedSparks.get(port); String sparkType = "of unknown type"; - if (spark instanceof CANSparkMax) { + if (spark instanceof SparkMax) { sparkType = "Max"; - } else if (spark instanceof CANSparkFlex) { + } else if (spark instanceof SparkFlex) { sparkType = "Flex"; } SmartDashboard.putNumber(String.format("Port %d Spark %s Temp", port, sparkType), temp); @@ -202,7 +204,10 @@ private static void reportSparkTemp(int port, CANSparkBase spark) { System.err.println("Port " + port + " spark is operating at " + temp + " degrees Celsius! It will be disabled until the robot code is restarted."); } - spark.setSmartCurrentLimit(1); + spark.configure( + new SparkMaxConfig().smartCurrentLimit(1), + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters); } } diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java index f95efbed..94444dd2 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java @@ -1,9 +1,14 @@ package org.carlmontrobotics.lib199; -import com.revrobotics.CANSparkMax; -import com.revrobotics.CANSparkBase.ControlType; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.ClosedLoopConfig; +import com.revrobotics.spark.config.SparkMaxConfig; +// import com.revrobotics.SparkBase.ControlType; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkPIDController; +import com.revrobotics.spark.ClosedLoopSlot; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.SparkBase.ControlType; +import com.revrobotics.spark.SparkClosedLoopController; import edu.wpi.first.util.sendable.Sendable; import edu.wpi.first.util.sendable.SendableBuilder; @@ -12,27 +17,33 @@ public class SparkVelocityPIDController implements Sendable { @SuppressWarnings("unused") - private final CANSparkMax spark; - private final SparkPIDController pidController; + private final SparkMax spark; + private final SparkClosedLoopController pidController; private final RelativeEncoder encoder; private final String name; private double targetSpeed, tolerance; private double currentP, currentI, currentD, kS, kV; - public SparkVelocityPIDController(String name, CANSparkMax spark, double defaultP, double defaultI, double defaultD, double kS, double kV, double targetSpeed, double tolerance) { + public SparkVelocityPIDController(String name, SparkMax spark, double defaultP, double defaultI, double defaultD, double kS, double kV, double targetSpeed, double tolerance) { this.spark = spark; - this.pidController = spark.getPIDController(); + this.pidController = spark.getClosedLoopController(); this.encoder = spark.getEncoder(); this.name = name; this.targetSpeed = targetSpeed; this.tolerance = tolerance; - pidController.setP(this.currentP = defaultP); - pidController.setI(this.currentI = defaultI); - pidController.setD(this.currentD = defaultD); + + spark.configure(new SparkMaxConfig().apply( + new ClosedLoopConfig().pid( + this.currentP = defaultP, + this.currentI = defaultI, + this.currentD = defaultD + )), + SparkBase.ResetMode.kNoResetSafeParameters,//we only want to change pid params + SparkBase.PersistMode.kNoPersistParameters); this.kS = kS; this.kV = kV; - pidController.setReference(targetSpeed, ControlType.kVelocity, 0, calculateFF(targetSpeed)); + pidController.setReference(targetSpeed, ControlType.kVelocity, ClosedLoopSlot.kSlot0, calculateFF(targetSpeed)); SendableRegistry.addLW(this, "SparkVelocityPIDController", spark.getDeviceId()); } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java index 3ad8f1f6..91b9cac5 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java @@ -6,18 +6,17 @@ import org.carlmontrobotics.lib199.Mocks; import org.carlmontrobotics.lib199.REVLibErrorAnswer; -import com.revrobotics.CANSparkBase; -import com.revrobotics.CANSparkBase.ExternalFollower; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkLowLevel.MotorType; +import com.revrobotics.spark.config.ClosedLoopConfig; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkLowLevel.MotorType; import com.revrobotics.REVLibError; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkAbsoluteEncoder; -import com.revrobotics.SparkMaxAlternateEncoder; -import com.revrobotics.SparkAnalogSensor; -import com.revrobotics.SparkPIDController; -import com.revrobotics.SparkRelativeEncoder; -import com.revrobotics.SparkRelativeEncoder.Type; +import com.revrobotics.spark.SparkAbsoluteEncoder; +import com.revrobotics.spark.SparkMaxAlternateEncoder; +import com.revrobotics.spark.SparkAnalogSensor; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.SparkRelativeEncoder; import edu.wpi.first.hal.SimDevice; import edu.wpi.first.wpilibj.motorcontrol.MotorController; @@ -31,7 +30,7 @@ public class MockSparkBase extends MockedMotorBase { public final MotorType type; private final MockedEncoder encoder; - private final SparkPIDController pidController; + private final SparkClosedLoopController pidController; private final MockedSparkMaxPIDController pidControllerImpl; private SparkAbsoluteEncoder absoluteEncoder = null; private MockedEncoder absoluteEncoderImpl = null; @@ -47,7 +46,7 @@ public class MockSparkBase extends MockedMotorBase { * @param port the port to associate this {@code MockSparkMax} with. Will be used to create the {@link SimDevice} and facilitate motor following. * @param type the type of the simulated motor. If this is set to {@link MotorType#kBrushless}, the builtin encoder simulation will be configured * to follow the inversion state of the motor and its {@code setInverted} method will be disabled. - * @param name the name of the type of controller ("CANSparkMax" or "CANSparkFlex") + * @param name the name of the type of controller ("SparkMax" or "SparkFlex") * @param countsPerRev the number of counts per revolution of this controller's built-in encoder. */ public MockSparkBase(int port, MotorType type, String name, int countsPerRev) { @@ -69,8 +68,8 @@ public REVLibError setInverted(boolean inverted) { } pidControllerImpl = new MockedSparkMaxPIDController(this); - pidController = Mocks.createMock(SparkPIDController.class, pidControllerImpl, new REVLibErrorAnswer()); - pidController.setFeedbackDevice(encoder); + pidController = Mocks.createMock(SparkClosedLoopController.class, pidControllerImpl, new REVLibErrorAnswer()); + pidController.feedbackSensor(encoder); controllers.put(port, this); @@ -96,11 +95,11 @@ public void set(double speed) { pidControllerImpl.setDutyCycle(speed); } - public REVLibError follow(CANSparkBase leader) { + public REVLibError follow(SparkBase leader) { return follow(leader, false); } - public REVLibError follow(CANSparkBase leader, boolean invert) { + public REVLibError follow(SparkBase leader, boolean invert) { pidControllerImpl.follow(leader, invert); // No need to lookup the spark max if we already have it return REVLibError.kOk; } @@ -168,7 +167,7 @@ public REVLibError disableVoltageCompensation() { return REVLibError.kOk; } - public SparkPIDController getPIDController() { + public SparkClosedLoopController getPIDController() { return pidController; } @@ -293,7 +292,7 @@ public double getOutputCurrent() { @Override public void disable() { - // CANSparkBase sets the motor speed to zero rather than actually disabling the motor + // SparkBase sets the motor speed to zero rather than actually disabling the motor set(0); } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java index 47fb3a11..833ba787 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java @@ -3,8 +3,8 @@ import org.carlmontrobotics.lib199.DummySparkMaxAnswer; import org.carlmontrobotics.lib199.Mocks; -import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.CANSparkFlex; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkFlex; public class MockSparkFlex extends MockSparkBase { @@ -12,7 +12,7 @@ public MockSparkFlex(int port, MotorType type) { super(port, type, "CANSparkFlex", 7168); } - public static CANSparkFlex createMockSparkFlex(int portPWM, MotorType type) { - return Mocks.createMock(CANSparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); + public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { + return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); } } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java index 653862e9..1bf0b7cc 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java @@ -3,8 +3,8 @@ import org.carlmontrobotics.lib199.DummySparkMaxAnswer; import org.carlmontrobotics.lib199.Mocks; -import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.CANSparkMax; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; public class MockSparkMax extends MockSparkBase { @@ -12,7 +12,7 @@ public MockSparkMax(int port, MotorType type) { super(port, type, "CANSparkMax", 42); } - public static CANSparkMax createMockSparkMax(int portPWM, MotorType type) { - return Mocks.createMock(CANSparkMax.class, new MockSparkMax(portPWM, type), new DummySparkMaxAnswer()); + public static SparkMax createMockSparkMax(int portPWM, MotorType type) { + return Mocks.createMock(SparkMax.class, new MockSparkMax(portPWM, type), new DummySparkMaxAnswer()); } } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java index 13629f0f..c987ad79 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java @@ -3,14 +3,15 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.revrobotics.CANSparkMax; -import com.revrobotics.MotorFeedbackSensor; import com.revrobotics.REVLibError; -import com.revrobotics.SparkAbsoluteEncoder; -import com.revrobotics.SparkMaxAlternateEncoder; -import com.revrobotics.SparkAnalogSensor; -import com.revrobotics.SparkPIDController; -import com.revrobotics.SparkRelativeEncoder; +import com.revrobotics.spark.SparkAbsoluteEncoder; +import com.revrobotics.spark.SparkAnalogSensor; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkMaxAlternateEncoder; +import com.revrobotics.spark.SparkRelativeEncoder; +import com.revrobotics.spark.config.ClosedLoopConfig; +import com.revrobotics.spark.config.MAXMotionConfig; import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.controller.PIDController; @@ -24,7 +25,7 @@ public class MockedSparkMaxPIDController { public final Map slots = new ConcurrentHashMap<>(); public final MockedMotorBase motor; public Slot activeSlot; - public CANSparkMax.ControlType controlType = CANSparkMax.ControlType.kDutyCycle; + public SparkMax.ControlType controlType = SparkMax.ControlType.kDutyCycle; public MotorController leader = null; public boolean invertLeader = false; public double setpoint = 0.0; @@ -74,7 +75,7 @@ public double calculate(double currentDraw) { public void setDutyCycle(double speed) { setpoint = speed; - controlType = CANSparkMax.ControlType.kDutyCycle; + controlType = SparkMax.ControlType.kDutyCycle; stopFollowing(); motor.setClosedLoopControl(false); } @@ -214,8 +215,8 @@ public double getP(int slotID) { return slot.pidController.getP(); } - public SparkPIDController.AccelStrategy getSmartMotionAccelStrategy(int slotID) { - return SparkPIDController.AccelStrategy.kTrapezoidal; + public MAXMotionConfig.MAXMotionPositionMode getSmartMotionAccelStrategy(int slotID) { + return MAXMotionConfig.MAXMotionPositionMode.kMAXMotionTrapezoidal; } public double getSmartMotionAllowedClosedLoopError(int slotID) { @@ -245,8 +246,8 @@ public REVLibError setD(double gain, int slotID) { return REVLibError.kOk; } - public REVLibError setFeedbackDevice(MotorFeedbackSensor sensor) { - if (sensor instanceof SparkRelativeEncoder + public REVLibError setFeedbackDevice(Object sensor) { + if ( sensor instanceof SparkRelativeEncoder || sensor instanceof SparkMaxAlternateEncoder || sensor instanceof SparkAnalogSensor || sensor instanceof SparkAbsoluteEncoder @@ -386,20 +387,20 @@ public REVLibError setP(double gain, int slotID) { return REVLibError.kOk; } - public REVLibError setReference(double value, CANSparkMax.ControlType ctrl) { + public REVLibError setReference(double value, SparkMax.ControlType ctrl) { return setReference(value, ctrl, 0); } - public REVLibError setReference(double value, CANSparkMax.ControlType ctrl, int pidSlot) { + public REVLibError setReference(double value, SparkMax.ControlType ctrl, int pidSlot) { return setReference(value, ctrl, pidSlot, 0); } - public REVLibError setReference(double value, CANSparkMax.ControlType ctrl, int pidSlot, double arbFeedforward) { - return setReference(value, ctrl, pidSlot, arbFeedforward, SparkPIDController.ArbFFUnits.kVoltage); + public REVLibError setReference(double value, SparkMax.ControlType ctrl, int pidSlot, double arbFeedforward) { + return setReference(value, ctrl, pidSlot, arbFeedforward, SparkClosedLoopController.ArbFFUnits.kVoltage); } - public REVLibError setReference(double value, CANSparkMax.ControlType ctrl, int pidSlot, double arbFeedforward, SparkPIDController.ArbFFUnits arbFFUnits) { - if(ctrl == CANSparkMax.ControlType.kSmartVelocity) { + public REVLibError setReference(double value, SparkMax.ControlType ctrl, int pidSlot, double arbFeedforward, SparkClosedLoopController.ArbFFUnits arbFFUnits) { + if(ctrl == SparkMax.ControlType.kSmartVelocity) { System.err.println("(MockedSparkMaxPIDController): setReference() with ControlType.kSmartVelocity is not currently implemented"); return REVLibError.kNotImplemented; } @@ -440,8 +441,8 @@ public REVLibError setReference(double value, CANSparkMax.ControlType ctrl, int return REVLibError.kOk; } - public REVLibError setSmartMotionAccelStrategy(SparkPIDController.AccelStrategy accelStrategy, int slotID) { - if(accelStrategy != SparkPIDController.AccelStrategy.kTrapezoidal) { + public REVLibError setSmartMotionAccelStrategy(MAXMotionConfig.MAXMotionPositionMode accelStrategy, int slotID) { + if(accelStrategy != MAXMotionConfig.MAXMotionPositionMode.kMAXMotionTrapezoidal) { System.err.println("(MockedSparkMaxPIDController) Ignoring command to set accel strategy on slot " + slotID + " to " + accelStrategy + ". Only AccelStrategy.kTrapezoidal is supported."); return REVLibError.kParamNotImplementedDeprecated; } diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index d39dc473..05909dd3 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -11,8 +11,9 @@ import com.ctre.phoenix6.signals.AbsoluteSensorRangeValue; import com.ctre.phoenix6.configs.CANcoderConfiguration; import com.ctre.phoenix6.hardware.CANcoder; -import com.revrobotics.CANSparkMax; -import com.revrobotics.CANSparkBase.IdleMode; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; + import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.controller.PIDController; @@ -23,7 +24,7 @@ import edu.wpi.first.math.kinematics.SwerveModuleState; import edu.wpi.first.math.trajectory.TrapezoidProfile; import edu.wpi.first.math.util.Units; -import edu.wpi.first.units.Mass; +import edu.wpi.first.units.measure.Mass; import edu.wpi.first.units.Measure; import edu.wpi.first.util.sendable.Sendable; import edu.wpi.first.util.sendable.SendableBuilder; @@ -42,7 +43,7 @@ public enum ModuleType {FL, FR, BL, BR}; private SwerveConfig config; private ModuleType type; - private CANSparkMax drive, turn; + private SparkMax drive, turn; private CANcoder turnEncoder; private PIDController drivePIDController; private ProfiledPIDController turnPIDController; @@ -57,7 +58,7 @@ public enum ModuleType {FL, FR, BL, BR}; private double turnSpeedCorrectionVolts, turnFFVolts, turnVolts; private double maxTurnVelocityWithoutTippingRps; - public SwerveModule(SwerveConfig config, ModuleType type, CANSparkMax drive, CANSparkMax turn, CANcoder turnEncoder, + public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkMax turn, CANcoder turnEncoder, int arrIndex, Supplier pitchDegSupplier, Supplier rollDegSupplier) { //SmartDashboard.putNumber("Target Angle (deg)", 0.0); String moduleString = type.toString(); @@ -71,8 +72,10 @@ public SwerveModule(SwerveConfig config, ModuleType type, CANSparkMax drive, CAN double positionConstant = config.wheelDiameterMeters * Math.PI / config.driveGearing; drive.setInverted(config.driveInversion[arrIndex]); - drive.getEncoder().setPositionConversionFactor(positionConstant); - drive.getEncoder().setVelocityConversionFactor(positionConstant / 60); + // drive.getEncoder().setPositionConversionFactor(positionConstant); //no such thing + // drive.getEncoder().setVelocityConversionFactor(positionConstant / 60); //no such thing + final double drivePositionFactor = positionConstant; + final double turnPositionFactor = positionConstant / 60; turn.setInverted(config.turnInversion[arrIndex]); maxControllableAccerlationRps2 = 0; final double normalForceNewtons = 83.2 /* lbf */ * 4.4482 /* N/lbf */ / 4 /* numModules */; @@ -128,7 +131,7 @@ public SwerveModule(SwerveConfig config, ModuleType type, CANSparkMax drive, CAN turnPIDController.setTolerance(turnToleranceRot); CANcoderConfiguration configs = new CANcoderConfiguration(); - configs.MagnetSensor.AbsoluteSensorRange = AbsoluteSensorRangeValue.Signed_PlusMinusHalf; + configs.MagnetSensor.AbsoluteSensorDiscontinuityPoint = .5; this.turnEncoder = turnEncoder; this.turnEncoder.getConfigurator().apply(configs); @@ -303,7 +306,7 @@ private void setAngle(double angle) { * @return module angle in degrees */ public double getModuleAngle() { - return MathUtil.inputModulus(Units.rotationsToDegrees(turnEncoder.getAbsolutePosition().getValue()) - turnZeroDeg, -180, 180); + return MathUtil.inputModulus(Units.rotationsToDegrees(turnEncoder.getAbsolutePosition().getValueAsDouble()) - turnZeroDeg, -180, 180); } /** @@ -374,10 +377,10 @@ public void updateSmartDashboard() { if (drivePIDController.getPositionTolerance() != driveTolerance) { drivePIDController.setTolerance(driveTolerance); } - double drivekS = SmartDashboard.getNumber(moduleString + " Drive kS", forwardSimpleMotorFF.ks); - double drivekV = SmartDashboard.getNumber(moduleString + " Drive kV", forwardSimpleMotorFF.kv); - double drivekA = SmartDashboard.getNumber(moduleString + " Drive kA", forwardSimpleMotorFF.ka); - if (forwardSimpleMotorFF.ks != drivekS || forwardSimpleMotorFF.kv != drivekV || forwardSimpleMotorFF.ka != drivekA) { + double drivekS = SmartDashboard.getNumber(moduleString + " Drive kS", forwardSimpleMotorFF.getKs()); + double drivekV = SmartDashboard.getNumber(moduleString + " Drive kV", forwardSimpleMotorFF.getKv()); + double drivekA = SmartDashboard.getNumber(moduleString + " Drive kA", forwardSimpleMotorFF.getKa()); + if (forwardSimpleMotorFF.getKs() != drivekS || forwardSimpleMotorFF.getKv() != drivekV || forwardSimpleMotorFF.getKa() != drivekA) { forwardSimpleMotorFF = new SimpleMotorFeedforward(drivekS, drivekV, drivekA); backwardSimpleMotorFF = new SimpleMotorFeedforward(drivekS, drivekV, drivekA); } @@ -434,8 +437,8 @@ public void initSendable(SendableBuilder builder) { builder.setActuator(true); builder.setSafeState(() -> setSpeed(0)); builder.setSmartDashboardType("SwerveModule"); - builder.addDoubleProperty("Incremental Position", () -> turnEncoder.getPosition().getValue(), null); - builder.addDoubleProperty("Absolute Angle (deg)", () -> Units.rotationsToDegrees(turnEncoder.getAbsolutePosition().getValue()), null); + builder.addDoubleProperty("Incremental Position", () -> turnEncoder.getPosition().getValueAsDouble(), null); + builder.addDoubleProperty("Absolute Angle (deg)", () -> Units.rotationsToDegrees(turnEncoder.getAbsolutePosition().getValueAsDouble()), null); builder.addDoubleProperty("Turn Measured Pos (deg)", this::getModuleAngle, null); builder.addDoubleProperty("Encoder Position", drive.getEncoder()::getPosition, null); // Display the speed that the robot thinks it is travelling at. @@ -463,7 +466,7 @@ public void initSendable(SendableBuilder builder) { * @param turnMoiKgM2 the moment of inertia of the part of the module turned by the turn motor (in kg m^2) * @return a SwerveModuleSim that simulates the physics of this swerve module. */ - public SwerveModuleSim createSim(Measure massOnWheel, double turnGearing, double turnMoiKgM2) { + public SwerveModuleSim createSim(Mass massOnWheel, double turnGearing, double turnMoiKgM2) { double driveMoiKgM2 = massOnWheel.in(Kilogram) * Math.pow(config.wheelDiameterMeters/2, 2); return new SwerveModuleSim(drive.getDeviceId(), config.driveGearing, driveMoiKgM2, turn.getDeviceId(), turnEncoder.getDeviceID(), turnGearing, turnMoiKgM2); diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index 14c52890..3cdcccae 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -5,10 +5,10 @@ import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.system.plant.DCMotor; -import edu.wpi.first.units.Distance; -import edu.wpi.first.units.Mass; +import edu.wpi.first.units.measure.Distance; +import edu.wpi.first.units.measure.Mass; import edu.wpi.first.units.Measure; -import edu.wpi.first.units.Mult; +import edu.wpi.first.units.measure.Mult; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.simulation.DCMotorSim; diff --git a/vendordeps/NavX.json b/vendordeps/NavX.json deleted file mode 100644 index e978a5f7..00000000 --- a/vendordeps/NavX.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "fileName": "NavX.json", - "name": "NavX", - "version": "2024.1.0", - "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", - "frcYear": "2024", - "mavenUrls": [ - "https://dev.studica.com/maven/release/2024/" - ], - "jsonUrl": "https://dev.studica.com/releases/2024/NavX.json", - "javaDependencies": [ - { - "groupId": "com.kauailabs.navx.frc", - "artifactId": "navx-frc-java", - "version": "2024.1.0" - } - ], - "jniDependencies": [], - "cppDependencies": [ - { - "groupId": "com.kauailabs.navx.frc", - "artifactId": "navx-frc-cpp", - "version": "2024.1.0", - "headerClassifier": "headers", - "sourcesClassifier": "sources", - "sharedLibrary": false, - "libName": "navx_frc", - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "linuxathena", - "linuxraspbian", - "linuxarm32", - "linuxarm64", - "linuxx86-64", - "osxuniversal", - "windowsx86-64" - ] - } - ] -} \ No newline at end of file diff --git a/vendordeps/PathplannerLib.json b/vendordeps/PathplannerLib-2025.2.1.json similarity index 85% rename from vendordeps/PathplannerLib.json rename to vendordeps/PathplannerLib-2025.2.1.json index 6dc648db..71e25f3d 100644 --- a/vendordeps/PathplannerLib.json +++ b/vendordeps/PathplannerLib-2025.2.1.json @@ -1,9 +1,9 @@ { - "fileName": "PathplannerLib.json", + "fileName": "PathplannerLib-2025.2.1.json", "name": "PathplannerLib", - "version": "2024.2.8", + "version": "2025.2.1", "uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786", - "frcYear": "2024", + "frcYear": "2025", "mavenUrls": [ "https://3015rangerrobotics.github.io/pathplannerlib/repo" ], @@ -12,7 +12,7 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-java", - "version": "2024.2.8" + "version": "2025.2.1" } ], "jniDependencies": [], @@ -20,7 +20,7 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-cpp", - "version": "2024.2.8", + "version": "2025.2.1", "libName": "PathplannerLib", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/Phoenix5.json b/vendordeps/Phoenix5-5.35.1.json similarity index 75% rename from vendordeps/Phoenix5.json rename to vendordeps/Phoenix5-5.35.1.json index ff7359e1..69df8b53 100644 --- a/vendordeps/Phoenix5.json +++ b/vendordeps/Phoenix5-5.35.1.json @@ -1,43 +1,56 @@ { - "fileName": "Phoenix5.json", + "fileName": "Phoenix5-5.35.1.json", "name": "CTRE-Phoenix (v5)", - "version": "5.33.1", - "frcYear": 2024, + "version": "5.35.1", + "frcYear": "2025", "uuid": "ab676553-b602-441f-a38d-f1296eff6537", "mavenUrls": [ "https://maven.ctr-electronics.com/release/" ], - "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2024-latest.json", + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2025-latest.json", "requires": [ { "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "errorMessage": "Phoenix 5 requires low-level libraries from Phoenix 6. Please add the Phoenix 6 vendordep before adding Phoenix 5.", - "offlineFileName": "Phoenix6.json", - "onlineUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json" + "offlineFileName": "Phoenix6-frc2025-latest.json", + "onlineUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-latest.json" + } + ], + "conflictsWith": [ + { + "uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af", + "errorMessage": "Users must use the Phoenix 5 replay vendordep when using the Phoenix 6 replay vendordep.", + "offlineFileName": "Phoenix6-replay-frc2025-latest.json" + }, + { + "uuid": "fbc886a4-2cec-40c0-9835-71086a8cc3df", + "errorMessage": "Users cannot have both the replay and regular Phoenix 5 vendordeps in their robot program.", + "offlineFileName": "Phoenix5-replay-frc2025-latest.json" } ], "javaDependencies": [ { "groupId": "com.ctre.phoenix", "artifactId": "api-java", - "version": "5.33.1" + "version": "5.35.1" }, { "groupId": "com.ctre.phoenix", "artifactId": "wpiapi-java", - "version": "5.33.1" + "version": "5.35.1" } ], "jniDependencies": [ { "groupId": "com.ctre.phoenix", "artifactId": "cci", - "version": "5.33.1", + "version": "5.35.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -45,12 +58,13 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "cci-sim", - "version": "5.33.1", + "version": "5.35.1", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -60,7 +74,7 @@ { "groupId": "com.ctre.phoenix", "artifactId": "wpiapi-cpp", - "version": "5.33.1", + "version": "5.35.1", "libName": "CTRE_Phoenix_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -68,6 +82,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -75,7 +90,7 @@ { "groupId": "com.ctre.phoenix", "artifactId": "api-cpp", - "version": "5.33.1", + "version": "5.35.1", "libName": "CTRE_Phoenix", "headerClassifier": "headers", "sharedLibrary": true, @@ -83,6 +98,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -90,7 +106,7 @@ { "groupId": "com.ctre.phoenix", "artifactId": "cci", - "version": "5.33.1", + "version": "5.35.1", "libName": "CTRE_PhoenixCCI", "headerClassifier": "headers", "sharedLibrary": true, @@ -98,6 +114,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -105,7 +122,7 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "wpiapi-cpp-sim", - "version": "5.33.1", + "version": "5.35.1", "libName": "CTRE_Phoenix_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -113,6 +130,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -120,7 +138,7 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "api-cpp-sim", - "version": "5.33.1", + "version": "5.35.1", "libName": "CTRE_PhoenixSim", "headerClassifier": "headers", "sharedLibrary": true, @@ -128,6 +146,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -135,7 +154,7 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "cci-sim", - "version": "5.33.1", + "version": "5.35.1", "libName": "CTRE_PhoenixCCISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -143,6 +162,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" diff --git a/vendordeps/Phoenix6.json b/vendordeps/Phoenix6-25.1.0.json similarity index 77% rename from vendordeps/Phoenix6.json rename to vendordeps/Phoenix6-25.1.0.json index 2b7d1720..473f6a89 100644 --- a/vendordeps/Phoenix6.json +++ b/vendordeps/Phoenix6-25.1.0.json @@ -1,76 +1,94 @@ { - "fileName": "Phoenix6.json", + "fileName": "Phoenix6-25.1.0.json", "name": "CTRE-Phoenix (v6)", - "version": "24.2.0", - "frcYear": 2024, + "version": "25.1.0", + "frcYear": "2025", "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "mavenUrls": [ "https://maven.ctr-electronics.com/release/" ], - "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json", + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-latest.json", "conflictsWith": [ { - "uuid": "3fcf3402-e646-4fa6-971e-18afe8173b1a", - "errorMessage": "The combined Phoenix-6-And-5 vendordep is no longer supported. Please remove the vendordep and instead add both the latest Phoenix 6 vendordep and Phoenix 5 vendordep.", - "offlineFileName": "Phoenix6And5.json" + "uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af", + "errorMessage": "Users can not have both the replay and regular Phoenix 6 vendordeps in their robot program.", + "offlineFileName": "Phoenix6-replay-frc2025-latest.json" } ], "javaDependencies": [ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-java", - "version": "24.2.0" + "version": "25.1.0" } ], "jniDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "api-cpp", + "version": "25.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "linuxathena" + ], + "simMode": "hwsim" + }, { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "24.2.0", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" }, { "groupId": "com.ctre.phoenix6.sim", - "artifactId": "tools-sim", - "version": "24.2.0", + "artifactId": "api-cpp-sim", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" }, { "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonSRX", - "version": "24.2.0", + "artifactId": "tools-sim", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" }, { "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonFX", - "version": "24.2.0", + "artifactId": "simTalonSRX", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -78,12 +96,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "24.2.0", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -91,12 +110,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "24.2.0", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -104,12 +124,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "24.2.0", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -117,12 +138,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "24.2.0", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -130,12 +152,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "24.2.0", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -143,12 +166,27 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "24.2.0", + "version": "25.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANrange", + "version": "25.1.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -158,7 +196,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-cpp", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_Phoenix6_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -166,6 +204,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -173,7 +212,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_PhoenixTools", "headerClassifier": "headers", "sharedLibrary": true, @@ -181,6 +220,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -188,7 +228,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "wpiapi-cpp-sim", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_Phoenix6_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -196,6 +236,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -203,7 +244,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_PhoenixTools_Sim", "headerClassifier": "headers", "sharedLibrary": true, @@ -211,6 +252,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -218,7 +260,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimTalonSRX", "headerClassifier": "headers", "sharedLibrary": true, @@ -226,21 +268,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonFX", - "version": "24.2.0", - "libName": "CTRE_SimTalonFX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -248,7 +276,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimVictorSPX", "headerClassifier": "headers", "sharedLibrary": true, @@ -256,6 +284,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -263,7 +292,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimPigeonIMU", "headerClassifier": "headers", "sharedLibrary": true, @@ -271,6 +300,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -278,7 +308,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimCANCoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -286,6 +316,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -293,7 +324,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimProTalonFX", "headerClassifier": "headers", "sharedLibrary": true, @@ -301,6 +332,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -308,7 +340,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimProCANcoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -316,6 +348,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -323,7 +356,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "24.2.0", + "version": "25.1.0", "libName": "CTRE_SimProPigeon2", "headerClassifier": "headers", "sharedLibrary": true, @@ -331,6 +364,23 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANrange", + "version": "25.1.0", + "libName": "CTRE_SimProCANrange", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" diff --git a/vendordeps/playingwithfusion2024.json b/vendordeps/PlayingWithFusion-2025.01.04.json similarity index 75% rename from vendordeps/playingwithfusion2024.json rename to vendordeps/PlayingWithFusion-2025.01.04.json index c8c51493..580c1217 100644 --- a/vendordeps/playingwithfusion2024.json +++ b/vendordeps/PlayingWithFusion-2025.01.04.json @@ -1,10 +1,10 @@ { - "fileName": "playingwithfusion2024.json", + "fileName": "PlayingWithFusion-2025.01.04.json", "name": "PlayingWithFusion", - "version": "2024.03.09", + "version": "2025.01.04", "uuid": "14b8ad04-24df-11ea-978f-2e728ce88125", - "frcYear": "2024", - "jsonUrl": "https://www.playingwithfusion.com/frc/playingwithfusion2024.json", + "frcYear": "2025", + "jsonUrl": "https://www.playingwithfusion.com/frc/playingwithfusion2025.json", "mavenUrls": [ "https://www.playingwithfusion.com/frc/maven/" ], @@ -12,23 +12,22 @@ { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-java", - "version": "2024.03.09" + "version": "2025.01.04" } ], "jniDependencies": [ { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-driver", - "version": "2024.03.09", - "isJar": false, + "version": "2025.01.04", "skipInvalidPlatforms": true, + "isJar": false, "validPlatforms": [ - "linuxathena", "windowsx86-64", + "linuxarm64", "linuxx86-64", - "osxuniversal", - "linuxarm32", - "linuxarm64" + "linuxathena", + "osxuniversal" ] } ], @@ -36,35 +35,33 @@ { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-cpp", - "version": "2024.03.09", + "version": "2025.01.04", "headerClassifier": "headers", "sharedLibrary": false, "libName": "PlayingWithFusion", "skipInvalidPlatforms": true, "binaryPlatforms": [ - "linuxathena", "windowsx86-64", + "linuxarm64", "linuxx86-64", - "osxuniversal", - "linuxarm32", - "linuxarm64" + "linuxathena", + "osxuniversal" ] }, { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-driver", - "version": "2024.03.09", + "version": "2025.01.04", "headerClassifier": "headers", "sharedLibrary": true, "libName": "PlayingWithFusionDriver", "skipInvalidPlatforms": true, "binaryPlatforms": [ - "linuxathena", "windowsx86-64", + "linuxarm64", "linuxx86-64", - "osxuniversal", - "linuxarm32", - "linuxarm64" + "linuxathena", + "osxuniversal" ] } ] diff --git a/vendordeps/REVLib.json b/vendordeps/REVLib-2025.0.0.json similarity index 88% rename from vendordeps/REVLib.json rename to vendordeps/REVLib-2025.0.0.json index f85acd40..cde60117 100644 --- a/vendordeps/REVLib.json +++ b/vendordeps/REVLib-2025.0.0.json @@ -1,25 +1,25 @@ { - "fileName": "REVLib.json", + "fileName": "REVLib-2025.0.0.json", "name": "REVLib", - "version": "2024.2.4", - "frcYear": "2024", + "version": "2025.0.0", + "frcYear": "2025", "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", "mavenUrls": [ "https://maven.revrobotics.com/" ], - "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2024.json", + "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2025.json", "javaDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-java", - "version": "2024.2.4" + "version": "2025.0.0" } ], "jniDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2024.2.4", + "version": "2025.0.0", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ @@ -37,7 +37,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-cpp", - "version": "2024.2.4", + "version": "2025.0.0", "libName": "REVLib", "headerClassifier": "headers", "sharedLibrary": false, @@ -55,7 +55,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2024.2.4", + "version": "2025.0.0", "libName": "REVLibDriver", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/Studica-2025.0.0.json b/vendordeps/Studica-2025.0.0.json new file mode 100644 index 00000000..ddb0e44b --- /dev/null +++ b/vendordeps/Studica-2025.0.0.json @@ -0,0 +1,71 @@ +{ + "fileName": "Studica-2025.0.0.json", + "name": "Studica", + "version": "2025.0.0", + "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", + "frcYear": "2025", + "mavenUrls": [ + "https://dev.studica.com/maven/release/2025/" + ], + "jsonUrl": "https://dev.studica.com/releases/2025/Studica-2025.0.0.json", + "cppDependencies": [ + { + "artifactId": "Studica-cpp", + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "linuxx86-64", + "osxuniversal", + "windowsx86-64" + ], + "groupId": "com.studica.frc", + "headerClassifier": "headers", + "libName": "Studica", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "version": "2025.0.0" + }, + { + "artifactId": "Studica-driver", + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "linuxx86-64", + "osxuniversal", + "windowsx86-64" + ], + "groupId": "com.studica.frc", + "headerClassifier": "headers", + "libName": "StudicaDriver", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "version": "2025.0.0" + } + ], + "javaDependencies": [ + { + "artifactId": "Studica-java", + "groupId": "com.studica.frc", + "version": "2025.0.0" + } + ], + "jniDependencies": [ + { + "artifactId": "Studica-driver", + "groupId": "com.studica.frc", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "linuxx86-64", + "osxuniversal", + "windowsx86-64" + ], + "version": "2025.0.0" + } + ] +} \ No newline at end of file diff --git a/vendordeps/WPILibNewCommands.json b/vendordeps/WPILibNewCommands.json index 67bf3898..3718e0ac 100644 --- a/vendordeps/WPILibNewCommands.json +++ b/vendordeps/WPILibNewCommands.json @@ -3,7 +3,7 @@ "name": "WPILib-New-Commands", "version": "1.0.0", "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", - "frcYear": "2024", + "frcYear": "2025", "mavenUrls": [], "jsonUrl": "", "javaDependencies": [ From d279fe37a6596a76d91fc0b6e8e3ee6ebbeebe8e Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:22:58 -0800 Subject: [PATCH 02/63] fix swerve stuff sim is still broken tests are still broken also I probably broke some functionality somewhere --- .../carlmontrobotics/lib199/MotorErrors.java | 54 ++++--- .../lib199/SparkVelocityPIDController.java | 13 +- .../lib199/sim/MockSparkBase.java | 12 +- .../lib199/sim/MockSparkFlex.java | 26 +-- .../lib199/sim/MockedEncoder.java | 150 +++++++++--------- .../lib199/swerve/SwerveModule.java | 38 +++-- .../lib199/swerve/SwerveModuleSim.java | 15 +- 7 files changed, 170 insertions(+), 138 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index e331d30b..90531cd2 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -8,6 +8,7 @@ import com.ctre.phoenix.ErrorCode; import com.revrobotics.spark.SparkBase; import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel; import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.SparkBase.Faults; import com.revrobotics.spark.config.SparkBaseConfig; @@ -21,9 +22,9 @@ public final class MotorErrors { private static final Map temperatureSparks = new ConcurrentSkipListMap<>(); private static final Map sparkTemperatureLimits = new ConcurrentHashMap<>(); private static final Map overheatedSparks = new ConcurrentHashMap<>(); - private static final Map flags = new ConcurrentSkipListMap<>( + private static final Map flags = new ConcurrentSkipListMap<>( (spark1, spark2) -> (spark1.getDeviceId() - spark2.getDeviceId())); - private static final Map stickyFlags = new ConcurrentSkipListMap<>( + private static final Map stickyFlags = new ConcurrentSkipListMap<>( (spark1, spark2) -> (spark1.getDeviceId() - spark2.getDeviceId())); public static final int kOverheatTripCount = 5; @@ -69,15 +70,16 @@ private static > void reportError(String vendor, T error, T ok public static void checkSparkErrors(SparkBase spark) { //Purposely obviously impersonal to differentiate from actual computer generated errors - short faults = spark.getFaults(); - short stickyFaults = spark.getStickyFaults(); - short prevFaults = flags.containsKey(spark) ? flags.get(spark) : 0; - short prevStickyFaults = stickyFlags.containsKey(spark) ? stickyFlags.get(spark) : 0; + // short faults = spark.getFaults(); + Faults faults = spark.getFaults(); + Faults stickyFaults = spark.getStickyFaults(); + Faults prevFaults = flags.containsKey(spark) ? flags.get(spark) : null; + Faults prevStickyFaults = stickyFlags.containsKey(spark) ? stickyFlags.get(spark) : null; - if (spark.getFaults() != 0 && prevFaults != faults) { + if (spark.hasActiveFault() && prevFaults != faults) { System.err.println("Whoops, big oopsie : fault error(s) with spark id : " + spark.getDeviceId() + ": [ " + formatFaults(spark) + "], ooF!"); } - if (spark.getStickyFaults() != 0 && prevStickyFaults != stickyFaults) { + if (spark.hasActiveFault() && prevStickyFaults != stickyFaults) { System.err.println("Bruh, you did an Error : sticky fault(s) error with spark id : " + spark.getDeviceId() + ": " + formatStickyFaults(spark) + ", Ouch!"); } spark.clearFaults(); @@ -91,23 +93,31 @@ public static void checkSparkMaxErrors(SparkMax spark) { } private static String formatFaults(SparkBase spark) { - String out = ""; - for(FaultID fault: FaultID.values()) { - if(spark.getFault(fault)) { - out += (fault.name() + " "); - } - } - return out; + Faults f = spark.getFaults(); + return "" //i hope this makes you proud of yourself, REVLib + + (f.can ? "CAN " : "") + + (f.escEeprom ? "Flash ROM " : "") + + (f.firmware ? "Firmware " : "") + + (f.gateDriver ? "Gate Driver " : "") + + (f.motorType ? "Motor Type " : "") + + (f.other ? "Other " : "") + + (f.sensor ? "Sensor " : "") + + (f.temperature ? "Temperature " : "") + ; } private static String formatStickyFaults(SparkBase spark) { - String out = ""; - for(FaultID fault: FaultID.values()) { - if(spark.getStickyFault(fault)) { - out += (fault.name() + " "); - } - } - return out; + Faults f = spark.getStickyFaults(); + return "" + + (f.can ? "CAN " : "") + + (f.escEeprom ? "Flash ROM " : "") + + (f.firmware ? "Firmware " : "") + + (f.gateDriver ? "Gate Driver " : "") + + (f.motorType ? "Motor Type " : "") + + (f.other ? "Other " : "") + + (f.sensor ? "Sensor " : "") + + (f.temperature ? "Temperature " : "") + ; } @Deprecated diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java index 94444dd2..27027b99 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java @@ -8,6 +8,8 @@ import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.SparkBase; import com.revrobotics.spark.SparkBase.ControlType; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; import com.revrobotics.spark.SparkClosedLoopController; import edu.wpi.first.util.sendable.Sendable; @@ -67,7 +69,7 @@ public double getTargetSpeed() { public void setTargetSpeed(double targetSpeed) { if(targetSpeed == this.targetSpeed) return; this.targetSpeed = targetSpeed; - pidController.setReference(targetSpeed, ControlType.kVelocity, 0, calculateFF(targetSpeed)); + pidController.setReference(targetSpeed, ControlType.kVelocity, ClosedLoopSlot.kSlot0, calculateFF(targetSpeed)); } public double getTolerance() { @@ -87,20 +89,21 @@ public void initSendable(SendableBuilder builder) { builder.setActuator(true); builder.setSmartDashboardType("SparkVelocityPIDController"); builder.addDoubleProperty("P", () -> currentP, p -> { - pidController.setP(p); + spark.configure(new SparkMaxConfig().apply(new ClosedLoopConfig().p(p)), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); + // pidController.setP(p); currentP = p; }); builder.addDoubleProperty("I", () -> currentI, i -> { - pidController.setI(i); + spark.configure(new SparkMaxConfig().apply(new ClosedLoopConfig().i(i)), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); currentI = i; }); builder.addDoubleProperty("D", () -> currentD, d -> { - pidController.setD(d); + spark.configure(new SparkMaxConfig().apply(new ClosedLoopConfig().d(d)), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); currentD = d; }); builder.addDoubleProperty("Target Speed", () -> targetSpeed, newSpeed -> { if(newSpeed == targetSpeed) return; - pidController.setReference(newSpeed, ControlType.kVelocity, 0, calculateFF(newSpeed)); + pidController.setReference(newSpeed, ControlType.kVelocity, ClosedLoopSlot.kSlot0, calculateFF(newSpeed)); targetSpeed = newSpeed; }); builder.addDoubleProperty("Tolerance", () -> tolerance, newTolerance -> tolerance = newTolerance); diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java index 91b9cac5..d28d7a20 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java @@ -56,12 +56,12 @@ public MockSparkBase(int port, MotorType type, String name, int countsPerRev) { if(type == MotorType.kBrushless) { encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false) { - @Override - public REVLibError setInverted(boolean inverted) { - System.err.println( - "(MockedEncoder) SparkRelativeEncoder cannot be inverted separately from the motor in brushless mode!"); - return REVLibError.kParamInvalid; - } + // @Override + // public REVLibError setInverted(boolean inverted) { + // System.err.println( + // "(MockedEncoder) SparkRelativeEncoder cannot be inverted separately from the motor in brushless mode!"); + // return REVLibError.kParamInvalid; + // } }; } else { encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false); diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java index 833ba787..49bac9c9 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java @@ -1,18 +1,18 @@ -package org.carlmontrobotics.lib199.sim; +// package org.carlmontrobotics.lib199.sim; -import org.carlmontrobotics.lib199.DummySparkMaxAnswer; -import org.carlmontrobotics.lib199.Mocks; +// import org.carlmontrobotics.lib199.DummySparkMaxAnswer; +// import org.carlmontrobotics.lib199.Mocks; -import com.revrobotics.spark.SparkLowLevel.MotorType; -import com.revrobotics.spark.SparkFlex; +// import com.revrobotics.spark.SparkLowLevel.MotorType; +// import com.revrobotics.spark.SparkFlex; -public class MockSparkFlex extends MockSparkBase { +// public class MockSparkFlex extends MockSparkBase { - public MockSparkFlex(int port, MotorType type) { - super(port, type, "CANSparkFlex", 7168); - } +// public MockSparkFlex(int port, MotorType type) { +// super(port, type, "CANSparkFlex", 7168); +// } - public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { - return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); - } -} +// public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { +// return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); +// } +// } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java index 0520b7da..ed813502 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java @@ -88,22 +88,22 @@ public REVLibError setPosition(double newPosition) { return REVLibError.kOk; } - @Override - public REVLibError setMeasurementPeriod(int period_ms) { - System.err.println("(MockedEncoder) setMeasurementPeriod not implemented"); - return REVLibError.kNotImplemented; - } - - @Override - public int getMeasurementPeriod() { - System.err.println("(MockedEncoder) getMeasurementPeriod not implemented"); - return 0; - } - - @Override - public int getCountsPerRevolution() { - return countsPerRev; - } + // @Override + // public REVLibError setMeasurementPeriod(int period_ms) { + // System.err.println("(MockedEncoder) setMeasurementPeriod not implemented"); + // return REVLibError.kNotImplemented; + // } + + // @Override + // public int getMeasurementPeriod() { + // System.err.println("(MockedEncoder) getMeasurementPeriod not implemented"); + // return 0; + // } + + // @Override + // public int getCountsPerRevolution() { + // return countsPerRev; + // } /** * @return The current position of the encoder, not accounting for the position offset ({@link #setPosition(double)} and {@link #setZeroOffset(double)}) @@ -128,65 +128,65 @@ public double getVelocity() { * velocityConversionFactor; } - @Override - public REVLibError setPositionConversionFactor(double factor) { - positionConversionFactor = factor; - return REVLibError.kOk; - } - - @Override - public double getPositionConversionFactor() { - return positionConversionFactor; - } - - @Override - public REVLibError setVelocityConversionFactor(double factor) { - velocityConversionFactor = factor; - return REVLibError.kOk; - } - - @Override - public double getVelocityConversionFactor() { - return velocityConversionFactor; - } - - @Override - public REVLibError setInverted(boolean inverted) { - this.inverted = inverted; - return REVLibError.kOk; - } - - @Override - public boolean getInverted() { - return inverted; - } - - @Override - public REVLibError setAverageDepth(int depth) { - System.err.println("(MockedEncoder) setAverageDepth not implemented"); - return REVLibError.kNotImplemented; - } - - @Override - public int getAverageDepth() { - System.err.println("(MockedEncoder) getAverageDepth not implemented"); - return 0; - } - - @Override - public REVLibError setZeroOffset(double offset) { - if (!absolute) { - System.err.println("(MockedEncoder) setZeroOffset cannot be called on a relative encoder"); - return REVLibError.kParamAccessMode; - } - positionOffset = offset; - return REVLibError.kOk; - } - - @Override - public double getZeroOffset() { - return positionOffset; - } + // @Override + // public REVLibError setPositionConversionFactor(double factor) { + // positionConversionFactor = factor; + // return REVLibError.kOk; + // } + + // @Override + // public double getPositionConversionFactor() { + // return positionConversionFactor; + // } + + // @Override + // public REVLibError setVelocityConversionFactor(double factor) { + // velocityConversionFactor = factor; + // return REVLibError.kOk; + // } + + // @Override + // public double getVelocityConversionFactor() { + // return velocityConversionFactor; + // } + + // @Override + // public REVLibError setInverted(boolean inverted) { + // this.inverted = inverted; + // return REVLibError.kOk; + // } + + // @Override + // public boolean getInverted() { + // return inverted; + // } + + // @Override + // public REVLibError setAverageDepth(int depth) { + // System.err.println("(MockedEncoder) setAverageDepth not implemented"); + // return REVLibError.kNotImplemented; + // } + + // @Override + // public int getAverageDepth() { + // System.err.println("(MockedEncoder) getAverageDepth not implemented"); + // return 0; + // } + + // @Override + // public REVLibError setZeroOffset(double offset) { + // if (!absolute) { + // System.err.println("(MockedEncoder) setZeroOffset cannot be called on a relative encoder"); + // return REVLibError.kParamAccessMode; + // } + // positionOffset = offset; + // return REVLibError.kOk; + // } + + // @Override + // public double getZeroOffset() { + // return positionOffset; + // } @Override public void close() { diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 05909dd3..679d65ce 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -8,12 +8,15 @@ import org.mockito.internal.reporting.SmartPrinter; -import com.ctre.phoenix6.signals.AbsoluteSensorRangeValue; +// import com.ctre.phoenix6.signals.AbsoluteSensorRangeValue; import com.ctre.phoenix6.configs.CANcoderConfiguration; import com.ctre.phoenix6.hardware.CANcoder; import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; - +import com.revrobotics.spark.config.SparkBaseConfig; +import com.revrobotics.spark.config.SparkMaxConfig; import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.controller.PIDController; @@ -86,7 +89,8 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM final double neoStallCurrentAmps = 166; double currentLimitAmps = neoFreeCurrentAmps + 2*motorTorqueLimitNewtonMeters / neoStallTorqueNewtonMeters * (neoStallCurrentAmps-neoFreeCurrentAmps); // SmartDashboard.putNumber(type.toString() + " current limit (amps)", currentLimitAmps); - drive.setSmartCurrentLimit((int)Math.min(50, currentLimitAmps)); + drive.configure(new SparkMaxConfig().smartCurrentLimit(Math.min(50,(int)currentLimitAmps)), + ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); this.forwardSimpleMotorFF = new SimpleMotorFeedforward(config.kForwardVolts[arrIndex], config.kForwardVels[arrIndex], @@ -100,7 +104,9 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM config.drivekD[arrIndex]); /* offset for 1 CANcoder count */ - drivetoleranceMPerS = (1.0 / (double)(drive.getEncoder().getCountsPerRevolution()) * positionConstant) / Units.millisecondsToSeconds(drive.getEncoder().getMeasurementPeriod() * drive.getEncoder().getAverageDepth()); + drivetoleranceMPerS = (1.0 + / (double)(drive.configAccessor.encoder.getCountsPerRevolution()) * positionConstant) + / Units.millisecondsToSeconds(drive.configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * drive.configAccessor.absoluteEncoder.getAverageDepth()); drivePIDController.setTolerance(drivetoleranceMPerS); //System.out.println("Velocity Constant: " + (positionConstant / 60)); @@ -342,9 +348,9 @@ public double getCurrentSpeed() { public void updateSmartDashboard() { String moduleString = type.toString(); // Display the position of the quadrature encoder. - SmartDashboard.putNumber(moduleString + " Incremental Position", turnEncoder.getPosition().getValue()); + SmartDashboard.putNumber(moduleString + " Incremental Position", turnEncoder.getPosition().getValueAsDouble()); // Display the position of the analog encoder. - SmartDashboard.putNumber(moduleString + " Absolute Angle (deg)", Units.rotationsToDegrees(turnEncoder.getAbsolutePosition().getValue())); + SmartDashboard.putNumber(moduleString + " Absolute Angle (deg)", Units.rotationsToDegrees(turnEncoder.getAbsolutePosition().getValueAsDouble())); // Display the module angle as calculated using the absolute encoder. SmartDashboard.putNumber(moduleString + " Turn Measured Pos (deg)", getModuleAngle()); SmartDashboard.putNumber(moduleString + " Encoder Position", drive.getEncoder().getPosition()); @@ -396,10 +402,10 @@ public void updateSmartDashboard() { if (turnPIDController.getPositionTolerance() != turnTolerance) { turnPIDController.setTolerance(turnTolerance); } - double kS = SmartDashboard.getNumber(moduleString + " Swerve kS", turnSimpleMotorFeedforward.ks); - double kV = SmartDashboard.getNumber(moduleString + " Swerve kV", turnSimpleMotorFeedforward.kv); - double kA = SmartDashboard.getNumber(moduleString + " Swerve kA", turnSimpleMotorFeedforward.ka); - if (turnSimpleMotorFeedforward.ks != kS || turnSimpleMotorFeedforward.kv != kV || turnSimpleMotorFeedforward.ka != kA) { + double kS = SmartDashboard.getNumber(moduleString + " Swerve kS", turnSimpleMotorFeedforward.getKs()); + double kV = SmartDashboard.getNumber(moduleString + " Swerve kV", turnSimpleMotorFeedforward.getKv()); + double kA = SmartDashboard.getNumber(moduleString + " Swerve kA", turnSimpleMotorFeedforward.getKa()); + if (turnSimpleMotorFeedforward.getKs() != kS || turnSimpleMotorFeedforward.getKv() != kV || turnSimpleMotorFeedforward.getKa() != kA) { turnSimpleMotorFeedforward = new SimpleMotorFeedforward(kS, kV, kA); maxAchievableTurnVelocityRps = 0.5 * turnSimpleMotorFeedforward.maxAchievableVelocity(12.0, 0); maxAchievableTurnAccelerationRps2 = 0.5 * turnSimpleMotorFeedforward.maxAchievableAcceleration(12.0, maxAchievableTurnVelocityRps); @@ -407,18 +413,20 @@ public void updateSmartDashboard() { } public void toggleMode() { - if (drive.getIdleMode() == IdleMode.kBrake && turn.getIdleMode() == IdleMode.kCoast) coast(); + if (drive.configAccessor.getIdleMode() == IdleMode.kBrake && turn.configAccessor.getIdleMode() == IdleMode.kCoast) coast(); else brake(); } public void brake() { - drive.setIdleMode(IdleMode.kBrake); - turn.setIdleMode(IdleMode.kBrake); + SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kBrake); + drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); } public void coast() { - drive.setIdleMode(IdleMode.kCoast); - turn.setIdleMode(IdleMode.kCoast); + SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kCoast); + drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); } /** diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index 3cdcccae..97a7802a 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -4,7 +4,9 @@ import org.carlmontrobotics.lib199.sim.MockedEncoder; import edu.wpi.first.math.MathUtil; +import edu.wpi.first.math.system.LinearSystem; import edu.wpi.first.math.system.plant.DCMotor; +import edu.wpi.first.math.system.plant.LinearSystemId; import edu.wpi.first.units.measure.Distance; import edu.wpi.first.units.measure.Mass; import edu.wpi.first.units.Measure; @@ -35,12 +37,21 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM int turnMotorPortNum, int turnEncoderPortNum, double turnGearing, double turnMoiKgM2) { driveMotorSim = new SimDeviceSim("CANMotor:CANSparkMax", drivePortNum); driveEncoderSim = new SimDeviceSim("CANEncoder:CANSparkMax", drivePortNum); - drivePhysicsSim = new DCMotorSim(DCMotor.getNEO(1), driveGearing, driveMoiKgM2); + DCMotor dcmotor = DCMotor.getNEO(1); + drivePhysicsSim = new DCMotorSim( + LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), + dcmotor, + 0.0);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) + + // drivePhysicsSim = new DCMotorSim(DCMotor.getNEO(1), driveGearing, driveMoiKgM2); this.driveGearing = driveGearing; turnMotorSim = new SimDeviceSim("CANMotor:CANSparkMax", turnMotorPortNum); turnEncoderSim = new SimDeviceSim("CANDutyCycle:CANCoder", turnEncoderPortNum); - turnPhysicsSim = new DCMotorSim(DCMotor.getNEO(1), turnGearing, turnMoiKgM2); + turnPhysicsSim = new DCMotorSim( + LinearSystemId.createDCMotorSystem(dcmotor, turnMoiKgM2, turnGearing), + dcmotor, + 0.0); } /** From c52cbd7f4bd80bf0d602f10b044a355f9da7a1c0 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:25:16 -0800 Subject: [PATCH 03/63] that wasn't supposed to be commented out! --- .../lib199/sim/MockSparkFlex.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java index 49bac9c9..833ba787 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java @@ -1,18 +1,18 @@ -// package org.carlmontrobotics.lib199.sim; +package org.carlmontrobotics.lib199.sim; -// import org.carlmontrobotics.lib199.DummySparkMaxAnswer; -// import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.DummySparkMaxAnswer; +import org.carlmontrobotics.lib199.Mocks; -// import com.revrobotics.spark.SparkLowLevel.MotorType; -// import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkFlex; -// public class MockSparkFlex extends MockSparkBase { +public class MockSparkFlex extends MockSparkBase { -// public MockSparkFlex(int port, MotorType type) { -// super(port, type, "CANSparkFlex", 7168); -// } + public MockSparkFlex(int port, MotorType type) { + super(port, type, "CANSparkFlex", 7168); + } -// public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { -// return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); -// } -// } + public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { + return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); + } +} From 5d10c7f2682d34bebe8444b91fb30986dba06a7e Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:14:27 -0800 Subject: [PATCH 04/63] make it build (when tests excluded) --- build.gradle | 2 +- .../lib199/MotorControllerFactory.java | 48 +- .../lib199/sim/MockSparkBase.java | 598 +++++++++--------- .../lib199/sim/MockSparkFlex.java | 26 +- .../lib199/sim/MockSparkMax.java | 26 +- 5 files changed, 352 insertions(+), 348 deletions(-) diff --git a/build.gradle b/build.gradle index 058d7b3d..4294faef 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ jar { from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } from sourceSets.main.allSource manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) - duplicatesStrategy = DuplicatesStrategy.FAIL + duplicatesStrategy = DuplicatesStrategy.INCLUDE } publishing { diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index cd0245dd..8951c727 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -23,8 +23,9 @@ import com.revrobotics.spark.SparkLowLevel; import com.revrobotics.spark.SparkClosedLoopController; -import org.carlmontrobotics.lib199.sim.MockSparkFlex; -import org.carlmontrobotics.lib199.sim.MockSparkMax; +// import org.carlmontrobotics.lib199.sim.MockSparkFlex; +// import org.carlmontrobotics.lib199.sim.MockSparkMax; +// FIXME: sorry but we don't need these right now import org.carlmontrobotics.lib199.sim.MockTalonSRX; import org.carlmontrobotics.lib199.sim.MockVictorSPX; import org.carlmontrobotics.lib199.sim.MockedCANCoder; @@ -91,18 +92,19 @@ public static SparkMax createSparkMax(int id, MotorErrors.TemperatureLimit tempe @Deprecated public static SparkMax createSparkMax(int id, int temperatureLimit) { - SparkMax spark; + SparkMax spark=null; if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); + System.err.println("heyy... lib199 doesn't have sim support sorri"); + // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); } SparkMaxConfig config = new SparkMaxConfig(); // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); //FIXME: What is kStatus0 // config.signals. - - MotorErrors.reportSparkMaxTemp(spark, temperatureLimit); + if (spark!=null) + MotorErrors.reportSparkMaxTemp(spark, temperatureLimit); // MotorErrors.reportError(config.follow(ExternalFollower.kFollowerDisabled, 0)); // config.follow(null, false); dont follow nothing because thats the norm @@ -131,35 +133,37 @@ public static SparkMax createSparkMax(int id, int temperatureLimit) { } public static SparkMax createSparkMax(int id, SparkBaseConfig config) { - SparkMax spark; + SparkMax spark = null; if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); + System.err.println("heyy... lib199 doesn't have sim support sorri"); + // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); } - - spark.configure( - config, - SparkBase.ResetMode.kResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters - ); + if (spark!=null) + spark.configure( + config, + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters + ); return spark; } public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { - SparkFlex spark; + SparkFlex spark = null; if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkFlex.createMockSparkFlex(id, SparkLowLevel.MotorType.kBrushless); + System.err.println("heyy... lib199 doesn't have sim support sorri"); + // spark = MockSparkFlex.createMockSparkFlex(id, SparkLowLevel.MotorType.kBrushless); } - - spark.configure( - config, - SparkBase.ResetMode.kResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters - ); + if (spark!=null) + spark.configure( + config, + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters + ); return spark; } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java index d28d7a20..a3c3d79d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java @@ -1,299 +1,299 @@ -package org.carlmontrobotics.lib199.sim; - -import java.util.concurrent.ConcurrentHashMap; - -import org.carlmontrobotics.lib199.Lib199Subsystem; -import org.carlmontrobotics.lib199.Mocks; -import org.carlmontrobotics.lib199.REVLibErrorAnswer; - -import com.revrobotics.spark.config.ClosedLoopConfig; -import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; -import com.revrobotics.spark.SparkClosedLoopController; -import com.revrobotics.spark.SparkLowLevel.MotorType; -import com.revrobotics.REVLibError; -import com.revrobotics.RelativeEncoder; -import com.revrobotics.spark.SparkAbsoluteEncoder; -import com.revrobotics.spark.SparkMaxAlternateEncoder; -import com.revrobotics.spark.SparkAnalogSensor; -import com.revrobotics.spark.SparkBase; -import com.revrobotics.spark.SparkRelativeEncoder; - -import edu.wpi.first.hal.SimDevice; -import edu.wpi.first.wpilibj.motorcontrol.MotorController; - -/** - * An extension of {@link MockedMotorBase} which implements spark-max-specific functionality - */ -public class MockSparkBase extends MockedMotorBase { - - private static final ConcurrentHashMap controllers = new ConcurrentHashMap<>(); - - public final MotorType type; - private final MockedEncoder encoder; - private final SparkClosedLoopController pidController; - private final MockedSparkMaxPIDController pidControllerImpl; - private SparkAbsoluteEncoder absoluteEncoder = null; - private MockedEncoder absoluteEncoderImpl = null; - private MockedEncoder alternateEncoder = null; - private SparkAnalogSensor analogSensor = null; - private MockedEncoder analogSensorImpl = null; - private final String name; - - /** - * Initializes a new {@link SimDevice} with the given parameters and creates the necessary sim values, and - * registers this class's {@link #run()} method to be called via {@link Lib199Subsystem#registerSimulationPeriodic(Runnable)}. - * - * @param port the port to associate this {@code MockSparkMax} with. Will be used to create the {@link SimDevice} and facilitate motor following. - * @param type the type of the simulated motor. If this is set to {@link MotorType#kBrushless}, the builtin encoder simulation will be configured - * to follow the inversion state of the motor and its {@code setInverted} method will be disabled. - * @param name the name of the type of controller ("SparkMax" or "SparkFlex") - * @param countsPerRev the number of counts per revolution of this controller's built-in encoder. - */ - public MockSparkBase(int port, MotorType type, String name, int countsPerRev) { - super(name, port); - this.type = type; - this.name = name; - - if(type == MotorType.kBrushless) { - encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false) { - // @Override - // public REVLibError setInverted(boolean inverted) { - // System.err.println( - // "(MockedEncoder) SparkRelativeEncoder cannot be inverted separately from the motor in brushless mode!"); - // return REVLibError.kParamInvalid; - // } - }; - } else { - encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false); - } - - pidControllerImpl = new MockedSparkMaxPIDController(this); - pidController = Mocks.createMock(SparkClosedLoopController.class, pidControllerImpl, new REVLibErrorAnswer()); - pidController.feedbackSensor(encoder); - - controllers.put(port, this); - - Lib199Subsystem.registerSimulationPeriodic(this); - } - - @Override - public double getRequestedSpeed() { - return pidControllerImpl.calculate(getCurrentDraw()); - } - - /** - * @param port the port of the controller to search for - * @return Queries the simulated motor controller with the given port - */ - public static MockSparkBase getControllerWithId(int port) { - return controllers.get(port); - } - - @Override - public void set(double speed) { - speed *= voltageCompensationNominalVoltage / defaultNominalVoltage; - pidControllerImpl.setDutyCycle(speed); - } - - public REVLibError follow(SparkBase leader) { - return follow(leader, false); - } - - public REVLibError follow(SparkBase leader, boolean invert) { - pidControllerImpl.follow(leader, invert); // No need to lookup the spark max if we already have it - return REVLibError.kOk; - } - - public REVLibError follow(ExternalFollower leader, int deviceID) { - return follow(leader, deviceID, false); - } - - public REVLibError follow(ExternalFollower leader, int deviceID, boolean invert) { - MotorController controller = null; - // Because ExternalFollower does not implement equals, this could result in bugs if the user passes in a custom ExternalFollower object, - // but I think that it's unlikely and users should use the builtin definitions anyway - if(leader.equals(ExternalFollower.kFollowerDisabled)) { - pidControllerImpl.stopFollowing(); - } else { - if(leader.equals(ExternalFollower.kFollowerSpark)) { - controller = getControllerWithId(deviceID); - } else if(leader.equals(ExternalFollower.kFollowerPhoenix)) { - // controller = MockPhoenixController.getControllerWithId(deviceID); - } - if(controller == null) { - System.err.println("Error: Attempted to follow unknown motor controller: " + leader + " " + deviceID); - return REVLibError.kFollowConfigMismatch; - } - pidControllerImpl.follow(controller, invert); - } - return REVLibError.kOk; - } - - public boolean isFollower() { - return pidControllerImpl.isFollower(); - } - - public int getDeviceId() { - return port; - } - - public RelativeEncoder getEncoder() { - return encoder; - } - - public RelativeEncoder getEncoder(SparkRelativeEncoder.Type type, int countsPerRev) { - if(type != Type.kHallSensor) { - System.err.println("Error: MockSparkMax only supports hall effect encoders"); - return null; - } - return getEncoder(); - } - - @Override - public void setInverted(boolean inverted) { - super.setInverted(inverted); - - // Set the encoder inversion directly to avoid the error message - if(type == MotorType.kBrushless) encoder.inverted = inverted; - } - - public REVLibError enableVoltageCompensation(double nominalVoltage) { - super.doEnableVoltageCompensation(nominalVoltage); - return REVLibError.kOk; - } - - public REVLibError disableVoltageCompensation() { - super.doDisableVoltageCompensation(); - return REVLibError.kOk; - } - - public SparkClosedLoopController getPIDController() { - return pidController; - } - - public double getAppliedOutput() { - // MockedMotorBase returns speed before rate limiting. - // The current output is the speed after rate limiting. - return (isInverted ? -1.0 : 1.0) * speed.get(); - } - - public double getBusVoltage() { - return defaultNominalVoltage; - } - - @Override - public void close() { - controllers.remove(port); - if (encoder != null) { - encoder.close(); - } - if (absoluteEncoderImpl != null) { - absoluteEncoderImpl.close(); - } - if (analogSensorImpl != null) { - analogSensorImpl.close(); - } - if (alternateEncoder != null) { - alternateEncoder.close(); - } - super.close(); - } - - /** - * Creates a simulated {@link SparkAbsoluteEncoder} linked to this simulated controller. - * After this method has been called once, its output is cached for future invocations. - * For this reason, the method is also {@code synchronized}. - * - * @param encoderType ignored - * @return the simulated encoder - */ - public synchronized SparkAbsoluteEncoder getAbsoluteEncoder(SparkAbsoluteEncoder.Type encoderType) { - if(absoluteEncoder == null) { - absoluteEncoderImpl = new MockedEncoder( - SimDevice.create("CANDutyCycle:" + name, port), 0, false, - true, true); - absoluteEncoder = Mocks.createMock(SparkAbsoluteEncoder.class, absoluteEncoderImpl, new REVLibErrorAnswer()); - } - return absoluteEncoder; - } - - /** - * Creates a simulated alternate encoder linked to this simulated controller. - * After this method has been called once, its output is cached for future invocations. - * This means that only the first call to this method will set the CPR of the encoder. - * For this reason, the method is also {@code synchronized}. - * - * @param countsPerRev the CPR of the absolute encoder - * @return the simulated encoder - */ - public RelativeEncoder getAlternateEncoder(int countsPerRev) { - return getAlternateEncoder(SparkMaxAlternateEncoder.Type.kQuadrature, countsPerRev); - } - - /** - * Creates a simulated {@link SparkMaxAlternateEncoder} linked to this simulated controller. - * After this method has been called once, its output is cached for future invocations. - * For this reason, the method is also {@code synchronized}. - * - * @param encoderType ignored - * @return the simulated encoder - */ - public synchronized RelativeEncoder getAlternateEncoder(SparkMaxAlternateEncoder.Type encoderType, int countsPerRev) { - if(alternateEncoder == null) { - alternateEncoder = new MockedEncoder(SimDevice.create("CANEncoder:%s[%d]-alternate".formatted(name, port)), 0, false, false); - } - return alternateEncoder; - } - - /** - * Creates a simulated {@link SparkAnalogSensor} linked to this simulated controller. - * After this method has been called once, its output is cached for future invocations. - * For this reason, the method is also {@code synchronized}. - * - * @param mode setting this to {@link SparkAnalogSensor.Mode#kAbsolute} makes the position relative to the position on startup. - * We will assume that this value is always zero, so this parameter has no effect. - * @return the simulated encoder - */ - public synchronized SparkAnalogSensor getAnalog(SparkAnalogSensor.Mode mode) { - if(analogSensor == null) { - analogSensorImpl = new MockedEncoder( - SimDevice.create("CANAIn:" + name, port), 0, true, true); - analogSensor = Mocks.createMock(SparkAnalogSensor.class, analogSensorImpl, new REVLibErrorAnswer()); - } - return analogSensor; - } - - public double getClosedLoopRampRate() { - return getRampRateClosedLoop(); - } - - public double getOpenLoopRampRate() { - return getRampRateOpenLoop(); - } - - public REVLibError setClosedLoopRampRate(double secondsFromNeutralToFull) { - setRampRateClosedLoop(secondsFromNeutralToFull); - return REVLibError.kOk; - } - - public REVLibError setOpenLoopRampRate(double secondsFromNeutralToFull) { - setRampRateOpenLoop(secondsFromNeutralToFull); - return REVLibError.kOk; - } - - public REVLibError setIdleMode(IdleMode mode) { - super.setBrakeModeEnabled(mode == IdleMode.kBrake); - return REVLibError.kOk; - } - - public double getOutputCurrent() { - return getCurrentDraw(); - } - - @Override - public void disable() { - // SparkBase sets the motor speed to zero rather than actually disabling the motor - set(0); - } - -} +// package org.carlmontrobotics.lib199.sim; + +// import java.util.concurrent.ConcurrentHashMap; + +// import org.carlmontrobotics.lib199.Lib199Subsystem; +// import org.carlmontrobotics.lib199.Mocks; +// import org.carlmontrobotics.lib199.REVLibErrorAnswer; + +// import com.revrobotics.spark.config.ClosedLoopConfig; +// import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +// import com.revrobotics.spark.SparkClosedLoopController; +// import com.revrobotics.spark.SparkLowLevel.MotorType; +// import com.revrobotics.REVLibError; +// import com.revrobotics.RelativeEncoder; +// import com.revrobotics.spark.SparkAbsoluteEncoder; +// import com.revrobotics.spark.SparkMaxAlternateEncoder; +// import com.revrobotics.spark.SparkAnalogSensor; +// import com.revrobotics.spark.SparkBase; +// import com.revrobotics.spark.SparkRelativeEncoder; + +// import edu.wpi.first.hal.SimDevice; +// import edu.wpi.first.wpilibj.motorcontrol.MotorController; + +// /** +// * An extension of {@link MockedMotorBase} which implements spark-max-specific functionality +// */ +// public class MockSparkBase extends MockedMotorBase { + +// private static final ConcurrentHashMap controllers = new ConcurrentHashMap<>(); + +// public final MotorType type; +// private final MockedEncoder encoder; +// private final SparkClosedLoopController pidController; +// private final MockedSparkMaxPIDController pidControllerImpl; +// private SparkAbsoluteEncoder absoluteEncoder = null; +// private MockedEncoder absoluteEncoderImpl = null; +// private MockedEncoder alternateEncoder = null; +// private SparkAnalogSensor analogSensor = null; +// private MockedEncoder analogSensorImpl = null; +// private final String name; + +// /** +// * Initializes a new {@link SimDevice} with the given parameters and creates the necessary sim values, and +// * registers this class's {@link #run()} method to be called via {@link Lib199Subsystem#registerSimulationPeriodic(Runnable)}. +// * +// * @param port the port to associate this {@code MockSparkMax} with. Will be used to create the {@link SimDevice} and facilitate motor following. +// * @param type the type of the simulated motor. If this is set to {@link MotorType#kBrushless}, the builtin encoder simulation will be configured +// * to follow the inversion state of the motor and its {@code setInverted} method will be disabled. +// * @param name the name of the type of controller ("SparkMax" or "SparkFlex") +// * @param countsPerRev the number of counts per revolution of this controller's built-in encoder. +// */ +// public MockSparkBase(int port, MotorType type, String name, int countsPerRev) { +// super(name, port); +// this.type = type; +// this.name = name; + +// if(type == MotorType.kBrushless) { +// encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false) { +// // @Override +// // public REVLibError setInverted(boolean inverted) { +// // System.err.println( +// // "(MockedEncoder) SparkRelativeEncoder cannot be inverted separately from the motor in brushless mode!"); +// // return REVLibError.kParamInvalid; +// // } +// }; +// } else { +// encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false); +// } + +// pidControllerImpl = new MockedSparkMaxPIDController(this); +// pidController = Mocks.createMock(SparkClosedLoopController.class, pidControllerImpl, new REVLibErrorAnswer()); +// pidController.feedbackSensor(encoder); + +// controllers.put(port, this); + +// Lib199Subsystem.registerSimulationPeriodic(this); +// } + +// @Override +// public double getRequestedSpeed() { +// return pidControllerImpl.calculate(getCurrentDraw()); +// } + +// /** +// * @param port the port of the controller to search for +// * @return Queries the simulated motor controller with the given port +// */ +// public static MockSparkBase getControllerWithId(int port) { +// return controllers.get(port); +// } + +// @Override +// public void set(double speed) { +// speed *= voltageCompensationNominalVoltage / defaultNominalVoltage; +// pidControllerImpl.setDutyCycle(speed); +// } + +// public REVLibError follow(SparkBase leader) { +// return follow(leader, false); +// } + +// public REVLibError follow(SparkBase leader, boolean invert) { +// pidControllerImpl.follow(leader, invert); // No need to lookup the spark max if we already have it +// return REVLibError.kOk; +// } + +// public REVLibError follow(ExternalFollower leader, int deviceID) { +// return follow(leader, deviceID, false); +// } + +// public REVLibError follow(ExternalFollower leader, int deviceID, boolean invert) { +// MotorController controller = null; +// // Because ExternalFollower does not implement equals, this could result in bugs if the user passes in a custom ExternalFollower object, +// // but I think that it's unlikely and users should use the builtin definitions anyway +// if(leader.equals(ExternalFollower.kFollowerDisabled)) { +// pidControllerImpl.stopFollowing(); +// } else { +// if(leader.equals(ExternalFollower.kFollowerSpark)) { +// controller = getControllerWithId(deviceID); +// } else if(leader.equals(ExternalFollower.kFollowerPhoenix)) { +// // controller = MockPhoenixController.getControllerWithId(deviceID); +// } +// if(controller == null) { +// System.err.println("Error: Attempted to follow unknown motor controller: " + leader + " " + deviceID); +// return REVLibError.kFollowConfigMismatch; +// } +// pidControllerImpl.follow(controller, invert); +// } +// return REVLibError.kOk; +// } + +// public boolean isFollower() { +// return pidControllerImpl.isFollower(); +// } + +// public int getDeviceId() { +// return port; +// } + +// public RelativeEncoder getEncoder() { +// return encoder; +// } + +// public RelativeEncoder getEncoder(SparkRelativeEncoder.Type type, int countsPerRev) { +// if(type != Type.kHallSensor) { +// System.err.println("Error: MockSparkMax only supports hall effect encoders"); +// return null; +// } +// return getEncoder(); +// } + +// @Override +// public void setInverted(boolean inverted) { +// super.setInverted(inverted); + +// // Set the encoder inversion directly to avoid the error message +// if(type == MotorType.kBrushless) encoder.inverted = inverted; +// } + +// public REVLibError enableVoltageCompensation(double nominalVoltage) { +// super.doEnableVoltageCompensation(nominalVoltage); +// return REVLibError.kOk; +// } + +// public REVLibError disableVoltageCompensation() { +// super.doDisableVoltageCompensation(); +// return REVLibError.kOk; +// } + +// public SparkClosedLoopController getPIDController() { +// return pidController; +// } + +// public double getAppliedOutput() { +// // MockedMotorBase returns speed before rate limiting. +// // The current output is the speed after rate limiting. +// return (isInverted ? -1.0 : 1.0) * speed.get(); +// } + +// public double getBusVoltage() { +// return defaultNominalVoltage; +// } + +// @Override +// public void close() { +// controllers.remove(port); +// if (encoder != null) { +// encoder.close(); +// } +// if (absoluteEncoderImpl != null) { +// absoluteEncoderImpl.close(); +// } +// if (analogSensorImpl != null) { +// analogSensorImpl.close(); +// } +// if (alternateEncoder != null) { +// alternateEncoder.close(); +// } +// super.close(); +// } + +// /** +// * Creates a simulated {@link SparkAbsoluteEncoder} linked to this simulated controller. +// * After this method has been called once, its output is cached for future invocations. +// * For this reason, the method is also {@code synchronized}. +// * +// * @param encoderType ignored +// * @return the simulated encoder +// */ +// public synchronized SparkAbsoluteEncoder getAbsoluteEncoder(SparkAbsoluteEncoder.Type encoderType) { +// if(absoluteEncoder == null) { +// absoluteEncoderImpl = new MockedEncoder( +// SimDevice.create("CANDutyCycle:" + name, port), 0, false, +// true, true); +// absoluteEncoder = Mocks.createMock(SparkAbsoluteEncoder.class, absoluteEncoderImpl, new REVLibErrorAnswer()); +// } +// return absoluteEncoder; +// } + +// /** +// * Creates a simulated alternate encoder linked to this simulated controller. +// * After this method has been called once, its output is cached for future invocations. +// * This means that only the first call to this method will set the CPR of the encoder. +// * For this reason, the method is also {@code synchronized}. +// * +// * @param countsPerRev the CPR of the absolute encoder +// * @return the simulated encoder +// */ +// public RelativeEncoder getAlternateEncoder(int countsPerRev) { +// return getAlternateEncoder(SparkMaxAlternateEncoder.Type.kQuadrature, countsPerRev); +// } + +// /** +// * Creates a simulated {@link SparkMaxAlternateEncoder} linked to this simulated controller. +// * After this method has been called once, its output is cached for future invocations. +// * For this reason, the method is also {@code synchronized}. +// * +// * @param encoderType ignored +// * @return the simulated encoder +// */ +// public synchronized RelativeEncoder getAlternateEncoder(SparkMaxAlternateEncoder.Type encoderType, int countsPerRev) { +// if(alternateEncoder == null) { +// alternateEncoder = new MockedEncoder(SimDevice.create("CANEncoder:%s[%d]-alternate".formatted(name, port)), 0, false, false); +// } +// return alternateEncoder; +// } + +// /** +// * Creates a simulated {@link SparkAnalogSensor} linked to this simulated controller. +// * After this method has been called once, its output is cached for future invocations. +// * For this reason, the method is also {@code synchronized}. +// * +// * @param mode setting this to {@link SparkAnalogSensor.Mode#kAbsolute} makes the position relative to the position on startup. +// * We will assume that this value is always zero, so this parameter has no effect. +// * @return the simulated encoder +// */ +// public synchronized SparkAnalogSensor getAnalog(SparkAnalogSensor.Mode mode) { +// if(analogSensor == null) { +// analogSensorImpl = new MockedEncoder( +// SimDevice.create("CANAIn:" + name, port), 0, true, true); +// analogSensor = Mocks.createMock(SparkAnalogSensor.class, analogSensorImpl, new REVLibErrorAnswer()); +// } +// return analogSensor; +// } + +// public double getClosedLoopRampRate() { +// return getRampRateClosedLoop(); +// } + +// public double getOpenLoopRampRate() { +// return getRampRateOpenLoop(); +// } + +// public REVLibError setClosedLoopRampRate(double secondsFromNeutralToFull) { +// setRampRateClosedLoop(secondsFromNeutralToFull); +// return REVLibError.kOk; +// } + +// public REVLibError setOpenLoopRampRate(double secondsFromNeutralToFull) { +// setRampRateOpenLoop(secondsFromNeutralToFull); +// return REVLibError.kOk; +// } + +// public REVLibError setIdleMode(IdleMode mode) { +// super.setBrakeModeEnabled(mode == IdleMode.kBrake); +// return REVLibError.kOk; +// } + +// public double getOutputCurrent() { +// return getCurrentDraw(); +// } + +// @Override +// public void disable() { +// // SparkBase sets the motor speed to zero rather than actually disabling the motor +// set(0); +// } + +// } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java index 833ba787..49bac9c9 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java @@ -1,18 +1,18 @@ -package org.carlmontrobotics.lib199.sim; +// package org.carlmontrobotics.lib199.sim; -import org.carlmontrobotics.lib199.DummySparkMaxAnswer; -import org.carlmontrobotics.lib199.Mocks; +// import org.carlmontrobotics.lib199.DummySparkMaxAnswer; +// import org.carlmontrobotics.lib199.Mocks; -import com.revrobotics.spark.SparkLowLevel.MotorType; -import com.revrobotics.spark.SparkFlex; +// import com.revrobotics.spark.SparkLowLevel.MotorType; +// import com.revrobotics.spark.SparkFlex; -public class MockSparkFlex extends MockSparkBase { +// public class MockSparkFlex extends MockSparkBase { - public MockSparkFlex(int port, MotorType type) { - super(port, type, "CANSparkFlex", 7168); - } +// public MockSparkFlex(int port, MotorType type) { +// super(port, type, "CANSparkFlex", 7168); +// } - public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { - return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); - } -} +// public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { +// return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); +// } +// } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java index 1bf0b7cc..444c3040 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java @@ -1,18 +1,18 @@ -package org.carlmontrobotics.lib199.sim; +// package org.carlmontrobotics.lib199.sim; -import org.carlmontrobotics.lib199.DummySparkMaxAnswer; -import org.carlmontrobotics.lib199.Mocks; +// import org.carlmontrobotics.lib199.DummySparkMaxAnswer; +// import org.carlmontrobotics.lib199.Mocks; -import com.revrobotics.spark.SparkLowLevel.MotorType; -import com.revrobotics.spark.SparkMax; +// import com.revrobotics.spark.SparkLowLevel.MotorType; +// import com.revrobotics.spark.SparkMax; -public class MockSparkMax extends MockSparkBase { +// public class MockSparkMax extends MockSparkBase { - public MockSparkMax(int port, MotorType type) { - super(port, type, "CANSparkMax", 42); - } +// public MockSparkMax(int port, MotorType type) { +// super(port, type, "CANSparkMax", 42); +// } - public static SparkMax createMockSparkMax(int portPWM, MotorType type) { - return Mocks.createMock(SparkMax.class, new MockSparkMax(portPWM, type), new DummySparkMaxAnswer()); - } -} +// public static SparkMax createMockSparkMax(int portPWM, MotorType type) { +// return Mocks.createMock(SparkMax.class, new MockSparkMax(portPWM, type), new DummySparkMaxAnswer()); +// } +// } From 67bac623ff912785705d09f0e8145a8f54d25a95 Mon Sep 17 00:00:00 2001 From: Sofie Budman Date: Fri, 7 Feb 2025 17:30:18 -0800 Subject: [PATCH 05/63] update swerve modueljava --- .../lib199/swerve/SwerveModule.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 679d65ce..4fd0c9fc 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -45,6 +45,9 @@ public class SwerveModule implements Sendable { public enum ModuleType {FL, FR, BL, BR}; private SwerveConfig config; + private SparkMaxConfig turnConfig = new SparkMaxConfig(); + private SparkMaxConfig driveConfig = new SparkMaxConfig(); + private ModuleType type; private SparkMax drive, turn; private CANcoder turnEncoder; @@ -74,12 +77,14 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM this.drive = drive; double positionConstant = config.wheelDiameterMeters * Math.PI / config.driveGearing; - drive.setInverted(config.driveInversion[arrIndex]); + driveConfig.inverted(config.driveInversion[arrIndex]); + turnConfig.inverted(config.turnInversion[arrIndex]); + // drive.getEncoder().setPositionConversionFactor(positionConstant); //no such thing // drive.getEncoder().setVelocityConversionFactor(positionConstant / 60); //no such thing final double drivePositionFactor = positionConstant; final double turnPositionFactor = positionConstant / 60; - turn.setInverted(config.turnInversion[arrIndex]); + maxControllableAccerlationRps2 = 0; final double normalForceNewtons = 83.2 /* lbf */ * 4.4482 /* N/lbf */ / 4 /* numModules */; double wheelTorqueLimitNewtonMeters = normalForceNewtons * config.mu * config.wheelDiameterMeters / 2; @@ -168,6 +173,10 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM SendableRegistry.addLW(this, "SwerveModule", type.toString()); + //do stuff here + drive.configure(driveConfig, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + turn.configure(turnConfig, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + } public ModuleType getType() { From 8ad74796189dcff6b5b4877aab07ec578dbdf3f8 Mon Sep 17 00:00:00 2001 From: Sofie Budman Date: Fri, 7 Feb 2025 17:45:02 -0800 Subject: [PATCH 06/63] 0 --- .../org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index 97a7802a..fc85d16a 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -38,10 +38,7 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM driveMotorSim = new SimDeviceSim("CANMotor:CANSparkMax", drivePortNum); driveEncoderSim = new SimDeviceSim("CANEncoder:CANSparkMax", drivePortNum); DCMotor dcmotor = DCMotor.getNEO(1); - drivePhysicsSim = new DCMotorSim( - LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), - dcmotor, - 0.0);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) + drivePhysicsSim = new DCMotorSim(LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), dcmotor, 0.0, 0.0);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) // drivePhysicsSim = new DCMotorSim(DCMotor.getNEO(1), driveGearing, driveMoiKgM2); this.driveGearing = driveGearing; From 987fb942073a1bd6c6ebda443884bdaa4f2d54da Mon Sep 17 00:00:00 2001 From: Sofie Budman Date: Fri, 7 Feb 2025 17:53:57 -0800 Subject: [PATCH 07/63] d --- .../org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index fc85d16a..b5cbb402 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -48,7 +48,7 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM turnPhysicsSim = new DCMotorSim( LinearSystemId.createDCMotorSystem(dcmotor, turnMoiKgM2, turnGearing), dcmotor, - 0.0); + 0.0,2.2); } /** From b5461662cf40fd0550bb423ce24ed900d2007d6e Mon Sep 17 00:00:00 2001 From: Sofie Budman Date: Fri, 7 Feb 2025 17:54:51 -0800 Subject: [PATCH 08/63] d --- .../org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index b5cbb402..84a3de62 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -38,7 +38,7 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM driveMotorSim = new SimDeviceSim("CANMotor:CANSparkMax", drivePortNum); driveEncoderSim = new SimDeviceSim("CANEncoder:CANSparkMax", drivePortNum); DCMotor dcmotor = DCMotor.getNEO(1); - drivePhysicsSim = new DCMotorSim(LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), dcmotor, 0.0, 0.0);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) + drivePhysicsSim = new DCMotorSim(LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), dcmotor, 2.0, 2.0,2);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) // drivePhysicsSim = new DCMotorSim(DCMotor.getNEO(1), driveGearing, driveMoiKgM2); this.driveGearing = driveGearing; @@ -48,7 +48,7 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM turnPhysicsSim = new DCMotorSim( LinearSystemId.createDCMotorSystem(dcmotor, turnMoiKgM2, turnGearing), dcmotor, - 0.0,2.2); + 0.0,2.2,2); } /** From 256b86ce0a66dcc052784f769cbbdfd7e860cf71 Mon Sep 17 00:00:00 2001 From: Sofie Budman Date: Fri, 7 Feb 2025 17:58:51 -0800 Subject: [PATCH 09/63] s --- .../org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index 84a3de62..cf4a7a47 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -38,7 +38,7 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM driveMotorSim = new SimDeviceSim("CANMotor:CANSparkMax", drivePortNum); driveEncoderSim = new SimDeviceSim("CANEncoder:CANSparkMax", drivePortNum); DCMotor dcmotor = DCMotor.getNEO(1); - drivePhysicsSim = new DCMotorSim(LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), dcmotor, 2.0, 2.0,2);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) + drivePhysicsSim = new DCMotorSim(LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), dcmotor);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) // drivePhysicsSim = new DCMotorSim(DCMotor.getNEO(1), driveGearing, driveMoiKgM2); this.driveGearing = driveGearing; @@ -47,8 +47,7 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM turnEncoderSim = new SimDeviceSim("CANDutyCycle:CANCoder", turnEncoderPortNum); turnPhysicsSim = new DCMotorSim( LinearSystemId.createDCMotorSystem(dcmotor, turnMoiKgM2, turnGearing), - dcmotor, - 0.0,2.2,2); + dcmotor); } /** From c782a711f0de910c4f67ccebc4edcc7272da1373 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:22:26 -0800 Subject: [PATCH 10/63] remove comment https://github.com/DeepBlueRobotics/lib199/pull/107#discussion_r1915882561 Co-authored-by: CoolSpy3 <55305038+CoolSpy3@users.noreply.github.com> --- .../org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java index cf4a7a47..4987540d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModuleSim.java @@ -39,8 +39,6 @@ public SwerveModuleSim(int drivePortNum, double driveGearing, double driveMoiKgM driveEncoderSim = new SimDeviceSim("CANEncoder:CANSparkMax", drivePortNum); DCMotor dcmotor = DCMotor.getNEO(1); drivePhysicsSim = new DCMotorSim(LinearSystemId.createDCMotorSystem(dcmotor, driveMoiKgM2, driveGearing), dcmotor);//FIXME WHAT DO WE WANT THE MEASUREMENT STDDEVS TO BE? (LAST ARG) - - // drivePhysicsSim = new DCMotorSim(DCMotor.getNEO(1), driveGearing, driveMoiKgM2); this.driveGearing = driveGearing; turnMotorSim = new SimDeviceSim("CANMotor:CANSparkMax", turnMotorPortNum); From b712b844e939084b3f5fb1ddca0c48434b08b66a Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:23:10 -0800 Subject: [PATCH 11/63] remove space https://github.com/DeepBlueRobotics/lib199/pull/107#discussion_r1915881032 Co-authored-by: CoolSpy3 <55305038+CoolSpy3@users.noreply.github.com> --- .../java/org/carlmontrobotics/lib199/swerve/SwerveModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 4fd0c9fc..a17c7989 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -142,7 +142,7 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM turnPIDController.setTolerance(turnToleranceRot); CANcoderConfiguration configs = new CANcoderConfiguration(); - configs.MagnetSensor.AbsoluteSensorDiscontinuityPoint = .5; + configs.MagnetSensor.AbsoluteSensorDiscontinuityPoint = .5; this.turnEncoder = turnEncoder; this.turnEncoder.getConfigurator().apply(configs); From 8d0248ddd6e4eea6ba5c2ff7c6b779dca631ec42 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 13:08:42 -0800 Subject: [PATCH 12/63] dubious fixes 1. duplicatestrategy to fail 2. no more CachedSparkMax 2. motorcontrollerfactory now configures sparks!! 3. motorErrors checks for null faults 4. swap from smartMotion to MAXmotion in mockedsparkmaxpidcontroller.java 5. cleaner builder pid configs in sparkvelocitypidcontroller.java 6. change from deprecated calculate(double, double) to calculateWithVelocities() in swervemodule and don't persist configs --- build.gradle | 2 +- .../lib199/CachedSparkMax.java | 29 ------------------- .../lib199/MotorControllerFactory.java | 27 ++--------------- .../carlmontrobotics/lib199/MotorErrors.java | 8 ++--- .../lib199/SparkVelocityPIDController.java | 15 +++++----- .../path/DifferentialDriveInterface.java | 1 + .../sim/MockedSparkMaxPIDController.java | 6 ++-- .../lib199/swerve/SwerveModule.java | 21 ++++++++------ 8 files changed, 32 insertions(+), 77 deletions(-) delete mode 100644 src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java diff --git a/build.gradle b/build.gradle index 4294faef..058d7b3d 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ jar { from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } from sourceSets.main.allSource manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) - duplicatesStrategy = DuplicatesStrategy.INCLUDE + duplicatesStrategy = DuplicatesStrategy.FAIL } publishing { diff --git a/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java b/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java deleted file mode 100644 index 4696302d..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/CachedSparkMax.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.carlmontrobotics.lib199; - -import com.revrobotics.RelativeEncoder; -import com.revrobotics.spark.SparkClosedLoopController; -import com.revrobotics.spark.SparkMax; - -@Deprecated -public class CachedSparkMax extends SparkMax { - - private RelativeEncoder encoder; - private SparkClosedLoopController pidController; - - public CachedSparkMax(int deviceId, MotorType type) { - super(deviceId, type); - this.encoder = null; - this.pidController = null; - } - - @Override - public RelativeEncoder getEncoder() { - return encoder == null ? (encoder = super.getEncoder()) : encoder; - } - - @Override - public SparkClosedLoopController getClosedLoopController() { - return pidController == null ? (pidController = super.getClosedLoopController()) : pidController; - } - -} diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 8951c727..d2286148 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -25,7 +25,6 @@ // import org.carlmontrobotics.lib199.sim.MockSparkFlex; // import org.carlmontrobotics.lib199.sim.MockSparkMax; -// FIXME: sorry but we don't need these right now import org.carlmontrobotics.lib199.sim.MockTalonSRX; import org.carlmontrobotics.lib199.sim.MockVictorSPX; import org.carlmontrobotics.lib199.sim.MockedCANCoder; @@ -129,6 +128,8 @@ public static SparkMax createSparkMax(int id, int temperatureLimit) { // MotorErrors.reportError(controller.setFF(0)); config.closedLoop.velocityFF(0); + spark.configure(config, SparkBase.ResetMode.kResetSafeParameters, PersistMode.kNoPersistParameters); + return spark; } @@ -168,29 +169,7 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } - private static void configureSpark(SparkBase spark, SparkMaxConfig config) { - MotorErrors.reportSparkTemp(spark, (int) spark.getMotorTemperature()); - - SparkMaxConfig newConfig = new SparkMaxConfig(); - - config.idleMode(IdleMode.kBrake); - - config.voltageCompensation(12); - config.smartCurrentLimit(50); - - config.closedLoop.minOutput(-1); - config.closedLoop.maxOutput(1); - config.closedLoop.p(0, ClosedLoopSlot.kSlot0); - config.closedLoop.i(0, ClosedLoopSlot.kSlot0); - config.closedLoop.d(0, ClosedLoopSlot.kSlot0); - config.closedLoop.velocityFF(0); - - spark.configure( - config, - SparkBase.ResetMode.kResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters - ); - } + //delete configureSpark(SparkBase, Config) because SparkBase.configure() already does that. /** * @deprecated Use {@link SensorFactory#createCANCoder(int)} instead. diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 90531cd2..b2347bfe 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -76,11 +76,11 @@ public static void checkSparkErrors(SparkBase spark) { Faults prevFaults = flags.containsKey(spark) ? flags.get(spark) : null; Faults prevStickyFaults = stickyFlags.containsKey(spark) ? stickyFlags.get(spark) : null; - if (spark.hasActiveFault() && prevFaults != faults) { - System.err.println("Whoops, big oopsie : fault error(s) with spark id : " + spark.getDeviceId() + ": [ " + formatFaults(spark) + "], ooF!"); + if (spark.hasActiveFault() && prevFaults!=null && prevFaults != faults) { + System.err.println("Fault Errors! (spark id " + spark.getDeviceId() + "): [" + formatFaults(spark) + "], ooF!"); } - if (spark.hasActiveFault() && prevStickyFaults != stickyFaults) { - System.err.println("Bruh, you did an Error : sticky fault(s) error with spark id : " + spark.getDeviceId() + ": " + formatStickyFaults(spark) + ", Ouch!"); + if (prevStickyFaults!=null && prevStickyFaults != stickyFaults) { + System.err.println("Sticky Faults! (spark id " + spark.getDeviceId() + "): [" + formatStickyFaults(spark) + "], Ouch!"); } spark.clearFaults(); flags.put(spark, faults); diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java index 27027b99..66bb7c03 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java @@ -2,6 +2,7 @@ import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.config.ClosedLoopConfig; +import com.revrobotics.spark.config.SparkBaseConfig; import com.revrobotics.spark.config.SparkMaxConfig; // import com.revrobotics.SparkBase.ControlType; import com.revrobotics.RelativeEncoder; @@ -84,22 +85,22 @@ public double calculateFF(double velocity) { return kS * Math.signum(velocity) + kV * velocity; } + private void instantClosedLoopConfig(ClosedLoopConfig clConfig) { + spark.configure(new SparkMaxConfig().apply(clConfig), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); + } + @Override public void initSendable(SendableBuilder builder) { builder.setActuator(true); builder.setSmartDashboardType("SparkVelocityPIDController"); builder.addDoubleProperty("P", () -> currentP, p -> { - spark.configure(new SparkMaxConfig().apply(new ClosedLoopConfig().p(p)), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - // pidController.setP(p); - currentP = p; + instantClosedLoopConfig(new ClosedLoopConfig().p(currentP = p)); }); builder.addDoubleProperty("I", () -> currentI, i -> { - spark.configure(new SparkMaxConfig().apply(new ClosedLoopConfig().i(i)), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - currentI = i; + instantClosedLoopConfig(new ClosedLoopConfig().i(currentI = i)); }); builder.addDoubleProperty("D", () -> currentD, d -> { - spark.configure(new SparkMaxConfig().apply(new ClosedLoopConfig().d(d)), ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - currentD = d; + instantClosedLoopConfig(new ClosedLoopConfig().d(currentD = d)); }); builder.addDoubleProperty("Target Speed", () -> targetSpeed, newSpeed -> { if(newSpeed == targetSpeed) return; diff --git a/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java b/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java index b17930f8..21d9a620 100644 --- a/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java +++ b/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java @@ -92,6 +92,7 @@ public default void configureAutoPath(RobotPath path) { */ @Override public default Command createAutoCommand(Trajectory trajectory, Supplier desiredHeading) { + //Ramsetecommand is deprecated but there's no equivalent... return new RamseteCommand(trajectory, this::getPose, createRamsete(), getKinematics(), this::drive, this); } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java index c987ad79..c4720aa7 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java @@ -55,16 +55,16 @@ public double calculate(double currentDraw) { case kVelocity: output = activeSlot.pidController.calculate(feedbackDevice.getVelocity(), setpoint); break; - case kSmartMotion: + case kMAXMotionPositionControl: output = activeSlot.profiledPIDController.calculate(feedbackDevice.getPosition(), setpoint); if(Math.abs(activeSlot.profiledPIDController.getGoal().velocity) < activeSlot.smartMotionMinVelocity) { - output = 0; + output = 0;//FIXME max motion doesn't have min velocity!!! } break; case kCurrent: output = activeSlot.pidController.calculate(currentDraw, setpoint); break; - case kSmartVelocity: + case kMAXMotionVelocityControl: default: throw new IllegalArgumentException("Unsupported ControlType: " + controlType); } diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index a17c7989..900cf29a 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -193,8 +193,11 @@ public void periodic() { public void drivePeriodic() { String moduleString = type.toString(); double actualSpeed = getCurrentSpeed(); - double targetVoltage = (actualSpeed >= 0 ? forwardSimpleMotorFF : - backwardSimpleMotorFF).calculate(desiredSpeed, calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()));//clippedAcceleration); + double targetVoltage = (actualSpeed >= 0 ? forwardSimpleMotorFF : backwardSimpleMotorFF) + .calculateWithVelocities( + desiredSpeed, + desiredSpeed+calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()) + );//clippedAcceleration); // Use robot characterization as a simple physical model to account for internal resistance, frcition, etc. // Add a PID adjustment for error correction (also "drives" the actual speed to the desired speed) @@ -241,7 +244,7 @@ public void turnPeriodic() { // SmartDashboard.putNumber("previous turn Velocity", prevTurnVelocity); // SmartDashboard.putNumber("state velocity",state.velocity); - turnFFVolts = turnSimpleMotorFeedforward.calculate(state.velocity, 0);//(state.velocity-prevTurnVelocity) / period); + turnFFVolts = turnSimpleMotorFeedforward.calculate(state.velocity);//(state.velocity-prevTurnVelocity) / period); turnVolts = turnFFVolts + turnSpeedCorrectionVolts; if (!turnPIDController.atGoal()) { turn.setVoltage(MathUtil.clamp(turnVolts, -12.0, 12.0)); @@ -388,8 +391,8 @@ public void updateSmartDashboard() { if (drivePIDController.getD() != drivekD) { drivePIDController.setD(drivekD); } - double driveTolerance = SmartDashboard.getNumber(moduleString + " Drive Tolerance", drivePIDController.getPositionTolerance()); - if (drivePIDController.getPositionTolerance() != driveTolerance) { + double driveTolerance = SmartDashboard.getNumber(moduleString + " Drive Tolerance", drivePIDController.getErrorTolerance()); + if (drivePIDController.getErrorTolerance() != driveTolerance) { drivePIDController.setTolerance(driveTolerance); } double drivekS = SmartDashboard.getNumber(moduleString + " Drive kS", forwardSimpleMotorFF.getKs()); @@ -428,14 +431,14 @@ public void toggleMode() { public void brake() { SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kBrake); - drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); - turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); + turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } public void coast() { SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kCoast); - drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); - turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); + turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } /** From f6017cbc5c504fa8f469902b87ada6aa0e292a59 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:04:44 -0800 Subject: [PATCH 13/63] revert gradle to before initial commit --- build.gradle | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 058d7b3d..67f117ba 100644 --- a/build.gradle +++ b/build.gradle @@ -89,13 +89,16 @@ test { wpi.sim.addGui().defaultEnabled = true wpi.sim.addDriverstation() -// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar') -// in order to make them all available at runtime. Also adding the manifest so WPILib -// knows where to look for our Robot Class. +// Setting up my Jar File. jar { - from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } + // Note: Do NOT add all the libraries to the jar. Doing so will cause robot programs to have + // 2 copies of each of the libraries classes, one from lib199 and one from the robot program. from sourceSets.main.allSource + + // Add the manifest so WPILib knows where to look for our Robot Class. manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) + + // There shouldn't be any duplicate classes. duplicatesStrategy = DuplicatesStrategy.FAIL } From e3f8b5c7607ef508d4f6f61dff52b7fc6eb77229 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:27:07 -0800 Subject: [PATCH 14/63] delete lib199/path* --- .../path/DifferentialDriveInterface.java | 99 ------ .../lib199/path/DrivetrainInterface.java | 69 ---- .../lib199/path/RobotPath.java | 322 ------------------ 3 files changed, 490 deletions(-) delete mode 100644 src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java delete mode 100644 src/main/java/org/carlmontrobotics/lib199/path/DrivetrainInterface.java delete mode 100644 src/main/java/org/carlmontrobotics/lib199/path/RobotPath.java diff --git a/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java b/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java deleted file mode 100644 index 21d9a620..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/path/DifferentialDriveInterface.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.carlmontrobotics.lib199.path; - -import java.util.function.Supplier; -import java.util.stream.DoubleStream; - -import edu.wpi.first.math.controller.RamseteController; -import edu.wpi.first.math.controller.SimpleMotorFeedforward; -import edu.wpi.first.math.geometry.Rotation2d; -import edu.wpi.first.math.kinematics.DifferentialDriveKinematics; -import edu.wpi.first.math.trajectory.Trajectory; -import edu.wpi.first.math.trajectory.TrajectoryConfig; -import edu.wpi.first.math.trajectory.constraint.DifferentialDriveVoltageConstraint; -import edu.wpi.first.wpilibj2.command.Command; -import edu.wpi.first.wpilibj2.command.RamseteCommand; - -public interface DifferentialDriveInterface extends DrivetrainInterface { - - /** - * Drives - * - * @param left power to left motor m/s - * @param right power to right motor m/s - */ - public void drive(double left, double right); - - /** - * Gets Differential Drive kinematics - * - * @return kinematics - */ - public DifferentialDriveKinematics getKinematics(); - - /** - * Gets max volts - * - * @return max volts - */ - public double getMaxVolt(); - - /** - * Gets characterization values in the form { kVolts, kVels, kAccels } - * - * @return characterization values in the form { kVolts, kVels, kAccels } - */ - public double[][] getCharacterizationValues(); - - /** - * @return The left encoder position in meters - */ - public double getLeftEncoderPosition(); - - /** - * @return The right encoder position in meters - */ - public double getRightEncoderPosition(); - - /** - * Creates Ramsete Controller - * - * @return Ramsete Controller - */ - public default RamseteController createRamsete() { - return new RamseteController(); - } - - /** - * Configures Trajectory - * - * @param path RobotPath object - */ - @Override - public default void configureAutoPath(RobotPath path) { - TrajectoryConfig config = path.getTrajectoryConfig(); - config.setKinematics(getKinematics()); - - double[][] charVals = getCharacterizationValues(); - config.addConstraint(new DifferentialDriveVoltageConstraint( - // new SimpleMotorFeedforward(Utils199.average(charVals[0]), - // Utils199.average(charVals[1]), Utils199.average(charVals[2])), - new SimpleMotorFeedforward(DoubleStream.of(charVals[0]).average().getAsDouble(), - DoubleStream.of(charVals[1]).average().getAsDouble(), - DoubleStream.of(charVals[2]).average().getAsDouble()), - getKinematics(), getMaxVolt())); - } - - /** - * Creates Ramsete Command - * - * @param trajectory Trajectory object - * @param desiredHeading Desired Heading - * @return Ramsete Command - */ - @Override - public default Command createAutoCommand(Trajectory trajectory, Supplier desiredHeading) { - //Ramsetecommand is deprecated but there's no equivalent... - return new RamseteCommand(trajectory, this::getPose, createRamsete(), getKinematics(), this::drive, this); - } - -} diff --git a/src/main/java/org/carlmontrobotics/lib199/path/DrivetrainInterface.java b/src/main/java/org/carlmontrobotics/lib199/path/DrivetrainInterface.java deleted file mode 100644 index e4023a08..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/path/DrivetrainInterface.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.carlmontrobotics.lib199.path; - -import java.util.function.Supplier; - -import edu.wpi.first.math.geometry.Pose2d; -import edu.wpi.first.math.geometry.Rotation2d; -import edu.wpi.first.math.trajectory.Trajectory; -import edu.wpi.first.wpilibj2.command.Command; -import edu.wpi.first.wpilibj2.command.Subsystem; - -public interface DrivetrainInterface extends Subsystem { - - /** - * Configures the constants for generating a trajectory - * - * @param path The configuration for generating a trajectory - */ - public void configureAutoPath(RobotPath path); - - /** - * Constructs a new autonomous Command that, when executed, will follow the - * provided trajectory. - * - * @param trajectory The trajectory to follow. - * @param desiredHeading A function that supplies the robot pose - use one of - * the odometry classes to provide this. - * @return Command - */ - public Command createAutoCommand(Trajectory trajectory, Supplier desiredHeading); - - /** - * Gets the max acceleration in m/s^2 - * - * @return max acceleration in m/s^2 - */ - public double getMaxAccelMps2(); - - /** - * Gets the max speed in m/s - * - * @return max speed in m/s - */ - public double getMaxSpeedMps(); - - /** - * Gets the current heading in degrees - * - * @return current heading in degrees - */ - public double getHeadingDeg(); - - /** - * @return The current position of the robot - */ - public Pose2d getPose(); - - /** - * Sets odometry to the specified pose - * - * @param initialPose The starting position of the robot on the field. - */ - public void setPose(Pose2d initialPose); - - /** - * Stops the drivetrain - */ - public void stop(); - -} diff --git a/src/main/java/org/carlmontrobotics/lib199/path/RobotPath.java b/src/main/java/org/carlmontrobotics/lib199/path/RobotPath.java deleted file mode 100644 index be65a34b..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/path/RobotPath.java +++ /dev/null @@ -1,322 +0,0 @@ -package org.carlmontrobotics.lib199.path; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; - -import edu.wpi.first.math.geometry.Pose2d; -import edu.wpi.first.math.geometry.Rotation2d; -import edu.wpi.first.math.geometry.Translation2d; -import edu.wpi.first.math.trajectory.Trajectory; -import edu.wpi.first.math.trajectory.TrajectoryConfig; -import edu.wpi.first.math.trajectory.TrajectoryGenerator; -import edu.wpi.first.wpilibj.Filesystem; -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.InstantCommand; - -//Findd Me -public class RobotPath { - - private List poses; - private Trajectory trajectory; - private TrajectoryConfig config; - private DrivetrainInterface dt; - private HeadingSupplier hs; - private double maxAccelMps2; - private double maxSpeedMps; - private boolean isInverted; - - /** - * Constructs a RobotPath Object - * - * @param pathName Name of the path - * @param dt Drivetrain object - * @param isInverted Whether the path is inverted - * @param initPos Initial position - * @throws IOException If an error occured loading the path - */ - public RobotPath(String pathName, DrivetrainInterface dt, boolean isInverted, Translation2d initPos) - throws IOException { - this(getPointsFromFile(pathName, dt, isInverted, initPos), isInverted, dt); - - } - - /** - * Constructs a RobotPath Object - * - * @param poses List of points in the .path file - * @param isInverted Whether the path is inverted - * @param dt Drivetrain object - */ - public RobotPath(List poses, boolean isInverted, DrivetrainInterface dt) { - this.poses = poses; - this.dt = dt; - this.isInverted = isInverted; - this.maxAccelMps2 = dt.getMaxAccelMps2(); - this.maxSpeedMps = dt.getMaxSpeedMps(); - } - - public Rotation2d getRotation2d(int index){ - return poses.get(index).getRotation(); - } - - /** - * Gets a path command for the given path - * - * @param faceInPathDirection Only for swerve drive, unless otherwise stated. - * Determines whether robot stays facing path - * @param stopAtEnd whether the robot should stop at the end - * @return PathCommand - */ - public Command getPathCommand(boolean faceInPathDirection, boolean stopAtEnd) { - if (trajectory == null) { - generateTrajectory(); - } - // We want the robot to stay facing the same direction (in this case), so save - // the current heading (make sure to update at the start of the command) - AtomicReference headingRef = new AtomicReference<>(dt.getPose().getRotation()); - Supplier desiredHeading = (!faceInPathDirection) ? () -> headingRef.get() : () -> hs.sample(); - Command command = dt.createAutoCommand(trajectory, desiredHeading); - command = new InstantCommand(hs::reset).andThen(command, new InstantCommand(hs::stop)); - if (stopAtEnd) { - command = command.andThen(new InstantCommand(dt::stop, dt)); - } - if (!faceInPathDirection) { - command = new InstantCommand(() -> headingRef.set(dt.getPose().getRotation())).andThen(command); - SmartDashboard.putNumber("Desired Path Heading", headingRef.get().getDegrees()); - } - return command; - } - - /** - * Tells the drivetrain to assume that the robot is at the starting position of - * this path. - */ - public void initializeDrivetrainPosition() { - if (trajectory == null) { - generateTrajectory(); - } - dt.setPose(trajectory.getInitialPose()); - } - - /** - * Generates trajectory using List of poses and TrajectoryConfig objects - */ - private void generateTrajectory() { - if (config == null) { - createConfig(); - } - trajectory = TrajectoryGenerator.generateTrajectory(poses, config); - hs = new HeadingSupplier(trajectory); - } - - /** - * Retrieves the TrajectoryConfig object for this path, creating it if it does - * not already exist - * - * @return the TrajectoryConfig object - */ - public TrajectoryConfig getTrajectoryConfig() { - if (config == null) { - createConfig(); - } - return config; - } - - private void createConfig() { - config = new TrajectoryConfig(this.getMaxSpeedMps(), this.getMaxAccelMps2()); - dt.configureAutoPath(this); - if (isInverted) { - config.setReversed(true); - } - } - - /** - * Inverts the robot path - * - * @return inverted robot path - */ - public RobotPath reversed() { - List newPoses = new ArrayList<>(poses); - Collections.reverse(newPoses); - return new RobotPath(newPoses, true, dt); - } - - /** - * Get points of a path from name of a .path file - * - * @param pathName Path name - * @param dt Drivetrain object - * @param isInverted Whether the path is inverted - * @param initPos Initial position - * @return List of points in path - * @throws IOException If an error occured loading the path - */ - public static List getPointsFromFile(String pathName, DrivetrainInterface dt, boolean isInverted, - Translation2d initPos) throws IOException { - return getPointsFromFile(getPathFile(pathName), dt, isInverted, initPos); - } - - /** - * Get points of a path from a .path file - * - * @param file Filename - * @param dt Drivetrain object - * @param isInverted Whether the path is inverted - * @param initPos Initial position - * @return List of points in path - * @throws IOException If an error occured loading the path - */ - public static List getPointsFromFile(File file, DrivetrainInterface dt, boolean isInverted, - Translation2d initPos) throws IOException { - ArrayList poses = new ArrayList(); - - try { - CSVParser csvParser = CSVFormat.DEFAULT.parse(new FileReader(file)); - double x, y, tanx, tany; - Rotation2d rot; - List records = csvParser.getRecords(); - - for (int i = 1; i < records.size(); i++) { - CSVRecord record = records.get(i); - x = Double.parseDouble(record.get(0)) + initPos.getX(); - y = Double.parseDouble(record.get(1)) + initPos.getY(); - tanx = Double.parseDouble(record.get(2)); - tany = Double.parseDouble(record.get(3)); - rot = new Rotation2d(tanx, tany); - if (isInverted) { - rot = rot.rotateBy(new Rotation2d(Math.PI)); - } - poses.add(new Pose2d(x, y, rot)); - } - csvParser.close(); - } catch (FileNotFoundException e) { - System.out.println("File named: " + file.getAbsolutePath() + " not found."); - e.printStackTrace(); - } - - return poses; - } - - /** - * Gets max acceleration for path - * - * @return Max acceleration mps2 - */ - public double getMaxAccelMps2() { - return this.maxAccelMps2; - } - - /** - * Gets max speed for path - * - * @return Max speed mps - */ - public double getMaxSpeedMps() { - return this.maxSpeedMps; - } - - /** - * Sets max acceleration for path - * - * @param maxAccelMps2 New max acceleration mps2 - * @return The robot path - */ - public RobotPath setMaxAccelMps2(double maxAccelMps2) { - checkConfig("maxAccelMps2"); - this.maxAccelMps2 = maxAccelMps2; - return this; - } - - /** - * Sets max speed for path - * - * @param maxSpeedMps New max speed mps - * @return The robot path - */ - public RobotPath setMaxSpeedMps(double maxSpeedMps) { - checkConfig("maxSpeedMps"); - this.maxSpeedMps = maxSpeedMps; - return this; - } - - private void checkConfig(String varName) { - if (config != null) { - System.out.println( - "Warning: Config has already been created. The changes to " + varName + " will not affect it"); - } - } - - /** - * Gets .path file given filename - * - * @param pathName name of file - * @return .path file - */ - public static File getPathFile(String pathName) { - return Filesystem.getDeployDirectory().toPath().resolve(Paths.get("PathWeaver/Paths/" + pathName + ".path")) - .toFile(); - } - - private static class HeadingSupplier { - private Trajectory trajectory; - private Timer timer; - private boolean timerStarted; - - /** - * Constructs a HeadingSupplier object - * - * @param trajectory Represents a time-parameterized trajectory. The trajectory - * contains of various States that represent the pose, - * curvature, time elapsed, velocity, and acceleration at that - * point. - */ - public HeadingSupplier(Trajectory trajectory) { - this.trajectory = trajectory; - timer = new Timer(); - timerStarted = false; - } - - /** - * Gets the trajectory rotation at current point in time - * - * @return current trajectory rotation at current point in time - */ - public Rotation2d sample() { - if (!timerStarted) { - timerStarted = true; - timer.start(); - } - return trajectory.sample(timer.get()).poseMeters.getRotation(); - } - - /** - * Reset the timer - */ - public void reset() { - timerStarted = false; - timer.reset(); - } - - /** - * Stops the timer - */ - public void stop() { - timer.stop(); - reset(); - } - } -} From d7f6b9b97b324cd5474485bfaa77f9197eae77d6 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:27:25 -0800 Subject: [PATCH 15/63] check raw bits instead of pointer values --- src/main/java/org/carlmontrobotics/lib199/MotorErrors.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index b2347bfe..1fb89669 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -76,10 +76,10 @@ public static void checkSparkErrors(SparkBase spark) { Faults prevFaults = flags.containsKey(spark) ? flags.get(spark) : null; Faults prevStickyFaults = stickyFlags.containsKey(spark) ? stickyFlags.get(spark) : null; - if (spark.hasActiveFault() && prevFaults!=null && prevFaults != faults) { + if (spark.hasActiveFault() && prevFaults!=null && prevFaults.rawBits != faults.rawBits) { System.err.println("Fault Errors! (spark id " + spark.getDeviceId() + "): [" + formatFaults(spark) + "], ooF!"); } - if (prevStickyFaults!=null && prevStickyFaults != stickyFaults) { + if (spark.hasStickyFault() && prevStickyFaults!=null && prevStickyFaults.rawBits != stickyFaults.rawBits) { System.err.println("Sticky Faults! (spark id " + spark.getDeviceId() + "): [" + formatStickyFaults(spark) + "], Ouch!"); } spark.clearFaults(); From 193cd3fe62a016fdb767d5ed0ff710f778ce85ec Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:27:52 -0800 Subject: [PATCH 16/63] add baseSparkConfig and deriatives --- .../lib199/MotorControllerFactory.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index d2286148..22a87e8b 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -14,6 +14,7 @@ import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.SparkBase.PersistMode; import com.revrobotics.spark.config.SparkBaseConfig; +import com.revrobotics.spark.config.SparkFlexConfig; import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; import com.revrobotics.spark.config.SparkMaxConfig; import com.revrobotics.servohub.ServoHub.ResetMode; @@ -128,7 +129,7 @@ public static SparkMax createSparkMax(int id, int temperatureLimit) { // MotorErrors.reportError(controller.setFF(0)); config.closedLoop.velocityFF(0); - spark.configure(config, SparkBase.ResetMode.kResetSafeParameters, PersistMode.kNoPersistParameters); + spark.configure(config, SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); return spark; } @@ -169,7 +170,28 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } - //delete configureSpark(SparkBase, Config) because SparkBase.configure() already does that. + private static SparkBaseConfig baseSparkConfig(MotorConfig motorConfig) { + SparkMaxConfig config = new SparkMaxConfig(); + + config.idleMode(IdleMode.kBrake); + + config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.smartCurrentLimit(motorConfig.currentLimitAmps); + + config.closedLoop + .minOutput(-1) + .maxOutput(1) + .pid(0,0,0) + .velocityFF(0); + + return config; + } + private static SparkMaxConfig baseSparkMaxConfig(MotorConfig motorConfig){ + return (SparkMaxConfig) baseSparkConfig(motorConfig);//FIXME apply needed config changes for each controller + } + private static SparkFlexConfig baseSparkFlexConfig(MotorConfig motorConfig){ + return (SparkFlexConfig) baseSparkConfig(motorConfig);//criminal casting usage + } /** * @deprecated Use {@link SensorFactory#createCANCoder(int)} instead. From 0e27e13fb1eabec6cd318eef0c4dc6d34f7640ae Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:28:10 -0800 Subject: [PATCH 17/63] update calculateWithVelocities --- .../org/carlmontrobotics/lib199/swerve/SwerveModule.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 900cf29a..60a29977 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -195,9 +195,10 @@ public void drivePeriodic() { double actualSpeed = getCurrentSpeed(); double targetVoltage = (actualSpeed >= 0 ? forwardSimpleMotorFF : backwardSimpleMotorFF) .calculateWithVelocities( - desiredSpeed, - desiredSpeed+calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()) + actualSpeed, + desiredSpeed );//clippedAcceleration); + //calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()) // Use robot characterization as a simple physical model to account for internal resistance, frcition, etc. // Add a PID adjustment for error correction (also "drives" the actual speed to the desired speed) From dbfca96f4f6795f3856e0adcdac4eafe20708dc6 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:45:36 -0800 Subject: [PATCH 18/63] factor in antiGravitationalAcceleration into drivePeriodic --- .../org/carlmontrobotics/lib199/swerve/SwerveModule.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 60a29977..1c92a939 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -32,6 +32,7 @@ import edu.wpi.first.util.sendable.Sendable; import edu.wpi.first.util.sendable.SendableBuilder; import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard; import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardTab; @@ -94,8 +95,7 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM final double neoStallCurrentAmps = 166; double currentLimitAmps = neoFreeCurrentAmps + 2*motorTorqueLimitNewtonMeters / neoStallTorqueNewtonMeters * (neoStallCurrentAmps-neoFreeCurrentAmps); // SmartDashboard.putNumber(type.toString() + " current limit (amps)", currentLimitAmps); - drive.configure(new SparkMaxConfig().smartCurrentLimit(Math.min(50,(int)currentLimitAmps)), - ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + driveConfig.smartCurrentLimit(Math.min(50, (int)currentLimitAmps)); this.forwardSimpleMotorFF = new SimpleMotorFeedforward(config.kForwardVolts[arrIndex], config.kForwardVels[arrIndex], @@ -193,10 +193,11 @@ public void periodic() { public void drivePeriodic() { String moduleString = type.toString(); double actualSpeed = getCurrentSpeed(); + double extraAccel = calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()); double targetVoltage = (actualSpeed >= 0 ? forwardSimpleMotorFF : backwardSimpleMotorFF) .calculateWithVelocities( actualSpeed, - desiredSpeed + desiredSpeed + extraAccel * TimedRobot.kDefaultPeriod//m/s + ( m/s^2 * s ) );//clippedAcceleration); //calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()) From a7a4b9bcc8816608e579eaab200763d3ede5fc31 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Tue, 11 Feb 2025 08:51:38 -0800 Subject: [PATCH 19/63] add comments to baseSparkXConfig methods --- .../org/carlmontrobotics/lib199/MotorControllerFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 22a87e8b..265bc841 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -187,9 +187,11 @@ private static SparkBaseConfig baseSparkConfig(MotorConfig motorConfig) { return config; } private static SparkMaxConfig baseSparkMaxConfig(MotorConfig motorConfig){ + //typical operating voltage: 12V. return (SparkMaxConfig) baseSparkConfig(motorConfig);//FIXME apply needed config changes for each controller } private static SparkFlexConfig baseSparkFlexConfig(MotorConfig motorConfig){ + //typical operating voltage: 12V. ( same as sparkMax ) return (SparkFlexConfig) baseSparkConfig(motorConfig);//criminal casting usage } From 90fc642fcd21f07b749edd515ed4698ae45d14c3 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 16 Feb 2025 12:45:20 -0800 Subject: [PATCH 20/63] remove dummySparkMaxAnswer --- .../lib199/DummySparkMaxAnswer.java | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java diff --git a/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java b/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java deleted file mode 100644 index 016ef4b3..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/DummySparkMaxAnswer.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.carlmontrobotics.lib199; - -import com.revrobotics.RelativeEncoder; -import com.revrobotics.spark.SparkAbsoluteEncoder; -import com.revrobotics.spark.SparkAnalogSensor; -import com.revrobotics.spark.SparkClosedLoopController; -import com.revrobotics.spark.SparkLimitSwitch; -import com.revrobotics.spark.SparkLowLevel; -import com.revrobotics.spark.SparkLowLevel.MotorType; -import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; -import com.revrobotics.spark.config.MAXMotionConfig; -import com.revrobotics.spark.SparkMax; - -import org.mockito.invocation.InvocationOnMock; - -public class DummySparkMaxAnswer extends REVLibErrorAnswer { - - private static final long serialVersionUID = 2284848703213263465L; - - public static final DummySparkMaxAnswer ANSWER = new DummySparkMaxAnswer(); - - public static final SparkMax DUMMY_SPARK_MAX = Mocks.mock(SparkMax.class, ANSWER); - - public static final RelativeEncoder DUMMY_ENCODER = Mocks.mock(RelativeEncoder.class, REVLibErrorAnswer.ANSWER); - public static final SparkAnalogSensor DUMMY_ANALOG_SENSOR = Mocks.mock(SparkAnalogSensor.class, REVLibErrorAnswer.ANSWER); - public static final SparkLimitSwitch DUMMY_LIMIT_SWITCH = Mocks.mock(SparkLimitSwitch.class, REVLibErrorAnswer.ANSWER); - public static final SparkClosedLoopController DUMMY_PID_CONTROLLER = Mocks.mock(SparkClosedLoopController.class, ANSWER); - public static final SparkAbsoluteEncoder DUMMY_ABSOLUTE_ENCODER = Mocks.mock(SparkAbsoluteEncoder.class, ANSWER); - - - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - Class returnType = invocation.getMethod().getReturnType(); - if(returnType == RelativeEncoder.class) { - return DUMMY_ENCODER; - } else if(returnType == SparkAnalogSensor.class) { - return DUMMY_ANALOG_SENSOR; - } else if(returnType == SparkLimitSwitch.class) { - return DUMMY_LIMIT_SWITCH; - } else if(returnType == SparkClosedLoopController.class) { - return DUMMY_PID_CONTROLLER; - } else if(returnType == MotorType.class) { - return MotorType.kBrushless; - } else if(returnType == IdleMode.class) { - return IdleMode.kBrake; - } else if(returnType == MAXMotionConfig.MAXMotionPositionMode.class) { - return MAXMotionConfig.MAXMotionPositionMode.kMAXMotionTrapezoidal; - } else if(returnType == SparkAbsoluteEncoder.class) { - return DUMMY_ABSOLUTE_ENCODER; - } - return super.answer(invocation); - } - -} \ No newline at end of file From f1404950367b4d7fdf38e16d3dc6aa93ad46fe01 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 16 Feb 2025 12:46:20 -0800 Subject: [PATCH 21/63] fix and apply drive pos/vel constants also turn on smartdashboard for debugging --- .../lib199/swerve/SwerveModule.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 1c92a939..ebc14745 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -77,14 +77,14 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM this.type = type; this.drive = drive; - double positionConstant = config.wheelDiameterMeters * Math.PI / config.driveGearing; driveConfig.inverted(config.driveInversion[arrIndex]); turnConfig.inverted(config.turnInversion[arrIndex]); - // drive.getEncoder().setPositionConversionFactor(positionConstant); //no such thing - // drive.getEncoder().setVelocityConversionFactor(positionConstant / 60); //no such thing - final double drivePositionFactor = positionConstant; - final double turnPositionFactor = positionConstant / 60; + double drivePositionFactor = config.wheelDiameterMeters * Math.PI / config.driveGearing; + final double driveVelocityFactor = drivePositionFactor / 60;//why by 60? + driveConfig.encoder + .positionConversionFactor(drivePositionFactor) + .velocityConversionFactor(driveVelocityFactor); maxControllableAccerlationRps2 = 0; final double normalForceNewtons = 83.2 /* lbf */ * 4.4482 /* N/lbf */ / 4 /* numModules */; @@ -108,10 +108,10 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM config.drivekI[arrIndex], config.drivekD[arrIndex]); - /* offset for 1 CANcoder count */ + /* offset for 1 relative encoder count */ drivetoleranceMPerS = (1.0 - / (double)(drive.configAccessor.encoder.getCountsPerRevolution()) * positionConstant) - / Units.millisecondsToSeconds(drive.configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * drive.configAccessor.absoluteEncoder.getAverageDepth()); + / (double)(drive.configAccessor.encoder.getCountsPerRevolution()) * drivePositionFactor) + / Units.millisecondsToSeconds(drive.configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * drive.configAccessor.encoder.getAverageDepth()); drivePIDController.setTolerance(drivetoleranceMPerS); //System.out.println("Velocity Constant: " + (positionConstant / 60)); @@ -134,21 +134,22 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM turnConstraints = new TrapezoidProfile.Constraints(maxAchievableTurnVelocityRps, maxAchievableTurnAccelerationRps2); lastAngle = 0.0; - turnPIDController = new ProfiledPIDController(config.turnkP[arrIndex], - config.turnkI[arrIndex], - config.turnkD[arrIndex], - turnConstraints); + turnPIDController = new ProfiledPIDController( + config.turnkP[arrIndex], + config.turnkI[arrIndex], + config.turnkD[arrIndex], + turnConstraints); turnPIDController.enableContinuousInput(-.5, .5); turnPIDController.setTolerance(turnToleranceRot); - - CANcoderConfiguration configs = new CANcoderConfiguration(); - configs.MagnetSensor.AbsoluteSensorDiscontinuityPoint = .5; - this.turnEncoder = turnEncoder; - this.turnEncoder.getConfigurator().apply(configs); this.driveModifier = config.driveModifier; this.reversed = config.reversed[arrIndex]; this.turnZeroDeg = config.turnZeroDeg[arrIndex]; + + CANcoderConfiguration CANconfig = new CANcoderConfiguration(); + CANconfig.MagnetSensor.AbsoluteSensorDiscontinuityPoint = .5; + // CANconfig.MagnetSensor.MagnetOffset=-turnZeroDeg; //done in getModuleAngle. + this.turnEncoder.getConfigurator().apply(CANconfig); turnPIDController.reset(getModuleAngle()); @@ -186,7 +187,7 @@ public ModuleType getType() { private double prevTurnVelocity = 0; public void periodic() { drivePeriodic(); - //updateSmartDashboard(); + updateSmartDashboard(); turnPeriodic(); } From 9646d2a7417a9a48a7603be8c0005dc5a3e4fd54 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:38:47 -0800 Subject: [PATCH 22/63] bring back sim support --- .../lib199/sim/MockSparkBase.java | 631 +++++++++--------- .../lib199/sim/MockSparkFlex.java | 26 +- .../lib199/sim/MockSparkMax.java | 26 +- 3 files changed, 358 insertions(+), 325 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java index a3c3d79d..45ab3d8c 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java @@ -1,299 +1,332 @@ -// package org.carlmontrobotics.lib199.sim; - -// import java.util.concurrent.ConcurrentHashMap; - -// import org.carlmontrobotics.lib199.Lib199Subsystem; -// import org.carlmontrobotics.lib199.Mocks; -// import org.carlmontrobotics.lib199.REVLibErrorAnswer; - -// import com.revrobotics.spark.config.ClosedLoopConfig; -// import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; -// import com.revrobotics.spark.SparkClosedLoopController; -// import com.revrobotics.spark.SparkLowLevel.MotorType; -// import com.revrobotics.REVLibError; -// import com.revrobotics.RelativeEncoder; -// import com.revrobotics.spark.SparkAbsoluteEncoder; -// import com.revrobotics.spark.SparkMaxAlternateEncoder; -// import com.revrobotics.spark.SparkAnalogSensor; -// import com.revrobotics.spark.SparkBase; -// import com.revrobotics.spark.SparkRelativeEncoder; - -// import edu.wpi.first.hal.SimDevice; -// import edu.wpi.first.wpilibj.motorcontrol.MotorController; - -// /** -// * An extension of {@link MockedMotorBase} which implements spark-max-specific functionality -// */ -// public class MockSparkBase extends MockedMotorBase { - -// private static final ConcurrentHashMap controllers = new ConcurrentHashMap<>(); - -// public final MotorType type; -// private final MockedEncoder encoder; -// private final SparkClosedLoopController pidController; -// private final MockedSparkMaxPIDController pidControllerImpl; -// private SparkAbsoluteEncoder absoluteEncoder = null; -// private MockedEncoder absoluteEncoderImpl = null; -// private MockedEncoder alternateEncoder = null; -// private SparkAnalogSensor analogSensor = null; -// private MockedEncoder analogSensorImpl = null; -// private final String name; - -// /** -// * Initializes a new {@link SimDevice} with the given parameters and creates the necessary sim values, and -// * registers this class's {@link #run()} method to be called via {@link Lib199Subsystem#registerSimulationPeriodic(Runnable)}. -// * -// * @param port the port to associate this {@code MockSparkMax} with. Will be used to create the {@link SimDevice} and facilitate motor following. -// * @param type the type of the simulated motor. If this is set to {@link MotorType#kBrushless}, the builtin encoder simulation will be configured -// * to follow the inversion state of the motor and its {@code setInverted} method will be disabled. -// * @param name the name of the type of controller ("SparkMax" or "SparkFlex") -// * @param countsPerRev the number of counts per revolution of this controller's built-in encoder. -// */ -// public MockSparkBase(int port, MotorType type, String name, int countsPerRev) { -// super(name, port); -// this.type = type; -// this.name = name; - -// if(type == MotorType.kBrushless) { -// encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false) { -// // @Override -// // public REVLibError setInverted(boolean inverted) { -// // System.err.println( -// // "(MockedEncoder) SparkRelativeEncoder cannot be inverted separately from the motor in brushless mode!"); -// // return REVLibError.kParamInvalid; -// // } -// }; -// } else { -// encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false); -// } - -// pidControllerImpl = new MockedSparkMaxPIDController(this); -// pidController = Mocks.createMock(SparkClosedLoopController.class, pidControllerImpl, new REVLibErrorAnswer()); -// pidController.feedbackSensor(encoder); - -// controllers.put(port, this); - -// Lib199Subsystem.registerSimulationPeriodic(this); -// } - -// @Override -// public double getRequestedSpeed() { -// return pidControllerImpl.calculate(getCurrentDraw()); -// } - -// /** -// * @param port the port of the controller to search for -// * @return Queries the simulated motor controller with the given port -// */ -// public static MockSparkBase getControllerWithId(int port) { -// return controllers.get(port); -// } - -// @Override -// public void set(double speed) { -// speed *= voltageCompensationNominalVoltage / defaultNominalVoltage; -// pidControllerImpl.setDutyCycle(speed); -// } - -// public REVLibError follow(SparkBase leader) { -// return follow(leader, false); -// } - -// public REVLibError follow(SparkBase leader, boolean invert) { -// pidControllerImpl.follow(leader, invert); // No need to lookup the spark max if we already have it -// return REVLibError.kOk; -// } - -// public REVLibError follow(ExternalFollower leader, int deviceID) { -// return follow(leader, deviceID, false); -// } - -// public REVLibError follow(ExternalFollower leader, int deviceID, boolean invert) { -// MotorController controller = null; -// // Because ExternalFollower does not implement equals, this could result in bugs if the user passes in a custom ExternalFollower object, -// // but I think that it's unlikely and users should use the builtin definitions anyway -// if(leader.equals(ExternalFollower.kFollowerDisabled)) { -// pidControllerImpl.stopFollowing(); -// } else { -// if(leader.equals(ExternalFollower.kFollowerSpark)) { -// controller = getControllerWithId(deviceID); -// } else if(leader.equals(ExternalFollower.kFollowerPhoenix)) { -// // controller = MockPhoenixController.getControllerWithId(deviceID); -// } -// if(controller == null) { -// System.err.println("Error: Attempted to follow unknown motor controller: " + leader + " " + deviceID); -// return REVLibError.kFollowConfigMismatch; -// } -// pidControllerImpl.follow(controller, invert); -// } -// return REVLibError.kOk; -// } - -// public boolean isFollower() { -// return pidControllerImpl.isFollower(); -// } - -// public int getDeviceId() { -// return port; -// } - -// public RelativeEncoder getEncoder() { -// return encoder; -// } - -// public RelativeEncoder getEncoder(SparkRelativeEncoder.Type type, int countsPerRev) { -// if(type != Type.kHallSensor) { -// System.err.println("Error: MockSparkMax only supports hall effect encoders"); -// return null; -// } -// return getEncoder(); -// } - -// @Override -// public void setInverted(boolean inverted) { -// super.setInverted(inverted); - -// // Set the encoder inversion directly to avoid the error message -// if(type == MotorType.kBrushless) encoder.inverted = inverted; -// } - -// public REVLibError enableVoltageCompensation(double nominalVoltage) { -// super.doEnableVoltageCompensation(nominalVoltage); -// return REVLibError.kOk; -// } - -// public REVLibError disableVoltageCompensation() { -// super.doDisableVoltageCompensation(); -// return REVLibError.kOk; -// } - -// public SparkClosedLoopController getPIDController() { -// return pidController; -// } - -// public double getAppliedOutput() { -// // MockedMotorBase returns speed before rate limiting. -// // The current output is the speed after rate limiting. -// return (isInverted ? -1.0 : 1.0) * speed.get(); -// } - -// public double getBusVoltage() { -// return defaultNominalVoltage; -// } - -// @Override -// public void close() { -// controllers.remove(port); -// if (encoder != null) { -// encoder.close(); -// } -// if (absoluteEncoderImpl != null) { -// absoluteEncoderImpl.close(); -// } -// if (analogSensorImpl != null) { -// analogSensorImpl.close(); -// } -// if (alternateEncoder != null) { -// alternateEncoder.close(); -// } -// super.close(); -// } - -// /** -// * Creates a simulated {@link SparkAbsoluteEncoder} linked to this simulated controller. -// * After this method has been called once, its output is cached for future invocations. -// * For this reason, the method is also {@code synchronized}. -// * -// * @param encoderType ignored -// * @return the simulated encoder -// */ -// public synchronized SparkAbsoluteEncoder getAbsoluteEncoder(SparkAbsoluteEncoder.Type encoderType) { -// if(absoluteEncoder == null) { -// absoluteEncoderImpl = new MockedEncoder( -// SimDevice.create("CANDutyCycle:" + name, port), 0, false, -// true, true); -// absoluteEncoder = Mocks.createMock(SparkAbsoluteEncoder.class, absoluteEncoderImpl, new REVLibErrorAnswer()); -// } -// return absoluteEncoder; -// } - -// /** -// * Creates a simulated alternate encoder linked to this simulated controller. -// * After this method has been called once, its output is cached for future invocations. -// * This means that only the first call to this method will set the CPR of the encoder. -// * For this reason, the method is also {@code synchronized}. -// * -// * @param countsPerRev the CPR of the absolute encoder -// * @return the simulated encoder -// */ -// public RelativeEncoder getAlternateEncoder(int countsPerRev) { -// return getAlternateEncoder(SparkMaxAlternateEncoder.Type.kQuadrature, countsPerRev); -// } - -// /** -// * Creates a simulated {@link SparkMaxAlternateEncoder} linked to this simulated controller. -// * After this method has been called once, its output is cached for future invocations. -// * For this reason, the method is also {@code synchronized}. -// * -// * @param encoderType ignored -// * @return the simulated encoder -// */ -// public synchronized RelativeEncoder getAlternateEncoder(SparkMaxAlternateEncoder.Type encoderType, int countsPerRev) { -// if(alternateEncoder == null) { -// alternateEncoder = new MockedEncoder(SimDevice.create("CANEncoder:%s[%d]-alternate".formatted(name, port)), 0, false, false); -// } -// return alternateEncoder; -// } - -// /** -// * Creates a simulated {@link SparkAnalogSensor} linked to this simulated controller. -// * After this method has been called once, its output is cached for future invocations. -// * For this reason, the method is also {@code synchronized}. -// * -// * @param mode setting this to {@link SparkAnalogSensor.Mode#kAbsolute} makes the position relative to the position on startup. -// * We will assume that this value is always zero, so this parameter has no effect. -// * @return the simulated encoder -// */ -// public synchronized SparkAnalogSensor getAnalog(SparkAnalogSensor.Mode mode) { -// if(analogSensor == null) { -// analogSensorImpl = new MockedEncoder( -// SimDevice.create("CANAIn:" + name, port), 0, true, true); -// analogSensor = Mocks.createMock(SparkAnalogSensor.class, analogSensorImpl, new REVLibErrorAnswer()); -// } -// return analogSensor; -// } - -// public double getClosedLoopRampRate() { -// return getRampRateClosedLoop(); -// } - -// public double getOpenLoopRampRate() { -// return getRampRateOpenLoop(); -// } - -// public REVLibError setClosedLoopRampRate(double secondsFromNeutralToFull) { -// setRampRateClosedLoop(secondsFromNeutralToFull); -// return REVLibError.kOk; -// } - -// public REVLibError setOpenLoopRampRate(double secondsFromNeutralToFull) { -// setRampRateOpenLoop(secondsFromNeutralToFull); -// return REVLibError.kOk; -// } - -// public REVLibError setIdleMode(IdleMode mode) { -// super.setBrakeModeEnabled(mode == IdleMode.kBrake); -// return REVLibError.kOk; -// } - -// public double getOutputCurrent() { -// return getCurrentDraw(); -// } - -// @Override -// public void disable() { -// // SparkBase sets the motor speed to zero rather than actually disabling the motor -// set(0); -// } - -// } +package org.carlmontrobotics.lib199.sim; + +import java.util.concurrent.ConcurrentHashMap; + +import org.carlmontrobotics.lib199.Lib199Subsystem; +import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.REVLibErrorAnswer; + +import com.revrobotics.spark.config.ClosedLoopConfig; +import com.revrobotics.spark.config.SparkMaxConfig; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.REVLibError; +import com.revrobotics.RelativeEncoder; +import com.revrobotics.sim.SparkAnalogSensorSim; +import com.revrobotics.sim.SparkMaxAlternateEncoderSim; +import com.revrobotics.sim.SparkAbsoluteEncoderSim; +import com.revrobotics.spark.SparkAbsoluteEncoder; +import com.revrobotics.spark.SparkMaxAlternateEncoder; +import com.revrobotics.spark.SparkAnalogSensor; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.SparkRelativeEncoder; +import com.revrobotics.spark.SparkSim; +import com.revrobotics.spark.SparkLowLevel; + +import edu.wpi.first.hal.SimDevice; +import edu.wpi.first.math.system.plant.DCMotor; +import edu.wpi.first.wpilibj.motorcontrol.MotorController; + +/** + * An extension of {@link MockedMotorBase} which implements spark-max-specific functionality + */ +public class MockSparkBase extends MockedMotorBase { + + private static final ConcurrentHashMap controllers = new ConcurrentHashMap<>(); + + public final MotorType type; + private final MockedEncoder encoder; + private final SparkBase motor; + private final SparkSim spark; + private final SparkClosedLoopController pidController; + private final MockedSparkMaxPIDController pidControllerImpl; + private SparkAbsoluteEncoder absoluteEncoder = null; + private SparkAbsoluteEncoderSim absoluteEncoderImpl = null; + private SparkMaxAlternateEncoder alternateEncoder = null; + private SparkMaxAlternateEncoderSim alternateEncoderImpl = null; + private SparkAnalogSensor analogSensor = null; + private SparkAnalogSensorSim analogSensorImpl = null; + private final String name; + + enum NEOType { + NEO(DCMotor.getNEO(1)), + NEO550(DCMotor.getNeo550(1)), + Vortex(DCMotor.getNeoVortex(1)), + UNKNOWN(DCMotor.getNEO(1)); + + public DCMotor dcMotor; + private NEOType(DCMotor dcmotordata){ + this.dcMotor=dcmotordata; + } + } + + /** + * Initializes a new {@link SimDevice} with the given parameters and creates the necessary sim values, and + * registers this class's {@link #run()} method to be called via {@link Lib199Subsystem#registerSimulationPeriodic(Runnable)}. + * + * @param port the port to associate this {@code MockSparkMax} with. Will be used to create the {@link SimDevice} and facilitate motor following. + * @param type the type of the simulated motor. If this is set to {@link MotorType#kBrushless}, the builtin encoder simulation will be configured + * to follow the inversion state of the motor and its {@code setInverted} method will be disabled. + * @param name the name of the type of controller ("SparkMax" or "SparkFlex") + * @param countsPerRev the number of counts per revolution of this controller's built-in encoder. + */ + public MockSparkBase(int port, MotorType type, String name, int countsPerRev, NEOType neoType) { + super(name, port); + this.type = type; + this.name = name; + + if (neoType != NEOType.Vortex){ //WARNING can't initialize a sparkbase without an actual spark... + this.motor = new SparkMax(port,type); + this.spark = new SparkSim( + this.motor, + neoType.dcMotor + ); + } else { //only vortex uses sparkflex + this.motor = new SparkFlex(port,type); + this.spark = new SparkSim( + this.motor, + neoType.dcMotor + ); + } + + if(type == MotorType.kBrushless) { + encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false) { + // @Override + // public REVLibError setInverted(boolean inverted) { + // System.err.println( + // "(MockedEncoder) SparkRelativeEncoder cannot be inverted separately from the motor in brushless mode!"); + // return REVLibError.kParamInvalid; + // } + }; + } else { + encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false); + } + + pidControllerImpl = new MockedSparkMaxPIDController(this); + pidController = Mocks.createMock(SparkClosedLoopController.class, pidControllerImpl, new REVLibErrorAnswer()); + // pidController.feedbackSensor(encoder); + + + controllers.put(port, this); + + Lib199Subsystem.registerSimulationPeriodic(this); + } + + @Override + public double getRequestedSpeed() { + return pidControllerImpl.calculate(getCurrentDraw()); + } + + /** + * @param port the port of the controller to search for + * @return Queries the simulated motor controller with the given port + */ + public static MockSparkBase getControllerWithId(int port) { + return controllers.get(port); + } + + @Override + public void set(double speed) { + speed *= voltageCompensationNominalVoltage / defaultNominalVoltage; + pidControllerImpl.setDutyCycle(speed); + } + + public REVLibError follow(SparkBase leader) { + return follow(leader, false); + } + + public REVLibError follow(SparkBase leader, boolean invert) { + pidControllerImpl.follow(leader, invert); // No need to lookup the spark max if we already have it + return REVLibError.kOk; + } + + public REVLibError follow(SparkBase leader, int deviceID) { + return follow(leader, deviceID, false); + } + + public REVLibError follow(SparkBase leader, int deviceID, boolean invert) { + MotorController controller = null; + //ERROR: no way to check if leader is sending following frames or not + controller = getControllerWithId(deviceID); + if(controller == null) { + System.err.println("Error: Attempted to follow unknown motor controller: " + leader + " " + deviceID); + return REVLibError.kFollowConfigMismatch; + } + pidControllerImpl.follow(controller, invert); + return REVLibError.kOk; + /* + // Because ExternalFollower does not implement equals, this could result in bugs if the user passes in a custom ExternalFollower object, + // but I think that it's unlikely and users should use the builtin definitions anyway + if(leader.equals(ExternalFollower.kFollowerDisabled)) { + pidControllerImpl.stopFollowing(); + } else { + if(leader.equals(ExternalFollower.kFollowerSpark)) { + controller = getControllerWithId(deviceID); + } else if(leader.equals(ExternalFollower.kFollowerPhoenix)) { + // controller = MockPhoenixController.getControllerWithId(deviceID); + } + if(controller == null) { + System.err.println("Error: Attempted to follow unknown motor controller: " + leader + " " + deviceID); + return REVLibError.kFollowConfigMismatch; + } + pidControllerImpl.follow(controller, invert); + } + return REVLibError.kOk; + */ + } + + public boolean isFollower() { + return pidControllerImpl.isFollower(); + } + + public int getDeviceId() { + return port; + } + + public RelativeEncoder getEncoder() { + return encoder; + } + + @Override + public void setInverted(boolean inverted) { + super.setInverted(inverted); + + // Set the encoder inversion directly to avoid the error message + if(type == MotorType.kBrushless) encoder.inverted = inverted; + } + + public REVLibError enableVoltageCompensation(double nominalVoltage) { + super.doEnableVoltageCompensation(nominalVoltage); + return REVLibError.kOk; + } + + public REVLibError disableVoltageCompensation() { + super.doDisableVoltageCompensation(); + return REVLibError.kOk; + } + + public SparkClosedLoopController getPIDController() { + return pidController; + } + + public double getAppliedOutput() { + // MockedMotorBase returns speed before rate limiting. + // The current output is the speed after rate limiting. + return (isInverted ? -1.0 : 1.0) * speed.get(); + } + + public double getBusVoltage() { + return defaultNominalVoltage; + } + + @Override + public void close() { + controllers.remove(port); + if (encoder != null) { + encoder.close(); + } + //simply drop all references for garbage collection (?) + absoluteEncoderImpl=null; + analogSensorImpl=null; + alternateEncoder=null; + super.close(); + } + + /** + * Creates a simulated {@link SparkAbsoluteEncoder} linked to this simulated controller. + * After this method has been called once, its output is cached for future invocations. + * For this reason, the method is also {@code synchronized}. + * + * @return the simulated encoder + */ + public synchronized SparkAbsoluteEncoder getAbsoluteEncoder() { + if(absoluteEncoder == null) { + if (motor instanceof SparkFlex){ + absoluteEncoderImpl = new SparkAbsoluteEncoderSim((SparkFlex)motor); + } else { + absoluteEncoderImpl = new SparkAbsoluteEncoderSim((SparkMax)motor); + } + absoluteEncoder = Mocks.createMock(SparkAbsoluteEncoder.class, absoluteEncoderImpl, new REVLibErrorAnswer()); + } + return absoluteEncoder; + } + + /** + * Creates a simulated alternate encoder linked to this simulated controller. + * After this method has been called once, its output is cached for future invocations. + * This means that only the first call to this method will set the CPR of the encoder. + * For this reason, the method is also {@code synchronized}. + * + * @param countsPerRev the CPR of the absolute encoder + * @return the simulated encoder + */ + public synchronized RelativeEncoder getAlternateEncoder(int countsPerRev) { + // return getAlternateEncoder(SparkMaxAlternateEncoder.Type.kQuadrature, countsPerRev); + if(alternateEncoder == null) { + if (motor instanceof SparkFlex){ + System.err.println("Error: Attempted to get Alternate Encoder of a SparkFlex: " + motor.getDeviceId()); + return encoder; + } + alternateEncoderImpl = new SparkMaxAlternateEncoderSim((SparkMax)motor); + alternateEncoder = Mocks.createMock(SparkMaxAlternateEncoder.class, absoluteEncoderImpl, new REVLibErrorAnswer()); + } + return alternateEncoder; + } + + /** + * Creates a simulated {@link SparkAnalogSensor} linked to this simulated controller. + * After this method has been called once, its output is cached for future invocations. + * For this reason, the method is also {@code synchronized}. + * + * @return the simulated encoder + */ + public synchronized SparkAnalogSensor getAnalog() { + if(analogSensor == null) { + if (motor instanceof SparkFlex){ + analogSensorImpl = new SparkAnalogSensorSim((SparkFlex)motor); + } else { + analogSensorImpl = new SparkAnalogSensorSim((SparkMax)motor); + } + analogSensor = Mocks.createMock(SparkAnalogSensor.class, analogSensorImpl, new REVLibErrorAnswer()); + } + return analogSensor; + } + + public double getClosedLoopRampRate() { + return getRampRateClosedLoop(); + } + + public double getOpenLoopRampRate() { + return getRampRateOpenLoop(); + } + + public REVLibError setClosedLoopRampRate(double secondsFromNeutralToFull) { + setRampRateClosedLoop(secondsFromNeutralToFull); + return REVLibError.kOk; + } + + public REVLibError setOpenLoopRampRate(double secondsFromNeutralToFull) { + setRampRateOpenLoop(secondsFromNeutralToFull); + return REVLibError.kOk; + } + + public REVLibError setIdleMode(IdleMode mode) { + super.setBrakeModeEnabled(mode == IdleMode.kBrake); + return REVLibError.kOk; + } + + public double getOutputCurrent() { + return getCurrentDraw(); + } + + @Override + public void disable() { + // SparkBase sets the motor speed to zero rather than actually disabling the motor + set(0); + } + +} diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java index 49bac9c9..78a46f71 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java @@ -1,18 +1,18 @@ -// package org.carlmontrobotics.lib199.sim; +package org.carlmontrobotics.lib199.sim; -// import org.carlmontrobotics.lib199.DummySparkMaxAnswer; -// import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.REVLibErrorAnswer; -// import com.revrobotics.spark.SparkLowLevel.MotorType; -// import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkFlex; -// public class MockSparkFlex extends MockSparkBase { +public class MockSparkFlex extends MockSparkBase { -// public MockSparkFlex(int port, MotorType type) { -// super(port, type, "CANSparkFlex", 7168); -// } + public MockSparkFlex(int port, MotorType type) { + super(port, type, "CANSparkFlex", 7168, NEOType.Vortex); + } -// public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { -// return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new DummySparkMaxAnswer()); -// } -// } + public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { + return Mocks.createMock(SparkFlex.class, new MockSparkFlex(portPWM, type), new REVLibErrorAnswer()); + } +} diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java index 444c3040..3bb57362 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkMax.java @@ -1,18 +1,18 @@ -// package org.carlmontrobotics.lib199.sim; +package org.carlmontrobotics.lib199.sim; -// import org.carlmontrobotics.lib199.DummySparkMaxAnswer; -// import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.REVLibErrorAnswer; -// import com.revrobotics.spark.SparkLowLevel.MotorType; -// import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; -// public class MockSparkMax extends MockSparkBase { +public class MockSparkMax extends MockSparkBase { -// public MockSparkMax(int port, MotorType type) { -// super(port, type, "CANSparkMax", 42); -// } + public MockSparkMax(int port, MotorType type, NEOType neoType) { + super(port, type, "CANSparkMax", 42, neoType); + } -// public static SparkMax createMockSparkMax(int portPWM, MotorType type) { -// return Mocks.createMock(SparkMax.class, new MockSparkMax(portPWM, type), new DummySparkMaxAnswer()); -// } -// } + public static SparkMax createMockSparkMax(int portPWM, MotorType type, NEOType neoType) { + return Mocks.createMock(SparkMax.class, new MockSparkMax(portPWM, type, neoType), new REVLibErrorAnswer()); + } +} From 589ae570b55419545fe1d408ac1e8907913a5f6d Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:39:02 -0800 Subject: [PATCH 23/63] update createDummySparkMax --- src/main/java/org/carlmontrobotics/lib199/MotorErrors.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 1fb89669..076d30d8 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -137,8 +137,9 @@ static void reportNextNSparkErrors(int n) { lastSparkErrorIndexReported = (lastSparkErrorIndexReported + n) % flags.size(); } + //what does this even supposed to do?? public static SparkMax createDummySparkMax() { - return DummySparkMaxAnswer.DUMMY_SPARK_MAX; + return Mocks.mock(SparkMax.class, new REVLibErrorAnswer()); } @Deprecated From c13146de9380f32d4998f79e7d234a4fcf264818 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:39:20 -0800 Subject: [PATCH 24/63] fix average depth not being gotten correctly --- .../java/org/carlmontrobotics/lib199/swerve/SwerveModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index ebc14745..9bd886ed 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -111,7 +111,7 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM /* offset for 1 relative encoder count */ drivetoleranceMPerS = (1.0 / (double)(drive.configAccessor.encoder.getCountsPerRevolution()) * drivePositionFactor) - / Units.millisecondsToSeconds(drive.configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * drive.configAccessor.encoder.getAverageDepth()); + / Units.millisecondsToSeconds(drive.configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * drive.configAccessor.encoder.getQuadratureAverageDepth()); drivePIDController.setTolerance(drivetoleranceMPerS); //System.out.println("Velocity Constant: " + (positionConstant / 60)); From bc5e330e3f31a7543919de80ba08ec7651deb427 Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Tue, 18 Feb 2025 15:43:24 -0800 Subject: [PATCH 25/63] put encoder back --- .../java/org/carlmontrobotics/lib199/swerve/SwerveModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 9bd886ed..b83c112d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -148,6 +148,7 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM CANcoderConfiguration CANconfig = new CANcoderConfiguration(); CANconfig.MagnetSensor.AbsoluteSensorDiscontinuityPoint = .5; + this.turnEncoder = turnEncoder; // CANconfig.MagnetSensor.MagnetOffset=-turnZeroDeg; //done in getModuleAngle. this.turnEncoder.getConfigurator().apply(CANconfig); From 32da18340af20581b2ef5e17f9d4dfa07c106dbb Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Tue, 4 Mar 2025 18:22:02 -0800 Subject: [PATCH 26/63] simplify sparkmax factory --- .../lib199/MotorControllerFactory.java | 94 ++++++++++--------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 265bc841..5fad3a4e 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -83,15 +83,13 @@ public static WPI_TalonSRX createTalon(int id) { return talon; } - //checks for spark max errors - - @Deprecated - public static SparkMax createSparkMax(int id, MotorErrors.TemperatureLimit temperatureLimit) { - return createSparkMax(id, temperatureLimit.limit); - } - - @Deprecated - public static SparkMax createSparkMax(int id, int temperatureLimit) { + /** + * Create a sparkMax controller (NEO or 550) with defautl settings. + * + * @param id the port of the motor controller + * @param motorConfig either MotorConfig.NEO or MotorConfig.NEO_550 + */ + public static SparkMax createSparkMax(int id, MotorConfig motorConfig) { SparkMax spark=null; if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); @@ -99,37 +97,20 @@ public static SparkMax createSparkMax(int id, int temperatureLimit) { System.err.println("heyy... lib199 doesn't have sim support sorri"); // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); } - SparkMaxConfig config = new SparkMaxConfig(); + // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); - //FIXME: What is kStatus0 - // config.signals. if (spark!=null) - MotorErrors.reportSparkMaxTemp(spark, temperatureLimit); - - // MotorErrors.reportError(config.follow(ExternalFollower.kFollowerDisabled, 0)); - // config.follow(null, false); dont follow nothing because thats the norm - // MotorErrors.reportError(config.setIdleMode(IdleMode.kBrake)); - config.idleMode(IdleMode.kBrake); - // MotorErrors.reportError(config.enableVoltageCompensation(12)); - config.voltageCompensation(12); - // MotorErrors.reportError(config.smartCurrentLimit(50)); - config.smartCurrentLimit(50); + MotorErrors.reportSparkMaxTemp(spark, motorConfig.temperatureLimitCelsius); MotorErrors.checkSparkMaxErrors(spark); - SparkClosedLoopController controller = spark.getClosedLoopController(); - // MotorErrors.reportError(controller.setOutputRange(-1, 1)); - config.closedLoop.minOutput(-1); - config.closedLoop.maxOutput(1); - // MotorErrors.reportError(controller.setP(0)); - // MotorErrors.reportError(controller.setI(0)); - // MotorErrors.reportError(controller.setD(0)); - config.closedLoop.p(0, ClosedLoopSlot.kSlot0); - config.closedLoop.i(0, ClosedLoopSlot.kSlot0); - config.closedLoop.d(0, ClosedLoopSlot.kSlot0); - // MotorErrors.reportError(controller.setFF(0)); - config.closedLoop.velocityFF(0); - - spark.configure(config, SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); + + if (motorConfig==MotorConfig.NEO || motorConfig==MotorConfig.NEO_550) + spark.configure(baseSparkMaxConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); + else if (motorConfig==MotorConfig.NEO_VORTEX) + spark.configure(baseSparkFlexConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); + else + spark.configure(baseSparkConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); + return spark; } @@ -170,13 +151,13 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } - private static SparkBaseConfig baseSparkConfig(MotorConfig motorConfig) { + private static SparkBaseConfig baseSparkConfig() { SparkMaxConfig config = new SparkMaxConfig(); config.idleMode(IdleMode.kBrake); config.voltageCompensation(12);//FIXME does this need to be different for different motors? - config.smartCurrentLimit(motorConfig.currentLimitAmps); + config.smartCurrentLimit(50); config.closedLoop .minOutput(-1) @@ -186,13 +167,42 @@ private static SparkBaseConfig baseSparkConfig(MotorConfig motorConfig) { return config; } - private static SparkMaxConfig baseSparkMaxConfig(MotorConfig motorConfig){ + /** + * Overrides an old config - but does not change other settings. + */ + private static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { + config.idleMode(IdleMode.kBrake); + + config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.smartCurrentLimit(50); + + config.closedLoop + .minOutput(-1) + .maxOutput(1) + .pid(0,0,0) + .velocityFF(0); + + return config; + } + /** + * Overrides an old config - but does not change other settings. + */ + private static SparkMaxConfig baseSparkMaxConfig(SparkMaxConfig config){ //typical operating voltage: 12V. - return (SparkMaxConfig) baseSparkConfig(motorConfig);//FIXME apply needed config changes for each controller + return (SparkMaxConfig) baseSparkConfig(config);//FIXME apply needed config changes for each controller } - private static SparkFlexConfig baseSparkFlexConfig(MotorConfig motorConfig){ + private static SparkMaxConfig baseSparkMaxConfig(){ + return (SparkMaxConfig) baseSparkConfig(); + } + /** + * Overrides an old config - but does not change other settings. + */ + private static SparkFlexConfig baseSparkFlexConfig(SparkMaxConfig config){ //typical operating voltage: 12V. ( same as sparkMax ) - return (SparkFlexConfig) baseSparkConfig(motorConfig);//criminal casting usage + return (SparkFlexConfig) baseSparkConfig(config);//criminal casting usage + } + private static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. + return (SparkFlexConfig) baseSparkConfig(); } /** From 7fc3dd21b39013ba161d63c27fe3dc1e7b26b96e Mon Sep 17 00:00:00 2001 From: Team 199 Driver Station Computer <35879629+DriverStationComputer@users.noreply.github.com> Date: Sat, 15 Mar 2025 10:45:09 -0700 Subject: [PATCH 27/63] mae confiogs public --- .../lib199/MotorControllerFactory.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 5fad3a4e..abe4386b 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -151,7 +151,7 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } - private static SparkBaseConfig baseSparkConfig() { + public static SparkBaseConfig baseSparkConfig() { SparkMaxConfig config = new SparkMaxConfig(); config.idleMode(IdleMode.kBrake); @@ -170,7 +170,7 @@ private static SparkBaseConfig baseSparkConfig() { /** * Overrides an old config - but does not change other settings. */ - private static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { + public static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { config.idleMode(IdleMode.kBrake); config.voltageCompensation(12);//FIXME does this need to be different for different motors? @@ -187,21 +187,21 @@ private static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { /** * Overrides an old config - but does not change other settings. */ - private static SparkMaxConfig baseSparkMaxConfig(SparkMaxConfig config){ + public static SparkMaxConfig baseSparkMaxConfig(SparkMaxConfig config){ //typical operating voltage: 12V. return (SparkMaxConfig) baseSparkConfig(config);//FIXME apply needed config changes for each controller } - private static SparkMaxConfig baseSparkMaxConfig(){ + public static SparkMaxConfig baseSparkMaxConfig(){ return (SparkMaxConfig) baseSparkConfig(); } /** * Overrides an old config - but does not change other settings. */ - private static SparkFlexConfig baseSparkFlexConfig(SparkMaxConfig config){ + public static SparkFlexConfig baseSparkFlexConfig(SparkMaxConfig config){ //typical operating voltage: 12V. ( same as sparkMax ) return (SparkFlexConfig) baseSparkConfig(config);//criminal casting usage } - private static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. + public static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. return (SparkFlexConfig) baseSparkConfig(); } From 57a5d84d26e70e7c4c4ec79c4526d85f91b0ab4d Mon Sep 17 00:00:00 2001 From: FriedLongJohns <81837862+FriedLongJohns@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:42:04 -0700 Subject: [PATCH 28/63] fix sparkflex constructor --- .../carlmontrobotics/lib199/MotorConfig.java | 1 - .../lib199/MotorControllerFactory.java | 43 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java b/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java index 0c12de1a..9693498f 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java @@ -11,7 +11,6 @@ public class MotorConfig { // See: https://www.chiefdelphi.com/t/rev-robotics-spark-flex-and-neo-vortex/442595/349?u=brettle // As a result I think 100C should be safe. I wouldn't increase it past 120. --Dean public static final MotorConfig NEO_VORTEX = new MotorConfig(100, 60); - public final int temperatureLimitCelsius, currentLimitAmps; public MotorConfig(int temperatureLimitCelsius, int currentLimitAmps) { diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index abe4386b..9269724e 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -83,8 +83,8 @@ public static WPI_TalonSRX createTalon(int id) { return talon; } - /** - * Create a sparkMax controller (NEO or 550) with defautl settings. + /** + * Create a default sparkMax controller (NEO or 550). * * @param id the port of the motor controller * @param motorConfig either MotorConfig.NEO or MotorConfig.NEO_550 @@ -114,7 +114,12 @@ else if (motorConfig==MotorConfig.NEO_VORTEX) return spark; } - + /** + * Create a sparkMax controller (NEO or 550) with custom settings. + * + * @param id the port of the motor controller + * @param config the custom config to set + */ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { SparkMax spark = null; if (RobotBase.isReal()) { @@ -132,7 +137,38 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { return spark; } + /** + * Create a default sparkFlex-Vortex controller. + * + * @param id the port of the motor controller + */ + public static SparkFlex createSparkFlex(int id) { + MotorConfig motorConfig = MotorConfig.NEO_VORTEX; + SparkFlex spark=null; + if (RobotBase.isReal()) { + spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); + } else { + System.err.println("heyy... lib199 doesn't have sim support sorri"); + // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); + } + + // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); + if (spark!=null) + MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); + + MotorErrors.checkSparkErrors(spark); + + spark.configure(baseSparkFlexConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); + + return spark; + } + /** + * Create a sparkFlex controller (VORTEX) with custom settings. + * + * @param id the port of the motor controller + * @param config the custom config to set + */ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { SparkFlex spark = null; if (RobotBase.isReal()) { @@ -151,6 +187,7 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } + public static SparkBaseConfig baseSparkConfig() { SparkMaxConfig config = new SparkMaxConfig(); From 08249e9dc1c060e163e7b919c7b591fdcf1bf8eb Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 12 Oct 2025 15:17:56 -0700 Subject: [PATCH 29/63] updated to wpilib 2025.3.2 --- .wpilib/wpilib_preferences.json | 2 +- build.gradle | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.wpilib/wpilib_preferences.json b/.wpilib/wpilib_preferences.json index 8d161f67..244061d4 100644 --- a/.wpilib/wpilib_preferences.json +++ b/.wpilib/wpilib_preferences.json @@ -1,6 +1,6 @@ { "enableCppIntellisense": false, "currentLanguage": "java", - "projectYear": "2024", + "projectYear": "2025", "teamNumber": 199 } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 67f117ba..53fdd890 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "edu.wpi.first.GradleRIO" version "2025.2.1" + id "edu.wpi.first.GradleRIO" version "2025.3.2" id "maven-publish" id "eclipse" } @@ -75,6 +75,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'junit:junit:4.13.2' implementation 'org.mockito:mockito-core:5.11.0' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.6' From 244a4369e94af1fdceff1a37837943cdceb4a809 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 12 Oct 2025 15:36:40 -0700 Subject: [PATCH 30/63] fixed the creation of spark flexes? --- .../lib199/MotorControllerFactory.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 9269724e..8e33230e 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -169,7 +169,7 @@ public static SparkFlex createSparkFlex(int id) { * @param id the port of the motor controller * @param config the custom config to set */ - public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { + public static SparkFlex createSparkFlex(int id, SparkFlexConfig config) { SparkFlex spark = null; if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); @@ -187,7 +187,7 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } - + //does this not do the same as baseSparkMaxConfig, as it also creates a Spark MaxConfig? public static SparkBaseConfig baseSparkConfig() { SparkMaxConfig config = new SparkMaxConfig(); @@ -207,6 +207,7 @@ public static SparkBaseConfig baseSparkConfig() { /** * Overrides an old config - but does not change other settings. */ + //does this not do the same as baseSparkMaxConfig, as it also creates a Spark MaxConfig? public static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { config.idleMode(IdleMode.kBrake); @@ -234,12 +235,36 @@ public static SparkMaxConfig baseSparkMaxConfig(){ /** * Overrides an old config - but does not change other settings. */ - public static SparkFlexConfig baseSparkFlexConfig(SparkMaxConfig config){ + public static SparkFlexConfig baseSparkFlexConfig(SparkFlexConfig config){ //typical operating voltage: 12V. ( same as sparkMax ) - return (SparkFlexConfig) baseSparkConfig(config);//criminal casting usage + config.idleMode(IdleMode.kBrake); + + config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.smartCurrentLimit(50); + + config.closedLoop + .minOutput(-1) + .maxOutput(1) + .pid(0,0,0) + .velocityFF(0); + + return config; } public static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. - return (SparkFlexConfig) baseSparkConfig(); + SparkFlexConfig config = new SparkFlexConfig(); + + config.idleMode(IdleMode.kBrake); + + config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.smartCurrentLimit(50); + + config.closedLoop + .minOutput(-1) + .maxOutput(1) + .pid(0,0,0) + .velocityFF(0); + + return config; } /** From b4438e81db9a676d8e5594d1c3821d965e064735 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:24:48 -0800 Subject: [PATCH 31/63] worked on some rev lib errors --- .../lib199/DummySparkMaxAnswerTest.java | 28 ++++++++++++------- .../lib199/MotorErrorsTest.java | 4 ++- .../lib199/sim/MockSparkMaxTest.java | 2 +- .../lib199/sim/MockedEncoderTest.java | 2 +- .../sim/MockedSparkMaxPIDControllerTest.java | 6 ++-- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.java b/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.java index 3b4888aa..4084fc99 100644 --- a/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.java @@ -4,22 +4,29 @@ import static org.junit.Assert.assertNotNull; import com.revrobotics.REVLibError; -import com.revrobotics.CANSparkMax; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkLowLevel.MotorType; +// import com.revrobotics.CANSparkMax; +// import com.revrobotics.CANSparkBase.IdleMode; +// import com.revrobotics.CANSparkLowLevel.MotorType; import com.revrobotics.SparkAnalogSensor.Mode; import com.revrobotics.SparkLimitSwitch.Type; -import com.revrobotics.SparkPIDController.AccelStrategy; +// import com.revrobotics.SparkPIDController.AccelStrategy; + +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.SparkLowLevel.MotorType; + +import com.revrobotics.spark.SparkLimitSwitch;// +import com.revrobotics.spark.SparkClosedLoopController; import org.junit.Test; public class DummySparkMaxAnswerTest { - public CANSparkMax createMockedSparkMax() { - return Mocks.mock(CANSparkMax.class, new DummySparkMaxAnswer()); + public SparkMax createMockedSparkMax() { + return Mocks.mock(SparkMax.class, new DummySparkMaxAnswer()); } - public static void assertTestResponses(CANSparkMax spark) { + public static void assertTestResponses(SparkMax spark) { // Check device id assertEquals(0, spark.getDeviceId()); @@ -31,16 +38,16 @@ public static void assertTestResponses(CANSparkMax spark) { assertNotNull(spark.getAnalog((Mode) null)); assertNotNull(spark.getEncoder()); assertNotNull(spark.getForwardLimitSwitch((Type) null)); - assertNotNull(spark.getPIDController()); + assertNotNull(spark.getClosedLoopController()); // Check that all REV specific objects return "null" values assertEquals(0, spark.getEncoder().getPosition(), 0.01); assertEquals(0, spark.getEncoder().getVelocity(), 0.01); assertEquals(IdleMode.kBrake, spark.getIdleMode()); assertEquals(MotorType.kBrushless, spark.getMotorType()); - assertEquals(AccelStrategy.kTrapezoidal, spark.getPIDController().getSmartMotionAccelStrategy(0)); + assertEquals(AccelStrategy.kTrapezoidal, spark.getClosedLoopController().getSmartMotionAccelStrategy(0)); assertEquals(REVLibError.kOk, spark.getLastError()); - assertEquals(REVLibError.kOk, spark.getPIDController().setP(0, 0)); + assertEquals(REVLibError.kOk, spark.getClosedLoopController().setP(0, 0)); assertEquals(REVLibError.kOk, spark.getEncoder().setAverageDepth(0)); assertEquals(REVLibError.kOk, spark.getAnalog((Mode) null).setInverted(false)); } @@ -51,3 +58,4 @@ public void testResponses() { } } + diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java index bdb06851..7e4ad708 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java @@ -10,9 +10,11 @@ import com.ctre.phoenix.ErrorCode; import com.revrobotics.REVLibError; -import com.revrobotics.CANSparkMax; +// import com.revrobotics.SparkMax; import com.revrobotics.CANSparkBase.FaultID; +import com.revrobotics.spark.SparkMax; + import org.carlmontrobotics.lib199.testUtils.ErrStreamTest; import org.junit.Test; diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java index d5fde3bb..7f194bce 100644 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java @@ -3,7 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import com.revrobotics.CANSparkLowLevel.MotorType; +import com.revrobotics.spark.SparkLowLevel.MotorType; import org.carlmontrobotics.lib199.testUtils.TestRules; import org.junit.ClassRule; diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java index 1f842f56..86720b41 100644 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java @@ -60,7 +60,7 @@ public void testFunctionality() { } private void testFunctionalityWithPositionConversionFactor(double factor, RelativeEncoder enc, SimDouble positionSim) { - assertEquals(REVLibError.kOk, enc.setPositionConversionFactor(factor)); + assertEquals(REVLibError.kOk, enc.setPositionConversionFactor(factor)); //FIXME: make this in a config with positionConversionFactor assertEquals(factor, enc.getPositionConversionFactor(), 0.01); testPosition(10, enc, factor, positionSim); testPosition(0, enc, factor, positionSim); diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java index b958dbd3..e5f08a2d 100644 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java @@ -7,8 +7,8 @@ import java.util.function.Supplier; import com.revrobotics.REVLibError; -import com.revrobotics.SparkPIDController; -import com.revrobotics.CANSparkLowLevel.MotorType; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkLowLevel.MotorType; import org.carlmontrobotics.lib199.Mocks; import org.carlmontrobotics.lib199.REVLibErrorAnswer; @@ -19,7 +19,7 @@ public class MockedSparkMaxPIDControllerTest { @Test public void testResponses() { MockSparkMax mockSparkMax = new MockSparkMax(0, MotorType.kBrushless); - SparkPIDController mock = Mocks.createMock(SparkPIDController.class, new MockedSparkMaxPIDController(mockSparkMax), new REVLibErrorAnswer()); + SparkClosedLoopController mock = Mocks.createMock(SparkClosedLoopController.class, new MockedSparkMaxPIDController(mockSparkMax), new REVLibErrorAnswer()); assertSlotValueUpdate(mock::setP, mock::setP, mock::getP, mock::getP); assertSlotValueUpdate(mock::setI, mock::setI, mock::getI, mock::getI); assertSlotValueUpdate(mock::setD, mock::setD, mock::getD, mock::getD); From 755be22ecae62786ba8f9d71f1c70e0d86ccdd37 Mon Sep 17 00:00:00 2001 From: Niosocket11 Date: Sat, 6 Dec 2025 18:49:00 -0800 Subject: [PATCH 32/63] Updated MockedEncoder with rev changes --- .../lib199/sim/MockedEncoder.java | 58 ++++++++++--------- .../lib199/sim/MockedEncoderTest.java | 24 +++++--- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java index ed813502..e4f8fc75 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java @@ -128,38 +128,42 @@ public double getVelocity() { * velocityConversionFactor; } - // @Override - // public REVLibError setPositionConversionFactor(double factor) { - // positionConversionFactor = factor; - // return REVLibError.kOk; - // } + //Let Mock to map these methods although they don't exist in the original class + public interface RemovedMethods { + REVLibError setPositionConversionFactor(double factor); + double getPositionConversionFactor(); + REVLibError setVelocityConversionFactor(double factor); + double getVelocityConversionFactor(); + REVLibError setInverted(boolean inverted); + boolean getInverted(); + } - // @Override - // public double getPositionConversionFactor() { - // return positionConversionFactor; - // } + public REVLibError setPositionConversionFactor(double factor) { + positionConversionFactor = factor; + return REVLibError.kOk; + } - // @Override - // public REVLibError setVelocityConversionFactor(double factor) { - // velocityConversionFactor = factor; - // return REVLibError.kOk; - // } + public double getPositionConversionFactor() { + return positionConversionFactor; + } - // @Override - // public double getVelocityConversionFactor() { - // return velocityConversionFactor; - // } + public REVLibError setVelocityConversionFactor(double factor) { + velocityConversionFactor = factor; + return REVLibError.kOk; + } - // @Override - // public REVLibError setInverted(boolean inverted) { - // this.inverted = inverted; - // return REVLibError.kOk; - // } + public double getVelocityConversionFactor() { + return velocityConversionFactor; + } - // @Override - // public boolean getInverted() { - // return inverted; - // } + public REVLibError setInverted(boolean inverted) { + this.inverted = inverted; + return REVLibError.kOk; + } + + public boolean getInverted() { + return inverted; + } // @Override // public REVLibError setAverageDepth(int depth) { diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java index 86720b41..d69a277e 100644 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/sim/MockedEncoderTest.java @@ -9,6 +9,12 @@ import com.revrobotics.REVLibError; import com.revrobotics.RelativeEncoder; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkFlexConfig; +import com.revrobotics.spark.config.SparkMaxConfig; import org.carlmontrobotics.lib199.Mocks; import org.carlmontrobotics.lib199.REVLibErrorAnswer; @@ -60,8 +66,8 @@ public void testFunctionality() { } private void testFunctionalityWithPositionConversionFactor(double factor, RelativeEncoder enc, SimDouble positionSim) { - assertEquals(REVLibError.kOk, enc.setPositionConversionFactor(factor)); //FIXME: make this in a config with positionConversionFactor - assertEquals(factor, enc.getPositionConversionFactor(), 0.01); + assertEquals(REVLibError.kOk, ((MockedEncoder) enc).setPositionConversionFactor(factor)); + assertEquals(factor, ((MockedEncoder) enc).getPositionConversionFactor(), 0.01); testPosition(10, enc, factor, positionSim); testPosition(0, enc, factor, positionSim); testPosition(-10, enc, factor, positionSim); @@ -73,7 +79,7 @@ private void testPosition(double position, RelativeEncoder enc, double conversio assertEquals(position, enc.getPosition(), 0.02); assertEquals(REVLibError.kOk, enc.setPosition(0)); assertEquals(0, enc.getPosition(), 0.02); - positionSim.set(position / enc.getPositionConversionFactor() + positionSim.get()); + positionSim.set(position / ((MockedEncoder) enc).getPositionConversionFactor() + positionSim.get()); assertEquals(position, enc.getPosition(), 0.02); } @@ -87,11 +93,11 @@ private boolean simDeviceExists(String deviceName) { private SafelyClosable createEncoder(int deviceId) { SimDevice device = SimDevice.create("testDevice", deviceId); - return (SafelyClosable)Mocks.createMock( - RelativeEncoder.class, - new MockedEncoder(device, 4096, false, false), - new REVLibErrorAnswer(), - SafelyClosable.class); + return (SafelyClosable)Mocks.createMock( + RelativeEncoder.class, + new MockedEncoder(device, 4096, false, false), + new REVLibErrorAnswer(), + SafelyClosable.class, MockedEncoder.RemovedMethods.class); } private void withEncoders(EncoderTest func) { @@ -112,5 +118,5 @@ private void withEncoder(int id, EncoderTest func) { private interface EncoderTest { public void test(RelativeEncoder encoder, SimDeviceSim sim, SimDouble posSim); } - + } From 0e814d3b6c52afe895490e99e47e951de24806ad Mon Sep 17 00:00:00 2001 From: Niosocket11 Date: Sat, 6 Dec 2025 20:40:44 -0800 Subject: [PATCH 33/63] Fixed remaining testing issues --- .../lib199/sim/MockSparkBase.java | 4 +- ...a => MockedSparkClosedLoopController.java} | 249 +++++++++++++----- ...rTest.java => DummySparkMaxAnswerTest.txt} | 0 .../lib199/MotorErrorsTest.java | 31 ++- .../lib199/REVLibErrorAnswerTest.java | 2 +- .../lib199/sim/MockSparkMaxTest.java | 7 +- ... MockedSparkClosedLoopControllerTest.java} | 7 +- 7 files changed, 212 insertions(+), 88 deletions(-) rename src/main/java/org/carlmontrobotics/lib199/sim/{MockedSparkMaxPIDController.java => MockedSparkClosedLoopController.java} (74%) rename src/test/java/org/carlmontrobotics/lib199/{DummySparkMaxAnswerTest.java => DummySparkMaxAnswerTest.txt} (100%) rename src/test/java/org/carlmontrobotics/lib199/sim/{MockedSparkMaxPIDControllerTest.java => MockedSparkClosedLoopControllerTest.java} (89%) diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java index 45ab3d8c..2c94b326 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java @@ -43,7 +43,7 @@ public class MockSparkBase extends MockedMotorBase { private final SparkBase motor; private final SparkSim spark; private final SparkClosedLoopController pidController; - private final MockedSparkMaxPIDController pidControllerImpl; + private final MockedSparkClosedLoopController pidControllerImpl; private SparkAbsoluteEncoder absoluteEncoder = null; private SparkAbsoluteEncoderSim absoluteEncoderImpl = null; private SparkMaxAlternateEncoder alternateEncoder = null; @@ -106,7 +106,7 @@ public MockSparkBase(int port, MotorType type, String name, int countsPerRev, NE encoder = new MockedEncoder(SimDevice.create("CANEncoder:" + name, port), countsPerRev, false, false); } - pidControllerImpl = new MockedSparkMaxPIDController(this); + pidControllerImpl = new MockedSparkClosedLoopController(this); pidController = Mocks.createMock(SparkClosedLoopController.class, pidControllerImpl, new REVLibErrorAnswer()); // pidController.feedbackSensor(encoder); diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkClosedLoopController.java similarity index 74% rename from src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java rename to src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkClosedLoopController.java index c4720aa7..62400f22 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedSparkClosedLoopController.java @@ -4,9 +4,12 @@ import java.util.concurrent.ConcurrentHashMap; import com.revrobotics.REVLibError; +import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.SparkAbsoluteEncoder; import com.revrobotics.spark.SparkAnalogSensor; +import com.revrobotics.spark.SparkBase; import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkClosedLoopController.ArbFFUnits; import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.SparkMaxAlternateEncoder; import com.revrobotics.spark.SparkRelativeEncoder; @@ -20,11 +23,12 @@ import edu.wpi.first.wpilibj.motorcontrol.MotorController; // NOT THREAD SAFE -public class MockedSparkMaxPIDController { +public class MockedSparkClosedLoopController { public final Map slots = new ConcurrentHashMap<>(); public final MockedMotorBase motor; public Slot activeSlot; + public ClosedLoopSlot activeClosedLoopSlot; public SparkMax.ControlType controlType = SparkMax.ControlType.kDutyCycle; public MotorController leader = null; public boolean invertLeader = false; @@ -35,6 +39,180 @@ public class MockedSparkMaxPIDController { public double positionPIDWrappingMinInput = 0.0; public double positionPIDWrappingMaxInput = 0.0; + public MockedSparkClosedLoopController(MockedMotorBase motor) { + this.motor = motor; + slots.put(0, activeSlot = new Slot(positionPIDWrappingMinInput, positionPIDWrappingMaxInput, positionPIDWrappingEnabled)); + } + + //Actually Real Methods + + /** Get the selected control type used when setReference(double, SparkBase.ControlType) was last called.*/ + public SparkBase.ControlType getControlType() { + return controlType; + } + + /** Get the I accumulator of the closed loop controller. */ + public double getIAccum() { + System.err.println("(MockedSparkMaxPIDController): getIAccum() is not currently implemented"); + return 0; + } + + /**Get the MAXMotion internal setpoint position. */ + public double getMAXMotionSetpointPosition() { + return setpoint; + } + + /**Get the MAXMotion internal setpoint velocity. */ + public double getMAXMotionSetpointVelocity() { + return setpoint; + } + + /** Get the selected closed loop PID slot. */ + public ClosedLoopSlot getSelectedSlot() { + return activeClosedLoopSlot; + } + + /**Get the internal setpoint of the closed loop controller. */ + public double getSetpoint() { + return setpoint; + } + + /** Determine if the setpoint has been reached.*/ + public boolean isAtSetpoint() {return false;} + + /** Set the I accumulator of the closed loop controller. */ + public REVLibError setIAccum(double iAccum) { + System.err.println("(MockedSparkMaxPIDController): setIAccum() is not currently implemented"); + return REVLibError.kNotImplemented; + } + /** Deprecated, for removal: This API element is subject to removal in a future version. + * Use {@link #setSetpoint(double, SparkBase.ControlType)} instead + */ + @Deprecated + public REVLibError setReference(double value, SparkMax.ControlType ctrl) { + return setReference(value, ctrl, ClosedLoopSlot.kSlot0); + } + /** Deprecated, for removal: This API element is subject to removal in a future version. + * Use {@link #setSetpoint(double, SparkBase.ControlType, ClosedLoopSlot)} instead + */ + @Deprecated + public REVLibError setReference(double value, SparkMax.ControlType ctrl, ClosedLoopSlot pidSlot) { + return setReference(value, ctrl, pidSlot, 0); + } + /** Deprecated, for removal: This API element is subject to removal in a future version. + * Use {@link #setSetpoint(double, SparkBase.ControlType, ClosedLoopSlot, double)} instead*/ + @Deprecated + public REVLibError setReference(double value, SparkMax.ControlType ctrl, ClosedLoopSlot pidSlot, double arbFeedforward) { + return setReference(value, ctrl, pidSlot, arbFeedforward, SparkClosedLoopController.ArbFFUnits.kVoltage); + } + /** Deprecated, for removal: This API element is subject to removal in a future version. + * Use {@link #setSetpoint(double, SparkBase.ControlType, ClosedLoopSlot, double, ArbFFUnits)} instead + */ + @Deprecated + public REVLibError setReference(double value, SparkMax.ControlType ctrl, ClosedLoopSlot pidSlot, double arbFeedforward, SparkClosedLoopController.ArbFFUnits arbFFUnits) { + if(ctrl == SparkMax.ControlType.kSmartVelocity) { + System.err.println("(MockedSparkMaxPIDController): setReference() with ControlType.kSmartVelocity is not currently implemented"); + return REVLibError.kNotImplemented; + } + + setpoint = value; + controlType = ctrl; + leader = null; + + switch(ctrl) { + case kDutyCycle: + case kVoltage: + motor.setClosedLoopControl(false); + break; + case kPosition: + case kVelocity: + case kSmartMotion: + case kMAXMotionPositionControl: + case kMAXMotionVelocityControl: + case kCurrent: + motor.setClosedLoopControl(true); + break; + case kSmartVelocity: + break; // This should never happen + } + + activeSlot = getSlot(pidSlot.value); + activeClosedLoopSlot = pidSlot; + + switch(arbFFUnits) { + case kVoltage: + break; + case kPercentOut: + arbFeedforward *= 12.0; + break; + default: + throw new IllegalArgumentException("Unsupported ArbFFUnits: " + arbFFUnits); + } + + this.arbFF = arbFeedforward; + + return REVLibError.kOk; + } + REVLibError setSetpoint(double setpoint, SparkBase.ControlType ctrl) {return null;}//Set the controller setpoint based on the selected control mode. + REVLibError setSetpoint(double setpoint, SparkBase.ControlType ctrl, ClosedLoopSlot slot) {return null;}//Set the controller setpoint based on the selected control mode. + REVLibError setSetpoint(double setpoint, SparkBase.ControlType ctrl, ClosedLoopSlot slot, double arbFeedforward) {return null;}//Set the controller setpoint based on the selected control mode. + REVLibError setSetpoint(double setpoint, SparkBase.ControlType ctrl, ClosedLoopSlot slot, double arbFeedforward, SparkClosedLoopController.ArbFFUnits arbFFUnits) {return null;}//Set the controller setpoint based on the selected control mode. + + public interface ExtraMethods { + double calculate(double currentDraw); + void setDutyCycle(double speed); + void follow(MotorController leader, boolean invert); + void stopFollowing(); + boolean isFollower(); + double getD(); + double getD(int slotID); + double getFF(); + double getFF(int slotID); + double getI(); + double getI(int slotID); + double getIMaxAccum(int slotID); + double getIZone(); + double getIZone(int slotID); + double getOutputMax(); + double getOutputMax(int slotID); + double getOutputMin(); + double getOutputMin(int slotID); + boolean getPositionPIDWrappingEnabled(); + double getPositionPIDWrappingMaxInput(); + double getPositionPIDWrappingMinInput(); + REVLibError setPositionPIDWrappingEnabled(boolean enable); + REVLibError setPositionPIDWrappingMaxInput(double max); + REVLibError setPositionPIDWrappingMinInput(double min); + double getP(); + double getP(int slotID); + MAXMotionConfig.MAXMotionPositionMode getSmartMotionAccelStrategy(int slotID); + double getSmartMotionAllowedClosedLoopError(int slotID); + double getSmartMotionMaxAccel(int slotID); + double getSmartMotionMaxVelocity(int slotID); + double getSmartMotionMinOutputVelocity(int slotID); + REVLibError setD(double gain); + REVLibError setD(double gain, int slotID); + REVLibError setFeedbackDevice(Object sensor); + REVLibError setFF(double gain); + REVLibError setFF(double gain, int slotID); + REVLibError setI(double gain); + REVLibError setI(double gain, int slotID); + REVLibError setIMaxAccum(double iMaxAccum, int slotID); + REVLibError setIZone(double IZone); + REVLibError setIZone(double IZone, int slotID); + REVLibError setOutputRange(double min, double max); + REVLibError setOutputRange(double min, double max, int slotID); + REVLibError setP(double gain); + REVLibError setP(double gain, int slotID); + REVLibError setSmartMotionAccelStrategy(MAXMotionConfig.MAXMotionPositionMode accelStrategy, int slotID); + REVLibError setSmartMotionAllowedClosedLoopError(double allowedErr, int slotId); + REVLibError setSmartMotionMaxAccel(double maxAccel, int slotID); + REVLibError setSmartMotionMaxVelocity(double maxVel, int slotID); + REVLibError setSmartMotionMinOutputVelocity(double minVel, int slotID); + Slot getSlot(int slotID); + } + + // Methods to interface with MockSparkMax public double calculate(double currentDraw) { if(leader != null) { @@ -94,13 +272,7 @@ public boolean isFollower() { return leader != null; } - public MockedSparkMaxPIDController(MockedMotorBase motor) { - this.motor = motor; - slots.put(0, activeSlot = new Slot(positionPIDWrappingMinInput, positionPIDWrappingMaxInput, positionPIDWrappingEnabled)); - } - // Overrides - public double getD() { return getD(0); } @@ -127,11 +299,6 @@ public double getI(int slotID) { return slot.pidController.getI(); } - public double getIAccum() { - System.err.println("(MockedSparkMaxPIDController): getIAccum() is not currently implemented"); - return 0; - } - public double getIMaxAccum(int slotID) { return getSlot(slotID).iMaxAccum; } @@ -343,11 +510,6 @@ public REVLibError setI(double gain, int slotID) { return REVLibError.kOk; } - public REVLibError setIAccum(double iAccum) { - System.err.println("(MockedSparkMaxPIDController): setIAccum() is not currently implemented"); - return REVLibError.kNotImplemented; - } - public REVLibError setIMaxAccum(double iMaxAccum, int slotID) { Slot slot = getSlot(slotID); slot.pidController.setIntegratorRange(-iMaxAccum, iMaxAccum); @@ -387,59 +549,6 @@ public REVLibError setP(double gain, int slotID) { return REVLibError.kOk; } - public REVLibError setReference(double value, SparkMax.ControlType ctrl) { - return setReference(value, ctrl, 0); - } - - public REVLibError setReference(double value, SparkMax.ControlType ctrl, int pidSlot) { - return setReference(value, ctrl, pidSlot, 0); - } - - public REVLibError setReference(double value, SparkMax.ControlType ctrl, int pidSlot, double arbFeedforward) { - return setReference(value, ctrl, pidSlot, arbFeedforward, SparkClosedLoopController.ArbFFUnits.kVoltage); - } - - public REVLibError setReference(double value, SparkMax.ControlType ctrl, int pidSlot, double arbFeedforward, SparkClosedLoopController.ArbFFUnits arbFFUnits) { - if(ctrl == SparkMax.ControlType.kSmartVelocity) { - System.err.println("(MockedSparkMaxPIDController): setReference() with ControlType.kSmartVelocity is not currently implemented"); - return REVLibError.kNotImplemented; - } - - setpoint = value; - controlType = ctrl; - leader = null; - - switch(ctrl) { - case kDutyCycle: - case kVoltage: - motor.setClosedLoopControl(false); - break; - case kPosition: - case kVelocity: - case kSmartMotion: - case kCurrent: - motor.setClosedLoopControl(true); - break; - case kSmartVelocity: - break; // This should never happen - } - - activeSlot = getSlot(pidSlot); - - switch(arbFFUnits) { - case kVoltage: - break; - case kPercentOut: - arbFeedforward *= 12.0; - break; - default: - throw new IllegalArgumentException("Unsupported ArbFFUnits: " + arbFFUnits); - } - - this.arbFF = arbFeedforward; - - return REVLibError.kOk; - } public REVLibError setSmartMotionAccelStrategy(MAXMotionConfig.MAXMotionPositionMode accelStrategy, int slotID) { if(accelStrategy != MAXMotionConfig.MAXMotionPositionMode.kMAXMotionTrapezoidal) { diff --git a/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.java b/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.txt similarity index 100% rename from src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.java rename to src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.txt diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java index 7e4ad708..754b0616 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java @@ -11,7 +11,7 @@ import com.ctre.phoenix.ErrorCode; import com.revrobotics.REVLibError; // import com.revrobotics.SparkMax; -import com.revrobotics.CANSparkBase.FaultID; +//import com.revrobotics.spark.SparkBase.Faults; import com.revrobotics.spark.SparkMax; @@ -21,6 +21,18 @@ import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; public class MotorErrorsTest extends ErrStreamTest { + public enum FaultID { + kBrownout(0), kOvercurrent(1), kOvervoltage(2), kMotorFault(3), kSensorFault(4), kStall(5), kEEPROMCRC(6), + kCANTX(7), kCANRX(8), kHasReset(9), kDRVFault(10), kOtherFault(11), kSoftLimitFwd(12), kSoftLimitRev(13), + kHardLimitFwd(14), kHardLimitRev(15); + + @SuppressWarnings("MemberName") + public final int value; + + FaultID(int value) { + this.value = value; + } + } public static class SensorFaultSparkMax { public short getFaults() { @@ -134,7 +146,7 @@ public void testOtherErrors() { @Test public void testFaultReporting() { - CANSparkMax sensorFaultSparkMax = Mocks.createMock(CANSparkMax.class, new SensorFaultSparkMax(), false); + SparkMax sensorFaultSparkMax = Mocks.createMock(SparkMax.class, new SensorFaultSparkMax(), false); errStream.reset(); MotorErrors.checkSparkMaxErrors(sensorFaultSparkMax); assertNotEquals(0, errStream.toByteArray().length); @@ -145,7 +157,7 @@ public void testFaultReporting() { @Test public void testStickyFaultReporting() { - CANSparkMax stickySensorFaultSparkMax = Mocks.createMock(CANSparkMax.class, new StickySensorFaultSparkMax(), false); + SparkMax stickySensorFaultSparkMax = Mocks.createMock(SparkMax.class, new StickySensorFaultSparkMax(), false); errStream.reset(); MotorErrors.checkSparkMaxErrors(stickySensorFaultSparkMax); assertNotEquals(0, errStream.toByteArray().length); @@ -154,10 +166,11 @@ public void testStickyFaultReporting() { assertEquals(0, errStream.toByteArray().length); } - @Test - public void testDummySparkMax() { - DummySparkMaxAnswerTest.assertTestResponses(MotorErrors.createDummySparkMax()); - } + //FIXME: do we need this? + // @Test + // public void testDummySparkMax() { + // DummySparkMaxAnswerTest.assertTestResponses(MotorErrors.createDummySparkMax()); + // } @Test public void testReportSparkMaxTemp() { @@ -169,9 +182,9 @@ public void testReportSparkMaxTemp() { } private void doTestReportSparkMaxTemp(int id) { - TemperatureSparkMax spark = (TemperatureSparkMax)Mocks.createMock(CANSparkMax.class, new TemperatureSparkMax.Instance(id), TemperatureSparkMax.class); + TemperatureSparkMax spark = (TemperatureSparkMax)Mocks.createMock(SparkMax.class, new TemperatureSparkMax.Instance(id), TemperatureSparkMax.class); String smartDashboardKey = "Port " + id + " Spark Max Temp"; - MotorErrors.reportSparkMaxTemp((CANSparkMax)spark, 40); + MotorErrors.reportSparkMaxTemp((SparkMax)spark, 40); spark.setTemperature(20); spark.setSmartCurrentLimit(50); diff --git a/src/test/java/org/carlmontrobotics/lib199/REVLibErrorAnswerTest.java b/src/test/java/org/carlmontrobotics/lib199/REVLibErrorAnswerTest.java index 748098ed..92e9c1bb 100644 --- a/src/test/java/org/carlmontrobotics/lib199/REVLibErrorAnswerTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/REVLibErrorAnswerTest.java @@ -18,7 +18,7 @@ public void testResponses() throws Exception { assertEquals(0, enc.getVelocity(), 0.01); // Check that REVLibError functions return REVLibError.kOk - assertEquals(REVLibError.kOk, enc.setInverted(false)); + assertEquals(REVLibError.kOk, enc.setPosition(0)); } } diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java index 7f194bce..51a08acb 100644 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/sim/MockSparkMaxTest.java @@ -5,6 +5,7 @@ import com.revrobotics.spark.SparkLowLevel.MotorType; +import org.carlmontrobotics.lib199.sim.MockSparkBase.NEOType; import org.carlmontrobotics.lib199.testUtils.TestRules; import org.junit.ClassRule; import org.junit.Rule; @@ -21,7 +22,7 @@ public class MockSparkMaxTest { @Test public void testHasEncoder() { - var mockSpark = new MockSparkMax(0, MotorType.kBrushless); + var mockSpark = new MockSparkMax(0, MotorType.kBrushless, NEOType.NEO); SimDeviceSim simSpark = new SimDeviceSim("CANMotor:CANSparkMax", 0); assertNotNull(simSpark); SimDeviceSim simEncoder = new SimDeviceSim("CANEncoder:CANSparkMax", 0); @@ -33,7 +34,7 @@ public void testHasEncoder() { @Test public void testCanRecreateIfClosed() { for (int i = 0; i < 2; i++) { - try (var mockSpark = new MockSparkMax(0, MotorType.kBrushless)) { + try (var mockSpark = new MockSparkMax(0, MotorType.kBrushless, NEOType.NEO)) { SimDeviceSim simSpark = new SimDeviceSim("CANMotor:CANSparkMax", 0); assertNotNull(simSpark); @@ -48,7 +49,7 @@ public void testCanRecreateIfClosed() { @Test public void testGetOutputCurrent() { - var mockSpark = new MockSparkMax(0, MotorType.kBrushless); + var mockSpark = new MockSparkMax(0, MotorType.kBrushless, NEOType.NEO); SimDeviceSim simSpark = new SimDeviceSim("CANMotor:CANSparkMax", 0); assertNotNull(simSpark); SimDouble simCurrent = simSpark.getDouble("motorCurrent"); diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkClosedLoopControllerTest.java similarity index 89% rename from src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java rename to src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkClosedLoopControllerTest.java index e5f08a2d..bf3ba5fa 100644 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkMaxPIDControllerTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/sim/MockedSparkClosedLoopControllerTest.java @@ -12,14 +12,15 @@ import org.carlmontrobotics.lib199.Mocks; import org.carlmontrobotics.lib199.REVLibErrorAnswer; +import org.carlmontrobotics.lib199.sim.MockSparkBase.NEOType; import org.junit.Test; -public class MockedSparkMaxPIDControllerTest { +public class MockedSparkClosedLoopControllerTest { @Test public void testResponses() { - MockSparkMax mockSparkMax = new MockSparkMax(0, MotorType.kBrushless); - SparkClosedLoopController mock = Mocks.createMock(SparkClosedLoopController.class, new MockedSparkMaxPIDController(mockSparkMax), new REVLibErrorAnswer()); + MockSparkMax mockSparkMax = new MockSparkMax(0, MotorType.kBrushless, NEOType.NEO); + MockedSparkClosedLoopController mock = new MockedSparkClosedLoopController(mockSparkMax); assertSlotValueUpdate(mock::setP, mock::setP, mock::getP, mock::getP); assertSlotValueUpdate(mock::setI, mock::setI, mock::getI, mock::getI); assertSlotValueUpdate(mock::setD, mock::setD, mock::getD, mock::getD); From 72bdbc80360280d6ea6344e39e43da717bf9ed57 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:08:22 -0800 Subject: [PATCH 34/63] resolved most comments --- .../lib199/MotorControllerFactory.java | 11 +++++++---- .../org/carlmontrobotics/lib199/MotorErrors.java | 13 +++++-------- .../lib199/SparkVelocityPIDController.java | 6 +++--- .../carlmontrobotics/lib199/sim/MockSparkBase.java | 14 +++++++------- .../carlmontrobotics/lib199/sim/MockSparkFlex.java | 2 +- .../lib199/swerve/SwerveModule.java | 7 ++----- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 8e33230e..637527df 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -13,6 +13,7 @@ import com.ctre.phoenix6.hardware.CANcoder; import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkLowLevel.MotorType; import com.revrobotics.spark.config.SparkBaseConfig; import com.revrobotics.spark.config.SparkFlexConfig; import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; @@ -24,11 +25,14 @@ import com.revrobotics.spark.SparkLowLevel; import com.revrobotics.spark.SparkClosedLoopController; +import org.carlmontrobotics.lib199.sim.MockSparkFlex; +import org.carlmontrobotics.lib199.sim.MockSparkMax; // import org.carlmontrobotics.lib199.sim.MockSparkFlex; // import org.carlmontrobotics.lib199.sim.MockSparkMax; import org.carlmontrobotics.lib199.sim.MockTalonSRX; import org.carlmontrobotics.lib199.sim.MockVictorSPX; import org.carlmontrobotics.lib199.sim.MockedCANCoder; +import org.mockito.Mock; import edu.wpi.first.cameraserver.CameraServer; import edu.wpi.first.cscore.UsbCamera; @@ -125,8 +129,7 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - System.err.println("heyy... lib199 doesn't have sim support sorri"); - // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); + spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); } if (spark!=null) spark.configure( @@ -150,7 +153,7 @@ public static SparkFlex createSparkFlex(int id) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); } else { System.err.println("heyy... lib199 doesn't have sim support sorri"); - // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); + spark = MockSparkFlex.createMockSparkFlex(id, MotorType.kBrushless); } // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); @@ -193,7 +196,7 @@ public static SparkBaseConfig baseSparkConfig() { config.idleMode(IdleMode.kBrake); - config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.voltageCompensation(12); config.smartCurrentLimit(50); config.closedLoop diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 076d30d8..4ee8941d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -27,6 +27,8 @@ public final class MotorErrors { private static final Map stickyFlags = new ConcurrentSkipListMap<>( (spark1, spark2) -> (spark1.getDeviceId() - spark2.getDeviceId())); + private static final SparkBaseConfig OVERHEAT_CONFIG = new SparkMaxConfig().smartCurrentLimit(1); + public static final int kOverheatTripCount = 5; static { @@ -137,17 +139,12 @@ static void reportNextNSparkErrors(int n) { lastSparkErrorIndexReported = (lastSparkErrorIndexReported + n) % flags.size(); } - //what does this even supposed to do?? - public static SparkMax createDummySparkMax() { - return Mocks.mock(SparkMax.class, new REVLibErrorAnswer()); - } - @Deprecated public static void reportSparkMaxTemp(SparkMax spark, TemperatureLimit temperatureLimit) { reportSparkMaxTemp(spark, temperatureLimit.limit); } - public static boolean isSparkMaxOverheated(SparkMax spark){ + public static boolean isSparkOverheated(SparkBase spark){ int id = spark.getDeviceId(); int motorMaxTemp = sparkTemperatureLimits.get(id); return ( spark.getMotorTemperature() >= motorMaxTemp ); @@ -216,8 +213,8 @@ private static void reportSparkTemp(int port, SparkBase spark) { + " degrees Celsius! It will be disabled until the robot code is restarted."); } spark.configure( - new SparkMaxConfig().smartCurrentLimit(1), - SparkBase.ResetMode.kResetSafeParameters, + OVERHEAT_CONFIG, + SparkBase.ResetMode.kNoResetSafeParameters, SparkBase.PersistMode.kNoPersistParameters); } } diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java index 66bb7c03..f8d1b486 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java @@ -1,6 +1,6 @@ package org.carlmontrobotics.lib199; -import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkBase; import com.revrobotics.spark.config.ClosedLoopConfig; import com.revrobotics.spark.config.SparkBaseConfig; import com.revrobotics.spark.config.SparkMaxConfig; @@ -20,14 +20,14 @@ public class SparkVelocityPIDController implements Sendable { @SuppressWarnings("unused") - private final SparkMax spark; + private final SparkBase spark; private final SparkClosedLoopController pidController; private final RelativeEncoder encoder; private final String name; private double targetSpeed, tolerance; private double currentP, currentI, currentD, kS, kV; - public SparkVelocityPIDController(String name, SparkMax spark, double defaultP, double defaultI, double defaultD, double kS, double kV, double targetSpeed, double tolerance) { + public SparkVelocityPIDController(String name, SparkBase spark, double defaultP, double defaultI, double defaultD, double kS, double kV, double targetSpeed, double tolerance) { this.spark = spark; this.pidController = spark.getClosedLoopController(); this.encoder = spark.getEncoder(); diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java index 2c94b326..ef466638 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkBase.java @@ -52,10 +52,10 @@ public class MockSparkBase extends MockedMotorBase { private SparkAnalogSensorSim analogSensorImpl = null; private final String name; - enum NEOType { + public enum NEOType { //is it fine if we make it public so that MotorControllerFactory can access it? NEO(DCMotor.getNEO(1)), NEO550(DCMotor.getNeo550(1)), - Vortex(DCMotor.getNeoVortex(1)), + VORTEX(DCMotor.getNeoVortex(1)), UNKNOWN(DCMotor.getNEO(1)); public DCMotor dcMotor; @@ -73,20 +73,21 @@ private NEOType(DCMotor dcmotordata){ * to follow the inversion state of the motor and its {@code setInverted} method will be disabled. * @param name the name of the type of controller ("SparkMax" or "SparkFlex") * @param countsPerRev the number of counts per revolution of this controller's built-in encoder. + * @param neoType the type of NEO motor */ public MockSparkBase(int port, MotorType type, String name, int countsPerRev, NEOType neoType) { super(name, port); this.type = type; this.name = name; - if (neoType != NEOType.Vortex){ //WARNING can't initialize a sparkbase without an actual spark... - this.motor = new SparkMax(port,type); + if (neoType == NEOType.VORTEX){ //only vortex uses sparkflex + this.motor = new SparkFlex(port,type); this.spark = new SparkSim( this.motor, neoType.dcMotor ); - } else { //only vortex uses sparkflex - this.motor = new SparkFlex(port,type); + } else { //WARNING can't initialize a sparkbase without an actual spark... + this.motor = new SparkMax(port,type); this.spark = new SparkSim( this.motor, neoType.dcMotor @@ -229,7 +230,6 @@ public void close() { if (encoder != null) { encoder.close(); } - //simply drop all references for garbage collection (?) absoluteEncoderImpl=null; analogSensorImpl=null; alternateEncoder=null; diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java index 78a46f71..acc97814 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockSparkFlex.java @@ -9,7 +9,7 @@ public class MockSparkFlex extends MockSparkBase { public MockSparkFlex(int port, MotorType type) { - super(port, type, "CANSparkFlex", 7168, NEOType.Vortex); + super(port, type, "CANSparkFlex", 7168, NEOType.VORTEX); } public static SparkFlex createMockSparkFlex(int portPWM, MotorType type) { diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index b83c112d..a7b235cc 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -8,7 +8,6 @@ import org.mockito.internal.reporting.SmartPrinter; -// import com.ctre.phoenix6.signals.AbsoluteSensorRangeValue; import com.ctre.phoenix6.configs.CANcoderConfiguration; import com.ctre.phoenix6.hardware.CANcoder; import com.revrobotics.spark.SparkMax; @@ -81,7 +80,7 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM turnConfig.inverted(config.turnInversion[arrIndex]); double drivePositionFactor = config.wheelDiameterMeters * Math.PI / config.driveGearing; - final double driveVelocityFactor = drivePositionFactor / 60;//why by 60? + final double driveVelocityFactor = drivePositionFactor / 60; driveConfig.encoder .positionConversionFactor(drivePositionFactor) .velocityConversionFactor(driveVelocityFactor); @@ -200,9 +199,7 @@ public void drivePeriodic() { .calculateWithVelocities( actualSpeed, desiredSpeed + extraAccel * TimedRobot.kDefaultPeriod//m/s + ( m/s^2 * s ) - );//clippedAcceleration); - //calculateAntiGravitationalA(pitchDegSupplier.get(), rollDegSupplier.get()) - + );//clippedAcceleration); // Use robot characterization as a simple physical model to account for internal resistance, frcition, etc. // Add a PID adjustment for error correction (also "drives" the actual speed to the desired speed) double pidVolts = drivePIDController.calculate(actualSpeed, desiredSpeed); From e3b64d2a12ce184902f60c0e2e12002626dec5e1 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 6 Dec 2025 23:42:47 -0800 Subject: [PATCH 35/63] bump java doc github workflow artifact version --- .github/workflows/javadoc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml index 3ff8c196..e5d5fd6e 100644 --- a/.github/workflows/javadoc.yml +++ b/.github/workflows/javadoc.yml @@ -17,7 +17,7 @@ jobs: - name: Generate Javadoc run: ./gradlew javadoc - name: Build Artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v4 with: path: ./build/docs/javadoc deploy-javadoc: From ed4d4258da87e4c171bdfa510728998d42ecbd7a Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 6 Dec 2025 23:53:05 -0800 Subject: [PATCH 36/63] replaced @links to setZeroOffset to @code because javadocs can't find setZeroOffset, (there might be a better way to fix it but I am not sure) --- .../org/carlmontrobotics/lib199/sim/MockedEncoder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java index e4f8fc75..6776a56c 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedEncoder.java @@ -43,8 +43,8 @@ public class MockedEncoder implements AbsoluteEncoder, AnalogInput, AutoCloseabl * @param countsPerRev The value that this.getCountsPerRevolution() should return * @param analog Whether the encoder is an analog sensor * @param absolute Whether the encoder is an absolute encoder. This flag caps the position to - * one rotation via. {@link MathUtil#inputModulus(double, double, double)}, disables - * {@link #setPosition(double)}, and enables {@link #setZeroOffset(double)}. + * one rotation via. {@link MathUtil#inputModulus(double, double, double)}, disables + * {@link #setPosition(double)}, and enables {@code setZeroOffset(double)}. * * {@link #getVelocity()} will return a value in rpm. */ @@ -58,8 +58,8 @@ public MockedEncoder(SimDevice device, int countsPerRev, boolean analog, * @param countsPerRev The value that this.getCountsPerRevolution() should return * @param analog Whether the encoder is an analog sensor * @param absolute Whether the encoder is an absolute encoder. This flag caps the position to - * one rotation via. {@link MathUtil#inputModulus(double, double, double)}, disables - * {@link #setPosition(double)}, and enables {@link #setZeroOffset(double)}. + * one rotation via. {@link MathUtil#inputModulus(double, double, double)}, disables + * {@link #setPosition(double)}, and enables {@code setZeroOffset(double)}. * @param useRps Whether getVelocity() should return rps instead of rpm. */ public MockedEncoder(SimDevice device, int countsPerRev, boolean analog, @@ -106,7 +106,7 @@ public REVLibError setPosition(double newPosition) { // } /** - * @return The current position of the encoder, not accounting for the position offset ({@link #setPosition(double)} and {@link #setZeroOffset(double)}) + * @return The current position of the encoder, not accounting for the position offset ({@link #setPosition(double)} and {@code setZeroOffset(double)}) */ public double getRawPosition() { double rotationsOrVolts = voltage != null ? voltage.get() : position.get(); From fa9f5f8cd77c8e903a217b739ce3877c51017cb2 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 7 Dec 2025 00:15:23 -0800 Subject: [PATCH 37/63] added ElasticLib --- .../org/carlmontrobotics/lib199/Elastic.java | 392 ++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 src/main/java/org/carlmontrobotics/lib199/Elastic.java diff --git a/src/main/java/org/carlmontrobotics/lib199/Elastic.java b/src/main/java/org/carlmontrobotics/lib199/Elastic.java new file mode 100644 index 00000000..b803d100 --- /dev/null +++ b/src/main/java/org/carlmontrobotics/lib199/Elastic.java @@ -0,0 +1,392 @@ +// Copyright (c) 2023-2025 Gold87 and other Elastic contributors +// This software can be modified and/or shared under the terms +// defined by the Elastic license: +// https://github.com/Gold872/elastic-dashboard/blob/main/LICENSE + +// From: https://frc-elastic.gitbook.io/docs/additional-features-and-references/robot-notifications-with-elasticlib and https://github.com/Gold872/elastic-dashboard/blob/main/elasticlib/Elastic.java + +package org.carlmontrobotics.lib199; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.PubSubOption; +import edu.wpi.first.networktables.StringPublisher; +import edu.wpi.first.networktables.StringTopic; + +public final class Elastic { + private static final StringTopic notificationTopic = + NetworkTableInstance.getDefault().getStringTopic("/Elastic/RobotNotifications"); + private static final StringPublisher notificationPublisher = + notificationTopic.publish(PubSubOption.sendAll(true), PubSubOption.keepDuplicates(true)); + private static final StringTopic selectedTabTopic = + NetworkTableInstance.getDefault().getStringTopic("/Elastic/SelectedTab"); + private static final StringPublisher selectedTabPublisher = + selectedTabTopic.publish(PubSubOption.keepDuplicates(true)); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Represents the possible levels of notifications for the Elastic dashboard. These levels are + * used to indicate the severity or type of notification. + */ + public enum NotificationLevel { + /** Informational Message */ + INFO, + /** Warning message */ + WARNING, + /** Error message */ + ERROR + } + + /** + * Sends an notification to the Elastic dashboard. The notification is serialized as a JSON string + * before being published. + * + * @param notification the {@link Notification} object containing notification details + */ + public static void sendNotification(Notification notification) { + try { + notificationPublisher.set(objectMapper.writeValueAsString(notification)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + + /** + * Selects the tab of the dashboard with the given name. If no tab matches the name, this will + * have no effect on the widgets or tabs in view. + * + *

    If the given name is a number, Elastic will select the tab whose index equals the number + * provided. + * + * @param tabName the name of the tab to select + */ + public static void selectTab(String tabName) { + selectedTabPublisher.set(tabName); + } + + /** + * Selects the tab of the dashboard at the given index. If this index is greater than or equal to + * the number of tabs, this will have no effect. + * + * @param tabIndex the index of the tab to select. + */ + public static void selectTab(int tabIndex) { + selectTab(Integer.toString(tabIndex)); + } + + /** + * Represents an notification object to be sent to the Elastic dashboard. This object holds + * properties such as level, title, description, display time, and dimensions to control how the + * notification is displayed on the dashboard. + */ + public static class Notification { + @JsonProperty("level") + private NotificationLevel level; + + @JsonProperty("title") + private String title; + + @JsonProperty("description") + private String description; + + @JsonProperty("displayTime") + private int displayTimeMillis; + + @JsonProperty("width") + private double width; + + @JsonProperty("height") + private double height; + + /** + * Creates a new Notification with all default parameters. This constructor is intended to be + * used with the chainable decorator methods + * + *

    Title and description fields are empty. + */ + public Notification() { + this(NotificationLevel.INFO, "", ""); + } + + /** + * Creates a new Notification with all properties specified. + * + * @param level the level of the notification (e.g., INFO, WARNING, ERROR) + * @param title the title text of the notification + * @param description the descriptive text of the notification + * @param displayTimeMillis the time in milliseconds for which the notification is displayed + * @param width the width of the notification display area + * @param height the height of the notification display area, inferred if below zero + */ + public Notification( + NotificationLevel level, + String title, + String description, + int displayTimeMillis, + double width, + double height) { + this.level = level; + this.title = title; + this.displayTimeMillis = displayTimeMillis; + this.description = description; + this.height = height; + this.width = width; + } + + /** + * Creates a new Notification with default display time and dimensions. + * + * @param level the level of the notification + * @param title the title text of the notification + * @param description the descriptive text of the notification + */ + public Notification(NotificationLevel level, String title, String description) { + this(level, title, description, 3000, 350, -1); + } + + /** + * Creates a new Notification with a specified display time and default dimensions. + * + * @param level the level of the notification + * @param title the title text of the notification + * @param description the descriptive text of the notification + * @param displayTimeMillis the display time in milliseconds + */ + public Notification( + NotificationLevel level, String title, String description, int displayTimeMillis) { + this(level, title, description, displayTimeMillis, 350, -1); + } + + /** + * Creates a new Notification with specified dimensions and default display time. If the height + * is below zero, it is automatically inferred based on screen size. + * + * @param level the level of the notification + * @param title the title text of the notification + * @param description the descriptive text of the notification + * @param width the width of the notification display area + * @param height the height of the notification display area, inferred if below zero + */ + public Notification( + NotificationLevel level, String title, String description, double width, double height) { + this(level, title, description, 3000, width, height); + } + + /** + * Updates the level of this notification + * + * @param level the level to set the notification to + */ + public void setLevel(NotificationLevel level) { + this.level = level; + } + + /** + * @return the level of this notification + */ + public NotificationLevel getLevel() { + return level; + } + + /** + * Updates the title of this notification + * + * @param title the title to set the notification to + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Gets the title of this notification + * + * @return the title of this notification + */ + public String getTitle() { + return title; + } + + /** + * Updates the description of this notification + * + * @param description the description to set the notification to + */ + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + /** + * Updates the display time of the notification + * + * @param seconds the number of seconds to display the notification for + */ + public void setDisplayTimeSeconds(double seconds) { + setDisplayTimeMillis((int) Math.round(seconds * 1000)); + } + + /** + * Updates the display time of the notification in milliseconds + * + * @param displayTimeMillis the number of milliseconds to display the notification for + */ + public void setDisplayTimeMillis(int displayTimeMillis) { + this.displayTimeMillis = displayTimeMillis; + } + + /** + * Gets the display time of the notification in milliseconds + * + * @return the number of milliseconds the notification is displayed for + */ + public int getDisplayTimeMillis() { + return displayTimeMillis; + } + + /** + * Updates the width of the notification + * + * @param width the width to set the notification to + */ + public void setWidth(double width) { + this.width = width; + } + + /** + * Gets the width of the notification + * + * @return the width of the notification + */ + public double getWidth() { + return width; + } + + /** + * Updates the height of the notification + * + *

    If the height is set to -1, the height will be determined automatically by the dashboard + * + * @param height the height to set the notification to + */ + public void setHeight(double height) { + this.height = height; + } + + /** + * Gets the height of the notification + * + * @return the height of the notification + */ + public double getHeight() { + return height; + } + + /** + * Modifies the notification's level and returns itself to allow for method chaining + * + * @param level the level to set the notification to + * @return the current notification + */ + public Notification withLevel(NotificationLevel level) { + this.level = level; + return this; + } + + /** + * Modifies the notification's title and returns itself to allow for method chaining + * + * @param title the title to set the notification to + * @return the current notification + */ + public Notification withTitle(String title) { + setTitle(title); + return this; + } + + /** + * Modifies the notification's description and returns itself to allow for method chaining + * + * @param description the description to set the notification to + * @return the current notification + */ + public Notification withDescription(String description) { + setDescription(description); + return this; + } + + /** + * Modifies the notification's display time and returns itself to allow for method chaining + * + * @param seconds the number of seconds to display the notification for + * @return the current notification + */ + public Notification withDisplaySeconds(double seconds) { + return withDisplayMilliseconds((int) Math.round(seconds * 1000)); + } + + /** + * Modifies the notification's display time and returns itself to allow for method chaining + * + * @param displayTimeMillis the number of milliseconds to display the notification for + * @return the current notification + */ + public Notification withDisplayMilliseconds(int displayTimeMillis) { + setDisplayTimeMillis(displayTimeMillis); + return this; + } + + /** + * Modifies the notification's width and returns itself to allow for method chaining + * + * @param width the width to set the notification to + * @return the current notification + */ + public Notification withWidth(double width) { + setWidth(width); + return this; + } + + /** + * Modifies the notification's height and returns itself to allow for method chaining + * + * @param height the height to set the notification to + * @return the current notification + */ + public Notification withHeight(double height) { + setHeight(height); + return this; + } + + /** + * Modifies the notification's height and returns itself to allow for method chaining + * + *

    This will set the height to -1 to have it automatically determined by the dashboard + * + * @return the current notification + */ + public Notification withAutomaticHeight() { + setHeight(-1); + return this; + } + + /** + * Modifies the notification to disable the auto dismiss behavior + * + *

    This sets the display time to 0 milliseconds + * + *

    The auto dismiss behavior can be re-enabled by setting the display time to a number + * greater than 0 + * + * @return the current notification + */ + public Notification withNoAutoDismiss() { + setDisplayTimeMillis(0); + return this; + } + } +} \ No newline at end of file From ef648054d6df9870166ee9433f74c51484a3d734 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:15:47 -0800 Subject: [PATCH 38/63] added some documentation for the elastic class --- src/main/java/org/carlmontrobotics/lib199/Elastic.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/carlmontrobotics/lib199/Elastic.java b/src/main/java/org/carlmontrobotics/lib199/Elastic.java index b803d100..d8d20dea 100644 --- a/src/main/java/org/carlmontrobotics/lib199/Elastic.java +++ b/src/main/java/org/carlmontrobotics/lib199/Elastic.java @@ -14,6 +14,12 @@ import edu.wpi.first.networktables.PubSubOption; import edu.wpi.first.networktables.StringPublisher; import edu.wpi.first.networktables.StringTopic; +/** + * A class that provides methods for interacting with the Elastic dashboard, including sending + * notifications and selecting tabs. This taken striaght from the official Elastic documentation + * (see https://frc-elastic.gitbook.io/docs/additional-features-and-references/robot-notifications-with-elasticlib for documentation + * and for original code https://github.com/Gold872/elastic-dashboard/blob/main/elasticlib/Elastic.java) + */ public final class Elastic { private static final StringTopic notificationTopic = From d330dd54908b04143336564bca5fd1859f03ea39 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 7 Dec 2025 20:03:48 -0800 Subject: [PATCH 39/63] removed error print statement for sim and uncommented MockSparkMax/Flex creators --- .../carlmontrobotics/lib199/MotorControllerFactory.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 637527df..9b9f955f 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -98,8 +98,7 @@ public static SparkMax createSparkMax(int id, MotorConfig motorConfig) { if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - System.err.println("heyy... lib199 doesn't have sim support sorri"); - // spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless); + spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); } // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); @@ -152,7 +151,6 @@ public static SparkFlex createSparkFlex(int id) { if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); } else { - System.err.println("heyy... lib199 doesn't have sim support sorri"); spark = MockSparkFlex.createMockSparkFlex(id, MotorType.kBrushless); } @@ -177,8 +175,7 @@ public static SparkFlex createSparkFlex(int id, SparkFlexConfig config) { if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); } else { - System.err.println("heyy... lib199 doesn't have sim support sorri"); - // spark = MockSparkFlex.createMockSparkFlex(id, SparkLowLevel.MotorType.kBrushless); + spark = MockSparkFlex.createMockSparkFlex(id, SparkLowLevel.MotorType.kBrushless); } if (spark!=null) spark.configure( From 158c96e6a58aa8045f51faf3b33401863cae981f Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:18:01 -0800 Subject: [PATCH 40/63] Tried adding sim support to CanCoders --- .../lib199/MotorControllerFactory.java | 2 +- .../carlmontrobotics/lib199/SensorFactory.java | 13 ++++++++++--- .../lib199/sim/MockedCANCoder.java | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 9b9f955f..ecf105ae 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -128,7 +128,7 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); + spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); //what if the Mocked motor is a 550 with a custom config? } if (spark!=null) spark.configure( diff --git a/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java b/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java index 2bcedc5d..1a657296 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java @@ -23,9 +23,16 @@ public class SensorFactory { * @return The CANCoder object */ public static CANcoder createCANCoder(int port) { - CANcoder canCoder = new CANcoder(port); - if (RobotBase.isSimulation()) - new MockedCANCoder(canCoder); + // CANcoder canCoder = new CANcoder(port); + // if (RobotBase.isSimulation()) + // canCoder = MockedCANCoder.createMock(canCoder); + // return canCoder; + CANcoder canCoder; + if (RobotBase.isReal()) { + canCoder = new CANcoder(port); + } else { + canCoder = MockedCANCoder.createMock(new CANcoder(port)); + } return canCoder; } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java index 02dfd603..01d3bd02 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java @@ -2,8 +2,15 @@ import java.util.HashMap; +import org.carlmontrobotics.lib199.ErrorCodeAnswer; +import org.carlmontrobotics.lib199.Mocks; +import org.carlmontrobotics.lib199.REVLibErrorAnswer; + +import com.ctre.phoenix6.configs.CANcoderConfigurator; import com.ctre.phoenix6.hardware.CANcoder; import com.ctre.phoenix6.sim.CANcoderSimState; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel.MotorType; import edu.wpi.first.hal.HALValue; import edu.wpi.first.hal.SimBoolean; @@ -22,12 +29,14 @@ public class MockedCANCoder { private SimDeviceSim deviceSim; private SimDouble position; // Rotations - Continuous private CANcoderSimState sim; + private CANcoderConfigurator configurator; public MockedCANCoder(CANcoder canCoder) { port = canCoder.getDeviceID(); device = SimDevice.create("CANDutyCycle:CANCoder", port); position = device.createDouble("position", Direction.kInput, 0); sim = canCoder.getSimState(); + configurator = canCoder.getConfigurator(); deviceSim = new SimDeviceSim("CANDutyCycle:CANCoder", port); deviceSim.registerValueChangedCallback(position, new SimValueCallback() { @Override @@ -38,4 +47,12 @@ public void callback(String name, int handle, int direction, HALValue value) { sims.put(port, this); } + public CANcoderConfigurator getConfigurator() { + return configurator; + } + + public static CANcoder createMock(CANcoder canCoder) { + return Mocks.createMock(CANcoder.class, new MockedCANCoder(canCoder)); + } + } \ No newline at end of file From c3bc9420cefee78210153d570c53d3ae7e0ca0fe Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Wed, 10 Dec 2025 21:16:46 -0800 Subject: [PATCH 41/63] fixed drive config accessor? --- .../lib199/swerve/SwerveModule.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index a7b235cc..5ae89815 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -64,6 +64,14 @@ public enum ModuleType {FL, FR, BL, BR}; private double turnSpeedCorrectionVolts, turnFFVolts, turnVolts; private double maxTurnVelocityWithoutTippingRps; + + // Store encoder and config values since configAccessor is not available in new REV API + private IdleMode driveIdleMode = IdleMode.kBrake; + private IdleMode turnIdleMode = IdleMode.kBrake; + private static final int NEO_HALL_COUNTS_PER_REV = 42; + private static final int ENCODER_POSITION_PERIOD_MS = 20; + private int encoderAverageDepth = 2; + public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkMax turn, CANcoder turnEncoder, int arrIndex, Supplier pitchDegSupplier, Supplier rollDegSupplier) { //SmartDashboard.putNumber("Target Angle (deg)", 0.0); @@ -81,9 +89,11 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM double drivePositionFactor = config.wheelDiameterMeters * Math.PI / config.driveGearing; final double driveVelocityFactor = drivePositionFactor / 60; + encoderAverageDepth = 2; // Store the value we're configuring driveConfig.encoder .positionConversionFactor(drivePositionFactor) - .velocityConversionFactor(driveVelocityFactor); + .velocityConversionFactor(driveVelocityFactor) + .quadratureAverageDepth(encoderAverageDepth); maxControllableAccerlationRps2 = 0; final double normalForceNewtons = 83.2 /* lbf */ * 4.4482 /* N/lbf */ / 4 /* numModules */; @@ -109,8 +119,8 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkM /* offset for 1 relative encoder count */ drivetoleranceMPerS = (1.0 - / (double)(drive.configAccessor.encoder.getCountsPerRevolution()) * drivePositionFactor) - / Units.millisecondsToSeconds(drive.configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * drive.configAccessor.encoder.getQuadratureAverageDepth()); + / (double)(NEO_HALL_COUNTS_PER_REV) * drivePositionFactor) + / Units.millisecondsToSeconds(ENCODER_POSITION_PERIOD_MS * encoderAverageDepth); drivePIDController.setTolerance(drivetoleranceMPerS); //System.out.println("Velocity Constant: " + (positionConstant / 60)); @@ -426,17 +436,21 @@ public void updateSmartDashboard() { } public void toggleMode() { - if (drive.configAccessor.getIdleMode() == IdleMode.kBrake && turn.configAccessor.getIdleMode() == IdleMode.kCoast) coast(); + if (driveIdleMode == IdleMode.kBrake && turnIdleMode == IdleMode.kCoast) coast(); else brake(); } public void brake() { + driveIdleMode = IdleMode.kBrake; + turnIdleMode = IdleMode.kBrake; SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kBrake); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } public void coast() { + driveIdleMode = IdleMode.kCoast; + turnIdleMode = IdleMode.kCoast; SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kCoast); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); From bc7f07ddd22b35f35bcc4a14b94bdd7a7ac514a2 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:47:24 -0800 Subject: [PATCH 42/63] resolved some comments --- .../lib199/MotorControllerFactory.java | 22 +++---- .../carlmontrobotics/lib199/MotorErrors.java | 62 ++++++++----------- .../lib199/SparkVelocityPIDController.java | 1 - .../lib199/swerve/SwerveModule.java | 20 +++--- .../lib199/DummySparkMaxAnswerTest.txt | 61 ------------------ .../lib199/MotorErrorsTest.java | 8 --- 6 files changed, 45 insertions(+), 129 deletions(-) delete mode 100644 src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.txt diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index ecf105ae..1ccfa95e 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -124,30 +124,29 @@ else if (motorConfig==MotorConfig.NEO_VORTEX) * @param config the custom config to set */ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { - SparkMax spark = null; + SparkMax spark; if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); //what if the Mocked motor is a 550 with a custom config? } - if (spark!=null) - spark.configure( - config, - SparkBase.ResetMode.kResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters - ); + spark.configure( + config, + SparkBase.ResetMode.kResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters + ); return spark; } /** - * Create a default sparkFlex-Vortex controller. + * Create a default SparkFlex-Vortex controller. * * @param id the port of the motor controller */ public static SparkFlex createSparkFlex(int id) { MotorConfig motorConfig = MotorConfig.NEO_VORTEX; - SparkFlex spark=null; + SparkFlex spark; if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); } else { @@ -155,8 +154,7 @@ public static SparkFlex createSparkFlex(int id) { } // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); - if (spark!=null) - MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); + MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); MotorErrors.checkSparkErrors(spark); @@ -181,7 +179,7 @@ public static SparkFlex createSparkFlex(int id, SparkFlexConfig config) { spark.configure( config, SparkBase.ResetMode.kResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters + SparkBase.PersistMode.kPersistParameters ); return spark; diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 4ee8941d..d5eb189c 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -12,6 +12,7 @@ import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.SparkBase.Faults; import com.revrobotics.spark.config.SparkBaseConfig; +import com.revrobotics.spark.config.SparkFlexConfig; import com.revrobotics.spark.config.SparkMaxConfig; import com.revrobotics.REVLibError; @@ -27,7 +28,9 @@ public final class MotorErrors { private static final Map stickyFlags = new ConcurrentSkipListMap<>( (spark1, spark2) -> (spark1.getDeviceId() - spark2.getDeviceId())); - private static final SparkBaseConfig OVERHEAT_CONFIG = new SparkMaxConfig().smartCurrentLimit(1); + private static final SparkBaseConfig OVERHEAT_MAX_CONFIG = new SparkMaxConfig().smartCurrentLimit(1); + private static final SparkBaseConfig OVERHEAT_FLEX_CONFIG = new SparkFlexConfig().smartCurrentLimit(1); + public static final int kOverheatTripCount = 5; @@ -75,8 +78,8 @@ public static void checkSparkErrors(SparkBase spark) { // short faults = spark.getFaults(); Faults faults = spark.getFaults(); Faults stickyFaults = spark.getStickyFaults(); - Faults prevFaults = flags.containsKey(spark) ? flags.get(spark) : null; - Faults prevStickyFaults = stickyFlags.containsKey(spark) ? stickyFlags.get(spark) : null; + Faults prevFaults = flags.getOrDefault(spark, null); + Faults prevStickyFaults = stickyFlags.getOrDefault(spark, null); if (spark.hasActiveFault() && prevFaults!=null && prevFaults.rawBits != faults.rawBits) { System.err.println("Fault Errors! (spark id " + spark.getDeviceId() + "): [" + formatFaults(spark) + "], ooF!"); @@ -85,17 +88,9 @@ public static void checkSparkErrors(SparkBase spark) { System.err.println("Sticky Faults! (spark id " + spark.getDeviceId() + "): [" + formatStickyFaults(spark) + "], Ouch!"); } spark.clearFaults(); - flags.put(spark, faults); - stickyFlags.put(spark, stickyFaults); - } - - @Deprecated - public static void checkSparkMaxErrors(SparkMax spark) { - checkSparkErrors((SparkBase)spark); } - private static String formatFaults(SparkBase spark) { - Faults f = spark.getFaults(); + private static String formatFaults(Faults f) { return "" //i hope this makes you proud of yourself, REVLib + (f.can ? "CAN " : "") + (f.escEeprom ? "Flash ROM " : "") @@ -108,18 +103,14 @@ private static String formatFaults(SparkBase spark) { ; } + private static String formatFaults(SparkBase spark) { + Faults f = spark.getFaults(); + return formatFaults(f); + } + private static String formatStickyFaults(SparkBase spark) { Faults f = spark.getStickyFaults(); - return "" - + (f.can ? "CAN " : "") - + (f.escEeprom ? "Flash ROM " : "") - + (f.firmware ? "Firmware " : "") - + (f.gateDriver ? "Gate Driver " : "") - + (f.motorType ? "Motor Type " : "") - + (f.other ? "Other " : "") - + (f.sensor ? "Sensor " : "") - + (f.temperature ? "Temperature " : "") - ; + return formatFaults(f); } @Deprecated @@ -139,22 +130,12 @@ static void reportNextNSparkErrors(int n) { lastSparkErrorIndexReported = (lastSparkErrorIndexReported + n) % flags.size(); } - @Deprecated - public static void reportSparkMaxTemp(SparkMax spark, TemperatureLimit temperatureLimit) { - reportSparkMaxTemp(spark, temperatureLimit.limit); - } - public static boolean isSparkOverheated(SparkBase spark){ int id = spark.getDeviceId(); int motorMaxTemp = sparkTemperatureLimits.get(id); return ( spark.getMotorTemperature() >= motorMaxTemp ); } - @Deprecated - public static void reportSparkMaxTemp(SparkMax spark, int temperatureLimit) { - reportSparkTemp((SparkBase) spark, temperatureLimit); - } - public static void reportSparkTemp(SparkBase spark, int temperatureLimit) { int id = spark.getDeviceId(); temperatureSparks.put(id, spark); @@ -212,10 +193,19 @@ private static void reportSparkTemp(int port, SparkBase spark) { System.err.println("Port " + port + " spark is operating at " + temp + " degrees Celsius! It will be disabled until the robot code is restarted."); } - spark.configure( - OVERHEAT_CONFIG, - SparkBase.ResetMode.kNoResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters); + if (spark.getClass() == SparkMax.class) { + spark.configure( + OVERHEAT_MAX_CONFIG, + SparkBase.ResetMode.kNoResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters); + } else if (spark.getClass() == SparkFlex.class) { + spark.configure( + OVERHEAT_FLEX_CONFIG, + SparkBase.ResetMode.kNoResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters); + }else{ + System.err.println("Unknown spark :("); + } } } diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java index f8d1b486..3a3886c6 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java +++ b/src/main/java/org/carlmontrobotics/lib199/SparkVelocityPIDController.java @@ -4,7 +4,6 @@ import com.revrobotics.spark.config.ClosedLoopConfig; import com.revrobotics.spark.config.SparkBaseConfig; import com.revrobotics.spark.config.SparkMaxConfig; -// import com.revrobotics.SparkBase.ControlType; import com.revrobotics.RelativeEncoder; import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.SparkBase; diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 5ae89815..248db5d8 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -26,8 +26,8 @@ import edu.wpi.first.math.kinematics.SwerveModuleState; import edu.wpi.first.math.trajectory.TrapezoidProfile; import edu.wpi.first.math.util.Units; -import edu.wpi.first.units.measure.Mass; import edu.wpi.first.units.Measure; +import edu.wpi.first.units.measure.Mass; import edu.wpi.first.util.sendable.Sendable; import edu.wpi.first.util.sendable.SendableBuilder; import edu.wpi.first.util.sendable.SendableRegistry; @@ -66,8 +66,7 @@ public enum ModuleType {FL, FR, BL, BR}; private double maxTurnVelocityWithoutTippingRps; // Store encoder and config values since configAccessor is not available in new REV API - private IdleMode driveIdleMode = IdleMode.kBrake; - private IdleMode turnIdleMode = IdleMode.kBrake; + private IdleMode idleMode = IdleMode.kBrake; private static final int NEO_HALL_COUNTS_PER_REV = 42; private static final int ENCODER_POSITION_PERIOD_MS = 20; private int encoderAverageDepth = 2; @@ -197,7 +196,7 @@ public ModuleType getType() { private double prevTurnVelocity = 0; public void periodic() { drivePeriodic(); - updateSmartDashboard(); + // updateSmartDashboard(); turnPeriodic(); } @@ -208,8 +207,8 @@ public void drivePeriodic() { double targetVoltage = (actualSpeed >= 0 ? forwardSimpleMotorFF : backwardSimpleMotorFF) .calculateWithVelocities( actualSpeed, - desiredSpeed + extraAccel * TimedRobot.kDefaultPeriod//m/s + ( m/s^2 * s ) - );//clippedAcceleration); + desiredSpeed + extraAccel * TimedRobot.kDefaultPeriod //m/s + ( m/s^2 * s ) + ); // Use robot characterization as a simple physical model to account for internal resistance, frcition, etc. // Add a PID adjustment for error correction (also "drives" the actual speed to the desired speed) double pidVolts = drivePIDController.calculate(actualSpeed, desiredSpeed); @@ -436,21 +435,20 @@ public void updateSmartDashboard() { } public void toggleMode() { - if (driveIdleMode == IdleMode.kBrake && turnIdleMode == IdleMode.kCoast) coast(); + if (idleMode == IdleMode.kBrake && idleMode == IdleMode.kCoast) coast(); else brake(); } public void brake() { - driveIdleMode = IdleMode.kBrake; - turnIdleMode = IdleMode.kBrake; + idleMode = IdleMode.kBrake; SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kBrake); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } public void coast() { - driveIdleMode = IdleMode.kCoast; - turnIdleMode = IdleMode.kCoast; + idleMode = IdleMode.kCoast; + idleMode = IdleMode.kCoast; SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kCoast); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); diff --git a/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.txt b/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.txt deleted file mode 100644 index 4084fc99..00000000 --- a/src/test/java/org/carlmontrobotics/lib199/DummySparkMaxAnswerTest.txt +++ /dev/null @@ -1,61 +0,0 @@ -package org.carlmontrobotics.lib199; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import com.revrobotics.REVLibError; -// import com.revrobotics.CANSparkMax; -// import com.revrobotics.CANSparkBase.IdleMode; -// import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.SparkAnalogSensor.Mode; -import com.revrobotics.SparkLimitSwitch.Type; -// import com.revrobotics.SparkPIDController.AccelStrategy; - -import com.revrobotics.spark.SparkMax; -import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; -import com.revrobotics.spark.SparkLowLevel.MotorType; - -import com.revrobotics.spark.SparkLimitSwitch;// -import com.revrobotics.spark.SparkClosedLoopController; - -import org.junit.Test; - -public class DummySparkMaxAnswerTest { - - public SparkMax createMockedSparkMax() { - return Mocks.mock(SparkMax.class, new DummySparkMaxAnswer()); - } - - public static void assertTestResponses(SparkMax spark) { - // Check device id - assertEquals(0, spark.getDeviceId()); - - // Check get and set methods - assertEquals(0, spark.get(), 0.01); - spark.set(0); - - // Check that all REV specific objects are non-null - assertNotNull(spark.getAnalog((Mode) null)); - assertNotNull(spark.getEncoder()); - assertNotNull(spark.getForwardLimitSwitch((Type) null)); - assertNotNull(spark.getClosedLoopController()); - - // Check that all REV specific objects return "null" values - assertEquals(0, spark.getEncoder().getPosition(), 0.01); - assertEquals(0, spark.getEncoder().getVelocity(), 0.01); - assertEquals(IdleMode.kBrake, spark.getIdleMode()); - assertEquals(MotorType.kBrushless, spark.getMotorType()); - assertEquals(AccelStrategy.kTrapezoidal, spark.getClosedLoopController().getSmartMotionAccelStrategy(0)); - assertEquals(REVLibError.kOk, spark.getLastError()); - assertEquals(REVLibError.kOk, spark.getClosedLoopController().setP(0, 0)); - assertEquals(REVLibError.kOk, spark.getEncoder().setAverageDepth(0)); - assertEquals(REVLibError.kOk, spark.getAnalog((Mode) null).setInverted(false)); - } - - @Test - public void testResponses() { - assertTestResponses(createMockedSparkMax()); - } - -} - diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java index 754b0616..cd27db62 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java @@ -10,8 +10,6 @@ import com.ctre.phoenix.ErrorCode; import com.revrobotics.REVLibError; -// import com.revrobotics.SparkMax; -//import com.revrobotics.spark.SparkBase.Faults; import com.revrobotics.spark.SparkMax; @@ -166,12 +164,6 @@ public void testStickyFaultReporting() { assertEquals(0, errStream.toByteArray().length); } - //FIXME: do we need this? - // @Test - // public void testDummySparkMax() { - // DummySparkMaxAnswerTest.assertTestResponses(MotorErrors.createDummySparkMax()); - // } - @Test public void testReportSparkMaxTemp() { assertTrue(MotorErrors.kOverheatTripCount > 0); From e023ca6d355a77bc02c6e1ec6549a580e4c76fb4 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:27:32 -0800 Subject: [PATCH 43/63] removed some depricated classes/unused imports --- .../lib199/MotorControllerFactory.java | 53 ++----------------- 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 1ccfa95e..b5de25fd 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -32,7 +32,6 @@ import org.carlmontrobotics.lib199.sim.MockTalonSRX; import org.carlmontrobotics.lib199.sim.MockVictorSPX; import org.carlmontrobotics.lib199.sim.MockedCANCoder; -import org.mockito.Mock; import edu.wpi.first.cameraserver.CameraServer; import edu.wpi.first.cscore.UsbCamera; @@ -209,7 +208,7 @@ public static SparkBaseConfig baseSparkConfig() { public static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { config.idleMode(IdleMode.kBrake); - config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.voltageCompensation(12); config.smartCurrentLimit(50); config.closedLoop @@ -237,7 +236,7 @@ public static SparkFlexConfig baseSparkFlexConfig(SparkFlexConfig config){ //typical operating voltage: 12V. ( same as sparkMax ) config.idleMode(IdleMode.kBrake); - config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.voltageCompensation(12); config.smartCurrentLimit(50); config.closedLoop @@ -253,7 +252,7 @@ public static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. config.idleMode(IdleMode.kBrake); - config.voltageCompensation(12);//FIXME does this need to be different for different motors? + config.voltageCompensation(12); config.smartCurrentLimit(50); config.closedLoop @@ -264,48 +263,4 @@ public static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. return config; } - - /** - * @deprecated Use {@link SensorFactory#createCANCoder(int)} instead. - */ - @Deprecated - public static CANcoder createCANCoder(int port) { - CANcoder canCoder = new CANcoder(port); - if(RobotBase.isSimulation()) new MockedCANCoder(canCoder); - return canCoder; - } - - /** - * Configures a USB Camera. - * See {@link CameraServer#startAutomaticCapture} for more details. - * This MUST be called AFTER AHRS initialization or the code will be unable to connect to the gyro. - * - * @return The configured camera - * - * @deprecated Use {@link SensorFactory#configureCamera()} instead. - */ - @Deprecated - public static UsbCamera configureCamera() { - UsbCamera camera = CameraServer.startAutomaticCapture(); - camera.setConnectionStrategy(ConnectionStrategy.kKeepOpen); - CameraServer.getServer().setSource(camera); - return camera; - } - - /** - * This method is equivalent to calling {@link #configureCamera()} {@code numCameras} times. - * The last camera will be set as the primary Camera feed. - * To change it, call {@code CameraServer.getServer().setSource()}. - * - * @param numCameras The number of cameras to configure - * @return The configured cameras. - * - * @deprecated Use {@link SensorFactory#configureCameras(int)} instead. - */ - @Deprecated - public static UsbCamera[] configureCameras(int numCameras) { - UsbCamera[] cameras = new UsbCamera[numCameras]; - for(int i = 0; i < numCameras; i++) cameras[i] = configureCamera(); - return cameras; - } -} +} \ No newline at end of file From 6b1160d425c52fca4ee21e7ee5ced0499dc7b44f Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 21 Dec 2025 13:22:37 -0800 Subject: [PATCH 44/63] added sparkConfig, SparkMotorType, MotorControllerType --- .../lib199/MotorControllerFactory.java | 38 +++++++++++++++++++ .../lib199/MotorControllerType.java | 6 +++ .../lib199/SparkMotorType.java | 19 ++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java create mode 100644 src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index b5de25fd..b268548c 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -184,6 +184,44 @@ public static SparkFlex createSparkFlex(int id, SparkFlexConfig config) { return spark; } + public static SparkBaseConfig sparkConfig(SparkMotorType motor){ + SparkBaseConfig config = null; + switch(motor.getControllerType()){ + case SPARK_MAX: + config = baseSparkMaxConfig(); + break; + case SPARK_FLEX: + config = baseSparkFlexConfig(); + break; + } + //configs that apply to all motors + config.idleMode(IdleMode.kBrake); + config.voltageCompensation(12); + config.smartCurrentLimit(40); //40 amps is the fuse rating for fuses for each individual motor on the PDP + + config.closedLoop + .minOutput(-1) + .maxOutput(1) + .pid(0,0,0) + .velocityFF(0); + + + //motor specific configs + switch(motor){ + case NEO: + break; + case NEO550: + config.smartCurrentLimit(20); //so motor no go smoky + break; + case VORTEX, SOLO_VORTEX: // the config for a vortex should be the same if it uses a spark max with a solo adapter or a spark flex, so I just combined them together + break; + case NEO_2: + break; + } + + return config; + } + //does this not do the same as baseSparkMaxConfig, as it also creates a Spark MaxConfig? public static SparkBaseConfig baseSparkConfig() { SparkMaxConfig config = new SparkMaxConfig(); diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java new file mode 100644 index 00000000..d4803738 --- /dev/null +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java @@ -0,0 +1,6 @@ +package org.carlmontrobotics.lib199; + +public enum MotorControllerType { + SPARK_MAX, + SPARK_FLEX +} diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java b/src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java new file mode 100644 index 00000000..9fcb416a --- /dev/null +++ b/src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java @@ -0,0 +1,19 @@ +package org.carlmontrobotics.lib199; + +public enum SparkMotorType { + NEO(MotorControllerType.SPARK_MAX), + NEO550(MotorControllerType.SPARK_MAX), + VORTEX(MotorControllerType.SPARK_FLEX), + SOLO_VORTEX(MotorControllerType.SPARK_MAX), //a spark flex with a solo addapter, we don't really use those but I included it here just in case + NEO_2(MotorControllerType.SPARK_MAX); + + private final MotorControllerType controllerType; + + SparkMotorType(MotorControllerType controllerType) { + this.controllerType = controllerType; + } + + public MotorControllerType getControllerType() { + return controllerType; + } +} \ No newline at end of file From a4f37e41900bc0eaa0a23f41d1e8c1c424f41da7 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:47:36 -0800 Subject: [PATCH 45/63] Deprecated Victor controllers and createSparkMax(int, MotorConfig), made createSparkFlex(int) return createSparkFlex(id, sparkConfig(SparkMotorType.VORTEX)); and deleted old SparkConfig methods --- .../lib199/MotorControllerFactory.java | 135 ++---------------- .../carlmontrobotics/lib199/MotorErrors.java | 2 +- 2 files changed, 15 insertions(+), 122 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index b268548c..6f39313f 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -42,6 +42,10 @@ * Add your docs here. */ public class MotorControllerFactory { + @Deprecated + /** + * @deprecated VictorSPX motor controllers are no longer legal for the 2026 season: https://community.firstinspires.org/2025-robot-rules-preview-for-2026 + */ public static WPI_VictorSPX createVictor(int port) { WPI_VictorSPX victor; if (RobotBase.isReal()) { @@ -86,35 +90,20 @@ public static WPI_TalonSRX createTalon(int id) { return talon; } + @Deprecated /** + * @deprecated Use {@link MotorControllerFactory#createSparkMax(int id, MotorConfig motorConfig)} instead. * Create a default sparkMax controller (NEO or 550). * * @param id the port of the motor controller * @param motorConfig either MotorConfig.NEO or MotorConfig.NEO_550 */ public static SparkMax createSparkMax(int id, MotorConfig motorConfig) { - SparkMax spark=null; - if (RobotBase.isReal()) { - spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); - } else { - spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); + if (motorConfig.temperatureLimitCelsius == MotorConfig.NEO.temperatureLimitCelsius) { + return createSparkMax(id, sparkConfig(SparkMotorType.NEO)); + }else{ + return createSparkMax(id, sparkConfig(SparkMotorType.NEO550)); } - - // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); - if (spark!=null) - MotorErrors.reportSparkMaxTemp(spark, motorConfig.temperatureLimitCelsius); - - MotorErrors.checkSparkMaxErrors(spark); - - if (motorConfig==MotorConfig.NEO || motorConfig==MotorConfig.NEO_550) - spark.configure(baseSparkMaxConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); - else if (motorConfig==MotorConfig.NEO_VORTEX) - spark.configure(baseSparkFlexConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); - else - spark.configure(baseSparkConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); - - - return spark; } /** * Create a sparkMax controller (NEO or 550) with custom settings. @@ -143,23 +132,7 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { * @param id the port of the motor controller */ public static SparkFlex createSparkFlex(int id) { - MotorConfig motorConfig = MotorConfig.NEO_VORTEX; - - SparkFlex spark; - if (RobotBase.isReal()) { - spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); - } else { - spark = MockSparkFlex.createMockSparkFlex(id, MotorType.kBrushless); - } - - // config.setPeriodicFramePeriod(SparkLowLevel.PeriodicFrame.kStatus0, 1); - MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); - - MotorErrors.checkSparkErrors(spark); - - spark.configure(baseSparkFlexConfig(), SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kPersistParameters); - - return spark; + return createSparkFlex(id, sparkConfig(SparkMotorType.VORTEX)); } /** * Create a sparkFlex controller (VORTEX) with custom settings. @@ -167,7 +140,7 @@ public static SparkFlex createSparkFlex(int id) { * @param id the port of the motor controller * @param config the custom config to set */ - public static SparkFlex createSparkFlex(int id, SparkFlexConfig config) { + public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { SparkFlex spark = null; if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); @@ -188,10 +161,10 @@ public static SparkBaseConfig sparkConfig(SparkMotorType motor){ SparkBaseConfig config = null; switch(motor.getControllerType()){ case SPARK_MAX: - config = baseSparkMaxConfig(); + config = new SparkMaxConfig(); break; case SPARK_FLEX: - config = baseSparkFlexConfig(); + config = new SparkFlexConfig(); break; } //configs that apply to all motors @@ -221,84 +194,4 @@ public static SparkBaseConfig sparkConfig(SparkMotorType motor){ return config; } - - //does this not do the same as baseSparkMaxConfig, as it also creates a Spark MaxConfig? - public static SparkBaseConfig baseSparkConfig() { - SparkMaxConfig config = new SparkMaxConfig(); - - config.idleMode(IdleMode.kBrake); - - config.voltageCompensation(12); - config.smartCurrentLimit(50); - - config.closedLoop - .minOutput(-1) - .maxOutput(1) - .pid(0,0,0) - .velocityFF(0); - - return config; - } - /** - * Overrides an old config - but does not change other settings. - */ - //does this not do the same as baseSparkMaxConfig, as it also creates a Spark MaxConfig? - public static SparkBaseConfig baseSparkConfig(SparkMaxConfig config) { - config.idleMode(IdleMode.kBrake); - - config.voltageCompensation(12); - config.smartCurrentLimit(50); - - config.closedLoop - .minOutput(-1) - .maxOutput(1) - .pid(0,0,0) - .velocityFF(0); - - return config; - } - /** - * Overrides an old config - but does not change other settings. - */ - public static SparkMaxConfig baseSparkMaxConfig(SparkMaxConfig config){ - //typical operating voltage: 12V. - return (SparkMaxConfig) baseSparkConfig(config);//FIXME apply needed config changes for each controller - } - public static SparkMaxConfig baseSparkMaxConfig(){ - return (SparkMaxConfig) baseSparkConfig(); - } - /** - * Overrides an old config - but does not change other settings. - */ - public static SparkFlexConfig baseSparkFlexConfig(SparkFlexConfig config){ - //typical operating voltage: 12V. ( same as sparkMax ) - config.idleMode(IdleMode.kBrake); - - config.voltageCompensation(12); - config.smartCurrentLimit(50); - - config.closedLoop - .minOutput(-1) - .maxOutput(1) - .pid(0,0,0) - .velocityFF(0); - - return config; - } - public static SparkFlexConfig baseSparkFlexConfig(){//why? no Se. - SparkFlexConfig config = new SparkFlexConfig(); - - config.idleMode(IdleMode.kBrake); - - config.voltageCompensation(12); - config.smartCurrentLimit(50); - - config.closedLoop - .minOutput(-1) - .maxOutput(1) - .pid(0,0,0) - .velocityFF(0); - - return config; - } } \ No newline at end of file diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index d5eb189c..42a75d98 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -130,7 +130,7 @@ static void reportNextNSparkErrors(int n) { lastSparkErrorIndexReported = (lastSparkErrorIndexReported + n) % flags.size(); } - public static boolean isSparkOverheated(SparkBase spark){ + public static boolean isSparkOverheated(SparkBase spark){ int id = spark.getDeviceId(); int motorMaxTemp = sparkTemperatureLimits.get(id); return ( spark.getMotorTemperature() >= motorMaxTemp ); From 3c24b3cd5f6e58138d822bfca81c5f34271a70e7 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:02:25 -0800 Subject: [PATCH 46/63] Revert "Tried adding sim support to CanCoders" This reverts commit 158c96e6a58aa8045f51faf3b33401863cae981f. --- .../lib199/MotorControllerFactory.java | 2 +- .../carlmontrobotics/lib199/SensorFactory.java | 13 +++---------- .../lib199/sim/MockedCANCoder.java | 17 ----------------- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 6f39313f..d333ace0 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -116,7 +116,7 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); } else { - spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); //what if the Mocked motor is a 550 with a custom config? + spark = MockSparkMax.createMockSparkMax(id, SparkLowLevel.MotorType.kBrushless, MockSparkMax.NEOType.NEO); } spark.configure( config, diff --git a/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java b/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java index 1a657296..2bcedc5d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/SensorFactory.java @@ -23,16 +23,9 @@ public class SensorFactory { * @return The CANCoder object */ public static CANcoder createCANCoder(int port) { - // CANcoder canCoder = new CANcoder(port); - // if (RobotBase.isSimulation()) - // canCoder = MockedCANCoder.createMock(canCoder); - // return canCoder; - CANcoder canCoder; - if (RobotBase.isReal()) { - canCoder = new CANcoder(port); - } else { - canCoder = MockedCANCoder.createMock(new CANcoder(port)); - } + CANcoder canCoder = new CANcoder(port); + if (RobotBase.isSimulation()) + new MockedCANCoder(canCoder); return canCoder; } diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java index 01d3bd02..02dfd603 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java @@ -2,15 +2,8 @@ import java.util.HashMap; -import org.carlmontrobotics.lib199.ErrorCodeAnswer; -import org.carlmontrobotics.lib199.Mocks; -import org.carlmontrobotics.lib199.REVLibErrorAnswer; - -import com.ctre.phoenix6.configs.CANcoderConfigurator; import com.ctre.phoenix6.hardware.CANcoder; import com.ctre.phoenix6.sim.CANcoderSimState; -import com.revrobotics.spark.SparkFlex; -import com.revrobotics.spark.SparkLowLevel.MotorType; import edu.wpi.first.hal.HALValue; import edu.wpi.first.hal.SimBoolean; @@ -29,14 +22,12 @@ public class MockedCANCoder { private SimDeviceSim deviceSim; private SimDouble position; // Rotations - Continuous private CANcoderSimState sim; - private CANcoderConfigurator configurator; public MockedCANCoder(CANcoder canCoder) { port = canCoder.getDeviceID(); device = SimDevice.create("CANDutyCycle:CANCoder", port); position = device.createDouble("position", Direction.kInput, 0); sim = canCoder.getSimState(); - configurator = canCoder.getConfigurator(); deviceSim = new SimDeviceSim("CANDutyCycle:CANCoder", port); deviceSim.registerValueChangedCallback(position, new SimValueCallback() { @Override @@ -47,12 +38,4 @@ public void callback(String name, int handle, int direction, HALValue value) { sims.put(port, this); } - public CANcoderConfigurator getConfigurator() { - return configurator; - } - - public static CANcoder createMock(CANcoder canCoder) { - return Mocks.createMock(CANcoder.class, new MockedCANCoder(canCoder)); - } - } \ No newline at end of file From cf50401a60522471be90d2cb9a18f3c53f2ff604 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 21 Dec 2025 18:38:08 -0800 Subject: [PATCH 47/63] removed some deprecated stuff from MotorErrors, Lib199Subsytem, deleted VarType and fixed MotorErrorTest --- .../lib199/Lib199Subsystem.java | 37 ------------------- .../carlmontrobotics/lib199/MotorErrors.java | 22 ----------- .../lib199/logging/VarType.java | 10 ----- .../lib199/sim/MockedCANCoder.java | 1 - .../lib199/MotorErrorsTest.java | 24 ++++++------ 5 files changed, 12 insertions(+), 82 deletions(-) delete mode 100644 src/main/java/org/carlmontrobotics/lib199/logging/VarType.java diff --git a/src/main/java/org/carlmontrobotics/lib199/Lib199Subsystem.java b/src/main/java/org/carlmontrobotics/lib199/Lib199Subsystem.java index 3b6ed67c..2f013360 100644 --- a/src/main/java/org/carlmontrobotics/lib199/Lib199Subsystem.java +++ b/src/main/java/org/carlmontrobotics/lib199/Lib199Subsystem.java @@ -13,9 +13,6 @@ public class Lib199Subsystem implements Subsystem { private static final CopyOnWriteArrayList periodicSimulationMethods = new CopyOnWriteArrayList<>(); private static final Consumer RUN_RUNNABLE = Runnable::run; - @Deprecated - public static final long asyncSleepTime = 20; - static { ensureRegistered(); } @@ -43,37 +40,10 @@ public static void registerPeriodic(Runnable method) { periodicMethods.add(method); } - @Deprecated - /** - * @deprecated Use registerSimulationPeriodic - * @param method - */ - public static void simulationPeriodic(Runnable method) { - registerSimulationPeriodic(method); - } - public static void registerSimulationPeriodic(Runnable method) { periodicSimulationMethods.add(method); } - @Deprecated - /** - * @deprecated Use registerPeriodic - * @param method - */ - public static void registerAsyncPeriodic(Runnable method) { - registerPeriodic(method); - } - - @Deprecated - /** - * @deprecated Use registerSimulationPeriodic - * @param method - */ - public static void registerAsyncSimulationPeriodic(Runnable method) { - registerSimulationPeriodic(method); - } - @Override public void periodic() { periodicMethods.forEach(RUN_RUNNABLE); @@ -84,13 +54,6 @@ public void simulationPeriodic() { periodicSimulationMethods.forEach(RUN_RUNNABLE); } - @Deprecated - /** - * No longer does anything. - */ - public synchronized void asyncPeriodic() { - } - private Lib199Subsystem() {} } diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 42a75d98..385c43fd 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -113,11 +113,6 @@ private static String formatStickyFaults(SparkBase spark) { return formatFaults(f); } - @Deprecated - public static void printSparkMaxErrorMessages() { - printSparkErrorMessages(); - } - public static void printSparkErrorMessages() { flags.keySet().forEach(MotorErrors::checkSparkErrors); } @@ -143,11 +138,6 @@ public static void reportSparkTemp(SparkBase spark, int temperatureLimit) { overheatedSparks.put(id, 0); } - @Deprecated - public static void doReportSparkMaxTemp() { - doReportSparkTemp(); - } - public static void doReportSparkTemp() { temperatureSparks.forEach(MotorErrors::reportSparkTemp); } @@ -210,16 +200,4 @@ private static void reportSparkTemp(int port, SparkBase spark) { } private MotorErrors() {} - - @Deprecated - public static enum TemperatureLimit { - NEO(70), NEO_550(40); - - public final int limit; - - private TemperatureLimit(int limit) { - this.limit = limit; - } - } - } diff --git a/src/main/java/org/carlmontrobotics/lib199/logging/VarType.java b/src/main/java/org/carlmontrobotics/lib199/logging/VarType.java deleted file mode 100644 index 5ae5244b..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/logging/VarType.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.carlmontrobotics.lib199.logging; - -/** - * Represents the type of a variable in the logging code - * @deprecated Instead use WPILib's Logging API - */ -@Deprecated -public enum VarType { - BOOLEAN, INTEGER, DOUBLE, STRING; -} \ No newline at end of file diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java index 02dfd603..a7847a60 100644 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java +++ b/src/main/java/org/carlmontrobotics/lib199/sim/MockedCANCoder.java @@ -37,5 +37,4 @@ public void callback(String name, int handle, int direction, HALValue value) { }, true); sims.put(port, this); } - } \ No newline at end of file diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java index cd27db62..d0ac84ea 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java @@ -146,10 +146,10 @@ public void testOtherErrors() { public void testFaultReporting() { SparkMax sensorFaultSparkMax = Mocks.createMock(SparkMax.class, new SensorFaultSparkMax(), false); errStream.reset(); - MotorErrors.checkSparkMaxErrors(sensorFaultSparkMax); + MotorErrors.checkSparkErrors(sensorFaultSparkMax); assertNotEquals(0, errStream.toByteArray().length); errStream.reset(); - MotorErrors.checkSparkMaxErrors(sensorFaultSparkMax); + MotorErrors.checkSparkErrors(sensorFaultSparkMax); assertEquals(0, errStream.toByteArray().length); } @@ -157,10 +157,10 @@ public void testFaultReporting() { public void testStickyFaultReporting() { SparkMax stickySensorFaultSparkMax = Mocks.createMock(SparkMax.class, new StickySensorFaultSparkMax(), false); errStream.reset(); - MotorErrors.checkSparkMaxErrors(stickySensorFaultSparkMax); + MotorErrors.checkSparkErrors(stickySensorFaultSparkMax); assertNotEquals(0, errStream.toByteArray().length); errStream.reset(); - MotorErrors.checkSparkMaxErrors(stickySensorFaultSparkMax); + MotorErrors.checkSparkErrors(stickySensorFaultSparkMax); assertEquals(0, errStream.toByteArray().length); } @@ -176,30 +176,30 @@ public void testReportSparkMaxTemp() { private void doTestReportSparkMaxTemp(int id) { TemperatureSparkMax spark = (TemperatureSparkMax)Mocks.createMock(SparkMax.class, new TemperatureSparkMax.Instance(id), TemperatureSparkMax.class); String smartDashboardKey = "Port " + id + " Spark Max Temp"; - MotorErrors.reportSparkMaxTemp((SparkMax)spark, 40); + MotorErrors.reportSparkTemp((SparkMax)spark, 40); spark.setTemperature(20); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); assertEquals(50, spark.getSmartCurrentLimit()); spark.setTemperature(20); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); assertEquals(50, spark.getSmartCurrentLimit()); if(MotorErrors.kOverheatTripCount > 1) { spark.setTemperature(51); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(51, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); assertEquals(50, spark.getSmartCurrentLimit()); spark.setTemperature(20); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); assertEquals(50, spark.getSmartCurrentLimit()); } @@ -212,7 +212,7 @@ private void doTestReportSparkMaxTemp(int id) { spark.setTemperature(51); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(51, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); } @@ -221,13 +221,13 @@ private void doTestReportSparkMaxTemp(int id) { spark.setTemperature(51); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(51, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); assertEquals(1, spark.getSmartCurrentLimit()); spark.setTemperature(20); spark.setSmartCurrentLimit(50); - MotorErrors.doReportSparkMaxTemp(); + MotorErrors.doReportSparkTemp(); assertEquals(20, SmartDashboard.getNumber(smartDashboardKey, 0), 0.01); assertEquals(1, spark.getSmartCurrentLimit()); From 88c7b6efab35d24a81773a8632e32b30dab69415 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 21 Dec 2025 20:38:41 -0800 Subject: [PATCH 48/63] made swerverModule work with any neo motor --- .../lib199/swerve/SwerveModule.java | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 248db5d8..e1345276 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -6,16 +6,18 @@ import java.util.function.Supplier; +import org.carlmontrobotics.lib199.SparkMotorType; import org.mockito.internal.reporting.SmartPrinter; import com.ctre.phoenix6.configs.CANcoderConfiguration; import com.ctre.phoenix6.hardware.CANcoder; -import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.SparkBase; import com.revrobotics.spark.SparkBase.PersistMode; import com.revrobotics.spark.SparkBase.ResetMode; import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; import com.revrobotics.spark.config.SparkBaseConfig; import com.revrobotics.spark.config.SparkMaxConfig; +import com.revrobotics.spark.config.SparkFlexConfig; import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.controller.PIDController; @@ -45,11 +47,11 @@ public class SwerveModule implements Sendable { public enum ModuleType {FL, FR, BL, BR}; private SwerveConfig config; - private SparkMaxConfig turnConfig = new SparkMaxConfig(); - private SparkMaxConfig driveConfig = new SparkMaxConfig(); + private SparkBaseConfig turnConfig; + private SparkBaseConfig driveConfig; private ModuleType type; - private SparkMax drive, turn; + private SparkBase drive, turn; private CANcoder turnEncoder; private PIDController drivePIDController; private ProfiledPIDController turnPIDController; @@ -70,9 +72,30 @@ public enum ModuleType {FL, FR, BL, BR}; private static final int NEO_HALL_COUNTS_PER_REV = 42; private static final int ENCODER_POSITION_PERIOD_MS = 20; private int encoderAverageDepth = 2; + + SparkMotorType driveMotorType; + SparkMotorType turnMotorType; - public SwerveModule(SwerveConfig config, ModuleType type, SparkMax drive, SparkMax turn, CANcoder turnEncoder, + public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, SparkBase turn, SparkMotorType driveMotorType, SparkMotorType turnMotorType, CANcoder turnEncoder, int arrIndex, Supplier pitchDegSupplier, Supplier rollDegSupplier) { + this.driveMotorType = driveMotorType; + this.turnMotorType = turnMotorType; + switch(driveMotorType) { + case NEO, NEO550, NEO_2, SOLO_VORTEX: + driveConfig = new SparkMaxConfig(); + break; + case VORTEX: + driveConfig = new SparkFlexConfig(); + break; + } + switch(turnMotorType) { + case NEO, NEO550, NEO_2, SOLO_VORTEX: + turnConfig = new SparkMaxConfig(); + break; + case VORTEX: + turnConfig = new SparkFlexConfig(); + break; + } //SmartDashboard.putNumber("Target Angle (deg)", 0.0); String moduleString = type.toString(); this.timer = new Timer(); @@ -441,15 +464,30 @@ public void toggleMode() { public void brake() { idleMode = IdleMode.kBrake; - SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kBrake); + SparkBaseConfig config = null; + switch(driveMotorType){ + case NEO, NEO550, NEO_2, SOLO_VORTEX: + config = new SparkMaxConfig().idleMode(IdleMode.kBrake); + break; + case VORTEX: + config = new SparkFlexConfig().idleMode(IdleMode.kBrake); + break; + } drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } public void coast() { idleMode = IdleMode.kCoast; - idleMode = IdleMode.kCoast; - SparkBaseConfig config = new SparkMaxConfig().idleMode(IdleMode.kCoast); + SparkBaseConfig config = null; + switch(driveMotorType){ + case NEO, NEO550, NEO_2, SOLO_VORTEX: + config = new SparkMaxConfig().idleMode(IdleMode.kCoast); + break; + case VORTEX: + config = new SparkFlexConfig().idleMode(IdleMode.kCoast); + break; + } drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } From c04004c1fcc642bc7b7cfbac83fbb89b12cf7dbb Mon Sep 17 00:00:00 2001 From: CoolSpy3 <55305038+CoolSpy3@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:04:12 -0800 Subject: [PATCH 49/63] fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/carlmontrobotics/lib199/Elastic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/Elastic.java b/src/main/java/org/carlmontrobotics/lib199/Elastic.java index d8d20dea..b872ae8d 100644 --- a/src/main/java/org/carlmontrobotics/lib199/Elastic.java +++ b/src/main/java/org/carlmontrobotics/lib199/Elastic.java @@ -16,7 +16,7 @@ import edu.wpi.first.networktables.StringTopic; /** * A class that provides methods for interacting with the Elastic dashboard, including sending - * notifications and selecting tabs. This taken striaght from the official Elastic documentation + * notifications and selecting tabs. This taken straight from the official Elastic documentation * (see https://frc-elastic.gitbook.io/docs/additional-features-and-references/robot-notifications-with-elasticlib for documentation * and for original code https://github.com/Gold872/elastic-dashboard/blob/main/elasticlib/Elastic.java) */ From f6580af6a437dbe5255c82afef73be6c50917d4c Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:12:50 -0800 Subject: [PATCH 50/63] MotorErrorsTest --- .../lib199/MotorErrorsTest.java | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java index d0ac84ea..af4f50e7 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java @@ -10,7 +10,7 @@ import com.ctre.phoenix.ErrorCode; import com.revrobotics.REVLibError; - +import com.revrobotics.spark.SparkBase.Faults; import com.revrobotics.spark.SparkMax; import org.carlmontrobotics.lib199.testUtils.ErrStreamTest; @@ -19,36 +19,19 @@ import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; public class MotorErrorsTest extends ErrStreamTest { - public enum FaultID { - kBrownout(0), kOvercurrent(1), kOvervoltage(2), kMotorFault(3), kSensorFault(4), kStall(5), kEEPROMCRC(6), - kCANTX(7), kCANRX(8), kHasReset(9), kDRVFault(10), kOtherFault(11), kSoftLimitFwd(12), kSoftLimitRev(13), - kHardLimitFwd(14), kHardLimitRev(15); - - @SuppressWarnings("MemberName") - public final int value; - - FaultID(int value) { - this.value = value; - } - } - public static class SensorFaultSparkMax { + Faults sensorFault = new Faults(4); public short getFaults() { - return (short)FaultID.kSensorFault.value; - } - - public boolean getFault(FaultID faultID) { - return faultID == FaultID.kSensorFault; + // return (short)FaultID.kSensorFault.value; + return (short)sensorFault.rawBits; } } public static class StickySensorFaultSparkMax { + Faults sensorFault = new Faults(4); public short getStickyFaults() { - return (short)FaultID.kSensorFault.value; - } - - public boolean getStickyFault(FaultID faultID) { - return faultID == FaultID.kSensorFault; + // return (short)FaultID.kSensorFault.value; + return (short)sensorFault.rawBits; } } From bbe6188b048dbddfd9c86c3e09fa358fa71e0b4e Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:16:22 -0800 Subject: [PATCH 51/63] moved Elastic to vendorLibs --- .../org/carlmontrobotics/lib199/{ => vendorLibs}/Elastic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/org/carlmontrobotics/lib199/{ => vendorLibs}/Elastic.java (99%) diff --git a/src/main/java/org/carlmontrobotics/lib199/Elastic.java b/src/main/java/org/carlmontrobotics/lib199/vendorLibs/Elastic.java similarity index 99% rename from src/main/java/org/carlmontrobotics/lib199/Elastic.java rename to src/main/java/org/carlmontrobotics/lib199/vendorLibs/Elastic.java index b872ae8d..8c8cca42 100644 --- a/src/main/java/org/carlmontrobotics/lib199/Elastic.java +++ b/src/main/java/org/carlmontrobotics/lib199/vendorLibs/Elastic.java @@ -5,7 +5,7 @@ // From: https://frc-elastic.gitbook.io/docs/additional-features-and-references/robot-notifications-with-elasticlib and https://github.com/Gold872/elastic-dashboard/blob/main/elasticlib/Elastic.java -package org.carlmontrobotics.lib199; +package org.carlmontrobotics.lib199.vendorLibs; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; From 3d7ceb39f46b35686537768b5182d0ac448eba6f Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 28 Dec 2025 02:09:49 -0800 Subject: [PATCH 52/63] added LimelightHelpers as a vendorLib --- .../lib199/vendorLibs/LimelightHelpers.java | 1705 +++++++++++++++++ 1 file changed, 1705 insertions(+) create mode 100644 src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java diff --git a/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java b/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java new file mode 100644 index 00000000..f0ac142d --- /dev/null +++ b/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java @@ -0,0 +1,1705 @@ +/* +docs: https://docs.limelightvision.io/docs/docs-limelight/apis/limelight-lib +code: https://github.com/LimelightVision/limelightlib-wpijava/blob/main/LimelightHelpers.java +*/ + +//LimelightHelpers v1.12 (REQUIRES LLOS 2025.0 OR LATER) + +package org.carlmontrobotics.lib199.vendorLibs; + +import edu.wpi.first.networktables.DoubleArrayEntry; +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableEntry; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.TimestampedDoubleArray; +import org.carlmontrobotics.lib199.vendorLibs.LimelightHelpers.LimelightResults; +import org.carlmontrobotics.lib199.vendorLibs.LimelightHelpers.PoseEstimate; +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.Translation3d; +import edu.wpi.first.math.util.Units; +import edu.wpi.first.math.geometry.Rotation3d; +import edu.wpi.first.math.geometry.Translation2d; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.ConcurrentHashMap; + +/** + * LimelightHelpers provides static methods and classes for interfacing with Limelight vision cameras in FRC. + * This library supports all Limelight features including AprilTag tracking, Neural Networks, and standard color/retroreflective tracking. + */ +public class LimelightHelpers { + + private static final Map doubleArrayEntries = new ConcurrentHashMap<>(); + + /** + * Represents a Color/Retroreflective Target Result extracted from JSON Output + */ + public static class LimelightTarget_Retro { + + @JsonProperty("t6c_ts") + private double[] cameraPose_TargetSpace; + + @JsonProperty("t6r_fs") + private double[] robotPose_FieldSpace; + + @JsonProperty("t6r_ts") + private double[] robotPose_TargetSpace; + + @JsonProperty("t6t_cs") + private double[] targetPose_CameraSpace; + + @JsonProperty("t6t_rs") + private double[] targetPose_RobotSpace; + + public Pose3d getCameraPose_TargetSpace() + { + return toPose3D(cameraPose_TargetSpace); + } + public Pose3d getRobotPose_FieldSpace() + { + return toPose3D(robotPose_FieldSpace); + } + public Pose3d getRobotPose_TargetSpace() + { + return toPose3D(robotPose_TargetSpace); + } + public Pose3d getTargetPose_CameraSpace() + { + return toPose3D(targetPose_CameraSpace); + } + public Pose3d getTargetPose_RobotSpace() + { + return toPose3D(targetPose_RobotSpace); + } + + public Pose2d getCameraPose_TargetSpace2D() + { + return toPose2D(cameraPose_TargetSpace); + } + public Pose2d getRobotPose_FieldSpace2D() + { + return toPose2D(robotPose_FieldSpace); + } + public Pose2d getRobotPose_TargetSpace2D() + { + return toPose2D(robotPose_TargetSpace); + } + public Pose2d getTargetPose_CameraSpace2D() + { + return toPose2D(targetPose_CameraSpace); + } + public Pose2d getTargetPose_RobotSpace2D() + { + return toPose2D(targetPose_RobotSpace); + } + + @JsonProperty("ta") + public double ta; + + @JsonProperty("tx") + public double tx; + + @JsonProperty("ty") + public double ty; + + @JsonProperty("txp") + public double tx_pixels; + + @JsonProperty("typ") + public double ty_pixels; + + @JsonProperty("tx_nocross") + public double tx_nocrosshair; + + @JsonProperty("ty_nocross") + public double ty_nocrosshair; + + @JsonProperty("ts") + public double ts; + + public LimelightTarget_Retro() { + cameraPose_TargetSpace = new double[6]; + robotPose_FieldSpace = new double[6]; + robotPose_TargetSpace = new double[6]; + targetPose_CameraSpace = new double[6]; + targetPose_RobotSpace = new double[6]; + } + + } + + /** + * Represents an AprilTag/Fiducial Target Result extracted from JSON Output + */ + public static class LimelightTarget_Fiducial { + + @JsonProperty("fID") + public double fiducialID; + + @JsonProperty("fam") + public String fiducialFamily; + + @JsonProperty("t6c_ts") + private double[] cameraPose_TargetSpace; + + @JsonProperty("t6r_fs") + private double[] robotPose_FieldSpace; + + @JsonProperty("t6r_ts") + private double[] robotPose_TargetSpace; + + @JsonProperty("t6t_cs") + private double[] targetPose_CameraSpace; + + @JsonProperty("t6t_rs") + private double[] targetPose_RobotSpace; + + public Pose3d getCameraPose_TargetSpace() + { + return toPose3D(cameraPose_TargetSpace); + } + public Pose3d getRobotPose_FieldSpace() + { + return toPose3D(robotPose_FieldSpace); + } + public Pose3d getRobotPose_TargetSpace() + { + return toPose3D(robotPose_TargetSpace); + } + public Pose3d getTargetPose_CameraSpace() + { + return toPose3D(targetPose_CameraSpace); + } + public Pose3d getTargetPose_RobotSpace() + { + return toPose3D(targetPose_RobotSpace); + } + + public Pose2d getCameraPose_TargetSpace2D() + { + return toPose2D(cameraPose_TargetSpace); + } + public Pose2d getRobotPose_FieldSpace2D() + { + return toPose2D(robotPose_FieldSpace); + } + public Pose2d getRobotPose_TargetSpace2D() + { + return toPose2D(robotPose_TargetSpace); + } + public Pose2d getTargetPose_CameraSpace2D() + { + return toPose2D(targetPose_CameraSpace); + } + public Pose2d getTargetPose_RobotSpace2D() + { + return toPose2D(targetPose_RobotSpace); + } + + @JsonProperty("ta") + public double ta; + + @JsonProperty("tx") + public double tx; + + @JsonProperty("ty") + public double ty; + + @JsonProperty("txp") + public double tx_pixels; + + @JsonProperty("typ") + public double ty_pixels; + + @JsonProperty("tx_nocross") + public double tx_nocrosshair; + + @JsonProperty("ty_nocross") + public double ty_nocrosshair; + + @JsonProperty("ts") + public double ts; + + public LimelightTarget_Fiducial() { + cameraPose_TargetSpace = new double[6]; + robotPose_FieldSpace = new double[6]; + robotPose_TargetSpace = new double[6]; + targetPose_CameraSpace = new double[6]; + targetPose_RobotSpace = new double[6]; + } + } + + /** + * Represents a Barcode Target Result extracted from JSON Output + */ + public static class LimelightTarget_Barcode { + + /** + * Barcode family type (e.g. "QR", "DataMatrix", etc.) + */ + @JsonProperty("fam") + public String family; + + /** + * Gets the decoded data content of the barcode + */ + @JsonProperty("data") + public String data; + + @JsonProperty("txp") + public double tx_pixels; + + @JsonProperty("typ") + public double ty_pixels; + + @JsonProperty("tx") + public double tx; + + @JsonProperty("ty") + public double ty; + + @JsonProperty("tx_nocross") + public double tx_nocrosshair; + + @JsonProperty("ty_nocross") + public double ty_nocrosshair; + + @JsonProperty("ta") + public double ta; + + @JsonProperty("pts") + public double[][] corners; + + public LimelightTarget_Barcode() { + } + + public String getFamily() { + return family; + } + } + + /** + * Represents a Neural Classifier Pipeline Result extracted from JSON Output + */ + public static class LimelightTarget_Classifier { + + @JsonProperty("class") + public String className; + + @JsonProperty("classID") + public double classID; + + @JsonProperty("conf") + public double confidence; + + @JsonProperty("zone") + public double zone; + + @JsonProperty("tx") + public double tx; + + @JsonProperty("txp") + public double tx_pixels; + + @JsonProperty("ty") + public double ty; + + @JsonProperty("typ") + public double ty_pixels; + + public LimelightTarget_Classifier() { + } + } + + /** + * Represents a Neural Detector Pipeline Result extracted from JSON Output + */ + public static class LimelightTarget_Detector { + + @JsonProperty("class") + public String className; + + @JsonProperty("classID") + public double classID; + + @JsonProperty("conf") + public double confidence; + + @JsonProperty("ta") + public double ta; + + @JsonProperty("tx") + public double tx; + + @JsonProperty("ty") + public double ty; + + @JsonProperty("txp") + public double tx_pixels; + + @JsonProperty("typ") + public double ty_pixels; + + @JsonProperty("tx_nocross") + public double tx_nocrosshair; + + @JsonProperty("ty_nocross") + public double ty_nocrosshair; + + public LimelightTarget_Detector() { + } + } + + /** + * Limelight Results object, parsed from a Limelight's JSON results output. + */ + public static class LimelightResults { + + public String error; + + @JsonProperty("pID") + public double pipelineID; + + @JsonProperty("tl") + public double latency_pipeline; + + @JsonProperty("cl") + public double latency_capture; + + public double latency_jsonParse; + + @JsonProperty("ts") + public double timestamp_LIMELIGHT_publish; + + @JsonProperty("ts_rio") + public double timestamp_RIOFPGA_capture; + + @JsonProperty("v") + @JsonFormat(shape = Shape.NUMBER) + public boolean valid; + + @JsonProperty("botpose") + public double[] botpose; + + @JsonProperty("botpose_wpired") + public double[] botpose_wpired; + + @JsonProperty("botpose_wpiblue") + public double[] botpose_wpiblue; + + @JsonProperty("botpose_tagcount") + public double botpose_tagcount; + + @JsonProperty("botpose_span") + public double botpose_span; + + @JsonProperty("botpose_avgdist") + public double botpose_avgdist; + + @JsonProperty("botpose_avgarea") + public double botpose_avgarea; + + @JsonProperty("t6c_rs") + public double[] camerapose_robotspace; + + public Pose3d getBotPose3d() { + return toPose3D(botpose); + } + + public Pose3d getBotPose3d_wpiRed() { + return toPose3D(botpose_wpired); + } + + public Pose3d getBotPose3d_wpiBlue() { + return toPose3D(botpose_wpiblue); + } + + public Pose2d getBotPose2d() { + return toPose2D(botpose); + } + + public Pose2d getBotPose2d_wpiRed() { + return toPose2D(botpose_wpired); + } + + public Pose2d getBotPose2d_wpiBlue() { + return toPose2D(botpose_wpiblue); + } + + @JsonProperty("Retro") + public LimelightTarget_Retro[] targets_Retro; + + @JsonProperty("Fiducial") + public LimelightTarget_Fiducial[] targets_Fiducials; + + @JsonProperty("Classifier") + public LimelightTarget_Classifier[] targets_Classifier; + + @JsonProperty("Detector") + public LimelightTarget_Detector[] targets_Detector; + + @JsonProperty("Barcode") + public LimelightTarget_Barcode[] targets_Barcode; + + public LimelightResults() { + botpose = new double[6]; + botpose_wpired = new double[6]; + botpose_wpiblue = new double[6]; + camerapose_robotspace = new double[6]; + targets_Retro = new LimelightTarget_Retro[0]; + targets_Fiducials = new LimelightTarget_Fiducial[0]; + targets_Classifier = new LimelightTarget_Classifier[0]; + targets_Detector = new LimelightTarget_Detector[0]; + targets_Barcode = new LimelightTarget_Barcode[0]; + + } + + + } + + /** + * Represents a Limelight Raw Fiducial result from Limelight's NetworkTables output. + */ + public static class RawFiducial { + public int id = 0; + public double txnc = 0; + public double tync = 0; + public double ta = 0; + public double distToCamera = 0; + public double distToRobot = 0; + public double ambiguity = 0; + + + public RawFiducial(int id, double txnc, double tync, double ta, double distToCamera, double distToRobot, double ambiguity) { + this.id = id; + this.txnc = txnc; + this.tync = tync; + this.ta = ta; + this.distToCamera = distToCamera; + this.distToRobot = distToRobot; + this.ambiguity = ambiguity; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + RawFiducial other = (RawFiducial) obj; + return id == other.id && + Double.compare(txnc, other.txnc) == 0 && + Double.compare(tync, other.tync) == 0 && + Double.compare(ta, other.ta) == 0 && + Double.compare(distToCamera, other.distToCamera) == 0 && + Double.compare(distToRobot, other.distToRobot) == 0 && + Double.compare(ambiguity, other.ambiguity) == 0; + } + + } + + /** + * Represents a Limelight Raw Neural Detector result from Limelight's NetworkTables output. + */ + public static class RawDetection { + public int classId = 0; + public double txnc = 0; + public double tync = 0; + public double ta = 0; + public double corner0_X = 0; + public double corner0_Y = 0; + public double corner1_X = 0; + public double corner1_Y = 0; + public double corner2_X = 0; + public double corner2_Y = 0; + public double corner3_X = 0; + public double corner3_Y = 0; + + + public RawDetection(int classId, double txnc, double tync, double ta, + double corner0_X, double corner0_Y, + double corner1_X, double corner1_Y, + double corner2_X, double corner2_Y, + double corner3_X, double corner3_Y ) { + this.classId = classId; + this.txnc = txnc; + this.tync = tync; + this.ta = ta; + this.corner0_X = corner0_X; + this.corner0_Y = corner0_Y; + this.corner1_X = corner1_X; + this.corner1_Y = corner1_Y; + this.corner2_X = corner2_X; + this.corner2_Y = corner2_Y; + this.corner3_X = corner3_X; + this.corner3_Y = corner3_Y; + } + } + + /** + * Represents a 3D Pose Estimate. + */ + public static class PoseEstimate { + public Pose2d pose; + public double timestampSeconds; + public double latency; + public int tagCount; + public double tagSpan; + public double avgTagDist; + public double avgTagArea; + + public RawFiducial[] rawFiducials; + public boolean isMegaTag2; + + /** + * Instantiates a PoseEstimate object with default values + */ + public PoseEstimate() { + this.pose = new Pose2d(); + this.timestampSeconds = 0; + this.latency = 0; + this.tagCount = 0; + this.tagSpan = 0; + this.avgTagDist = 0; + this.avgTagArea = 0; + this.rawFiducials = new RawFiducial[]{}; + this.isMegaTag2 = false; + } + + public PoseEstimate(Pose2d pose, double timestampSeconds, double latency, + int tagCount, double tagSpan, double avgTagDist, + double avgTagArea, RawFiducial[] rawFiducials, boolean isMegaTag2) { + + this.pose = pose; + this.timestampSeconds = timestampSeconds; + this.latency = latency; + this.tagCount = tagCount; + this.tagSpan = tagSpan; + this.avgTagDist = avgTagDist; + this.avgTagArea = avgTagArea; + this.rawFiducials = rawFiducials; + this.isMegaTag2 = isMegaTag2; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + PoseEstimate that = (PoseEstimate) obj; + // We don't compare the timestampSeconds as it isn't relevant for equality and makes + // unit testing harder + return Double.compare(that.latency, latency) == 0 + && tagCount == that.tagCount + && Double.compare(that.tagSpan, tagSpan) == 0 + && Double.compare(that.avgTagDist, avgTagDist) == 0 + && Double.compare(that.avgTagArea, avgTagArea) == 0 + && pose.equals(that.pose) + && Arrays.equals(rawFiducials, that.rawFiducials); + } + + } + + /** + * Encapsulates the state of an internal Limelight IMU. + */ + public static class IMUData { + public double robotYaw = 0.0; + public double Roll = 0.0; + public double Pitch = 0.0; + public double Yaw = 0.0; + public double gyroX = 0.0; + public double gyroY = 0.0; + public double gyroZ = 0.0; + public double accelX = 0.0; + public double accelY = 0.0; + public double accelZ = 0.0; + + public IMUData() {} + + public IMUData(double[] imuData) { + if (imuData != null && imuData.length >= 10) { + this.robotYaw = imuData[0]; + this.Roll = imuData[1]; + this.Pitch = imuData[2]; + this.Yaw = imuData[3]; + this.gyroX = imuData[4]; + this.gyroY = imuData[5]; + this.gyroZ = imuData[6]; + this.accelX = imuData[7]; + this.accelY = imuData[8]; + this.accelZ = imuData[9]; + } + } + } + + + private static ObjectMapper mapper; + + /** + * Print JSON Parse time to the console in milliseconds + */ + static boolean profileJSON = false; + + static final String sanitizeName(String name) { + if ("".equals(name) || name == null) { + return "limelight"; + } + return name; + } + + /** + * Takes a 6-length array of pose data and converts it to a Pose3d object. + * Array format: [x, y, z, roll, pitch, yaw] where angles are in degrees. + * @param inData Array containing pose data [x, y, z, roll, pitch, yaw] + * @return Pose3d object representing the pose, or empty Pose3d if invalid data + */ + public static Pose3d toPose3D(double[] inData){ + if(inData.length < 6) + { + //System.err.println("Bad LL 3D Pose Data!"); + return new Pose3d(); + } + return new Pose3d( + new Translation3d(inData[0], inData[1], inData[2]), + new Rotation3d(Units.degreesToRadians(inData[3]), Units.degreesToRadians(inData[4]), + Units.degreesToRadians(inData[5]))); + } + + /** + * Takes a 6-length array of pose data and converts it to a Pose2d object. + * Uses only x, y, and yaw components, ignoring z, roll, and pitch. + * Array format: [x, y, z, roll, pitch, yaw] where angles are in degrees. + * @param inData Array containing pose data [x, y, z, roll, pitch, yaw] + * @return Pose2d object representing the pose, or empty Pose2d if invalid data + */ + public static Pose2d toPose2D(double[] inData){ + if(inData.length < 6) + { + //System.err.println("Bad LL 2D Pose Data!"); + return new Pose2d(); + } + Translation2d tran2d = new Translation2d(inData[0], inData[1]); + Rotation2d r2d = new Rotation2d(Units.degreesToRadians(inData[5])); + return new Pose2d(tran2d, r2d); + } + + /** + * Converts a Pose3d object to an array of doubles in the format [x, y, z, roll, pitch, yaw]. + * Translation components are in meters, rotation components are in degrees. + * + * @param pose The Pose3d object to convert + * @return A 6-element array containing [x, y, z, roll, pitch, yaw] + */ + public static double[] pose3dToArray(Pose3d pose) { + double[] result = new double[6]; + result[0] = pose.getTranslation().getX(); + result[1] = pose.getTranslation().getY(); + result[2] = pose.getTranslation().getZ(); + result[3] = Units.radiansToDegrees(pose.getRotation().getX()); + result[4] = Units.radiansToDegrees(pose.getRotation().getY()); + result[5] = Units.radiansToDegrees(pose.getRotation().getZ()); + return result; + } + + /** + * Converts a Pose2d object to an array of doubles in the format [x, y, z, roll, pitch, yaw]. + * Translation components are in meters, rotation components are in degrees. + * Note: z, roll, and pitch will be 0 since Pose2d only contains x, y, and yaw. + * + * @param pose The Pose2d object to convert + * @return A 6-element array containing [x, y, 0, 0, 0, yaw] + */ + public static double[] pose2dToArray(Pose2d pose) { + double[] result = new double[6]; + result[0] = pose.getTranslation().getX(); + result[1] = pose.getTranslation().getY(); + result[2] = 0; + result[3] = Units.radiansToDegrees(0); + result[4] = Units.radiansToDegrees(0); + result[5] = Units.radiansToDegrees(pose.getRotation().getRadians()); + return result; + } + + private static double extractArrayEntry(double[] inData, int position){ + if(inData.length < position+1) + { + return 0; + } + return inData[position]; + } + + private static PoseEstimate getBotPoseEstimate(String limelightName, String entryName, boolean isMegaTag2) { + DoubleArrayEntry poseEntry = LimelightHelpers.getLimelightDoubleArrayEntry(limelightName, entryName); + + TimestampedDoubleArray tsValue = poseEntry.getAtomic(); + double[] poseArray = tsValue.value; + long timestamp = tsValue.timestamp; + + if (poseArray.length == 0) { + // Handle the case where no data is available + return null; // or some default PoseEstimate + } + + var pose = toPose2D(poseArray); + double latency = extractArrayEntry(poseArray, 6); + int tagCount = (int)extractArrayEntry(poseArray, 7); + double tagSpan = extractArrayEntry(poseArray, 8); + double tagDist = extractArrayEntry(poseArray, 9); + double tagArea = extractArrayEntry(poseArray, 10); + + // Convert server timestamp from microseconds to seconds and adjust for latency + double adjustedTimestamp = (timestamp / 1000000.0) - (latency / 1000.0); + + RawFiducial[] rawFiducials = new RawFiducial[tagCount]; + int valsPerFiducial = 7; + int expectedTotalVals = 11 + valsPerFiducial * tagCount; + + if (poseArray.length != expectedTotalVals) { + // Don't populate fiducials + } else { + for(int i = 0; i < tagCount; i++) { + int baseIndex = 11 + (i * valsPerFiducial); + int id = (int)poseArray[baseIndex]; + double txnc = poseArray[baseIndex + 1]; + double tync = poseArray[baseIndex + 2]; + double ta = poseArray[baseIndex + 3]; + double distToCamera = poseArray[baseIndex + 4]; + double distToRobot = poseArray[baseIndex + 5]; + double ambiguity = poseArray[baseIndex + 6]; + rawFiducials[i] = new RawFiducial(id, txnc, tync, ta, distToCamera, distToRobot, ambiguity); + } + } + + return new PoseEstimate(pose, adjustedTimestamp, latency, tagCount, tagSpan, tagDist, tagArea, rawFiducials, isMegaTag2); + } + + /** + * Gets the latest raw fiducial/AprilTag detection results from NetworkTables. + * + * @param limelightName Name/identifier of the Limelight + * @return Array of RawFiducial objects containing detection details + */ + public static RawFiducial[] getRawFiducials(String limelightName) { + var entry = LimelightHelpers.getLimelightNTTableEntry(limelightName, "rawfiducials"); + var rawFiducialArray = entry.getDoubleArray(new double[0]); + int valsPerEntry = 7; + if (rawFiducialArray.length % valsPerEntry != 0) { + return new RawFiducial[0]; + } + + int numFiducials = rawFiducialArray.length / valsPerEntry; + RawFiducial[] rawFiducials = new RawFiducial[numFiducials]; + + for (int i = 0; i < numFiducials; i++) { + int baseIndex = i * valsPerEntry; + int id = (int) extractArrayEntry(rawFiducialArray, baseIndex); + double txnc = extractArrayEntry(rawFiducialArray, baseIndex + 1); + double tync = extractArrayEntry(rawFiducialArray, baseIndex + 2); + double ta = extractArrayEntry(rawFiducialArray, baseIndex + 3); + double distToCamera = extractArrayEntry(rawFiducialArray, baseIndex + 4); + double distToRobot = extractArrayEntry(rawFiducialArray, baseIndex + 5); + double ambiguity = extractArrayEntry(rawFiducialArray, baseIndex + 6); + + rawFiducials[i] = new RawFiducial(id, txnc, tync, ta, distToCamera, distToRobot, ambiguity); + } + + return rawFiducials; + } + + /** + * Gets the latest raw neural detector results from NetworkTables + * + * @param limelightName Name/identifier of the Limelight + * @return Array of RawDetection objects containing detection details + */ + public static RawDetection[] getRawDetections(String limelightName) { + var entry = LimelightHelpers.getLimelightNTTableEntry(limelightName, "rawdetections"); + var rawDetectionArray = entry.getDoubleArray(new double[0]); + int valsPerEntry = 12; + if (rawDetectionArray.length % valsPerEntry != 0) { + return new RawDetection[0]; + } + + int numDetections = rawDetectionArray.length / valsPerEntry; + RawDetection[] rawDetections = new RawDetection[numDetections]; + + for (int i = 0; i < numDetections; i++) { + int baseIndex = i * valsPerEntry; // Starting index for this detection's data + int classId = (int) extractArrayEntry(rawDetectionArray, baseIndex); + double txnc = extractArrayEntry(rawDetectionArray, baseIndex + 1); + double tync = extractArrayEntry(rawDetectionArray, baseIndex + 2); + double ta = extractArrayEntry(rawDetectionArray, baseIndex + 3); + double corner0_X = extractArrayEntry(rawDetectionArray, baseIndex + 4); + double corner0_Y = extractArrayEntry(rawDetectionArray, baseIndex + 5); + double corner1_X = extractArrayEntry(rawDetectionArray, baseIndex + 6); + double corner1_Y = extractArrayEntry(rawDetectionArray, baseIndex + 7); + double corner2_X = extractArrayEntry(rawDetectionArray, baseIndex + 8); + double corner2_Y = extractArrayEntry(rawDetectionArray, baseIndex + 9); + double corner3_X = extractArrayEntry(rawDetectionArray, baseIndex + 10); + double corner3_Y = extractArrayEntry(rawDetectionArray, baseIndex + 11); + + rawDetections[i] = new RawDetection(classId, txnc, tync, ta, corner0_X, corner0_Y, corner1_X, corner1_Y, corner2_X, corner2_Y, corner3_X, corner3_Y); + } + + return rawDetections; + } + + /** + * Prints detailed information about a PoseEstimate to standard output. + * Includes timestamp, latency, tag count, tag span, average tag distance, + * average tag area, and detailed information about each detected fiducial. + * + * @param pose The PoseEstimate object to print. If null, prints "No PoseEstimate available." + */ + public static void printPoseEstimate(PoseEstimate pose) { + if (pose == null) { + System.out.println("No PoseEstimate available."); + return; + } + + System.out.printf("Pose Estimate Information:%n"); + System.out.printf("Timestamp (Seconds): %.3f%n", pose.timestampSeconds); + System.out.printf("Latency: %.3f ms%n", pose.latency); + System.out.printf("Tag Count: %d%n", pose.tagCount); + System.out.printf("Tag Span: %.2f meters%n", pose.tagSpan); + System.out.printf("Average Tag Distance: %.2f meters%n", pose.avgTagDist); + System.out.printf("Average Tag Area: %.2f%% of image%n", pose.avgTagArea); + System.out.printf("Is MegaTag2: %b%n", pose.isMegaTag2); + System.out.println(); + + if (pose.rawFiducials == null || pose.rawFiducials.length == 0) { + System.out.println("No RawFiducials data available."); + return; + } + + System.out.println("Raw Fiducials Details:"); + for (int i = 0; i < pose.rawFiducials.length; i++) { + RawFiducial fiducial = pose.rawFiducials[i]; + System.out.printf(" Fiducial #%d:%n", i + 1); + System.out.printf(" ID: %d%n", fiducial.id); + System.out.printf(" TXNC: %.2f%n", fiducial.txnc); + System.out.printf(" TYNC: %.2f%n", fiducial.tync); + System.out.printf(" TA: %.2f%n", fiducial.ta); + System.out.printf(" Distance to Camera: %.2f meters%n", fiducial.distToCamera); + System.out.printf(" Distance to Robot: %.2f meters%n", fiducial.distToRobot); + System.out.printf(" Ambiguity: %.2f%n", fiducial.ambiguity); + System.out.println(); + } + } + + public static Boolean validPoseEstimate(PoseEstimate pose) { + return pose != null && pose.rawFiducials != null && pose.rawFiducials.length != 0; + } + + public static NetworkTable getLimelightNTTable(String tableName) { + return NetworkTableInstance.getDefault().getTable(sanitizeName(tableName)); + } + + public static void Flush() { + NetworkTableInstance.getDefault().flush(); + } + + public static NetworkTableEntry getLimelightNTTableEntry(String tableName, String entryName) { + return getLimelightNTTable(tableName).getEntry(entryName); + } + + public static DoubleArrayEntry getLimelightDoubleArrayEntry(String tableName, String entryName) { + String key = tableName + "/" + entryName; + return doubleArrayEntries.computeIfAbsent(key, k -> { + NetworkTable table = getLimelightNTTable(tableName); + return table.getDoubleArrayTopic(entryName).getEntry(new double[0]); + }); + } + + public static double getLimelightNTDouble(String tableName, String entryName) { + return getLimelightNTTableEntry(tableName, entryName).getDouble(0.0); + } + + public static void setLimelightNTDouble(String tableName, String entryName, double val) { + getLimelightNTTableEntry(tableName, entryName).setDouble(val); + } + + public static void setLimelightNTDoubleArray(String tableName, String entryName, double[] val) { + getLimelightNTTableEntry(tableName, entryName).setDoubleArray(val); + } + + public static double[] getLimelightNTDoubleArray(String tableName, String entryName) { + return getLimelightNTTableEntry(tableName, entryName).getDoubleArray(new double[0]); + } + + + public static String getLimelightNTString(String tableName, String entryName) { + return getLimelightNTTableEntry(tableName, entryName).getString(""); + } + + public static String[] getLimelightNTStringArray(String tableName, String entryName) { + return getLimelightNTTableEntry(tableName, entryName).getStringArray(new String[0]); + } + + + public static URL getLimelightURLString(String tableName, String request) { + String urlString = "http://" + sanitizeName(tableName) + ".local:5807/" + request; + URL url; + try { + url = new URL(urlString); + return url; + } catch (MalformedURLException e) { + System.err.println("bad LL URL"); + } + return null; + } + ///// + ///// + + /** + * Does the Limelight have a valid target? + * @param limelightName Name of the Limelight camera ("" for default) + * @return True if a valid target is present, false otherwise + */ + public static boolean getTV(String limelightName) { + return 1.0 == getLimelightNTDouble(limelightName, "tv"); + } + + /** + * Gets the horizontal offset from the crosshair to the target in degrees. + * @param limelightName Name of the Limelight camera ("" for default) + * @return Horizontal offset angle in degrees + */ + public static double getTX(String limelightName) { + return getLimelightNTDouble(limelightName, "tx"); + } + + /** + * Gets the vertical offset from the crosshair to the target in degrees. + * @param limelightName Name of the Limelight camera ("" for default) + * @return Vertical offset angle in degrees + */ + public static double getTY(String limelightName) { + return getLimelightNTDouble(limelightName, "ty"); + } + + /** + * Gets the horizontal offset from the principal pixel/point to the target in degrees. This is the most accurate 2d metric if you are using a calibrated camera and you don't need adjustable crosshair functionality. + * @param limelightName Name of the Limelight camera ("" for default) + * @return Horizontal offset angle in degrees + */ + public static double getTXNC(String limelightName) { + return getLimelightNTDouble(limelightName, "txnc"); + } + + /** + * Gets the vertical offset from the principal pixel/point to the target in degrees. This is the most accurate 2d metric if you are using a calibrated camera and you don't need adjustable crosshair functionality. + * @param limelightName Name of the Limelight camera ("" for default) + * @return Vertical offset angle in degrees + */ + public static double getTYNC(String limelightName) { + return getLimelightNTDouble(limelightName, "tync"); + } + + /** + * Gets the target area as a percentage of the image (0-100%). + * @param limelightName Name of the Limelight camera ("" for default) + * @return Target area percentage (0-100) + */ + public static double getTA(String limelightName) { + return getLimelightNTDouble(limelightName, "ta"); + } + + /** + * T2D is an array that contains several targeting metrcis + * @param limelightName Name of the Limelight camera + * @return Array containing [targetValid, targetCount, targetLatency, captureLatency, tx, ty, txnc, tync, ta, tid, targetClassIndexDetector, + * targetClassIndexClassifier, targetLongSidePixels, targetShortSidePixels, targetHorizontalExtentPixels, targetVerticalExtentPixels, targetSkewDegrees] + */ + public static double[] getT2DArray(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "t2d"); + } + + /** + * Gets the number of targets currently detected. + * @param limelightName Name of the Limelight camera + * @return Number of detected targets + */ + public static int getTargetCount(String limelightName) { + double[] t2d = getT2DArray(limelightName); + if(t2d.length == 17) + { + return (int)t2d[1]; + } + return 0; + } + + /** + * Gets the classifier class index from the currently running neural classifier pipeline + * @param limelightName Name of the Limelight camera + * @return Class index from classifier pipeline + */ + public static int getClassifierClassIndex (String limelightName) { + double[] t2d = getT2DArray(limelightName); + if(t2d.length == 17) + { + return (int)t2d[10]; + } + return 0; + } + + /** + * Gets the detector class index from the primary result of the currently running neural detector pipeline. + * @param limelightName Name of the Limelight camera + * @return Class index from detector pipeline + */ + public static int getDetectorClassIndex (String limelightName) { + double[] t2d = getT2DArray(limelightName); + if(t2d.length == 17) + { + return (int)t2d[11]; + } + return 0; + } + + /** + * Gets the current neural classifier result class name. + * @param limelightName Name of the Limelight camera + * @return Class name string from classifier pipeline + */ + public static String getClassifierClass (String limelightName) { + return getLimelightNTString(limelightName, "tcclass"); + } + + /** + * Gets the primary neural detector result class name. + * @param limelightName Name of the Limelight camera + * @return Class name string from detector pipeline + */ + public static String getDetectorClass (String limelightName) { + return getLimelightNTString(limelightName, "tdclass"); + } + + /** + * Gets the pipeline's processing latency contribution. + * @param limelightName Name of the Limelight camera + * @return Pipeline latency in milliseconds + */ + public static double getLatency_Pipeline(String limelightName) { + return getLimelightNTDouble(limelightName, "tl"); + } + + /** + * Gets the capture latency. + * @param limelightName Name of the Limelight camera + * @return Capture latency in milliseconds + */ + public static double getLatency_Capture(String limelightName) { + return getLimelightNTDouble(limelightName, "cl"); + } + + /** + * Gets the active pipeline index. + * @param limelightName Name of the Limelight camera + * @return Current pipeline index (0-9) + */ + public static double getCurrentPipelineIndex(String limelightName) { + return getLimelightNTDouble(limelightName, "getpipe"); + } + + /** + * Gets the current pipeline type. + * @param limelightName Name of the Limelight camera + * @return Pipeline type string (e.g. "retro", "apriltag", etc) + */ + public static String getCurrentPipelineType(String limelightName) { + return getLimelightNTString(limelightName, "getpipetype"); + } + + /** + * Gets the full JSON results dump. + * @param limelightName Name of the Limelight camera + * @return JSON string containing all current results + */ + public static String getJSONDump(String limelightName) { + return getLimelightNTString(limelightName, "json"); + } + + /** + * Switch to getBotPose + * + * @param limelightName + * @return + */ + @Deprecated + public static double[] getBotpose(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose"); + } + + /** + * Switch to getBotPose_wpiRed + * + * @param limelightName + * @return + */ + @Deprecated + public static double[] getBotpose_wpiRed(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose_wpired"); + } + + /** + * Switch to getBotPose_wpiBlue + * + * @param limelightName + * @return + */ + @Deprecated + public static double[] getBotpose_wpiBlue(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose_wpiblue"); + } + + public static double[] getBotPose(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose"); + } + + public static double[] getBotPose_wpiRed(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose_wpired"); + } + + public static double[] getBotPose_wpiBlue(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose_wpiblue"); + } + + public static double[] getBotPose_TargetSpace(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "botpose_targetspace"); + } + + public static double[] getCameraPose_TargetSpace(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "camerapose_targetspace"); + } + + public static double[] getTargetPose_CameraSpace(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "targetpose_cameraspace"); + } + + public static double[] getTargetPose_RobotSpace(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "targetpose_robotspace"); + } + + public static double[] getTargetColor(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "tc"); + } + + public static double getFiducialID(String limelightName) { + return getLimelightNTDouble(limelightName, "tid"); + } + + public static String getNeuralClassID(String limelightName) { + return getLimelightNTString(limelightName, "tclass"); + } + + public static String[] getRawBarcodeData(String limelightName) { + return getLimelightNTStringArray(limelightName, "rawbarcodes"); + } + + ///// + ///// + + public static Pose3d getBotPose3d(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose"); + return toPose3D(poseArray); + } + + /** + * (Not Recommended) Gets the robot's 3D pose in the WPILib Red Alliance Coordinate System. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the robot's position and orientation in Red Alliance field space + */ + public static Pose3d getBotPose3d_wpiRed(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose_wpired"); + return toPose3D(poseArray); + } + + /** + * (Recommended) Gets the robot's 3D pose in the WPILib Blue Alliance Coordinate System. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the robot's position and orientation in Blue Alliance field space + */ + public static Pose3d getBotPose3d_wpiBlue(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose_wpiblue"); + return toPose3D(poseArray); + } + + /** + * Gets the robot's 3D pose with respect to the currently tracked target's coordinate system. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the robot's position and orientation relative to the target + */ + public static Pose3d getBotPose3d_TargetSpace(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "botpose_targetspace"); + return toPose3D(poseArray); + } + + /** + * Gets the camera's 3D pose with respect to the currently tracked target's coordinate system. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the camera's position and orientation relative to the target + */ + public static Pose3d getCameraPose3d_TargetSpace(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "camerapose_targetspace"); + return toPose3D(poseArray); + } + + /** + * Gets the target's 3D pose with respect to the camera's coordinate system. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the target's position and orientation relative to the camera + */ + public static Pose3d getTargetPose3d_CameraSpace(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "targetpose_cameraspace"); + return toPose3D(poseArray); + } + + /** + * Gets the target's 3D pose with respect to the robot's coordinate system. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the target's position and orientation relative to the robot + */ + public static Pose3d getTargetPose3d_RobotSpace(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "targetpose_robotspace"); + return toPose3D(poseArray); + } + + /** + * Gets the camera's 3D pose with respect to the robot's coordinate system. + * @param limelightName Name/identifier of the Limelight + * @return Pose3d object representing the camera's position and orientation relative to the robot + */ + public static Pose3d getCameraPose3d_RobotSpace(String limelightName) { + double[] poseArray = getLimelightNTDoubleArray(limelightName, "camerapose_robotspace"); + return toPose3D(poseArray); + } + + /** + * Gets the Pose2d for easy use with Odometry vision pose estimator + * (addVisionMeasurement) + * + * @param limelightName + * @return + */ + public static Pose2d getBotPose2d_wpiBlue(String limelightName) { + + double[] result = getBotPose_wpiBlue(limelightName); + return toPose2D(result); + } + + /** + * Gets the MegaTag1 Pose2d and timestamp for use with WPILib pose estimator (addVisionMeasurement) in the WPILib Blue alliance coordinate system. + * + * @param limelightName + * @return + */ + public static PoseEstimate getBotPoseEstimate_wpiBlue(String limelightName) { + return getBotPoseEstimate(limelightName, "botpose_wpiblue", false); + } + + /** + * Gets the MegaTag2 Pose2d and timestamp for use with WPILib pose estimator (addVisionMeasurement) in the WPILib Blue alliance coordinate system. + * Make sure you are calling setRobotOrientation() before calling this method. + * + * @param limelightName + * @return + */ + public static PoseEstimate getBotPoseEstimate_wpiBlue_MegaTag2(String limelightName) { + return getBotPoseEstimate(limelightName, "botpose_orb_wpiblue", true); + } + + /** + * Gets the Pose2d for easy use with Odometry vision pose estimator + * (addVisionMeasurement) + * + * @param limelightName + * @return + */ + public static Pose2d getBotPose2d_wpiRed(String limelightName) { + + double[] result = getBotPose_wpiRed(limelightName); + return toPose2D(result); + + } + + /** + * Gets the Pose2d and timestamp for use with WPILib pose estimator (addVisionMeasurement) when you are on the RED + * alliance + * @param limelightName + * @return + */ + public static PoseEstimate getBotPoseEstimate_wpiRed(String limelightName) { + return getBotPoseEstimate(limelightName, "botpose_wpired", false); + } + + /** + * Gets the Pose2d and timestamp for use with WPILib pose estimator (addVisionMeasurement) when you are on the RED + * alliance + * @param limelightName + * @return + */ + public static PoseEstimate getBotPoseEstimate_wpiRed_MegaTag2(String limelightName) { + return getBotPoseEstimate(limelightName, "botpose_orb_wpired", true); + } + + /** + * Gets the Pose2d for easy use with Odometry vision pose estimator + * (addVisionMeasurement) + * + * @param limelightName + * @return + */ + public static Pose2d getBotPose2d(String limelightName) { + + double[] result = getBotPose(limelightName); + return toPose2D(result); + + } + + /** + * Gets the current IMU data from NetworkTables. + * IMU data is formatted as [robotYaw, Roll, Pitch, Yaw, gyroX, gyroY, gyroZ, accelX, accelY, accelZ]. + * Returns all zeros if data is invalid or unavailable. + * + * @param limelightName Name/identifier of the Limelight + * @return IMUData object containing all current IMU data + */ + public static IMUData getIMUData(String limelightName) { + double[] imuData = getLimelightNTDoubleArray(limelightName, "imu"); + if (imuData == null || imuData.length < 10) { + return new IMUData(); // Returns object with all zeros + } + return new IMUData(imuData); + } + + ///// + ///// + + public static void setPipelineIndex(String limelightName, int pipelineIndex) { + setLimelightNTDouble(limelightName, "pipeline", pipelineIndex); + } + + + public static void setPriorityTagID(String limelightName, int ID) { + setLimelightNTDouble(limelightName, "priorityid", ID); + } + + /** + * Sets LED mode to be controlled by the current pipeline. + * @param limelightName Name of the Limelight camera + */ + public static void setLEDMode_PipelineControl(String limelightName) { + setLimelightNTDouble(limelightName, "ledMode", 0); + } + + public static void setLEDMode_ForceOff(String limelightName) { + setLimelightNTDouble(limelightName, "ledMode", 1); + } + + public static void setLEDMode_ForceBlink(String limelightName) { + setLimelightNTDouble(limelightName, "ledMode", 2); + } + + public static void setLEDMode_ForceOn(String limelightName) { + setLimelightNTDouble(limelightName, "ledMode", 3); + } + + /** + * Enables standard side-by-side stream mode. + * @param limelightName Name of the Limelight camera + */ + public static void setStreamMode_Standard(String limelightName) { + setLimelightNTDouble(limelightName, "stream", 0); + } + + /** + * Enables Picture-in-Picture mode with secondary stream in the corner. + * @param limelightName Name of the Limelight camera + */ + public static void setStreamMode_PiPMain(String limelightName) { + setLimelightNTDouble(limelightName, "stream", 1); + } + + /** + * Enables Picture-in-Picture mode with primary stream in the corner. + * @param limelightName Name of the Limelight camera + */ + public static void setStreamMode_PiPSecondary(String limelightName) { + setLimelightNTDouble(limelightName, "stream", 2); + } + + + /** + * Sets the crop window for the camera. The crop window in the UI must be completely open. + * @param limelightName Name of the Limelight camera + * @param cropXMin Minimum X value (-1 to 1) + * @param cropXMax Maximum X value (-1 to 1) + * @param cropYMin Minimum Y value (-1 to 1) + * @param cropYMax Maximum Y value (-1 to 1) + */ + public static void setCropWindow(String limelightName, double cropXMin, double cropXMax, double cropYMin, double cropYMax) { + double[] entries = new double[4]; + entries[0] = cropXMin; + entries[1] = cropXMax; + entries[2] = cropYMin; + entries[3] = cropYMax; + setLimelightNTDoubleArray(limelightName, "crop", entries); + } + + /** + * Sets 3D offset point for easy 3D targeting. + */ + public static void setFiducial3DOffset(String limelightName, double offsetX, double offsetY, double offsetZ) { + double[] entries = new double[3]; + entries[0] = offsetX; + entries[1] = offsetY; + entries[2] = offsetZ; + setLimelightNTDoubleArray(limelightName, "fiducial_offset_set", entries); + } + + /** + * Sets robot orientation values used by MegaTag2 localization algorithm. + * + * @param limelightName Name/identifier of the Limelight + * @param yaw Robot yaw in degrees. 0 = robot facing red alliance wall in FRC + * @param yawRate (Unnecessary) Angular velocity of robot yaw in degrees per second + * @param pitch (Unnecessary) Robot pitch in degrees + * @param pitchRate (Unnecessary) Angular velocity of robot pitch in degrees per second + * @param roll (Unnecessary) Robot roll in degrees + * @param rollRate (Unnecessary) Angular velocity of robot roll in degrees per second + */ + public static void SetRobotOrientation(String limelightName, double yaw, double yawRate, + double pitch, double pitchRate, + double roll, double rollRate) { + SetRobotOrientation_INTERNAL(limelightName, yaw, yawRate, pitch, pitchRate, roll, rollRate, true); + } + + public static void SetRobotOrientation_NoFlush(String limelightName, double yaw, double yawRate, + double pitch, double pitchRate, + double roll, double rollRate) { + SetRobotOrientation_INTERNAL(limelightName, yaw, yawRate, pitch, pitchRate, roll, rollRate, false); + } + + private static void SetRobotOrientation_INTERNAL(String limelightName, double yaw, double yawRate, + double pitch, double pitchRate, + double roll, double rollRate, boolean flush) { + + double[] entries = new double[6]; + entries[0] = yaw; + entries[1] = yawRate; + entries[2] = pitch; + entries[3] = pitchRate; + entries[4] = roll; + entries[5] = rollRate; + setLimelightNTDoubleArray(limelightName, "robot_orientation_set", entries); + if(flush) + { + Flush(); + } + } + + /** + * Configures the IMU mode for MegaTag2 Localization + * + * @param limelightName Name/identifier of the Limelight + * @param mode IMU mode. + */ + public static void SetIMUMode(String limelightName, int mode) { + setLimelightNTDouble(limelightName, "imumode_set", mode); + } + + /** + * Configures the complementary filter alpha value for IMU Assist Modes (Modes 3 and 4) + * + * @param limelightName Name/identifier of the Limelight + * @param alpha Defaults to .001. Higher values will cause the internal IMU to converge onto the assist source more rapidly. + */ + public static void SetIMUAssistAlpha(String limelightName, double alpha) { + setLimelightNTDouble(limelightName, "imuassistalpha_set", alpha); + } + + + /** + * Configures the throttle value. Set to 100-200 while disabled to reduce thermal output/temperature. + * + * @param limelightName Name/identifier of the Limelight + * @param throttle Defaults to 0. Your Limelgiht will process one frame after skipping frames. + */ + public static void SetThrottle(String limelightName, int throttle) { + setLimelightNTDouble(limelightName, "throttle_set", throttle); + } + + /** + * Sets the 3D point-of-interest offset for the current fiducial pipeline. + * https://docs.limelightvision.io/docs/docs-limelight/pipeline-apriltag/apriltag-3d#point-of-interest-tracking + * + * @param limelightName Name/identifier of the Limelight + * @param x X offset in meters + * @param y Y offset in meters + * @param z Z offset in meters + */ + public static void SetFidcuial3DOffset(String limelightName, double x, double y, + double z) { + + double[] entries = new double[3]; + entries[0] = x; + entries[1] = y; + entries[2] = z; + setLimelightNTDoubleArray(limelightName, "fiducial_offset_set", entries); + } + + /** + * Overrides the valid AprilTag IDs that will be used for localization. + * Tags not in this list will be ignored for robot pose estimation. + * + * @param limelightName Name/identifier of the Limelight + * @param validIDs Array of valid AprilTag IDs to track + */ + public static void SetFiducialIDFiltersOverride(String limelightName, int[] validIDs) { + double[] validIDsDouble = new double[validIDs.length]; + for (int i = 0; i < validIDs.length; i++) { + validIDsDouble[i] = validIDs[i]; + } + setLimelightNTDoubleArray(limelightName, "fiducial_id_filters_set", validIDsDouble); + } + + /** + * Sets the downscaling factor for AprilTag detection. + * Increasing downscale can improve performance at the cost of potentially reduced detection range. + * + * @param limelightName Name/identifier of the Limelight + * @param downscale Downscale factor. Valid values: 1.0 (no downscale), 1.5, 2.0, 3.0, 4.0. Set to 0 for pipeline control. + */ + public static void SetFiducialDownscalingOverride(String limelightName, float downscale) + { + int d = 0; // pipeline + if (downscale == 1.0) + { + d = 1; + } + if (downscale == 1.5) + { + d = 2; + } + if (downscale == 2) + { + d = 3; + } + if (downscale == 3) + { + d = 4; + } + if (downscale == 4) + { + d = 5; + } + setLimelightNTDouble(limelightName, "fiducial_downscale_set", d); + } + + /** + * Sets the camera pose relative to the robot. + * @param limelightName Name of the Limelight camera + * @param forward Forward offset in meters + * @param side Side offset in meters + * @param up Up offset in meters + * @param roll Roll angle in degrees + * @param pitch Pitch angle in degrees + * @param yaw Yaw angle in degrees + */ + public static void setCameraPose_RobotSpace(String limelightName, double forward, double side, double up, double roll, double pitch, double yaw) { + double[] entries = new double[6]; + entries[0] = forward; + entries[1] = side; + entries[2] = up; + entries[3] = roll; + entries[4] = pitch; + entries[5] = yaw; + setLimelightNTDoubleArray(limelightName, "camerapose_robotspace_set", entries); + } + + ///// + ///// + + public static void setPythonScriptData(String limelightName, double[] outgoingPythonData) { + setLimelightNTDoubleArray(limelightName, "llrobot", outgoingPythonData); + } + + public static double[] getPythonScriptData(String limelightName) { + return getLimelightNTDoubleArray(limelightName, "llpython"); + } + + ///// + ///// + + /** + * Asynchronously take snapshot. + */ + public static CompletableFuture takeSnapshot(String tableName, String snapshotName) { + return CompletableFuture.supplyAsync(() -> { + return SYNCH_TAKESNAPSHOT(tableName, snapshotName); + }); + } + + private static boolean SYNCH_TAKESNAPSHOT(String tableName, String snapshotName) { + URL url = getLimelightURLString(tableName, "capturesnapshot"); + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + if (snapshotName != null && !"".equals(snapshotName)) { + connection.setRequestProperty("snapname", snapshotName); + } + + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + return true; + } else { + System.err.println("Bad LL Request"); + } + } catch (IOException e) { + System.err.println(e.getMessage()); + } + return false; + } + + /** + * Gets the latest JSON results output and returns a LimelightResults object. + * @param limelightName Name of the Limelight camera + * @return LimelightResults object containing all current target data + */ + public static LimelightResults getLatestResults(String limelightName) { + + long start = System.nanoTime(); + LimelightHelpers.LimelightResults results = new LimelightHelpers.LimelightResults(); + if (mapper == null) { + mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + try { + results = mapper.readValue(getJSONDump(limelightName), LimelightResults.class); + } catch (JsonProcessingException e) { + results.error = "lljson error: " + e.getMessage(); + } + + long end = System.nanoTime(); + double millis = (end - start) * .000001; + results.latency_jsonParse = millis; + if (profileJSON) { + System.out.printf("lljson: %.2f\r\n", millis); + } + + return results; + } +} \ No newline at end of file From 3c3086e267e135e62f92e047b0fbe91c3b635f6e Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 28 Dec 2025 02:14:39 -0800 Subject: [PATCH 53/63] make javadocs happy --- .../carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java b/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java index f0ac142d..47aefa0f 100644 --- a/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java +++ b/src/main/java/org/carlmontrobotics/lib199/vendorLibs/LimelightHelpers.java @@ -1536,7 +1536,7 @@ public static void SetIMUAssistAlpha(String limelightName, double alpha) { * Configures the throttle value. Set to 100-200 while disabled to reduce thermal output/temperature. * * @param limelightName Name/identifier of the Limelight - * @param throttle Defaults to 0. Your Limelgiht will process one frame after skipping frames. + * @param throttle Defaults to 0. Your Limelgiht will process one frame after skipping throttle frames. */ public static void SetThrottle(String limelightName, int throttle) { setLimelightNTDouble(limelightName, "throttle_set", throttle); From c24939a0fb4ca6896c8e5dff34f5b1bb8496437b Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 28 Dec 2025 11:52:53 -0800 Subject: [PATCH 54/63] resolved a few comments --- .../lib199/MotorControllerFactory.java | 22 ++++++++++++++ .../carlmontrobotics/lib199/MotorErrors.java | 29 +++++++++++-------- .../lib199/swerve/SwerveModule.java | 23 ++++++++------- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index d333ace0..9ab789a2 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -157,6 +157,28 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { return spark; } + public static SparkBaseConfig createConfig(MotorControllerType type) { + SparkBaseConfig config = null; + switch(type){ + case SPARK_MAX: + config = new SparkMaxConfig(); + break; + case SPARK_FLEX: + config = new SparkFlexConfig(); + break; + } + return config; + } + + public static MotorControllerType getControllerType(SparkBase motor){ + if(motor instanceof SparkMax){ + return MotorControllerType.SPARK_MAX; + }else if(motor instanceof SparkFlex){ + return MotorControllerType.SPARK_FLEX; + } + return null; + } + public static SparkBaseConfig sparkConfig(SparkMotorType motor){ SparkBaseConfig config = null; switch(motor.getControllerType()){ diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 385c43fd..8a54b862 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -18,6 +18,8 @@ import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import org.carlmontrobotics.lib199.MotorControllerFactory; + public final class MotorErrors { private static final Map temperatureSparks = new ConcurrentSkipListMap<>(); @@ -183,18 +185,21 @@ private static void reportSparkTemp(int port, SparkBase spark) { System.err.println("Port " + port + " spark is operating at " + temp + " degrees Celsius! It will be disabled until the robot code is restarted."); } - if (spark.getClass() == SparkMax.class) { - spark.configure( - OVERHEAT_MAX_CONFIG, - SparkBase.ResetMode.kNoResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters); - } else if (spark.getClass() == SparkFlex.class) { - spark.configure( - OVERHEAT_FLEX_CONFIG, - SparkBase.ResetMode.kNoResetSafeParameters, - SparkBase.PersistMode.kNoPersistParameters); - }else{ - System.err.println("Unknown spark :("); + switch(MotorControllerFactory.getControllerType(spark)){ + case SPARK_MAX: + spark.configure( + OVERHEAT_MAX_CONFIG, + SparkBase.ResetMode.kNoResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters); + break; + case SPARK_FLEX: + spark.configure( + OVERHEAT_FLEX_CONFIG, + SparkBase.ResetMode.kNoResetSafeParameters, + SparkBase.PersistMode.kNoPersistParameters); + break; + default: + System.err.println("Unknown spark :("); } } } diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index e1345276..3df6cc94 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -6,6 +6,7 @@ import java.util.function.Supplier; +import org.carlmontrobotics.lib199.MotorControllerType; import org.carlmontrobotics.lib199.SparkMotorType; import org.mockito.internal.reporting.SmartPrinter; @@ -73,26 +74,26 @@ public enum ModuleType {FL, FR, BL, BR}; private static final int ENCODER_POSITION_PERIOD_MS = 20; private int encoderAverageDepth = 2; - SparkMotorType driveMotorType; - SparkMotorType turnMotorType; + MotorControllerType driveMotorType; + MotorControllerType turnMotorType; - public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, SparkBase turn, SparkMotorType driveMotorType, SparkMotorType turnMotorType, CANcoder turnEncoder, + public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, SparkBase turn, MotorControllerType driveMotorType, MotorControllerType turnMotorType, CANcoder turnEncoder, int arrIndex, Supplier pitchDegSupplier, Supplier rollDegSupplier) { this.driveMotorType = driveMotorType; this.turnMotorType = turnMotorType; switch(driveMotorType) { - case NEO, NEO550, NEO_2, SOLO_VORTEX: + case SPARK_MAX: driveConfig = new SparkMaxConfig(); break; - case VORTEX: + case SPARK_FLEX: driveConfig = new SparkFlexConfig(); break; } switch(turnMotorType) { - case NEO, NEO550, NEO_2, SOLO_VORTEX: + case SPARK_MAX: turnConfig = new SparkMaxConfig(); break; - case VORTEX: + case SPARK_FLEX: turnConfig = new SparkFlexConfig(); break; } @@ -466,10 +467,10 @@ public void brake() { idleMode = IdleMode.kBrake; SparkBaseConfig config = null; switch(driveMotorType){ - case NEO, NEO550, NEO_2, SOLO_VORTEX: + case SPARK_MAX: config = new SparkMaxConfig().idleMode(IdleMode.kBrake); break; - case VORTEX: + case SPARK_FLEX: config = new SparkFlexConfig().idleMode(IdleMode.kBrake); break; } @@ -481,10 +482,10 @@ public void coast() { idleMode = IdleMode.kCoast; SparkBaseConfig config = null; switch(driveMotorType){ - case NEO, NEO550, NEO_2, SOLO_VORTEX: + case SPARK_MAX: config = new SparkMaxConfig().idleMode(IdleMode.kCoast); break; - case VORTEX: + case SPARK_FLEX: config = new SparkFlexConfig().idleMode(IdleMode.kCoast); break; } From b4d6e0be778b752b1cbda7ff7343c6efc9060a41 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:41:04 -0800 Subject: [PATCH 55/63] resolved more comments --- .../carlmontrobotics/lib199/MotorConfig.java | 15 ++++-- .../lib199/MotorControllerFactory.java | 37 +++++--------- .../lib199/MotorControllerType.java | 11 ++++- .../carlmontrobotics/lib199/MotorErrors.java | 2 + .../lib199/SparkMotorType.java | 19 ------- .../lib199/swerve/SwerveModule.java | 49 +++++-------------- 6 files changed, 46 insertions(+), 87 deletions(-) delete mode 100644 src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java b/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java index 9693498f..5f1dbabe 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorConfig.java @@ -2,20 +2,27 @@ public class MotorConfig { - public static final MotorConfig NEO = new MotorConfig(70, 40); - public static final MotorConfig NEO_550 = new MotorConfig(40, 20); + public static final MotorConfig NEO = new MotorConfig(70, 40, MotorControllerType.SPARK_MAX); + public static final MotorConfig NEO_550 = new MotorConfig(70, 40, MotorControllerType.SPARK_MAX); + public static final MotorConfig NEO_2 = new MotorConfig(70, 40, MotorControllerType.SPARK_MAX); //TODO: find the max temp for NEO 2.0 // The temp limit of 100C for the Vortex is based on the fact that its temp sensors are mounted directly on the // windings (which is not the case for the Neo or Neo550, causing them to have very delayed temp readings) and the // fact that the winding enamel will melt at 140C. // See: https://www.chiefdelphi.com/t/rev-robotics-spark-flex-and-neo-vortex/442595/349?u=brettle // As a result I think 100C should be safe. I wouldn't increase it past 120. --Dean - public static final MotorConfig NEO_VORTEX = new MotorConfig(100, 60); + public static final MotorConfig NEO_VORTEX = new MotorConfig(100, 40, MotorControllerType.SPARK_FLEX); + public static final MotorConfig NEO_SOLO_VORTEX = new MotorConfig(100, 40, MotorControllerType.SPARK_MAX); public final int temperatureLimitCelsius, currentLimitAmps; + public final MotorControllerType controllerType; - public MotorConfig(int temperatureLimitCelsius, int currentLimitAmps) { + public MotorConfig(int temperatureLimitCelsius, int currentLimitAmps, MotorControllerType controllerType) { this.temperatureLimitCelsius = temperatureLimitCelsius; this.currentLimitAmps = currentLimitAmps; + this.controllerType = controllerType; } + public MotorControllerType getControllerType() { + return controllerType; + } } diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 9ab789a2..aa059d14 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -99,11 +99,7 @@ public static WPI_TalonSRX createTalon(int id) { * @param motorConfig either MotorConfig.NEO or MotorConfig.NEO_550 */ public static SparkMax createSparkMax(int id, MotorConfig motorConfig) { - if (motorConfig.temperatureLimitCelsius == MotorConfig.NEO.temperatureLimitCelsius) { - return createSparkMax(id, sparkConfig(SparkMotorType.NEO)); - }else{ - return createSparkMax(id, sparkConfig(SparkMotorType.NEO550)); - } + return createSparkMax(id, motorConfig, sparkConfig(motorConfig)); } /** * Create a sparkMax controller (NEO or 550) with custom settings. @@ -111,7 +107,7 @@ public static SparkMax createSparkMax(int id, MotorConfig motorConfig) { * @param id the port of the motor controller * @param config the custom config to set */ - public static SparkMax createSparkMax(int id, SparkBaseConfig config) { + public static SparkMax createSparkMax(int id, MotorConfig motorConfig, SparkBaseConfig config) { SparkMax spark; if (RobotBase.isReal()) { spark = new SparkMax(id, SparkLowLevel.MotorType.kBrushless); @@ -123,6 +119,8 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { SparkBase.ResetMode.kResetSafeParameters, SparkBase.PersistMode.kNoPersistParameters ); + MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); + MotorErrors.checkSparkErrors(spark); return spark; } @@ -132,7 +130,7 @@ public static SparkMax createSparkMax(int id, SparkBaseConfig config) { * @param id the port of the motor controller */ public static SparkFlex createSparkFlex(int id) { - return createSparkFlex(id, sparkConfig(SparkMotorType.VORTEX)); + return createSparkFlex(id, MotorConfig.NEO_VORTEX, sparkConfig(MotorConfig.NEO_VORTEX)); } /** * Create a sparkFlex controller (VORTEX) with custom settings. @@ -140,7 +138,7 @@ public static SparkFlex createSparkFlex(int id) { * @param id the port of the motor controller * @param config the custom config to set */ - public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { + public static SparkFlex createSparkFlex(int id, MotorConfig motorConfig, SparkBaseConfig config) { SparkFlex spark = null; if (RobotBase.isReal()) { spark = new SparkFlex(id, SparkLowLevel.MotorType.kBrushless); @@ -154,6 +152,9 @@ public static SparkFlex createSparkFlex(int id, SparkBaseConfig config) { SparkBase.PersistMode.kPersistParameters ); + MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); + MotorErrors.checkSparkErrors(spark); + return spark; } @@ -179,9 +180,9 @@ public static MotorControllerType getControllerType(SparkBase motor){ return null; } - public static SparkBaseConfig sparkConfig(SparkMotorType motor){ + public static SparkBaseConfig sparkConfig(MotorConfig motorConfig){ SparkBaseConfig config = null; - switch(motor.getControllerType()){ + switch(motorConfig.getControllerType()){ case SPARK_MAX: config = new SparkMaxConfig(); break; @@ -192,7 +193,7 @@ public static SparkBaseConfig sparkConfig(SparkMotorType motor){ //configs that apply to all motors config.idleMode(IdleMode.kBrake); config.voltageCompensation(12); - config.smartCurrentLimit(40); //40 amps is the fuse rating for fuses for each individual motor on the PDP + config.smartCurrentLimit(motorConfig.currentLimitAmps); config.closedLoop .minOutput(-1) @@ -200,20 +201,6 @@ public static SparkBaseConfig sparkConfig(SparkMotorType motor){ .pid(0,0,0) .velocityFF(0); - - //motor specific configs - switch(motor){ - case NEO: - break; - case NEO550: - config.smartCurrentLimit(20); //so motor no go smoky - break; - case VORTEX, SOLO_VORTEX: // the config for a vortex should be the same if it uses a spark max with a solo adapter or a spark flex, so I just combined them together - break; - case NEO_2: - break; - } - return config; } } \ No newline at end of file diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java index d4803738..15608bf5 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerType.java @@ -1,6 +1,15 @@ package org.carlmontrobotics.lib199; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.config.SparkBaseConfig; + public enum MotorControllerType { SPARK_MAX, - SPARK_FLEX + SPARK_FLEX; + public static MotorControllerType getMotorControllerType(SparkBase motor){ + return MotorControllerFactory.getControllerType(motor); + } + public SparkBaseConfig createConfig(){ + return MotorControllerFactory.createConfig(this); + } } diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java index 8a54b862..d053d72f 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorErrors.java @@ -90,6 +90,8 @@ public static void checkSparkErrors(SparkBase spark) { System.err.println("Sticky Faults! (spark id " + spark.getDeviceId() + "): [" + formatStickyFaults(spark) + "], Ouch!"); } spark.clearFaults(); + flags.put(spark, faults); + stickyFlags.put(spark, stickyFaults); } private static String formatFaults(Faults f) { diff --git a/src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java b/src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java deleted file mode 100644 index 9fcb416a..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/SparkMotorType.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.carlmontrobotics.lib199; - -public enum SparkMotorType { - NEO(MotorControllerType.SPARK_MAX), - NEO550(MotorControllerType.SPARK_MAX), - VORTEX(MotorControllerType.SPARK_FLEX), - SOLO_VORTEX(MotorControllerType.SPARK_MAX), //a spark flex with a solo addapter, we don't really use those but I included it here just in case - NEO_2(MotorControllerType.SPARK_MAX); - - private final MotorControllerType controllerType; - - SparkMotorType(MotorControllerType controllerType) { - this.controllerType = controllerType; - } - - public MotorControllerType getControllerType() { - return controllerType; - } -} \ No newline at end of file diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 3df6cc94..374f561c 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -7,7 +7,6 @@ import java.util.function.Supplier; import org.carlmontrobotics.lib199.MotorControllerType; -import org.carlmontrobotics.lib199.SparkMotorType; import org.mockito.internal.reporting.SmartPrinter; import com.ctre.phoenix6.configs.CANcoderConfiguration; @@ -77,26 +76,12 @@ public enum ModuleType {FL, FR, BL, BR}; MotorControllerType driveMotorType; MotorControllerType turnMotorType; - public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, SparkBase turn, MotorControllerType driveMotorType, MotorControllerType turnMotorType, CANcoder turnEncoder, + public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, SparkBase turn, CANcoder turnEncoder, int arrIndex, Supplier pitchDegSupplier, Supplier rollDegSupplier) { - this.driveMotorType = driveMotorType; - this.turnMotorType = turnMotorType; - switch(driveMotorType) { - case SPARK_MAX: - driveConfig = new SparkMaxConfig(); - break; - case SPARK_FLEX: - driveConfig = new SparkFlexConfig(); - break; - } - switch(turnMotorType) { - case SPARK_MAX: - turnConfig = new SparkMaxConfig(); - break; - case SPARK_FLEX: - turnConfig = new SparkFlexConfig(); - break; - } + driveMotorType = MotorControllerType.getMotorControllerType(drive); + turnMotorType = MotorControllerType.getMotorControllerType(turn); + driveConfig = driveMotorType.createConfig(); + turnConfig = turnMotorType.createConfig(); //SmartDashboard.putNumber("Target Angle (deg)", 0.0); String moduleString = type.toString(); this.timer = new Timer(); @@ -466,31 +451,19 @@ public void toggleMode() { public void brake() { idleMode = IdleMode.kBrake; SparkBaseConfig config = null; - switch(driveMotorType){ - case SPARK_MAX: - config = new SparkMaxConfig().idleMode(IdleMode.kBrake); - break; - case SPARK_FLEX: - config = new SparkFlexConfig().idleMode(IdleMode.kBrake); - break; - } + config = driveMotorType.createConfig().idleMode(idleMode); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); + config = turnMotorType.createConfig().idleMode(idleMode); + turn.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } public void coast() { idleMode = IdleMode.kCoast; SparkBaseConfig config = null; - switch(driveMotorType){ - case SPARK_MAX: - config = new SparkMaxConfig().idleMode(IdleMode.kCoast); - break; - case SPARK_FLEX: - config = new SparkFlexConfig().idleMode(IdleMode.kCoast); - break; - } + config = driveMotorType.createConfig().idleMode(idleMode); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - turn .configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); + config = turnMotorType.createConfig().idleMode(idleMode); + turn.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } /** From 413cbba91c1cee993536279928c202ed1c1fb1bc Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:05:25 -0800 Subject: [PATCH 56/63] made MotorErrorsTest use sparkSimFaultManager --- .../lib199/MotorErrorsTest.java | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java index af4f50e7..e785fb9f 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorErrorsTest.java @@ -10,6 +10,7 @@ import com.ctre.phoenix.ErrorCode; import com.revrobotics.REVLibError; +import com.revrobotics.sim.SparkSimFaultManager; import com.revrobotics.spark.SparkBase.Faults; import com.revrobotics.spark.SparkMax; @@ -19,23 +20,7 @@ import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; public class MotorErrorsTest extends ErrStreamTest { - public static class SensorFaultSparkMax { - Faults sensorFault = new Faults(4); - public short getFaults() { - // return (short)FaultID.kSensorFault.value; - return (short)sensorFault.rawBits; - } - } - - public static class StickySensorFaultSparkMax { - Faults sensorFault = new Faults(4); - public short getStickyFaults() { - // return (short)FaultID.kSensorFault.value; - return (short)sensorFault.rawBits; - } - } - - public static interface TemperatureSparkMax { + public static interface TemperatureSparkMax { public void setTemperature(double temperature); public int getSmartCurrentLimit(); @@ -127,7 +112,10 @@ public void testOtherErrors() { @Test public void testFaultReporting() { - SparkMax sensorFaultSparkMax = Mocks.createMock(SparkMax.class, new SensorFaultSparkMax(), false); + Faults sensorFault = new Faults(4); + SparkMax sensorFaultSparkMax = MotorControllerFactory.createSparkMax(1, MotorConfig.NEO, MotorControllerFactory.sparkConfig(MotorConfig.NEO)); + SparkSimFaultManager sparkSimFaultManager = new SparkSimFaultManager(sensorFaultSparkMax); + sparkSimFaultManager.setFaults(sensorFault); errStream.reset(); MotorErrors.checkSparkErrors(sensorFaultSparkMax); assertNotEquals(0, errStream.toByteArray().length); @@ -138,7 +126,10 @@ public void testFaultReporting() { @Test public void testStickyFaultReporting() { - SparkMax stickySensorFaultSparkMax = Mocks.createMock(SparkMax.class, new StickySensorFaultSparkMax(), false); + Faults sensorFault = new Faults(4); + SparkMax stickySensorFaultSparkMax = MotorControllerFactory.createSparkMax(1, MotorConfig.NEO, MotorControllerFactory.sparkConfig(MotorConfig.NEO)); + SparkSimFaultManager sparkSimFaultManager = new SparkSimFaultManager(stickySensorFaultSparkMax); + sparkSimFaultManager.setStickyFaults(sensorFault); errStream.reset(); MotorErrors.checkSparkErrors(stickySensorFaultSparkMax); assertNotEquals(0, errStream.toByteArray().length); From cc1795d0e1f40d03a8bb604a3a06d0a8b1eb621f Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 3 Jan 2026 10:03:25 -0500 Subject: [PATCH 57/63] removed a spark null check and replaced a switch with createConfig() --- .../lib199/MotorControllerFactory.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index aa059d14..979d8018 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -145,12 +145,6 @@ public static SparkFlex createSparkFlex(int id, MotorConfig motorConfig, SparkBa } else { spark = MockSparkFlex.createMockSparkFlex(id, SparkLowLevel.MotorType.kBrushless); } - if (spark!=null) - spark.configure( - config, - SparkBase.ResetMode.kResetSafeParameters, - SparkBase.PersistMode.kPersistParameters - ); MotorErrors.reportSparkTemp(spark, motorConfig.temperatureLimitCelsius); MotorErrors.checkSparkErrors(spark); @@ -159,15 +153,7 @@ public static SparkFlex createSparkFlex(int id, MotorConfig motorConfig, SparkBa } public static SparkBaseConfig createConfig(MotorControllerType type) { - SparkBaseConfig config = null; - switch(type){ - case SPARK_MAX: - config = new SparkMaxConfig(); - break; - case SPARK_FLEX: - config = new SparkFlexConfig(); - break; - } + SparkBaseConfig config = type.createConfig(); return config; } From 7b2bf1baf3062113d10ebd3f684983e29da18783 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 3 Jan 2026 10:20:18 -0500 Subject: [PATCH 58/63] added back configAccessor --- .../lib199/swerve/SwerveModule.java | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java index 374f561c..1619a0ae 100644 --- a/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java +++ b/src/main/java/org/carlmontrobotics/lib199/swerve/SwerveModule.java @@ -14,6 +14,8 @@ import com.revrobotics.spark.SparkBase; import com.revrobotics.spark.SparkBase.PersistMode; import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; import com.revrobotics.spark.config.SparkBaseConfig; import com.revrobotics.spark.config.SparkMaxConfig; @@ -66,12 +68,6 @@ public enum ModuleType {FL, FR, BL, BR}; private double turnSpeedCorrectionVolts, turnFFVolts, turnVolts; private double maxTurnVelocityWithoutTippingRps; - - // Store encoder and config values since configAccessor is not available in new REV API - private IdleMode idleMode = IdleMode.kBrake; - private static final int NEO_HALL_COUNTS_PER_REV = 42; - private static final int ENCODER_POSITION_PERIOD_MS = 20; - private int encoderAverageDepth = 2; MotorControllerType driveMotorType; MotorControllerType turnMotorType; @@ -97,11 +93,10 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, Spark double drivePositionFactor = config.wheelDiameterMeters * Math.PI / config.driveGearing; final double driveVelocityFactor = drivePositionFactor / 60; - encoderAverageDepth = 2; // Store the value we're configuring driveConfig.encoder .positionConversionFactor(drivePositionFactor) .velocityConversionFactor(driveVelocityFactor) - .quadratureAverageDepth(encoderAverageDepth); + .quadratureAverageDepth(2); maxControllableAccerlationRps2 = 0; final double normalForceNewtons = 83.2 /* lbf */ * 4.4482 /* N/lbf */ / 4 /* numModules */; @@ -126,9 +121,18 @@ public SwerveModule(SwerveConfig config, ModuleType type, SparkBase drive, Spark config.drivekD[arrIndex]); /* offset for 1 relative encoder count */ - drivetoleranceMPerS = (1.0 - / (double)(NEO_HALL_COUNTS_PER_REV) * drivePositionFactor) - / Units.millisecondsToSeconds(ENCODER_POSITION_PERIOD_MS * encoderAverageDepth); + switch(MotorControllerType.getMotorControllerType(drive)) { + case SPARK_MAX: + drivetoleranceMPerS = (1.0 + / (double)(((SparkMax)drive).configAccessor.encoder.getCountsPerRevolution()) * drivePositionFactor) + / Units.millisecondsToSeconds(((SparkMax)drive).configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * ((SparkMax)drive).configAccessor.encoder.getQuadratureAverageDepth()); + break; + case SPARK_FLEX: + drivetoleranceMPerS = (1.0 + / (double)(((SparkFlex)drive).configAccessor.encoder.getCountsPerRevolution()) * drivePositionFactor) + / Units.millisecondsToSeconds(((SparkFlex)drive).configAccessor.signals.getPrimaryEncoderPositionPeriodMs() * ((SparkFlex)drive).configAccessor.encoder.getQuadratureAverageDepth()); + break; + } drivePIDController.setTolerance(drivetoleranceMPerS); //System.out.println("Velocity Constant: " + (positionConstant / 60)); @@ -444,25 +448,45 @@ public void updateSmartDashboard() { } public void toggleMode() { - if (idleMode == IdleMode.kBrake && idleMode == IdleMode.kCoast) coast(); - else brake(); + switch(MotorControllerType.getMotorControllerType(drive)) { + case SPARK_MAX: + switch (MotorControllerType.getMotorControllerType(turn)) { + case SPARK_MAX: + if (((SparkMax)drive).configAccessor.getIdleMode() == IdleMode.kBrake && ((SparkMax)turn).configAccessor.getIdleMode() == IdleMode.kCoast) coast(); + else brake(); + break; + case SPARK_FLEX: + if (((SparkMax)drive).configAccessor.getIdleMode() == IdleMode.kBrake && ((SparkFlex)turn).configAccessor.getIdleMode() == IdleMode.kCoast) coast(); + else brake(); + break; + } + case SPARK_FLEX: + switch (MotorControllerType.getMotorControllerType(turn)) { + case SPARK_MAX: + if (((SparkFlex)drive).configAccessor.getIdleMode() == IdleMode.kBrake && ((SparkMax)turn).configAccessor.getIdleMode() == IdleMode.kCoast) coast(); + else brake(); + break; + case SPARK_FLEX: + if (((SparkFlex)drive).configAccessor.getIdleMode() == IdleMode.kBrake && ((SparkFlex)turn).configAccessor.getIdleMode() == IdleMode.kCoast) coast(); + else brake(); + break; + } + break; + } } public void brake() { - idleMode = IdleMode.kBrake; - SparkBaseConfig config = null; - config = driveMotorType.createConfig().idleMode(idleMode); + SparkBaseConfig config = driveMotorType.createConfig().idleMode(IdleMode.kBrake); drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - config = turnMotorType.createConfig().idleMode(idleMode); + config = turnMotorType.createConfig().idleMode(IdleMode.kBrake); turn.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } public void coast() { - idleMode = IdleMode.kCoast; - SparkBaseConfig config = null; - config = driveMotorType.createConfig().idleMode(idleMode); + SparkBaseConfig config = driveMotorType.createConfig().idleMode(IdleMode.kCoast); + drive.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); - config = turnMotorType.createConfig().idleMode(idleMode); + config = turnMotorType.createConfig().idleMode(IdleMode.kCoast); turn.configure(config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters); } From b82dd2fd725f6ae6665b290ee50e69445513b810 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 3 Jan 2026 10:25:28 -0500 Subject: [PATCH 59/63] resloved comment --- .../lib199/MotorControllerFactory.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 979d8018..6ec19646 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -167,15 +167,7 @@ public static MotorControllerType getControllerType(SparkBase motor){ } public static SparkBaseConfig sparkConfig(MotorConfig motorConfig){ - SparkBaseConfig config = null; - switch(motorConfig.getControllerType()){ - case SPARK_MAX: - config = new SparkMaxConfig(); - break; - case SPARK_FLEX: - config = new SparkFlexConfig(); - break; - } + SparkBaseConfig config = motorConfig.controllerType.createConfig(); //configs that apply to all motors config.idleMode(IdleMode.kBrake); config.voltageCompensation(12); From 374ed92c1e0e489581b162b0288adeeb957d4bee Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 3 Jan 2026 11:24:10 -0500 Subject: [PATCH 60/63] fixed big oppise --- .../lib199/MotorControllerFactory.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 6ec19646..8f87f001 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -153,7 +153,15 @@ public static SparkFlex createSparkFlex(int id, MotorConfig motorConfig, SparkBa } public static SparkBaseConfig createConfig(MotorControllerType type) { - SparkBaseConfig config = type.createConfig(); + SparkBaseConfig config = null; + switch(type){ + case SPARK_MAX: + config = new SparkMaxConfig(); + break; + case SPARK_FLEX: + config = new SparkFlexConfig(); + break; + } return config; } From 54ccade27d66cc5fd0240ab3034cba0430a30fc9 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sat, 3 Jan 2026 23:17:36 -0500 Subject: [PATCH 61/63] Completely removed Victor motors as they are not supported anymore and organized MotorContollerFactory imports --- .../lib199/MotorControllerFactory.java | 58 ++++--------------- .../lib199/sim/MockVictorSPX.java | 19 ------ .../lib199/MotorControllerFactoryTest.java | 19 +++--- .../lib199/sim/MockVictorSPXTest.java | 12 ---- 4 files changed, 20 insertions(+), 88 deletions(-) delete mode 100644 src/main/java/org/carlmontrobotics/lib199/sim/MockVictorSPX.java delete mode 100644 src/test/java/org/carlmontrobotics/lib199/sim/MockVictorSPXTest.java diff --git a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java index 8f87f001..66cdb2a2 100644 --- a/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java +++ b/src/main/java/org/carlmontrobotics/lib199/MotorControllerFactory.java @@ -7,64 +7,29 @@ package org.carlmontrobotics.lib199; +import org.carlmontrobotics.lib199.sim.MockSparkFlex; +import org.carlmontrobotics.lib199.sim.MockSparkMax; +// import org.carlmontrobotics.lib199.sim.MockSparkFlex; +// import org.carlmontrobotics.lib199.sim.MockSparkMax; +import org.carlmontrobotics.lib199.sim.MockTalonSRX; + import com.ctre.phoenix.motorcontrol.NeutralMode; import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; -import com.ctre.phoenix.motorcontrol.can.WPI_VictorSPX; -import com.ctre.phoenix6.hardware.CANcoder; +import com.revrobotics.spark.SparkBase; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel; import com.revrobotics.spark.SparkMax; -import com.revrobotics.spark.SparkBase.PersistMode; -import com.revrobotics.spark.SparkLowLevel.MotorType; import com.revrobotics.spark.config.SparkBaseConfig; -import com.revrobotics.spark.config.SparkFlexConfig; import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.config.SparkFlexConfig; import com.revrobotics.spark.config.SparkMaxConfig; -import com.revrobotics.servohub.ServoHub.ResetMode; -import com.revrobotics.spark.ClosedLoopSlot; -import com.revrobotics.spark.SparkBase; -import com.revrobotics.spark.SparkFlex; -import com.revrobotics.spark.SparkLowLevel; -import com.revrobotics.spark.SparkClosedLoopController; - -import org.carlmontrobotics.lib199.sim.MockSparkFlex; -import org.carlmontrobotics.lib199.sim.MockSparkMax; -// import org.carlmontrobotics.lib199.sim.MockSparkFlex; -// import org.carlmontrobotics.lib199.sim.MockSparkMax; -import org.carlmontrobotics.lib199.sim.MockTalonSRX; -import org.carlmontrobotics.lib199.sim.MockVictorSPX; -import org.carlmontrobotics.lib199.sim.MockedCANCoder; -import edu.wpi.first.cameraserver.CameraServer; -import edu.wpi.first.cscore.UsbCamera; -import edu.wpi.first.cscore.VideoSource.ConnectionStrategy; import edu.wpi.first.wpilibj.RobotBase; /** * Add your docs here. */ public class MotorControllerFactory { - @Deprecated - /** - * @deprecated VictorSPX motor controllers are no longer legal for the 2026 season: https://community.firstinspires.org/2025-robot-rules-preview-for-2026 - */ - public static WPI_VictorSPX createVictor(int port) { - WPI_VictorSPX victor; - if (RobotBase.isReal()) { - victor = new WPI_VictorSPX(port); - } else { - victor = MockVictorSPX.createMockVictorSPX(port); - } - - // Put all configurations for the victor motor controllers in here. - MotorErrors.reportError(victor.configNominalOutputForward(0, 10)); - MotorErrors.reportError(victor.configNominalOutputReverse(0, 10)); - MotorErrors.reportError(victor.configPeakOutputForward(1, 10)); - MotorErrors.reportError(victor.configPeakOutputReverse(-1, 10)); - MotorErrors.reportError(victor.configNeutralDeadband(0.001, 10)); - victor.setNeutralMode(NeutralMode.Brake); - - return victor; - } - public static WPI_TalonSRX createTalon(int id) { WPI_TalonSRX talon; if (RobotBase.isReal()) { @@ -89,10 +54,7 @@ public static WPI_TalonSRX createTalon(int id) { return talon; } - - @Deprecated /** - * @deprecated Use {@link MotorControllerFactory#createSparkMax(int id, MotorConfig motorConfig)} instead. * Create a default sparkMax controller (NEO or 550). * * @param id the port of the motor controller diff --git a/src/main/java/org/carlmontrobotics/lib199/sim/MockVictorSPX.java b/src/main/java/org/carlmontrobotics/lib199/sim/MockVictorSPX.java deleted file mode 100644 index bb59d10d..00000000 --- a/src/main/java/org/carlmontrobotics/lib199/sim/MockVictorSPX.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.carlmontrobotics.lib199.sim; - -import com.ctre.phoenix.motorcontrol.can.WPI_VictorSPX; - -import org.carlmontrobotics.lib199.ErrorCodeAnswer; -import org.carlmontrobotics.lib199.Mocks; - -import edu.wpi.first.wpilibj.motorcontrol.VictorSP; - -public class MockVictorSPX extends MockPhoenixController { - public MockVictorSPX(int portPWM) { - super(portPWM); - motorPWM = new VictorSP(portPWM); - } - - public static WPI_VictorSPX createMockVictorSPX(int portPWM) { - return Mocks.createMock(WPI_VictorSPX.class, new MockVictorSPX(portPWM), new ErrorCodeAnswer(), AutoCloseable.class); - } -} diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java index c887d01a..4fb02db0 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java @@ -15,14 +15,15 @@ public class MotorControllerFactoryTest extends ErrStreamTest { @Rule public TestRules.ResetSimDeviceSimData simTestRule = new TestRules.ResetSimDeviceSimData(); - @Test - // AutoClosable.close() throws Exception - public void testCreateNoErrors() throws Exception { - // Call close to free PWM ports - ((AutoCloseable)MotorControllerFactory.createTalon(0)).close(); - ((AutoCloseable)MotorControllerFactory.createVictor(1)).close(); - MotorControllerFactory.createSparkMax(2, MotorConfig.NEO); - assertEquals(0, errStream.toByteArray().length); - } + //FIXME: should this be rewritten for another motor? + // @Test + // // AutoClosable.close() throws Exception + // public void testCreateNoErrors() throws Exception { + // // Call close to free PWM ports + // ((AutoCloseable)MotorControllerFactory.createTalon(0)).close(); + // ((AutoCloseable)MotorControllerFactory.createVictor(1)).close(); + // MotorControllerFactory.createSparkMax(2, MotorConfig.NEO); + // assertEquals(0, errStream.toByteArray().length); + // } } diff --git a/src/test/java/org/carlmontrobotics/lib199/sim/MockVictorSPXTest.java b/src/test/java/org/carlmontrobotics/lib199/sim/MockVictorSPXTest.java deleted file mode 100644 index b6fdd307..00000000 --- a/src/test/java/org/carlmontrobotics/lib199/sim/MockVictorSPXTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.carlmontrobotics.lib199.sim; - -import com.ctre.phoenix.motorcontrol.can.BaseMotorController; - -public class MockVictorSPXTest extends MockPheonixControllerTest { - - @Override - protected BaseMotorController createController(int portPWM) { - return MockVictorSPX.createMockVictorSPX(portPWM); - } - -} From 30e2947ca24a6d914a5f414d678ba3f94be699ab Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 4 Jan 2026 10:23:12 -0500 Subject: [PATCH 62/63] fixed another oppsie --- .../lib199/MotorControllerFactoryTest.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java b/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java index 4fb02db0..87ccc3ed 100644 --- a/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java +++ b/src/test/java/org/carlmontrobotics/lib199/MotorControllerFactoryTest.java @@ -15,15 +15,13 @@ public class MotorControllerFactoryTest extends ErrStreamTest { @Rule public TestRules.ResetSimDeviceSimData simTestRule = new TestRules.ResetSimDeviceSimData(); - //FIXME: should this be rewritten for another motor? - // @Test - // // AutoClosable.close() throws Exception - // public void testCreateNoErrors() throws Exception { - // // Call close to free PWM ports - // ((AutoCloseable)MotorControllerFactory.createTalon(0)).close(); - // ((AutoCloseable)MotorControllerFactory.createVictor(1)).close(); - // MotorControllerFactory.createSparkMax(2, MotorConfig.NEO); - // assertEquals(0, errStream.toByteArray().length); - // } + @Test + // AutoClosable.close() throws Exception + public void testCreateNoErrors() throws Exception { + // Call close to free PWM ports + ((AutoCloseable)MotorControllerFactory.createTalon(0)).close(); + MotorControllerFactory.createSparkMax(2, MotorConfig.NEO); + assertEquals(0, errStream.toByteArray().length); + } } From b8896bbcec07047bddacc4bc79e6db7f1a356050 Mon Sep 17 00:00:00 2001 From: Tim <73599525+timtogan@users.noreply.github.com> Date: Sun, 4 Jan 2026 13:28:47 -0500 Subject: [PATCH 63/63] updated all vendorlibs --- ....2.1.json => PathplannerLib-2025.2.7.json} | 8 +- ....1.0.json => Phoenix6-frc2025-latest.json} | 144 ++++++++++++++---- .../{REVLib-2025.0.0.json => REVLib.json} | 15 +- ...ca-2025.0.0.json => Studica-2025.0.1.json} | 14 +- ....01.04.json => playingwithfusion2025.json} | 18 ++- 5 files changed, 146 insertions(+), 53 deletions(-) rename vendordeps/{PathplannerLib-2025.2.1.json => PathplannerLib-2025.2.7.json} (87%) rename vendordeps/{Phoenix6-25.1.0.json => Phoenix6-frc2025-latest.json} (75%) rename vendordeps/{REVLib-2025.0.0.json => REVLib.json} (86%) rename vendordeps/{Studica-2025.0.0.json => Studica-2025.0.1.json} (89%) rename vendordeps/{PlayingWithFusion-2025.01.04.json => playingwithfusion2025.json} (82%) diff --git a/vendordeps/PathplannerLib-2025.2.1.json b/vendordeps/PathplannerLib-2025.2.7.json similarity index 87% rename from vendordeps/PathplannerLib-2025.2.1.json rename to vendordeps/PathplannerLib-2025.2.7.json index 71e25f3d..d3f84e53 100644 --- a/vendordeps/PathplannerLib-2025.2.1.json +++ b/vendordeps/PathplannerLib-2025.2.7.json @@ -1,7 +1,7 @@ { - "fileName": "PathplannerLib-2025.2.1.json", + "fileName": "PathplannerLib-2025.2.7.json", "name": "PathplannerLib", - "version": "2025.2.1", + "version": "2025.2.7", "uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786", "frcYear": "2025", "mavenUrls": [ @@ -12,7 +12,7 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-java", - "version": "2025.2.1" + "version": "2025.2.7" } ], "jniDependencies": [], @@ -20,7 +20,7 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-cpp", - "version": "2025.2.1", + "version": "2025.2.7", "libName": "PathplannerLib", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/Phoenix6-25.1.0.json b/vendordeps/Phoenix6-frc2025-latest.json similarity index 75% rename from vendordeps/Phoenix6-25.1.0.json rename to vendordeps/Phoenix6-frc2025-latest.json index 473f6a89..6f40c840 100644 --- a/vendordeps/Phoenix6-25.1.0.json +++ b/vendordeps/Phoenix6-frc2025-latest.json @@ -1,7 +1,7 @@ { - "fileName": "Phoenix6-25.1.0.json", + "fileName": "Phoenix6-frc2025-latest.json", "name": "CTRE-Phoenix (v6)", - "version": "25.1.0", + "version": "25.4.0", "frcYear": "2025", "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "mavenUrls": [ @@ -19,14 +19,14 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-java", - "version": "25.1.0" + "version": "25.4.0" } ], "jniDependencies": [ { "groupId": "com.ctre.phoenix6", "artifactId": "api-cpp", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -40,7 +40,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -54,7 +54,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "api-cpp-sim", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -68,7 +68,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -82,7 +82,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -96,7 +96,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -110,7 +110,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -124,7 +124,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -138,7 +138,21 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "25.1.0", + "version": "25.4.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFXS", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -152,7 +166,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -166,7 +180,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "25.1.0", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -180,7 +194,35 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANrange", - "version": "25.1.0", + "version": "25.4.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdi", + "version": "25.4.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdle", + "version": "25.4.0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -196,7 +238,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-cpp", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_Phoenix6_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -212,7 +254,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_PhoenixTools", "headerClassifier": "headers", "sharedLibrary": true, @@ -228,7 +270,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "wpiapi-cpp-sim", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_Phoenix6_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -244,7 +286,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_PhoenixTools_Sim", "headerClassifier": "headers", "sharedLibrary": true, @@ -260,7 +302,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimTalonSRX", "headerClassifier": "headers", "sharedLibrary": true, @@ -276,7 +318,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimVictorSPX", "headerClassifier": "headers", "sharedLibrary": true, @@ -292,7 +334,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimPigeonIMU", "headerClassifier": "headers", "sharedLibrary": true, @@ -308,7 +350,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimCANCoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -324,7 +366,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimProTalonFX", "headerClassifier": "headers", "sharedLibrary": true, @@ -337,10 +379,26 @@ ], "simMode": "swsim" }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFXS", + "version": "25.4.0", + "libName": "CTRE_SimProTalonFXS", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimProCANcoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -356,7 +414,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimProPigeon2", "headerClassifier": "headers", "sharedLibrary": true, @@ -372,7 +430,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANrange", - "version": "25.1.0", + "version": "25.4.0", "libName": "CTRE_SimProCANrange", "headerClassifier": "headers", "sharedLibrary": true, @@ -384,6 +442,38 @@ "osxuniversal" ], "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdi", + "version": "25.4.0", + "libName": "CTRE_SimProCANdi", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANdle", + "version": "25.4.0", + "libName": "CTRE_SimProCANdle", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "osxuniversal" + ], + "simMode": "swsim" } ] } \ No newline at end of file diff --git a/vendordeps/REVLib-2025.0.0.json b/vendordeps/REVLib.json similarity index 86% rename from vendordeps/REVLib-2025.0.0.json rename to vendordeps/REVLib.json index cde60117..ac62be88 100644 --- a/vendordeps/REVLib-2025.0.0.json +++ b/vendordeps/REVLib.json @@ -1,7 +1,7 @@ { - "fileName": "REVLib-2025.0.0.json", + "fileName": "REVLib.json", "name": "REVLib", - "version": "2025.0.0", + "version": "2025.0.3", "frcYear": "2025", "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", "mavenUrls": [ @@ -12,19 +12,18 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-java", - "version": "2025.0.0" + "version": "2025.0.3" } ], "jniDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2025.0.0", + "version": "2025.0.3", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ "windowsx86-64", - "windowsx86", "linuxarm64", "linuxx86-64", "linuxathena", @@ -37,14 +36,13 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-cpp", - "version": "2025.0.0", + "version": "2025.0.3", "libName": "REVLib", "headerClassifier": "headers", "sharedLibrary": false, "skipInvalidPlatforms": true, "binaryPlatforms": [ "windowsx86-64", - "windowsx86", "linuxarm64", "linuxx86-64", "linuxathena", @@ -55,14 +53,13 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2025.0.0", + "version": "2025.0.3", "libName": "REVLibDriver", "headerClassifier": "headers", "sharedLibrary": false, "skipInvalidPlatforms": true, "binaryPlatforms": [ "windowsx86-64", - "windowsx86", "linuxarm64", "linuxx86-64", "linuxathena", diff --git a/vendordeps/Studica-2025.0.0.json b/vendordeps/Studica-2025.0.1.json similarity index 89% rename from vendordeps/Studica-2025.0.0.json rename to vendordeps/Studica-2025.0.1.json index ddb0e44b..5010be04 100644 --- a/vendordeps/Studica-2025.0.0.json +++ b/vendordeps/Studica-2025.0.1.json @@ -1,13 +1,13 @@ { - "fileName": "Studica-2025.0.0.json", + "fileName": "Studica-2025.0.1.json", "name": "Studica", - "version": "2025.0.0", + "version": "2025.0.1", "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", "frcYear": "2025", "mavenUrls": [ "https://dev.studica.com/maven/release/2025/" ], - "jsonUrl": "https://dev.studica.com/releases/2025/Studica-2025.0.0.json", + "jsonUrl": "https://dev.studica.com/releases/2025/Studica-2025.0.1.json", "cppDependencies": [ { "artifactId": "Studica-cpp", @@ -24,7 +24,7 @@ "libName": "Studica", "sharedLibrary": false, "skipInvalidPlatforms": true, - "version": "2025.0.0" + "version": "2025.0.1" }, { "artifactId": "Studica-driver", @@ -41,14 +41,14 @@ "libName": "StudicaDriver", "sharedLibrary": false, "skipInvalidPlatforms": true, - "version": "2025.0.0" + "version": "2025.0.1" } ], "javaDependencies": [ { "artifactId": "Studica-java", "groupId": "com.studica.frc", - "version": "2025.0.0" + "version": "2025.0.1" } ], "jniDependencies": [ @@ -65,7 +65,7 @@ "osxuniversal", "windowsx86-64" ], - "version": "2025.0.0" + "version": "2025.0.1" } ] } \ No newline at end of file diff --git a/vendordeps/PlayingWithFusion-2025.01.04.json b/vendordeps/playingwithfusion2025.json similarity index 82% rename from vendordeps/PlayingWithFusion-2025.01.04.json rename to vendordeps/playingwithfusion2025.json index 580c1217..61a56411 100644 --- a/vendordeps/PlayingWithFusion-2025.01.04.json +++ b/vendordeps/playingwithfusion2025.json @@ -1,7 +1,7 @@ { - "fileName": "PlayingWithFusion-2025.01.04.json", + "fileName": "playingwithfusion2025.json", "name": "PlayingWithFusion", - "version": "2025.01.04", + "version": "2025.01.23", "uuid": "14b8ad04-24df-11ea-978f-2e728ce88125", "frcYear": "2025", "jsonUrl": "https://www.playingwithfusion.com/frc/playingwithfusion2025.json", @@ -12,21 +12,23 @@ { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-java", - "version": "2025.01.04" + "version": "2025.01.23" } ], "jniDependencies": [ { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-driver", - "version": "2025.01.04", + "version": "2025.01.23", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ "windowsx86-64", + "windowsx86", "linuxarm64", "linuxx86-64", "linuxathena", + "linuxarm32", "osxuniversal" ] } @@ -35,32 +37,36 @@ { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-cpp", - "version": "2025.01.04", + "version": "2025.01.23", "headerClassifier": "headers", "sharedLibrary": false, "libName": "PlayingWithFusion", "skipInvalidPlatforms": true, "binaryPlatforms": [ "windowsx86-64", + "windowsx86", "linuxarm64", "linuxx86-64", "linuxathena", + "linuxarm32", "osxuniversal" ] }, { "groupId": "com.playingwithfusion.frc", "artifactId": "PlayingWithFusion-driver", - "version": "2025.01.04", + "version": "2025.01.23", "headerClassifier": "headers", "sharedLibrary": true, "libName": "PlayingWithFusionDriver", "skipInvalidPlatforms": true, "binaryPlatforms": [ "windowsx86-64", + "windowsx86", "linuxarm64", "linuxx86-64", "linuxathena", + "linuxarm32", "osxuniversal" ] }