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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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();