Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 5 additions & 46 deletions lib/src/main/java/com/team2813/lib2813/control/InvertType.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
*/
package com.team2813.lib2813.control;

import static java.util.stream.Collectors.toUnmodifiableMap;

import java.util.*;
import java.util.stream.Stream;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;

public enum InvertType {
CLOCKWISE(true),
COUNTER_CLOCKWISE(false),
CLOCKWISE,
COUNTER_CLOCKWISE,
FOLLOW_MASTER,
OPPOSE_MASTER;

Expand All @@ -32,44 +31,4 @@ public enum InvertType {
*/
public static final Set<InvertType> rotationValues =
Collections.unmodifiableSet(EnumSet.of(CLOCKWISE, COUNTER_CLOCKWISE));

private final Optional<Boolean> sparkMaxInvert;

InvertType() {
sparkMaxInvert = Optional.empty();
}

InvertType(boolean sparkMaxInvert) {
this.sparkMaxInvert = Optional.of(sparkMaxInvert);
}

/**
* Gets an {@link InvertType} from a spark max invert
*
* @param v the value to search for
* @return {@link Optional#empty()} if no {@link InvertType} is found, otherwise, an optional
* describing the {@link InvertType}
*/
public static Optional<InvertType> fromSparkMaxInvert(boolean v) {
return Optional.of(Maps.sparkMaxMap.get(v));
}

public Optional<Boolean> sparkMaxInvert() {
return sparkMaxInvert;
}

private boolean forceSparkMaxInvert() {
return sparkMaxInvert.orElseThrow();
}

/**
* Contains the maps for {@link InvertType#fromSparkMaxInvert(boolean)}. In a static class so that
* they will only be initialized if they are needed.
*/
private static final class Maps {
private static final Map<Boolean, InvertType> sparkMaxMap =
Stream.of(InvertType.values())
.filter((j) -> j.sparkMaxInvert.isPresent())
.collect(toUnmodifiableMap(InvertType::forceSparkMaxInvert, (j) -> j, (a, b) -> null));
}
}
41 changes: 0 additions & 41 deletions lib/src/test/java/com/team2813/lib2813/control/InvertTypeTest.java

This file was deleted.

2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

rootProject.name = 'lib2813'
include 'lib', 'limelight', 'vision', 'testing'
include 'vendor:ctre'
include 'vendor:ctre', 'vendor:rev'
45 changes: 45 additions & 0 deletions vendor/rev/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
id 'java-common-conventions'
id 'publishing-conventions'
id 'edu.wpi.first.GradleRIO' version '2026.1.1'
}

dependencies {
implementation wpi.java.deps.wpilib()
implementation wpi.java.vendor.java()
implementation project(':lib')

nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop)
nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop)
simulationDebug wpi.sim.enableDebug()

nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop)
nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop)
simulationRelease wpi.sim.enableRelease()

testImplementation(platform('org.junit:junit-bom:5.13.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation('com.google.truth:truth:1.4.4')
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
}

// the magic line that makes tests work :)
wpi.java.configureTestTasks(test)

tasks.named('test') {
useJUnitPlatform()
}

mavenPublishing {
pom {
name = '2813 REV Robotics Vendor Library'
description = 'REV Robotics extensions to team2813:lib'
developers {
developer {
id = 'cuttestkittensrule'
name = 'Kyle'
email = 'mangoiscute95@gmail.com'
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2026 Prospect Robotics SWENext Club

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.team2813.lib2813.vendor.rev;

import com.team2813.lib2813.control.InvertType;
import java.util.Objects;
import java.util.Optional;

/** Utility methods for working with {@link InvertType} for REV Robotics motors. */
public class InvertTypes {

/**
* Gets the SPARK MAX invert value for the given {@link InvertType}
*
* @return The invert, or {@link Optional#empty()} if the provided {@code invertType} is not a
* rotational value.
*/
public static Optional<Boolean> toSparkMaxInvert(InvertType invertType) {
return switch (Objects.requireNonNull(invertType, "invertType should not be null")) {
case CLOCKWISE -> Optional.of(Boolean.TRUE);
case COUNTER_CLOCKWISE -> Optional.of(Boolean.FALSE);
default -> Optional.empty();
};
}

/** Gets the {@link InvertType} for the given SPARK MAX invert value. */
public static InvertType toInvertType(boolean sparkMaxInvert) {
return sparkMaxInvert ? InvertType.CLOCKWISE : InvertType.COUNTER_CLOCKWISE;
}

private InvertTypes() {
throw new AssertionError("Not instantiable");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,15 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.team2813.lib2813.util;
package com.team2813.lib2813.vendor.rev;

import com.revrobotics.REVLibError;
import edu.wpi.first.wpilibj.DriverStation;
import java.util.function.Supplier;

public class ConfigUtils {
public class RevUtils {
private static final int ATTEMPTS = 10;

// make class non-instantiable
private ConfigUtils() {
throw new AssertionError("cannot create ConfigUtils instance");
}

public static void revConfig(Supplier<REVLibError> configMethod) {
REVLibError errorCode = configMethod.get();
for (int i = 1; i <= ATTEMPTS && errorCode != REVLibError.kOk; i++) {
Expand All @@ -38,4 +33,8 @@ public static void revConfig(Supplier<REVLibError> configMethod) {
DriverStation.reportError(String.format("%s: Config Failed", errorCode.toString()), false);
}
}

private RevUtils() {
throw new AssertionError("Not instantiable");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.team2813.lib2813.control.motors;
package com.team2813.lib2813.vendor.rev.motor;

import com.revrobotics.RelativeEncoder;
import com.revrobotics.spark.ClosedLoopSlot;
Expand All @@ -27,13 +27,15 @@
import com.team2813.lib2813.control.ControlMode;
import com.team2813.lib2813.control.InvertType;
import com.team2813.lib2813.control.PIDMotor;
import com.team2813.lib2813.util.ConfigUtils;
import com.team2813.lib2813.vendor.rev.InvertTypes;
import com.team2813.lib2813.vendor.rev.RevUtils;
import edu.wpi.first.units.Units;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.measure.AngularVelocity;
import edu.wpi.first.units.measure.Current;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class SparkMaxWrapper implements PIDMotor {
private final List<SparkMax> followers = new ArrayList<>();
Expand All @@ -51,15 +53,15 @@ public class SparkMaxWrapper implements PIDMotor {
* @param type The motor type connected to the controller. Brushless motor wires must be connected
* to their matching colors and the hall sensor must be plugged in. Brushed motors must be
* connected to the Red and Black terminals only.
* @param inverted Whether the motor is inverted
* @param invertType Whether the motor is inverted
*/
public SparkMaxWrapper(int deviceId, SparkLowLevel.MotorType type, InvertType inverted) {
public SparkMaxWrapper(int deviceId, SparkLowLevel.MotorType type, InvertType invertType) {
motor = new SparkMax(deviceId, type);
this.inverted = inverted.sparkMaxInvert().orElseThrow();
this.inverted = toSparkMaxInvert(invertType);
config = new SparkMaxConfig().apply(new AlternateEncoderConfig().inverted(this.inverted));
persistMode = SparkBase.PersistMode.kNoPersistParameters;
resetMode = SparkBase.ResetMode.kResetSafeParameters;
ConfigUtils.revConfig(() -> motor.configure(config, resetMode, persistMode));
RevUtils.revConfig(() -> motor.configure(config, resetMode, persistMode));
encoder = motor.getEncoder();
}

Expand Down Expand Up @@ -116,7 +118,7 @@ public void configPIDF(int slot, double p, double i, double d, double f) {
ClosedLoopConfig closedLoopConfig = new ClosedLoopConfig().pid(p, i, d, cSlot);
closedLoopConfig.feedForward.kV(f, cSlot);
config.apply(closedLoopConfig);
ConfigUtils.revConfig(() -> motor.configure(config, resetMode, persistMode));
RevUtils.revConfig(() -> motor.configure(config, resetMode, persistMode));
}

@Override
Expand All @@ -134,18 +136,26 @@ public void configPID(double p, double i, double d) {
configPIDF(0, p, i, d, 0);
}

public void addFollower(int deviceId, SparkLowLevel.MotorType type, InvertType inverted) {
public void addFollower(int deviceId, SparkLowLevel.MotorType type, InvertType invertType) {
SparkMax follower = new SparkMax(deviceId, type);
boolean isInverted =
switch (inverted) {
case CLOCKWISE, COUNTER_CLOCKWISE -> inverted.sparkMaxInvert().orElseThrow();
switch (invertType) {
case CLOCKWISE, COUNTER_CLOCKWISE -> toSparkMaxInvert(invertType);
case FOLLOW_MASTER -> this.inverted;
case OPPOSE_MASTER -> !this.inverted;
};
ConfigUtils.revConfig(
RevUtils.revConfig(
() ->
follower.configure(
new SparkMaxConfig().follow(motor).inverted(isInverted), resetMode, persistMode));
followers.add(follower); // add to follower list so CANSparkMax follower object is preserved
}

private static boolean toSparkMaxInvert(InvertType invertType) {
Objects.requireNonNull(invertType, "invertType should not be null");
return InvertTypes.toSparkMaxInvert(invertType)
.orElseThrow(
() ->
new IllegalArgumentException("invertType must be CLOCKWISE or COUNTER_CLOCKWISE"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2026 Prospect Robotics SWENext Club

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.team2813.lib2813.vendor.rev;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import com.team2813.lib2813.control.InvertType;
import java.util.Optional;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.FieldSource;
import org.junit.jupiter.params.provider.ValueSource;

/** Tests for {@link InvertTypes}. */
class InvertTypesTest {

@ParameterizedTest
@FieldSource("com.team2813.lib2813.control.InvertType#rotationValues")
public void toSparkMaxInvert(InvertType invertType) {
Optional<Boolean> value = InvertTypes.toSparkMaxInvert(invertType);

assertWithMessage("No InvertedValue exists for InvertType.%s", invertType)
.that(value)
.isPresent();
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
public void toInvertType(boolean sparkMaxInvert) {
InvertType invertType = InvertTypes.toInvertType(sparkMaxInvert);

assertThat(invertType).isNotNull();
assertThat(InvertTypes.toSparkMaxInvert(invertType)).hasValue(sparkMaxInvert);
}
}
File renamed without changes.