README updated
@@ -11,6 +11,9 @@
|
||||
SwerveDrive swerveDrive = SwerveParser.fromJSONDirectory(new File(Filesystem.getDeployDirectory(), "swerve"));
|
||||
```
|
||||
|
||||
# Library Information
|
||||
* The library is located in [swervelib/](./swervelib) with documentation in [docs/](./docs) and example JSON in [swerve-deploy](./swerve-deploy).
|
||||
|
||||
# Images
|
||||

|
||||

|
||||
|
||||
|
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 335 B |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 280 B |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 394 B After Width: | Height: | Size: 394 B |
709
swervelib/SwerveDrive.java
Normal file
@@ -0,0 +1,709 @@
|
||||
package frc.robot.subsystems.swervedrive.swerve;
|
||||
|
||||
import com.ctre.phoenix.sensors.CANCoder;
|
||||
import com.ctre.phoenix.sensors.WPI_Pigeon2;
|
||||
import edu.wpi.first.math.VecBuilder;
|
||||
import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator;
|
||||
import edu.wpi.first.math.filter.SlewRateLimiter;
|
||||
import edu.wpi.first.math.geometry.Pose2d;
|
||||
import edu.wpi.first.math.geometry.Rotation2d;
|
||||
import edu.wpi.first.math.geometry.Twist2d;
|
||||
import edu.wpi.first.math.kinematics.ChassisSpeeds;
|
||||
import edu.wpi.first.math.kinematics.SwerveModulePosition;
|
||||
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.RobotController;
|
||||
import edu.wpi.first.wpilibj.Timer;
|
||||
import edu.wpi.first.wpilibj.drive.RobotDriveBase;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
import edu.wpi.first.wpilibj.smartdashboard.Field2d;
|
||||
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
|
||||
import frc.robot.Robot;
|
||||
import frc.robot.subsystems.swervedrive.swerve.SwerveModule.SwerveModuleLocation;
|
||||
import frc.robot.subsystems.swervedrive.swerve.SwerveModule.Verbosity;
|
||||
import frc.robot.subsystems.swervedrive.swerve.SwerveMotor.ModuleMotorType;
|
||||
import frc.robot.subsystems.swervedrive.swerve.kinematics.SwerveDriveKinematics2;
|
||||
import frc.robot.subsystems.swervedrive.swerve.kinematics.SwerveModuleState2;
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* SwerveDrive base which is meant to be platform agnostic. This implementation expect second order kinematics because
|
||||
* second order kinematics prevents the drift that builds up when using first order kinematics. As per this <a
|
||||
* href="https://www.chiefdelphi.com/t/whitepaper-swerve-drive-skew-and-second-order-kinematics/416964">ChiefDelphi
|
||||
* post</a>
|
||||
*/
|
||||
public class SwerveDrive extends RobotDriveBase implements Sendable, AutoCloseable
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Count of SwerveModule instances created.
|
||||
*/
|
||||
private static int instances;
|
||||
|
||||
/**
|
||||
* Front left swerve drive
|
||||
*/
|
||||
public final SwerveModule<?, ?, ?> m_frontLeft;
|
||||
/**
|
||||
* Back left swerve drive
|
||||
*/
|
||||
public final SwerveModule<?, ?, ?> m_backLeft;
|
||||
/**
|
||||
* Front right swerve drive
|
||||
*/
|
||||
public final SwerveModule<?, ?, ?> m_frontRight;
|
||||
/**
|
||||
* Back right swerve drive
|
||||
*/
|
||||
public final SwerveModule<?, ?, ?> m_backRight;
|
||||
/**
|
||||
* Maximum speed in meters per second.
|
||||
*/
|
||||
public final double m_driverMaxSpeedMPS, m_driverMaxAngularVelocity, m_physicalMaxSpeedMPS;
|
||||
/**
|
||||
* Swerve drive kinematics.
|
||||
*/
|
||||
private final SwerveDriveKinematics2 m_swerveKinematics;
|
||||
/**
|
||||
* Swerve drive pose estimator for attempting to figure out our current position.
|
||||
*/
|
||||
private final SwerveDrivePoseEstimator m_swervePoseEstimator;
|
||||
/**
|
||||
* Pigeon 2.0 centered on the robot.
|
||||
*/
|
||||
private final WPI_Pigeon2 m_pigeonIMU;
|
||||
/**
|
||||
* Field2d displayed on shuffleboard with current position.
|
||||
*/
|
||||
private final Field2d m_field = new Field2d();
|
||||
/**
|
||||
* The slew rate limiters to make control smooth.
|
||||
*/
|
||||
private final SlewRateLimiter m_xLimiter, m_yLimiter, m_turningLimiter;
|
||||
private final Timer m_driveTimer;
|
||||
/**
|
||||
* Invert the gyro reading.
|
||||
*/
|
||||
private boolean m_gyroInverted;
|
||||
private double m_angle;
|
||||
private ChassisSpeeds m_prevChassisSpeed = new ChassisSpeeds(0, 0, 0);
|
||||
private double m_timerPrev;
|
||||
|
||||
/**
|
||||
* Constructor for Swerve Drive assuming modules have been created and configured with PIDF and conversions.
|
||||
*
|
||||
* @param frontLeft Front left swerve module configured.
|
||||
* @param backLeft Back left swerve module.
|
||||
* @param frontRight Front right swerve module.
|
||||
* @param backRight Back right swerve module
|
||||
* @param pigeon Pigeon IMU.
|
||||
* @param driverMaxSpeedMetersPerSecond Maximum speed for all modules to follow when in teleop.
|
||||
* @param driverMaxAngularVelocityRadiansPerSecond Maximum angular velocity for turning when using the drive
|
||||
* function.
|
||||
* @param driverMaxDriveAccelerationMetersPerSecond Maximum acceleration in meters per second for the drive motors
|
||||
* when in teleop for the slew rate limiter.
|
||||
* @param driverMaxAngularAccelerationRadiansPerSecond Maximum angular acceleration in meters per second for the
|
||||
* steering motors when in teleop for the slew rate limiters.
|
||||
* @param physicalMaxSpeedMPS Maximum speed a module can go physically, used to desaturate
|
||||
* wheel speeds.
|
||||
* @param gyroInverted Invert the gyroscope for the robot.
|
||||
* @param start The starting pose on the field.
|
||||
*/
|
||||
public SwerveDrive(SwerveModule<?, ?, ?> frontLeft,
|
||||
SwerveModule<?, ?, ?> frontRight,
|
||||
SwerveModule<?, ?, ?> backLeft,
|
||||
SwerveModule<?, ?, ?> backRight, WPI_Pigeon2 pigeon,
|
||||
double driverMaxSpeedMetersPerSecond, double driverMaxAngularVelocityRadiansPerSecond,
|
||||
double driverMaxDriveAccelerationMetersPerSecond,
|
||||
double driverMaxAngularAccelerationRadiansPerSecond,
|
||||
double physicalMaxSpeedMPS,
|
||||
boolean gyroInverted, Pose2d start)
|
||||
{
|
||||
instances++;
|
||||
if (instances > 1)
|
||||
{
|
||||
throw new RuntimeException("Cannot use more than one swerve drive instance at a time.");
|
||||
}
|
||||
m_driveTimer = new Timer();
|
||||
m_driveTimer.reset();
|
||||
m_driveTimer.start();
|
||||
m_timerPrev = m_driveTimer.get();
|
||||
|
||||
m_frontLeft = frontLeft;
|
||||
m_backRight = backRight;
|
||||
m_backLeft = backLeft;
|
||||
m_frontRight = frontRight;
|
||||
m_gyroInverted = gyroInverted;
|
||||
|
||||
m_driverMaxSpeedMPS = driverMaxSpeedMetersPerSecond;
|
||||
m_driverMaxAngularVelocity = driverMaxAngularVelocityRadiansPerSecond;
|
||||
m_xLimiter = new SlewRateLimiter(driverMaxDriveAccelerationMetersPerSecond);
|
||||
m_yLimiter = new SlewRateLimiter(driverMaxDriveAccelerationMetersPerSecond);
|
||||
m_turningLimiter = new SlewRateLimiter(driverMaxAngularAccelerationRadiansPerSecond);
|
||||
m_physicalMaxSpeedMPS = physicalMaxSpeedMPS;
|
||||
|
||||
m_pigeonIMU = pigeon;
|
||||
configurePigeonIMU(); // Reset pigeon to 0 and default settings.
|
||||
|
||||
m_swerveKinematics = new SwerveDriveKinematics2(frontLeft.swerveModuleLocation,
|
||||
frontRight.swerveModuleLocation,
|
||||
backLeft.swerveModuleLocation,
|
||||
backRight.swerveModuleLocation);
|
||||
m_swervePoseEstimator = new SwerveDrivePoseEstimator(
|
||||
m_swerveKinematics,
|
||||
getRotation(),
|
||||
getPositions(),
|
||||
start,
|
||||
VecBuilder.fill(0.1, 0.1, 0.1), // x,y,heading in radians; state std dev, higher=less weight
|
||||
VecBuilder.fill(0.9, 1.0, 0.9)); // x,y,heading in radians; Vision measurement std dev, higher=less weight
|
||||
|
||||
// Inspired by https://github.com/Team364/BaseFalconSwerve/blob/main/src/main/java/frc/robot/subsystems/Swerve.java
|
||||
SmartDashboard.putData(m_field);
|
||||
SmartDashboard.putData(m_pigeonIMU);
|
||||
|
||||
setVoltageCompensation();
|
||||
zeroModules(); // Set all modules to 0.
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable voltage compensation to the current battery voltage on all modules.
|
||||
*/
|
||||
public void setVoltageCompensation()
|
||||
{
|
||||
double currentVoltage = RobotController.getBatteryVoltage();
|
||||
m_frontLeft.setVoltageCompensation(currentVoltage);
|
||||
m_frontRight.setVoltageCompensation(currentVoltage);
|
||||
m_backLeft.setVoltageCompensation(currentVoltage);
|
||||
m_backRight.setVoltageCompensation(currentVoltage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create swerve drive modules
|
||||
*
|
||||
* @param driveGearRatio Drive gear ratio in form of (rotation:1 AKA rotations/1) to get the encoder ticks
|
||||
* per rotation.
|
||||
* @param steerGearRatio Steering motor gear ratio (usually 12.8:1 for MK4 in form of rotations:1 or
|
||||
* rotations/1), only applied if using Neo's.
|
||||
* @param wheelDiameterMeters The wheel diameter of the swerve drive module in meters.
|
||||
* @param wheelBaseMeters The Distance between front and back wheels of the robot in meters.
|
||||
* @param driveTrainWidthMeters The Distance between centers of right and left wheels in meters.
|
||||
* @param steeringMotorFreeSpeedRPM The RPM free speed of the steering motor.
|
||||
* @param maxSpeedMPS The maximum drive speed in meters per second.
|
||||
* @param maxDriveAcceleration The maximum drive acceleration in meters^2 per second.
|
||||
* @param steeringMotorInverted The steering motor is inverted.
|
||||
* @param drivingMotorInverted The driving motor is inverted.
|
||||
* @param configs The swerve module configuration classes for the swerve drive given.
|
||||
* @return Array of swerve modules in the order of front left, front right, back left, back right.
|
||||
*/
|
||||
public static SwerveModule<?, ?, ?>[] createModules(
|
||||
double driveGearRatio, double steerGearRatio, double wheelDiameterMeters, double wheelBaseMeters,
|
||||
double driveTrainWidthMeters, double steeringMotorFreeSpeedRPM, double maxSpeedMPS,
|
||||
double maxDriveAcceleration, boolean steeringMotorInverted, boolean drivingMotorInverted,
|
||||
SwerveModuleConfig<?, ?, ?>[] configs)
|
||||
{
|
||||
SwerveModule<?, ?, ?>[] modules = new SwerveModule[configs.length];
|
||||
for (int i = 0; i < configs.length; i++)
|
||||
{
|
||||
int loc;
|
||||
switch (configs[i].loc)
|
||||
{
|
||||
case FrontLeft:
|
||||
loc = 0;
|
||||
break;
|
||||
case BackLeft:
|
||||
loc = 2;
|
||||
break;
|
||||
case FrontRight:
|
||||
loc = 1;
|
||||
break;
|
||||
case BackRight:
|
||||
loc = 3;
|
||||
break;
|
||||
default:
|
||||
loc = i;
|
||||
}
|
||||
|
||||
modules[loc] = configs[i].createModule(driveGearRatio, steerGearRatio, wheelDiameterMeters, wheelBaseMeters,
|
||||
driveTrainWidthMeters, steeringMotorFreeSpeedRPM, maxSpeedMPS,
|
||||
maxDriveAcceleration, steeringMotorInverted, drivingMotorInverted);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the speed to 0 and angle to 0 for all the swerve drive modules.
|
||||
*/
|
||||
public void zeroModules()
|
||||
{
|
||||
// feedWatchdog();
|
||||
// m_frontLeft.setAngle(0, 0);
|
||||
// m_backLeft.setAngle(0, 0);
|
||||
// m_frontRight.setAngle(0, 0);
|
||||
// m_backRight.setAngle(0, 0);
|
||||
m_frontRight.set(0);
|
||||
m_backLeft.set(0);
|
||||
m_frontLeft.set(0);
|
||||
m_backRight.set(0);
|
||||
set(0, 0, 0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the PigeonIMU with factory default settings and a zeroed gyroscope.
|
||||
*/
|
||||
public void configurePigeonIMU()
|
||||
{
|
||||
m_pigeonIMU.configFactoryDefault();
|
||||
zeroGyro();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the swerve drive odometry.
|
||||
*
|
||||
* @return Swerve drive odometry.
|
||||
*/
|
||||
public SwerveDrivePoseEstimator update()
|
||||
{
|
||||
Pose2d pos = m_swervePoseEstimator.update(getRotation(), getPositions());
|
||||
if (!Robot.isReal())
|
||||
{
|
||||
m_pigeonIMU.setYaw(pos.getRotation().getDegrees());
|
||||
}
|
||||
return m_swervePoseEstimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drive swerve based off controller input.
|
||||
*
|
||||
* @param forward The speed (-1 to 1) in which the Y axis should go.
|
||||
* @param strafe The speed (-1 to 1) in which the X axis should go.
|
||||
* @param turn The speed (-1 to 1) in which the robot should turn.
|
||||
* @param fieldRelative Whether or not to use field relative controls.
|
||||
*/
|
||||
public void drive(double forward, double strafe, double turn, boolean fieldRelative)
|
||||
{
|
||||
// 2. Apply deadband/Dead-Zone
|
||||
forward = Math.abs(forward) > m_deadband ? forward : 0.0;
|
||||
strafe = Math.abs(strafe) > m_deadband ? strafe : 0.0;
|
||||
turn = Math.abs(turn) > m_deadband ? turn : 0.0;
|
||||
|
||||
// If nothing is asked of us we do nothing.
|
||||
if ((Math.abs(forward) + Math.abs(strafe) + Math.abs(turn)) <= m_deadband)
|
||||
{
|
||||
zeroModules();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Make the driving smoother
|
||||
forward = m_xLimiter.calculate(forward) * m_driverMaxSpeedMPS;
|
||||
strafe = m_yLimiter.calculate(strafe) * m_driverMaxSpeedMPS;
|
||||
turn = m_turningLimiter.calculate(turn) * m_driverMaxAngularVelocity;
|
||||
|
||||
set(forward, strafe, turn, fieldRelative);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Swerve drive function
|
||||
*
|
||||
* @param forward forward meters per second, strafe facing left from alliance wall
|
||||
* @param strafe strafe meters per second, forward away from alliance wall
|
||||
* @param radianPerSecond radians per second
|
||||
* @param fieldRelative field relative
|
||||
*/
|
||||
public void set(double forward, double strafe, double radianPerSecond, boolean fieldRelative)
|
||||
{
|
||||
ChassisSpeeds node = fieldRelative ? ChassisSpeeds.fromFieldRelativeSpeeds(forward, strafe, radianPerSecond,
|
||||
getRotation())
|
||||
: new ChassisSpeeds(forward, strafe, radianPerSecond);
|
||||
set(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the swerve drive module states.
|
||||
*
|
||||
* @param node Chassis speeds to set the swerve module too.
|
||||
*/
|
||||
public void set(ChassisSpeeds node)
|
||||
{
|
||||
|
||||
// Taken from https://www.chiefdelphi.com/t/whitepaper-swerve-drive-skew-and-second-order-kinematics/416964/5?u=nstrike
|
||||
double timerDt = (m_driveTimer.get() - m_timerPrev);
|
||||
Pose2d robot_pose_vel = new Pose2d(node.vxMetersPerSecond * timerDt,
|
||||
node.vyMetersPerSecond * timerDt,
|
||||
Rotation2d.fromRadians(node.omegaRadiansPerSecond * timerDt));
|
||||
Twist2d twist_vel = new Pose2d(0, 0, new Rotation2d(0)).log(robot_pose_vel);
|
||||
ChassisSpeeds updated_chassis_speeds = new ChassisSpeeds(
|
||||
twist_vel.dx / timerDt, twist_vel.dy / timerDt, twist_vel.dtheta / timerDt);
|
||||
|
||||
SwerveModuleState2[] moduleStates = m_swerveKinematics.toSwerveModuleStates(updated_chassis_speeds);
|
||||
// new Translation2d((m_frontLeft.swerveModuleLocation.getX() + m_frontRight.swerveModuleLocation.getX()) / 2,
|
||||
// (m_frontLeft.swerveModuleLocation.getY() + m_backLeft.swerveModuleLocation.getY()) / 2));
|
||||
|
||||
setModuleStates(moduleStates);
|
||||
m_prevChassisSpeed = updated_chassis_speeds;
|
||||
m_timerPrev = m_driveTimer.get();
|
||||
|
||||
try
|
||||
{
|
||||
if (!Robot.isReal())
|
||||
{
|
||||
m_angle += updated_chassis_speeds.omegaRadiansPerSecond * 0.02;
|
||||
}
|
||||
this.update();
|
||||
m_field.setRobotPose(m_swervePoseEstimator.getEstimatedPosition());
|
||||
} catch (Exception e)
|
||||
{
|
||||
System.err.println("Cannot update SwerveDrive Odometry!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the swerve module states given an array of states. Normalize the wheel speeds to abide by maximum supplied
|
||||
*
|
||||
* @param states Module states in a specified order. [front left, front right, back left, back right]
|
||||
* @throws RuntimeException If the CANCoder is inaccessible or not configured.
|
||||
*/
|
||||
public void setModuleStates(SwerveModuleState2[] states)
|
||||
{
|
||||
feedWatchdog(); // Required
|
||||
SwerveDriveKinematics2.desaturateWheelSpeeds(states, m_physicalMaxSpeedMPS);
|
||||
m_frontLeft.setState(states[0]);
|
||||
m_frontRight.setState(states[1]);
|
||||
m_backLeft.setState(states[2]);
|
||||
m_backRight.setState(states[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the wheels into an X formation to prevent movement.
|
||||
*/
|
||||
public void setX()
|
||||
{
|
||||
setModuleStates(new SwerveModuleState2[]{new SwerveModuleState2(0, Rotation2d.fromDegrees(45), 0),
|
||||
new SwerveModuleState2(0, Rotation2d.fromDegrees(-45), 0),
|
||||
new SwerveModuleState2(0, Rotation2d.fromDegrees(-45), 0),
|
||||
new SwerveModuleState2(0, Rotation2d.fromDegrees(45), 0)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert the gyroscope reading.
|
||||
*
|
||||
* @param isInverted Inversion of the gryoscope, true is inverted.
|
||||
*/
|
||||
public void setGyroInverted(boolean isInverted)
|
||||
{
|
||||
m_gyroInverted = isInverted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current robot rotation.
|
||||
*
|
||||
* @return {@link Rotation2d} of the robot.
|
||||
*/
|
||||
public Rotation2d getRotation()
|
||||
{
|
||||
if (Robot.isReal())
|
||||
{
|
||||
return m_gyroInverted ? Rotation2d.fromDegrees(360 - m_pigeonIMU.getYaw()) : Rotation2d.fromDegrees(
|
||||
m_pigeonIMU.getYaw());
|
||||
} else
|
||||
{
|
||||
return new Rotation2d(m_angle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Pose, used in autonomous.
|
||||
*
|
||||
* @return Current pose based off odometry.
|
||||
*/
|
||||
public Pose2d getPose()
|
||||
{
|
||||
return m_swervePoseEstimator.getEstimatedPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current swerve module positions in order.
|
||||
*
|
||||
* @return Swerve module positions array.
|
||||
*/
|
||||
public SwerveModulePosition[] getPositions()
|
||||
{
|
||||
return new SwerveModulePosition[]{m_frontLeft.getPosition(), m_frontRight.getPosition(),
|
||||
m_backLeft.getPosition(), m_backRight.getPosition()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current swerve module states in order.
|
||||
*
|
||||
* @return Swerve module states array.
|
||||
*/
|
||||
public SwerveModuleState2[] getStates()
|
||||
{
|
||||
return new SwerveModuleState2[]{m_frontLeft.getState(), m_frontRight.getState(),
|
||||
m_backLeft.getState(), m_backRight.getState()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the odometry given the position and using current rotation from the PigeonIMU 2.
|
||||
*
|
||||
* @param pose Current position on the field.
|
||||
*/
|
||||
public void resetOdometry(Pose2d pose)
|
||||
{
|
||||
m_swervePoseEstimator.resetPosition(getRotation(), getPositions(), pose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the drive encoders to currently read a position of 0.
|
||||
*/
|
||||
public void resetEncoders()
|
||||
{
|
||||
m_frontLeft.resetEncoders();
|
||||
m_backLeft.resetEncoders();
|
||||
m_frontRight.resetEncoders();
|
||||
m_backRight.resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current rotation of the gyroscope (pigeonIMU 2) to 0.
|
||||
*/
|
||||
public void zeroGyro()
|
||||
{
|
||||
m_pigeonIMU.setYaw(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all running and turning motors.
|
||||
*/
|
||||
@Override
|
||||
public void stopMotor()
|
||||
{
|
||||
feedWatchdog();
|
||||
m_frontLeft.stopMotor();
|
||||
m_frontRight.stopMotor();
|
||||
m_backLeft.stopMotor();
|
||||
m_backRight.stopMotor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update swerve module parameters based on data in the dashboard.
|
||||
*/
|
||||
public void subscribe()
|
||||
{
|
||||
m_frontRight.subscribe();
|
||||
m_frontLeft.subscribe();
|
||||
m_backLeft.subscribe();
|
||||
m_backRight.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish data from the Swerve Modules to the dashboard.
|
||||
*
|
||||
* @param level Verbosity level in terms of CAN utilization.
|
||||
*/
|
||||
public void publish(Verbosity level)
|
||||
{
|
||||
m_frontRight.publish(level);
|
||||
m_frontLeft.publish(level);
|
||||
m_backRight.publish(level);
|
||||
m_backLeft.publish(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PIDF coefficients for the closed loop PID onboard the motor controller. Tuning the PID
|
||||
* <p>
|
||||
* <b>P</b> = .5 and increase it by .1 until oscillations occur, then decrease by .05 then .005 until oscillations
|
||||
* stop and angle is perfect or near perfect.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>I</b> = 0 tune this if your PID never quite reaches the target, after tuning <b>D</b>. Increase this by
|
||||
* <b>P</b>*.01 each time and adjust accordingly.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>D</b> = 0 tune this if the PID accelerates too fast, it will smooth the motion. Increase this by <b>P</b>*10
|
||||
* and adjust accordingly.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>F</b> = 0 tune this if the PID is being used for velocity, the <b>F</b> is multiplied by the target and added
|
||||
* to the voltage output. Increase this by 0.01 until the PIDF reaches the desired state in a fast enough manner.
|
||||
* </p>
|
||||
* Documentation for this is best described by CTRE <a
|
||||
* href="https://docs.ctre-phoenix.com/en/stable/ch16_ClosedLoop.html#position-closed-loop-control-mode">here</a>.
|
||||
*
|
||||
* @param p Proportional gain for closed loop. This is multiplied by closed loop error in sensor units.
|
||||
* @param i Integral gain for closed loop. This is multiplied by closed loop error in sensor units every
|
||||
* PID Loop.
|
||||
* @param d Derivative gain for closed loop. This is multiplied by derivative error (sensor units per
|
||||
* PID loop).
|
||||
* @param f Feed Fwd gain for Closed loop.
|
||||
* @param integralZone Integral Zone can be used to auto clear the integral accumulator if the sensor pos is too
|
||||
* far from the target. This prevents unstable oscillation if the kI is too large. Value is in
|
||||
* sensor units.
|
||||
* @param moduleMotorType Swerve drive motor type.
|
||||
*/
|
||||
public void setPIDF(double p, double i, double d, double f, double integralZone,
|
||||
ModuleMotorType moduleMotorType)
|
||||
{
|
||||
m_frontRight.setPIDF(p, i, d, f, integralZone, moduleMotorType);
|
||||
m_frontLeft.setPIDF(p, i, d, f, integralZone, moduleMotorType);
|
||||
m_backRight.setPIDF(p, i, d, f, integralZone, moduleMotorType);
|
||||
m_backLeft.setPIDF(p, i, d, f, integralZone, moduleMotorType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize internal steering encoders with the absolute encoder.
|
||||
*/
|
||||
public void synchronizeEncoders()
|
||||
{
|
||||
m_backRight.synchronizeSteeringEncoder();
|
||||
m_backLeft.synchronizeSteeringEncoder();
|
||||
m_frontRight.synchronizeSteeringEncoder();
|
||||
m_frontLeft.synchronizeSteeringEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the robot drive base.
|
||||
*
|
||||
* @return string of the RobotDriveBase
|
||||
*/
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return "Swerve Drive Base";
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this {@link Sendable} object.
|
||||
*
|
||||
* @param builder sendable builder
|
||||
*/
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder)
|
||||
{
|
||||
// builder.setSmartDashboardType("SwerveDrive");
|
||||
// SendableRegistry.addChild(this, m_frontLeft);
|
||||
// SendableRegistry.addChild(this, m_frontRight);
|
||||
// SendableRegistry.addChild(this, m_backLeft);
|
||||
// SendableRegistry.addChild(this, m_backRight);
|
||||
// SendableRegistry.addChild(this, m_field);
|
||||
// SendableRegistry.addChild(this, m_pigeonIMU);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this resource, relinquishing any underlying resources. This method is invoked automatically on objects
|
||||
* managed by the {@code try}-with-resources statement.
|
||||
*
|
||||
* <p>While this interface method is declared to throw {@code
|
||||
* Exception}, implementers are <em>strongly</em> encouraged to declare concrete implementations of the {@code close}
|
||||
* method to throw more specific exceptions, or to throw no exception at all if the close operation cannot fail.
|
||||
*
|
||||
* <p> Cases where the close operation may fail require careful
|
||||
* attention by implementers. It is strongly advised to relinquish the underlying resources and to internally
|
||||
* <em>mark</em> the resource as closed, prior to throwing the exception. The {@code close} method is unlikely to be
|
||||
* invoked more than once and so this ensures that the resources are released in a timely manner. Furthermore it
|
||||
* reduces problems that could arise when the resource wraps, or is wrapped, by another resource.
|
||||
*
|
||||
* <p><em>Implementers of this interface are also strongly advised
|
||||
* to not have the {@code close} method throw {@link InterruptedException}.</em>
|
||||
* <p>
|
||||
* This exception interacts with a thread's interrupted status, and runtime misbehavior is likely to occur if an
|
||||
* {@code InterruptedException} is {@linkplain Throwable#addSuppressed suppressed}.
|
||||
* <p>
|
||||
* More generally, if it would cause problems for an exception to be suppressed, the {@code AutoCloseable.close}
|
||||
* method should not throw it.
|
||||
*
|
||||
* <p>Note that unlike the {@link Closeable#close close}
|
||||
* method of {@link Closeable}, this {@code close} method is <em>not</em> required to be idempotent. In other words,
|
||||
* calling this {@code close} method more than once may have some visible side effect, unlike {@code Closeable.close}
|
||||
* which is required to have no effect if called more than once.
|
||||
* <p>
|
||||
* However, implementers of this interface are strongly encouraged to make their {@code close} methods idempotent.
|
||||
*
|
||||
* @throws Exception if this resource cannot be closed
|
||||
*/
|
||||
@Override
|
||||
public void close() throws Exception
|
||||
{
|
||||
SendableRegistry.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for easier swerve module creation
|
||||
*
|
||||
* @param <DriveMotorType> The motor type for the drive motor on the swerve moduule.
|
||||
* @param <SteeringMotorType> The motor type for the steering motor on the module.
|
||||
* @param <AbsoluteEncoder> The absolute encoder type.
|
||||
*/
|
||||
public static class SwerveModuleConfig<DriveMotorType extends MotorController,
|
||||
SteeringMotorType extends MotorController,
|
||||
AbsoluteEncoder extends CANCoder>
|
||||
{
|
||||
|
||||
public DriveMotorType drive;
|
||||
public SteeringMotorType steering;
|
||||
public AbsoluteEncoder encoder;
|
||||
public double angleOffset;
|
||||
public SwerveModuleLocation loc;
|
||||
|
||||
/**
|
||||
* Swerve Module configuration class to define the motor CAN IDs and absolute encoder offset of a swerve module.
|
||||
*
|
||||
* @param driveMotor Drive motor for the swerve module.
|
||||
* @param steerMotor Steer motor for the swerve module.
|
||||
* @param encoderSteering CANCoder for the steering motor on the swerve module.
|
||||
* @param offset Absolute encoder offset.
|
||||
* @param location Swerve Moduule location on the chassis.
|
||||
*/
|
||||
public SwerveModuleConfig(DriveMotorType driveMotor, SteeringMotorType steerMotor, AbsoluteEncoder encoderSteering,
|
||||
double offset,
|
||||
SwerveModuleLocation location)
|
||||
{
|
||||
drive = driveMotor;
|
||||
steering = steerMotor;
|
||||
angleOffset = offset;
|
||||
encoder = encoderSteering;
|
||||
loc = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the swerve module from the configuration.
|
||||
*
|
||||
* @param driveGearRatio Drive gear ratio in form of (rotation:1 AKA rotations/1) to get the encoder
|
||||
* ticks per rotation.
|
||||
* @param steerGearRatio Steering motor gear ratio (usually 12.8:1 for MK4 in form of rotations:1 or
|
||||
* rotations/1), only applied if using Neo's.
|
||||
* @param wheelDiameterMeters The wheel diameter of the swerve drive module in meters.
|
||||
* @param wheelBaseMeters The Distance between front and back wheels of the robot in meters.
|
||||
* @param driveTrainWidthMeters The Distance between centers of right and left wheels in meters.
|
||||
* @param steeringMotorFreeSpeedRPM The RPM free speed of the steering motor.
|
||||
* @param maxSpeedMPS The maximum drive speed in meters per second.
|
||||
* @param maxDriveAcceleration The maximum drive acceleration in meters^2 per second.
|
||||
* @param steeringInverted The steering motor is inverted.
|
||||
* @param drivingInverted The driving motor is inverted.
|
||||
* @return The Swerve Module.
|
||||
*/
|
||||
public SwerveModule<DriveMotorType, SteeringMotorType, AbsoluteEncoder> createModule(double driveGearRatio,
|
||||
double steerGearRatio,
|
||||
double wheelDiameterMeters,
|
||||
double wheelBaseMeters,
|
||||
double driveTrainWidthMeters,
|
||||
double steeringMotorFreeSpeedRPM,
|
||||
double maxSpeedMPS,
|
||||
double maxDriveAcceleration,
|
||||
boolean steeringInverted,
|
||||
boolean drivingInverted)
|
||||
{
|
||||
return new SwerveModule<>(drive, steering, encoder, loc,
|
||||
driveGearRatio, steerGearRatio,
|
||||
angleOffset, wheelDiameterMeters,
|
||||
wheelBaseMeters,
|
||||
driveTrainWidthMeters,
|
||||
steeringMotorFreeSpeedRPM,
|
||||
maxSpeedMPS, maxDriveAcceleration, .6, .4, steeringInverted, drivingInverted);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
102
swervelib/SwerveEncoder.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package frc.robot.subsystems.swervedrive.swerve;
|
||||
|
||||
import com.ctre.phoenix.sensors.CANCoder;
|
||||
import com.ctre.phoenix.sensors.MagnetFieldStrength;
|
||||
import com.revrobotics.AbsoluteEncoder;
|
||||
import edu.wpi.first.wpilibj.AnalogEncoder;
|
||||
import edu.wpi.first.wpilibj.DutyCycleEncoder;
|
||||
import frc.robot.subsystems.swervedrive.swerve.encoders.CTRECANCoder;
|
||||
import frc.robot.subsystems.swervedrive.swerve.encoders.PWMAnalogEncoder;
|
||||
import frc.robot.subsystems.swervedrive.swerve.encoders.PWMDutyCycleEncoder;
|
||||
import frc.robot.subsystems.swervedrive.swerve.encoders.REVAbsoluteEncoder;
|
||||
|
||||
/**
|
||||
* Swerve Encoder class definition for common interfaces.
|
||||
*/
|
||||
public abstract class SwerveEncoder<AbsoluteEncoderType>
|
||||
{
|
||||
|
||||
/**
|
||||
* The encoder can be directly referenced for configuration purposes of onboard PIDs.
|
||||
*/
|
||||
public AbsoluteEncoderType m_encoder;
|
||||
|
||||
/**
|
||||
* Get the SwerveEncoder class from the given encoder types
|
||||
*
|
||||
* @param encoder Encoder
|
||||
* @param <AbsoluteEncoderType> One of DutyCycleEncoder, AnalogEncoder, AbsoluteEncoder, and CANCoder
|
||||
* @return SwerveEncoder subclass
|
||||
*/
|
||||
public static <AbsoluteEncoderType> SwerveEncoder<?> fromEncoder(AbsoluteEncoderType encoder)
|
||||
{
|
||||
if (encoder instanceof CANCoder)
|
||||
{
|
||||
return new CTRECANCoder((CANCoder) encoder);
|
||||
} else if (encoder instanceof DutyCycleEncoder)
|
||||
{
|
||||
return new PWMDutyCycleEncoder((DutyCycleEncoder) encoder);
|
||||
} else if (encoder instanceof AnalogEncoder)
|
||||
{
|
||||
return new PWMAnalogEncoder((AnalogEncoder) encoder);
|
||||
} else if (encoder instanceof AbsoluteEncoder)
|
||||
{
|
||||
return new REVAbsoluteEncoder((AbsoluteEncoder) encoder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the absolute encoder if possible.
|
||||
*/
|
||||
public abstract void configure();
|
||||
|
||||
/**
|
||||
* Reset encoder to factory default settings, if possible.
|
||||
*/
|
||||
public abstract void factoryDefault();
|
||||
|
||||
/**
|
||||
* Get the magnetic field strength, if available.
|
||||
*
|
||||
* @return CTRE MagneticFieldStrength Enum.
|
||||
*/
|
||||
public abstract MagnetFieldStrength getMagnetFieldStrength();
|
||||
|
||||
/**
|
||||
* Get the absolute position in degrees.
|
||||
*
|
||||
* @return Absolute position (0, 360]
|
||||
*/
|
||||
public abstract double getAbsolutePosition();
|
||||
|
||||
/**
|
||||
* Get the velocity of the absolute encoder in degrees per second.
|
||||
*
|
||||
* @return Velocity in degrees per second.
|
||||
*/
|
||||
public abstract double getVelocity();
|
||||
|
||||
/**
|
||||
* Configure the magnetic offset for the AbsoluteEncoder.
|
||||
*
|
||||
* @param offset Offset in degrees.
|
||||
*/
|
||||
public abstract void setOffset(double offset);
|
||||
|
||||
/**
|
||||
* Is the encoder reachable?
|
||||
*
|
||||
* @return True if reachable, false otherwise.
|
||||
*/
|
||||
public abstract boolean reachable();
|
||||
|
||||
/**
|
||||
* Configure the sensor direction
|
||||
*
|
||||
* @param isInverted Inverted or not.
|
||||
*/
|
||||
public abstract void setInverted(boolean isInverted);
|
||||
|
||||
}
|
||||
938
swervelib/SwerveModule.java
Normal file
@@ -0,0 +1,938 @@
|
||||
package frc.robot.subsystems.swervedrive.swerve;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.ctre.phoenix.motorcontrol.can.TalonFX;
|
||||
import com.ctre.phoenix.sensors.CANCoder;
|
||||
import com.ctre.phoenix.sensors.MagnetFieldStrength;
|
||||
import com.revrobotics.AbsoluteEncoder;
|
||||
import com.revrobotics.CANSparkMax;
|
||||
import edu.wpi.first.math.controller.SimpleMotorFeedforward;
|
||||
import edu.wpi.first.math.geometry.Rotation2d;
|
||||
import edu.wpi.first.math.geometry.Translation2d;
|
||||
import edu.wpi.first.math.kinematics.SwerveModulePosition;
|
||||
import edu.wpi.first.networktables.GenericEntry;
|
||||
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.AnalogEncoder;
|
||||
import edu.wpi.first.wpilibj.DutyCycleEncoder;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
import edu.wpi.first.wpilibj.shuffleboard.BuiltInWidgets;
|
||||
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
|
||||
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardTab;
|
||||
import edu.wpi.first.wpilibj.shuffleboard.SimpleWidget;
|
||||
import frc.robot.Robot;
|
||||
import frc.robot.subsystems.swervedrive.swerve.SwerveMotor.ModuleMotorType;
|
||||
import frc.robot.subsystems.swervedrive.swerve.kinematics.SwerveModuleState2;
|
||||
import java.io.Closeable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Swerve module for representing a single swerve module of the robot.
|
||||
*
|
||||
* @param <DriveMotorType> Main motor type that drives the wheel.
|
||||
* @param <AngleMotorType> Motor that controls the angle of the wheel.
|
||||
* @param <AbsoluteEncoderType> Absolute encoder for the swerve drive module.
|
||||
*/
|
||||
public class SwerveModule<DriveMotorType extends MotorController, AngleMotorType extends MotorController,
|
||||
AbsoluteEncoderType>
|
||||
implements MotorController, Sendable, AutoCloseable
|
||||
{
|
||||
|
||||
/**
|
||||
* Swerve Module location object relative to the center of the robot.
|
||||
*/
|
||||
public final Translation2d swerveModuleLocation;
|
||||
/**
|
||||
* Motor Controllers for drive motor of the swerve module.
|
||||
*/
|
||||
public final SwerveMotor driveMotor;
|
||||
/***
|
||||
* Motor Controller for the turning motor of the swerve drive module.
|
||||
*/
|
||||
public final SwerveMotor turningMotor;
|
||||
/**
|
||||
* Enum representing the swerve module's location on the robot, assuming square.
|
||||
*/
|
||||
private final SwerveModuleLocation swerveLocation;
|
||||
/**
|
||||
* Absolute encoder for the swerve module.
|
||||
*/
|
||||
private final SwerveEncoder<?> absoluteEncoder;
|
||||
/**
|
||||
* The Distance between centers of right and left wheels in meters.
|
||||
*/
|
||||
private final double driveTrainWidth;
|
||||
/**
|
||||
* The Distance between front and back wheels of the robot in meters.
|
||||
*/
|
||||
private final double wheelBase;
|
||||
/**
|
||||
* Drive feedforward for PID when driving by velocity.
|
||||
*/
|
||||
private final SimpleMotorFeedforward driveFeedforward;
|
||||
/**
|
||||
* kV for steering feedforward.
|
||||
*/
|
||||
private final double steeringKV;
|
||||
private final ShuffleboardTab moduleTab;
|
||||
private final HashMap<String, SimpleWidget> NT4Entries = new HashMap<>();
|
||||
/**
|
||||
* Angle offset of the CANCoder at initialization.
|
||||
*/
|
||||
public double angleOffset = 0;
|
||||
/**
|
||||
* Maximum speed in meters per second, used to eliminate unnecessary movement of the module.
|
||||
*/
|
||||
public double maxDriveSpeedMPS;
|
||||
/**
|
||||
* Inverted drive motor.
|
||||
*/
|
||||
private boolean invertedDrive = false;
|
||||
/**
|
||||
* Inverted turning motor.
|
||||
*/
|
||||
private boolean invertedTurn = false;
|
||||
/**
|
||||
* Power to drive motor from -1 to 1.
|
||||
*/
|
||||
private double drivePower = 0;
|
||||
/**
|
||||
* Store the last angle for optimization.
|
||||
*/
|
||||
private double targetAngle;
|
||||
/**
|
||||
* Angular velocity in radians per second.
|
||||
*/
|
||||
private double targetAngularVelocityRPS = 0;
|
||||
/**
|
||||
* Target velocity for the swerve module.
|
||||
*/
|
||||
private double targetVelocity = 0;
|
||||
|
||||
///////////////////////////// CONFIGURATION FUNCTIONS SECTION ///////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Swerve module constructor. Both motors <b>MUST</b> be a {@link MotorController} class. It is recommended to create
|
||||
* a command to reset the encoders when triggered and
|
||||
*
|
||||
* @param mainMotor Main drive motor. Must be a {@link MotorController} type.
|
||||
* @param angleMotor Angle motor for controlling the angle of the swerve module.
|
||||
* @param encoder Absolute encoder for the swerve module.
|
||||
* @param driveGearRatio Drive gear ratio in form of (rotation:1 AKA rotations/1) to get the encoder ticks
|
||||
* per rotation.
|
||||
* @param steerGearRatio Steering motor gear ratio (usually 12.8:1 for MK4 in form of rotations:1 or
|
||||
* rotations/1), only applied if using Neo's.
|
||||
* @param swervePosition Swerve Module position on the robot.
|
||||
* @param steeringOffsetDegrees The current offset of the absolute encoder from 0 in degrees.
|
||||
* @param wheelDiameterMeters The wheel diameter of the swerve drive module in meters.
|
||||
* @param wheelBaseMeters The Distance between front and back wheels of the robot in meters.
|
||||
* @param driveTrainWidthMeters The Distance between centers of right and left wheels in meters.
|
||||
* @param steeringMotorFreeSpeedRPM The RPM free speed of the steering motor.
|
||||
* @param maxSpeedMPS The maximum drive speed in meters per second.
|
||||
* @param maxDriveAcceleration The maximum drive acceleration in meters^2 per second.
|
||||
* @param drivingPowerLimit The power limit for the closed loop PID of the driver motor.
|
||||
* @param steeringPowerLimit The power limit for the closed loop PID of the steering motor.
|
||||
* @param steeringInverted The steering motor is inverted.
|
||||
* @param drivingInverted The driving motor is inverted.
|
||||
* @throws RuntimeException if an assertion fails or invalid swerve module location is given.
|
||||
*/
|
||||
public SwerveModule(DriveMotorType mainMotor, AngleMotorType angleMotor, AbsoluteEncoderType encoder,
|
||||
SwerveModuleLocation swervePosition, double driveGearRatio, double steerGearRatio,
|
||||
double steeringOffsetDegrees, double wheelDiameterMeters, double wheelBaseMeters,
|
||||
double driveTrainWidthMeters, double steeringMotorFreeSpeedRPM, double maxSpeedMPS,
|
||||
double maxDriveAcceleration, double drivingPowerLimit, double steeringPowerLimit,
|
||||
boolean steeringInverted, boolean drivingInverted)
|
||||
{
|
||||
// Steps to configure swerve drive are as follows
|
||||
// 1. Set Current limit of turning motor to 20 amps
|
||||
// 2. Enable voltage compensation with optimal battery voltage
|
||||
// 3. Configure CANCoder
|
||||
// 4. Set inverted motors.
|
||||
// 5. Configure status frames
|
||||
// 6. Set all motors to brake mode.
|
||||
// 7. Set velocity and position conversion factors on drive motor encoder.
|
||||
// 8. Set PIDF with integral zone on drive motor controller PID.
|
||||
// 9. Set velocity and position conversion factors on turning motor controller. (Usually P=0.01,I=0,D=0,F=0,IZ=1)
|
||||
// 10. Set PIDF with integral zone on turning motor controller.
|
||||
// 11. Check all CAN devices are active.
|
||||
// 12. Reset the angle on the internal encoder to the absolute encoder.
|
||||
requireNonNull(mainMotor);
|
||||
requireNonNull(angleMotor);
|
||||
requireNonNull(encoder);
|
||||
|
||||
this.wheelBase = wheelBaseMeters;
|
||||
this.driveTrainWidth = driveTrainWidthMeters;
|
||||
|
||||
assert mainMotor instanceof CANSparkMax || mainMotor instanceof TalonFX;
|
||||
assert angleMotor instanceof CANSparkMax || angleMotor instanceof TalonFX;
|
||||
assert encoder instanceof DutyCycleEncoder || encoder instanceof AnalogEncoder || encoder instanceof CANCoder ||
|
||||
encoder instanceof AbsoluteEncoder;
|
||||
|
||||
absoluteEncoder = SwerveEncoder.fromEncoder(encoder);
|
||||
assert absoluteEncoder != null;
|
||||
|
||||
absoluteEncoder.factoryDefault();
|
||||
|
||||
driveMotor = SwerveMotor.fromMotor(mainMotor,
|
||||
absoluteEncoder,
|
||||
ModuleMotorType.DRIVE,
|
||||
driveGearRatio,
|
||||
wheelDiameterMeters,
|
||||
0,
|
||||
drivingPowerLimit);
|
||||
|
||||
turningMotor = SwerveMotor.fromMotor(angleMotor,
|
||||
absoluteEncoder,
|
||||
ModuleMotorType.TURNING,
|
||||
steerGearRatio,
|
||||
wheelDiameterMeters,
|
||||
steeringMotorFreeSpeedRPM,
|
||||
steeringPowerLimit);
|
||||
|
||||
swerveLocation = swervePosition;
|
||||
swerveModuleLocation = getSwerveModulePosition(swervePosition);
|
||||
|
||||
assert driveMotor != null;
|
||||
assert turningMotor != null;
|
||||
|
||||
// Set the maximum speed for each swerve module for use when trying to optimize movements.
|
||||
// Drive feedforward gains
|
||||
// public static final double KS = 0;
|
||||
// public static final double KV = 12 / MAX_SPEED; // Volt-seconds per meter (max voltage divided by max
|
||||
// speed)
|
||||
// public static final double KA = 12 / MAX_ACCELERATION; // Volt-seconds^2 per meter (max voltage divided
|
||||
// by max accel)
|
||||
maxDriveSpeedMPS = maxSpeedMPS;
|
||||
driveFeedforward = new SimpleMotorFeedforward(0, 12 / maxDriveSpeedMPS, 12 / maxDriveAcceleration);
|
||||
steeringKV = (12 * 60) / (steeringMotorFreeSpeedRPM * Math.toRadians(360 / steerGearRatio));
|
||||
|
||||
driveMotor.setInverted(drivingInverted);
|
||||
invertedDrive = drivingInverted;
|
||||
turningMotor.setInverted(steeringInverted);
|
||||
invertedTurn = steeringInverted;
|
||||
|
||||
absoluteEncoder.configure();
|
||||
|
||||
setAngleOffset(steeringOffsetDegrees);
|
||||
resetEncoders();
|
||||
|
||||
// Convert CANCoder to read data as unsigned 0 to 360 for synchronization purposes.
|
||||
absoluteEncoder.configure();
|
||||
|
||||
assert activeCAN();
|
||||
|
||||
resetEncoders();
|
||||
synchronizeSteeringEncoder();
|
||||
|
||||
driveMotor.saveConfig();
|
||||
turningMotor.saveConfig();
|
||||
|
||||
// targetAngle = getState().angle.getDegrees();
|
||||
targetAngle = 0;
|
||||
|
||||
if (!remoteIntegratedEncoder() && Robot.isReal())
|
||||
{
|
||||
Robot.getInstance().addPeriodic(this::synchronizeSteeringEncoder, 0.5);
|
||||
}
|
||||
|
||||
// Shuffleboard Data
|
||||
moduleTab = Shuffleboard.getTab(SwerveModule.SwerveModuleLocationToString(swerveLocation));
|
||||
publish(Verbosity.SETUP);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert {@link SwerveModuleLocation} to {@link String} representation.
|
||||
*
|
||||
* @param swerveLocation Swerve position to convert.
|
||||
* @return {@link String} name of the {@link SwerveModuleLocation} enum.
|
||||
*/
|
||||
public static String SwerveModuleLocationToString(SwerveModuleLocation swerveLocation)
|
||||
{
|
||||
switch (swerveLocation)
|
||||
{
|
||||
case FrontLeft:
|
||||
return "Front Left Module";
|
||||
case BackLeft:
|
||||
return "Back Left Module";
|
||||
case FrontRight:
|
||||
return "Front Right Module";
|
||||
case BackRight:
|
||||
return "Back Right Module";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the REV encoders onboard the SparkMax's to 0, and set's the drive motor to position to 0 and synchronizes the
|
||||
* internal steering encoders with the absolute encoder.
|
||||
*/
|
||||
public void resetEncoders()
|
||||
{
|
||||
driveMotor.setEncoder(0);
|
||||
|
||||
if (!remoteIntegratedEncoder())
|
||||
{
|
||||
turningMotor.setEncoder(0);
|
||||
synchronizeSteeringEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the internal encoder for the steering motor with the value from the absolute encoder.
|
||||
*/
|
||||
public void synchronizeSteeringEncoder()
|
||||
{
|
||||
if (!remoteIntegratedEncoder())
|
||||
{
|
||||
if (absoluteEncoder.getMagnetFieldStrength() != MagnetFieldStrength.Good_GreenLED)
|
||||
{
|
||||
System.err.println("CANCoder magnetic field strength is unacceptable, will not synchronize encoders.");
|
||||
return;
|
||||
}
|
||||
turningMotor.setEncoder(absoluteEncoder.getAbsolutePosition() - angleOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////// END OF CONFIGURATION FUNCTIONS SECTION //////////////////////////
|
||||
|
||||
////////////////////////////// STATUS FUNCTIONS SECTION //////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Set the PIDF coefficients for the closed loop PID onboard the motor controller. Tuning the PID
|
||||
* <p>
|
||||
* <b>P</b> = .5 and increase it by .1 until oscillations occur, then decrease by .05 then .005 until oscillations
|
||||
* stop and angle is perfect or near perfect.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>I</b> = 0 tune this if your PID never quite reaches the target, after tuning <b>D</b>. Increase this by
|
||||
* <b>P</b>*.01 each time and adjust accordingly.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>D</b> = 0 tune this if the PID accelerates too fast, it will smooth the motion. Increase this by <b>P</b>*10
|
||||
* and adjust accordingly.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>F</b> = 0 tune this if the PID is being used for velocity, the <b>F</b> is multiplied by the target and added
|
||||
* to the voltage output. Increase this by 0.01 until the PIDF reaches the desired state in a fast enough manner.
|
||||
* </p>
|
||||
* Documentation for this is best described by CTRE <a
|
||||
* href="https://docs.ctre-phoenix.com/en/stable/ch16_ClosedLoop.html#position-closed-loop-control-mode">here</a>.
|
||||
*
|
||||
* @param p Proportional gain for closed loop. This is multiplied by closed loop error in sensor units.
|
||||
* @param i Integral gain for closed loop. This is multiplied by closed loop error in sensor units every
|
||||
* PID Loop.
|
||||
* @param d Derivative gain for closed loop. This is multiplied by derivative error (sensor units per
|
||||
* PID loop).
|
||||
* @param f Feed Fwd gain for Closed loop.
|
||||
* @param integralZone Integral Zone can be used to auto clear the integral accumulator if the sensor pos is too
|
||||
* far from the target. This prevents unstable oscillation if the kI is too large. Value is in
|
||||
* sensor units.
|
||||
* @param moduleMotorType Swerve drive motor type.
|
||||
*/
|
||||
public void setPIDF(double p, double i, double d, double f, double integralZone,
|
||||
ModuleMotorType moduleMotorType)
|
||||
{
|
||||
if ((moduleMotorType == ModuleMotorType.DRIVE))
|
||||
{
|
||||
driveMotor.setPIDF(p, i, d, f, integralZone);
|
||||
} else
|
||||
{
|
||||
turningMotor.setPIDF(p, i, d, f, integralZone);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////// END OF STATUS FUNCTIONS SECTION ////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////// ODOMETRY AND STATE FUNCTIONS SECTION ///////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Configure the magnetic offset in the CANCoder.
|
||||
*
|
||||
* @param offset Magnetic offset in degrees.
|
||||
*/
|
||||
public void setAngleOffset(double offset)
|
||||
{
|
||||
// System.out.println(offset);
|
||||
angleOffset = offset;
|
||||
absoluteEncoder.setOffset(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the link is good on the swerve module and CAN bus is able to retrieve data.
|
||||
*
|
||||
* @return true on all devices are accessible over CAN.
|
||||
*/
|
||||
public boolean activeCAN()
|
||||
{
|
||||
boolean drive = driveMotor.reachable(), turn = turningMotor.reachable(), encoder = absoluteEncoder.reachable();
|
||||
|
||||
return drive && turn && encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the absolute encoder is able to be read directly by the motor controller instead of using the
|
||||
* synchronizeEncoder() function periodically.
|
||||
*
|
||||
* @return If the absolute sensor in the steering motor is directly accessed by the motor controller.
|
||||
*/
|
||||
public boolean remoteIntegratedEncoder()
|
||||
{
|
||||
return turningMotor.remoteIntegratedEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the angle of the swerve module.
|
||||
*
|
||||
* @param angle Angle in degrees.
|
||||
* @param feedforward The feedforward for the PID.
|
||||
*/
|
||||
public void setAngle(double angle, double feedforward)
|
||||
{
|
||||
turningMotor.setTarget(Math.round(angle) % 180, feedforward);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the angle of the swerve module without feedforward.
|
||||
*
|
||||
* @param angle Angle in degrees.
|
||||
*/
|
||||
private void setAngle(double angle)
|
||||
{
|
||||
turningMotor.setTarget(angle, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the drive motor velocity in MPS.
|
||||
*
|
||||
* @param velocity Velocity in MPS.
|
||||
*/
|
||||
public void setVelocity(double velocity)
|
||||
{
|
||||
targetVelocity = velocity;
|
||||
driveMotor.setTarget(velocity, driveFeedforward.calculate(velocity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module state.
|
||||
*
|
||||
* @return SwerveModuleState with the encoder inputs.
|
||||
* @throws RuntimeException Exception if CANCoder doesnt exist
|
||||
*/
|
||||
public SwerveModuleState2 getState()
|
||||
{
|
||||
double mps = driveMotor.get();
|
||||
double angularVelocityRPS;
|
||||
Rotation2d angle;
|
||||
angle = Rotation2d.fromDegrees(Math.round(
|
||||
Robot.isReal() ? absoluteEncoder.getAbsolutePosition() - angleOffset : targetAngle));
|
||||
angularVelocityRPS = Robot.isReal() ? Math.toRadians(absoluteEncoder.getVelocity()) : targetAngularVelocityRPS;
|
||||
//^ Convert degrees per second to radians per second.
|
||||
return new SwerveModuleState2(mps, angle, angularVelocityRPS);
|
||||
}
|
||||
|
||||
/////////////////// END OF ODOMETRY AND STATE FUNCTIONS SECTION ////////////////////////////////////////
|
||||
|
||||
/////////////////// DIAGNOSTIC AND TUNING FUNCTIONS SECTION ////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Set the module speed and angle based off the module state.
|
||||
*
|
||||
* @param state Module state.
|
||||
*/
|
||||
public void setState(SwerveModuleState2 state)
|
||||
{
|
||||
// state.angle = state.angle.minus(Rotation2d.fromDegrees(angleOffset));
|
||||
// inspired by https://github.com/first95/FRC2022/blob/1f57d6837e04d8c8a89f4d83d71b5d2172f41a0e/SwervyBot/src/main/java/frc/robot/SwerveModule.java#L22
|
||||
Rotation2d currentAngle = getState().angle;
|
||||
// SwerveModuleState optimizedState = SwerveModuleState.optimize(new SwerveModuleState(state.speedMetersPerSecond,
|
||||
// state.angle), currentAngle);
|
||||
SwerveModuleState2 optimizedState = SwerveModuleState2.optimize(state, currentAngle);
|
||||
double angle = optimizedState.angle.getDegrees(); // getDegrees returns in the range of -180 to 180 we want 0 to
|
||||
// 360.
|
||||
double velocity = (Math.abs(optimizedState.speedMetersPerSecond) <= (maxDriveSpeedMPS * 0.01)) ? 0
|
||||
:
|
||||
optimizedState.speedMetersPerSecond;
|
||||
// turn motor code
|
||||
// Prevent rotating module if speed is less then 1%. Prevents Jittering.
|
||||
angle = (Math.abs(optimizedState.speedMetersPerSecond) <= (maxDriveSpeedMPS * 0.01)) ? 0 : angle;
|
||||
setAngle(angle, optimizedState.angularVelocityRadPerSecond * steeringKV);
|
||||
setVelocity(velocity);
|
||||
targetAngle = angle;
|
||||
targetAngularVelocityRPS = optimizedState.angularVelocityRadPerSecond;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the swerve module position based off the sensors.
|
||||
*
|
||||
* @return Swerve Module position, with the angle as what the angle is supposed to be, not what it actually is.
|
||||
*/
|
||||
public SwerveModulePosition getPosition()
|
||||
{
|
||||
return new SwerveModulePosition(driveMotor.getPosition(), Rotation2d.fromDegrees(targetAngle));
|
||||
}
|
||||
|
||||
//////////////////////////// END OF DIAGNOSTIC AND TUNING FUNCTIONS SECTION /////////////////////////
|
||||
|
||||
/**
|
||||
* Subscribe from data within smart dashboard to make changes to the swerve module.
|
||||
*/
|
||||
public void subscribe()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////// ENUMS SECTION //////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Create a widget and add the entry to the hashmap of entries for network tables.
|
||||
*
|
||||
* @param name Key to display on shuffleboard.
|
||||
* @param defaultValue Default value.
|
||||
* @return Widget that can be modified.
|
||||
*/
|
||||
private SimpleWidget addEntry(String name, Object defaultValue)
|
||||
{
|
||||
if (NT4Entries.containsKey(name))
|
||||
{
|
||||
GenericEntry entry = NT4Entries.get(name).getEntry();
|
||||
if (defaultValue instanceof Integer)
|
||||
{
|
||||
entry.setInteger((int) defaultValue);
|
||||
} else if (defaultValue instanceof Double)
|
||||
{
|
||||
entry.setDouble((double) defaultValue);
|
||||
} else if (defaultValue instanceof Boolean)
|
||||
{
|
||||
entry.setBoolean((boolean) defaultValue);
|
||||
} else if (defaultValue instanceof Double[])
|
||||
{
|
||||
entry.setDoubleArray((Double[]) defaultValue);
|
||||
} else
|
||||
{
|
||||
throw new RuntimeException("Cannot publish value for " + name);
|
||||
}
|
||||
return NT4Entries.get(name);
|
||||
}
|
||||
try
|
||||
{
|
||||
SimpleWidget widget = moduleTab.add(name, defaultValue);
|
||||
NT4Entries.put(name, widget);
|
||||
return widget;
|
||||
} catch (Exception e)
|
||||
{
|
||||
System.err.println(e);
|
||||
}
|
||||
throw new RuntimeException("Error creating entry");
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish data to the smart dashboard relating to this swerve module.
|
||||
*
|
||||
* @param level Verbosity level, affects the CAN utilization, on HIGH it will enable the update button.
|
||||
*/
|
||||
public void publish(Verbosity level)
|
||||
{
|
||||
boolean goodLaptop = false;
|
||||
switch (level)
|
||||
{
|
||||
case SETUP:
|
||||
// PID
|
||||
addEntry("drive/pid/kP", driveMotor.kP);
|
||||
addEntry("drive/pid/kI", driveMotor.kI);
|
||||
addEntry("drive/pid/kD", driveMotor.kD);
|
||||
addEntry("drive/pid/kF", driveMotor.kF);
|
||||
addEntry("drive/pid/kIZ", driveMotor.kIZ);
|
||||
|
||||
addEntry("steer/pid/kP", turningMotor.kP);
|
||||
addEntry("steer/pid/kI", turningMotor.kI);
|
||||
addEntry("steer/pid/kD", turningMotor.kD);
|
||||
addEntry("steer/pid/kF", turningMotor.kF);
|
||||
addEntry("steer/pid/kIZ", turningMotor.kIZ);
|
||||
|
||||
// Pretty Widget Setup
|
||||
if (goodLaptop)
|
||||
{
|
||||
addEntry("Angle (Degrees)",
|
||||
new Double[]{targetAngle, turningMotor.get(), absoluteEncoder.getAbsolutePosition()})
|
||||
.withPosition(0, 1)
|
||||
.withSize(4, 4)
|
||||
.withProperties(Map.of("visible time", 5))
|
||||
.withWidget(BuiltInWidgets.kGraph);
|
||||
addEntry("Absolute Angle", absoluteEncoder.getAbsolutePosition())
|
||||
.withPosition(0, 0)
|
||||
.withSize(2, 1);
|
||||
addEntry("Integrated Angle", turningMotor.get())
|
||||
.withPosition(2, 0)
|
||||
.withSize(2, 1);
|
||||
addEntry("Target Angle", targetAngle)
|
||||
.withPosition(8, 4)
|
||||
.withSize(2, 2);
|
||||
|
||||
addEntry("Velocity (Meters Per Second)", new Double[]{targetVelocity, driveMotor.get()})
|
||||
.withPosition(4, 1)
|
||||
.withSize(4, 4)
|
||||
.withProperties(Map.of("visible time", 5))
|
||||
.withWidget(BuiltInWidgets.kGraph);
|
||||
addEntry("Motor Velocity", driveMotor.get())
|
||||
.withPosition(4, 0)
|
||||
.withSize(4, 1);
|
||||
addEntry("Target Velocity", targetVelocity)
|
||||
.withPosition(8, 2)
|
||||
.withSize(2, 2);
|
||||
|
||||
// Inverted Motors.
|
||||
addEntry("Steer Motor Inverted", invertedTurn)
|
||||
.withPosition(0, 5)
|
||||
.withSize(4, 1);
|
||||
addEntry("Drive Motor inverted", invertedDrive)
|
||||
.withPosition(4, 5)
|
||||
.withSize(4, 1);
|
||||
|
||||
addEntry("Absolute Encoder Magnetic Field", absoluteEncoder.getMagnetFieldStrength().value)
|
||||
.withPosition(10, 2)
|
||||
.withSize(2, 1);
|
||||
addEntry("Drive Motor Current", driveMotor.getAmps())
|
||||
.withPosition(10, 5)
|
||||
.withSize(2, 1);
|
||||
|
||||
// Angle Constants
|
||||
addEntry("Absolute Encoder Offset", angleOffset)
|
||||
.withProperties(Map.of("min", -180, "max", 180))
|
||||
.withPosition(10, 3)
|
||||
.withSize(2, 2)
|
||||
.withWidget(BuiltInWidgets.kDial);
|
||||
} else
|
||||
{
|
||||
addEntry("Target Angle", targetAngle)
|
||||
.withProperties(Map.of("min", -180, "max", 180))
|
||||
.withWidget(BuiltInWidgets.kDial)
|
||||
.withPosition(0, 0)
|
||||
.withSize(4, 2);
|
||||
addEntry("Integrated Angle", turningMotor.get())
|
||||
.withProperties(Map.of("min", 0, "max", 360))
|
||||
.withWidget(BuiltInWidgets.kDial)
|
||||
.withPosition(0, 2)
|
||||
.withSize(2, 2);
|
||||
addEntry("Absolute Angle", absoluteEncoder.getAbsolutePosition())
|
||||
.withProperties(Map.of("min", 0, "max", 360))
|
||||
.withWidget(BuiltInWidgets.kDial)
|
||||
.withPosition(2, 2)
|
||||
.withSize(2, 2);
|
||||
|
||||
addEntry("Target Velocity", targetVelocity)
|
||||
.withPosition(4, 0)
|
||||
.withProperties(Map.of("min", -maxDriveSpeedMPS, "max", maxDriveSpeedMPS))
|
||||
.withSize(4, 2)
|
||||
.withWidget(BuiltInWidgets.kNumberBar);
|
||||
addEntry("Motor Velocity", driveMotor.get())
|
||||
.withPosition(4, 2)
|
||||
.withProperties(Map.of("min", -maxDriveSpeedMPS, "max", maxDriveSpeedMPS))
|
||||
.withSize(4, 2)
|
||||
.withWidget(BuiltInWidgets.kNumberBar);
|
||||
|
||||
// Inverted Motors.
|
||||
addEntry("Steer Motor Inverted", invertedTurn)
|
||||
.withPosition(0, 4)
|
||||
.withSize(4, 1);
|
||||
addEntry("Drive Motor inverted", invertedDrive)
|
||||
.withPosition(4, 4)
|
||||
.withSize(4, 1);
|
||||
|
||||
addEntry("Absolute Encoder Magnetic Field", absoluteEncoder.getMagnetFieldStrength().value)
|
||||
.withPosition(8, 2)
|
||||
.withSize(2, 1);
|
||||
|
||||
addEntry("Drive Motor Current", driveMotor.getAmps())
|
||||
.withPosition(10, 2)
|
||||
.withSize(2, 1);
|
||||
|
||||
// Angle Constants
|
||||
addEntry("Absolute Encoder Offset", angleOffset)
|
||||
.withProperties(Map.of("min", -180, "max", 180))
|
||||
.withPosition(8, 3)
|
||||
.withSize(2, 2)
|
||||
.withWidget(BuiltInWidgets.kDial);
|
||||
}
|
||||
|
||||
addEntry("CAN Connection", activeCAN())
|
||||
.withPosition(8, 0)
|
||||
.withSize(4, 2);
|
||||
|
||||
case HIGH:
|
||||
// Update if button is set.
|
||||
// The higher the better, 2 and 3 are what we want.
|
||||
addEntry("Absolute Encoder Magnetic Field", absoluteEncoder.getMagnetFieldStrength().value);
|
||||
case NORMAL:
|
||||
// Steering Encoder Values
|
||||
if (goodLaptop)
|
||||
{
|
||||
addEntry("Angle (Degrees)",
|
||||
new Double[]{targetAngle, turningMotor.get(), absoluteEncoder.getAbsolutePosition()});
|
||||
}
|
||||
addEntry("Absolute Angle", absoluteEncoder.getAbsolutePosition());
|
||||
addEntry("Integrated Angle", turningMotor.get());
|
||||
|
||||
// Driving Encoder Values
|
||||
if (goodLaptop)
|
||||
{
|
||||
addEntry("Velocity (Meters Per Second)", new Double[]{targetVelocity, driveMotor.get()});
|
||||
}
|
||||
addEntry("Motor Velocity", driveMotor.get());
|
||||
addEntry("Drive Motor Current", driveMotor.getAmps());
|
||||
|
||||
// CAN Bus is accessible
|
||||
addEntry("CAN Connection", activeCAN());
|
||||
|
||||
case LOW:
|
||||
// PID
|
||||
addEntry("Target Velocity", targetVelocity);
|
||||
addEntry("Target Angle", targetAngle);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this {@link Sendable} object.
|
||||
*
|
||||
* @param builder sendable builder
|
||||
*/
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder)
|
||||
{
|
||||
builder.setSmartDashboardType(SwerveModuleLocationToString(swerveLocation) + " SwerveDriveModule");
|
||||
builder.setActuator(true);
|
||||
builder.setSafeState(this::stopMotor);
|
||||
}
|
||||
|
||||
//////////////////////////////////// END OF ENUMS SECTION //////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////// OVERRIDES SECTION ///////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Closes this resource, relinquishing any underlying resources. This method is invoked automatically on objects
|
||||
* managed by the {@code try}-with-resources statement.
|
||||
*
|
||||
* <p>While this interface method is declared to throw {@code
|
||||
* Exception}, implementers are <em>strongly</em> encouraged to declare concrete implementations of the {@code close}
|
||||
* method to throw more specific exceptions, or to throw no exception at all if the close operation cannot fail.
|
||||
*
|
||||
* <p> Cases where the close operation may fail require careful
|
||||
* attention by implementers. It is strongly advised to relinquish the underlying resources and to internally
|
||||
* <em>mark</em> the resource as closed, prior to throwing the exception. The {@code close} method is unlikely to be
|
||||
* invoked more than once and so this ensures that the resources are released in a timely manner. Furthermore it
|
||||
* reduces problems that could arise when the resource wraps, or is wrapped, by another resource.
|
||||
*
|
||||
* <p><em>Implementers of this interface are also strongly advised
|
||||
* to not have the {@code close} method throw {@link InterruptedException}.</em>
|
||||
* <p>
|
||||
* This exception interacts with a thread's interrupted status, and runtime misbehavior is likely to occur if an
|
||||
* {@code InterruptedException} is {@linkplain Throwable#addSuppressed suppressed}.
|
||||
* <p>
|
||||
* More generally, if it would cause problems for an exception to be suppressed, the {@code AutoCloseable.close}
|
||||
* method should not throw it.
|
||||
*
|
||||
* <p>Note that unlike the {@link Closeable#close close}
|
||||
* method of {@link Closeable}, this {@code close} method is <em>not</em> required to be idempotent. In other words,
|
||||
* calling this {@code close} method more than once may have some visible side effect, unlike {@code Closeable.close}
|
||||
* which is required to have no effect if called more than once.
|
||||
* <p>
|
||||
* However, implementers of this interface are strongly encouraged to make their {@code close} methods idempotent.
|
||||
*/
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
SendableRegistry.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the motor controller.
|
||||
*/
|
||||
@Override
|
||||
public void disable()
|
||||
{
|
||||
stopMotor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops motor movement. Motor can be moved again by calling set without having to re-enable the motor.
|
||||
*/
|
||||
@Override
|
||||
public void stopMotor()
|
||||
{
|
||||
// turningMotor.stop();
|
||||
driveMotor.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for setting the speed of a motor controller.
|
||||
*
|
||||
* @param speed The speed to set. Value should be between -1.0 and 1.0.
|
||||
*/
|
||||
@Override
|
||||
public void set(double speed)
|
||||
{
|
||||
drivePower = speed;
|
||||
driveMotor.set(speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for getting the current set speed of a motor controller.
|
||||
*
|
||||
* @return The current set speed. Value is between -1.0 and 1.0.
|
||||
*/
|
||||
@Override
|
||||
public double get()
|
||||
{
|
||||
return drivePower;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the swerve module position in {@link Translation2d} from the enum passed.
|
||||
*
|
||||
* @param swerveLocation Swerve module location enum.
|
||||
* @return Location as {@link Translation2d}.
|
||||
* @throws RuntimeException If Enum value is not defined.
|
||||
*/
|
||||
private Translation2d getSwerveModulePosition(SwerveModuleLocation swerveLocation)
|
||||
{
|
||||
// Modeling off of https://github.com/Stampede3630/2022-Code/blob/master/src/main/java/frc/robot/SwerveDrive.java
|
||||
switch (swerveLocation)
|
||||
{
|
||||
case FrontLeft:
|
||||
return new Translation2d(wheelBase / 2, driveTrainWidth / 2);
|
||||
case BackLeft:
|
||||
return new Translation2d(-wheelBase / 2, driveTrainWidth / 2);
|
||||
case FrontRight:
|
||||
return new Translation2d(wheelBase / 2, -driveTrainWidth / 2);
|
||||
case BackRight:
|
||||
return new Translation2d(-wheelBase / 2, -driveTrainWidth / 2);
|
||||
default:
|
||||
throw new RuntimeException("Invalid location given");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the voltage compensation for the swerve module motor.
|
||||
*
|
||||
* @param nominalVoltage Nominal voltage for operation to output to.
|
||||
*/
|
||||
public void setVoltageCompensation(double nominalVoltage)
|
||||
{
|
||||
driveMotor.setVoltageCompensation(nominalVoltage);
|
||||
turningMotor.setVoltageCompensation(nominalVoltage);
|
||||
}
|
||||
|
||||
//////////////////////////////////// END OF OVERRIDES SECTION ////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////// STATIC FUNCTIONS SECTION ////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Set the current limit for the swerve drive motor, remember this may cause jumping if used in conjunction with
|
||||
* voltage compensation. This is useful to protect the motor from current spikes.
|
||||
*
|
||||
* @param currentLimit Current limit in AMPS at free speed.
|
||||
* @param type Swerve Drive Motor type to configure.
|
||||
*/
|
||||
public void setCurrentLimit(int currentLimit, ModuleMotorType type)
|
||||
{
|
||||
if (type == ModuleMotorType.DRIVE)
|
||||
{
|
||||
driveMotor.setCurrentLimit(currentLimit);
|
||||
} else
|
||||
{
|
||||
turningMotor.setCurrentLimit(currentLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////// END OF STATIC FUNCTIONS SECTION //////////////////////////////////////////
|
||||
|
||||
/////////////////////////////////// EXTRA FUNCTIONS THAT PROBABLY WONT MATTER ////////////////////////////////
|
||||
|
||||
/**
|
||||
* Set the steering motor to be inverted.
|
||||
*
|
||||
* @param isInverted The state of inversion, true is inverted.
|
||||
*/
|
||||
public void setInvertedTurn(boolean isInverted)
|
||||
{
|
||||
invertedTurn = isInverted;
|
||||
turningMotor.setInverted(isInverted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invert the absolute encoder.
|
||||
*
|
||||
* @param isInverted The state of inversion, true is inverted.
|
||||
*/
|
||||
public void setInvertedAbsoluteEncoder(boolean isInverted)
|
||||
{
|
||||
absoluteEncoder.setInverted(isInverted);
|
||||
}
|
||||
|
||||
////////////// CUSTOM INVERSION FUNCTIONS SECTION //////////////////////////
|
||||
|
||||
/**
|
||||
* Common interface for returning if a motor controller is in the inverted state or not.
|
||||
*
|
||||
* @return isInverted The state of the inversion true is inverted.
|
||||
*/
|
||||
@Override
|
||||
public boolean getInverted()
|
||||
{
|
||||
return invertedDrive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for inverting direction of a motor controller.
|
||||
*
|
||||
* @param isInverted The state of inversion, true is inverted.
|
||||
*/
|
||||
@Override
|
||||
public void setInverted(boolean isInverted)
|
||||
{
|
||||
invertedDrive = isInverted;
|
||||
driveMotor.setInverted(isInverted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swerve Module location on the robot.
|
||||
*/
|
||||
public enum SwerveModuleLocation
|
||||
{
|
||||
FrontLeft,
|
||||
BackLeft,
|
||||
FrontRight,
|
||||
BackRight
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbosity levels for data publishing,
|
||||
*/
|
||||
public enum Verbosity
|
||||
{
|
||||
/**
|
||||
* The bare minimum and not utilize the CAN bus when reporting data. Only posts data from attributes.
|
||||
*/
|
||||
LOW,
|
||||
/**
|
||||
* Utilize the CAN bus minimally.
|
||||
*/
|
||||
NORMAL,
|
||||
/**
|
||||
* Extensively use the CAN bus to fetch data and report back.
|
||||
*/
|
||||
HIGH,
|
||||
/**
|
||||
* Creates every field for the module.
|
||||
*/
|
||||
SETUP
|
||||
}
|
||||
|
||||
////////////////////////////// END OF CUSTOM INVERSION FUNCTIONS SECTION /////////////////////////////////////
|
||||
|
||||
}
|
||||
204
swervelib/SwerveMotor.java
Normal file
@@ -0,0 +1,204 @@
|
||||
package frc.robot.subsystems.swervedrive.swerve;
|
||||
|
||||
import com.ctre.phoenix.motorcontrol.can.TalonFX;
|
||||
import com.revrobotics.CANSparkMax;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
import frc.robot.subsystems.swervedrive.swerve.motors.CTRESwerveMotor;
|
||||
import frc.robot.subsystems.swervedrive.swerve.motors.REVSwerveMotor;
|
||||
|
||||
public abstract class SwerveMotor
|
||||
{
|
||||
|
||||
/**
|
||||
* Module motor type.
|
||||
*/
|
||||
public ModuleMotorType m_motorType;
|
||||
/**
|
||||
* PIDF Values.
|
||||
*/
|
||||
public double kP, kI, kD, kF, kIZ;
|
||||
/**
|
||||
* Target value for PID.
|
||||
*/
|
||||
public double target;
|
||||
|
||||
/**
|
||||
* Constructor for Swerve Motor, expecting CANSparkMax or TalonFX. Clears sticky faults and restores factory
|
||||
* defaults.
|
||||
*
|
||||
* @param motor Motor controller.
|
||||
* @param absoluteEncoder The absolute encoder used for the module, if the motor is a turning motor and the encoder is
|
||||
* compatible it will set the encoder as the remote integrated encoder and does not need
|
||||
* periodic synchronization.
|
||||
* @param type Swerve module motor type.
|
||||
* @param gearRatio Gear ratio.
|
||||
* @param wheelDiameter Wheel diameter in meters.
|
||||
* @param freeSpeedRPM Free speed RPM of the motor.
|
||||
* @param powerLimit Power limit for the motor.
|
||||
* @param <MotorType> Motor type to use.
|
||||
*/
|
||||
public static <MotorType extends MotorController> SwerveMotor fromMotor(MotorType motor,
|
||||
SwerveEncoder<?> absoluteEncoder,
|
||||
ModuleMotorType type,
|
||||
double gearRatio,
|
||||
double wheelDiameter,
|
||||
double freeSpeedRPM, double powerLimit)
|
||||
{
|
||||
if (motor instanceof CANSparkMax)
|
||||
{
|
||||
return new REVSwerveMotor(((CANSparkMax) motor),
|
||||
absoluteEncoder,
|
||||
type,
|
||||
gearRatio,
|
||||
wheelDiameter,
|
||||
freeSpeedRPM,
|
||||
powerLimit);
|
||||
}
|
||||
if (motor instanceof TalonFX)
|
||||
{
|
||||
return new CTRESwerveMotor((TalonFX) motor,
|
||||
absoluteEncoder,
|
||||
type,
|
||||
gearRatio,
|
||||
wheelDiameter,
|
||||
freeSpeedRPM,
|
||||
powerLimit);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the maximum power (-1 to 1) the PID can output. This helps manage voltage pull for the drive base.
|
||||
*
|
||||
* @param min Minimum output.
|
||||
* @param max Maximum output.
|
||||
*/
|
||||
public abstract void setPIDOutputRange(double min, double max);
|
||||
|
||||
/**
|
||||
* Set the PIDF coefficients for the closed loop PID onboard the SparkMax.
|
||||
*
|
||||
* @param P Proportional gain for closed loop. This is multiplied by closed loop error in sensor units.
|
||||
* Default is 1.0
|
||||
* @param I Integral gain for closed loop. This is multiplied by closed loop error in sensor units every
|
||||
* PID Loop.
|
||||
* @param D Derivative gain for closed loop. This is multiplied by derivative error (sensor units per PID
|
||||
* loop). Default is 0.1
|
||||
* @param F Feed Fwd gain for Closed loop.
|
||||
* @param integralZone Integral Zone can be used to auto clear the integral accumulator if the sensor pos is too far
|
||||
* from the target. This prevents unstable oscillation if the kI is too large. Value is in sensor
|
||||
* units.
|
||||
*/
|
||||
public abstract void setPIDF(double P, double I, double D, double F, double integralZone);
|
||||
|
||||
/**
|
||||
* Configures the conversion factor based upon which motor.
|
||||
*
|
||||
* @param conversionFactor Conversion from RPM to MPS for drive motor, and rotations to degrees for the turning
|
||||
* motor.
|
||||
*/
|
||||
public abstract void setConversionFactor(double conversionFactor);
|
||||
|
||||
/**
|
||||
* Set the target for the PID loop.
|
||||
*
|
||||
* @param target The PID target to aim for.
|
||||
* @param feedforward The feedforward for this target.
|
||||
*/
|
||||
public abstract void setTarget(double target, double feedforward);
|
||||
|
||||
/**
|
||||
* Stop the motor by sending 0 volts to it.
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Set the speed of the drive motor from -1 to 1.
|
||||
*
|
||||
* @param speed Speed from -1 to 1.
|
||||
*/
|
||||
public abstract void set(double speed);
|
||||
|
||||
/**
|
||||
* Get the current value of the encoder corresponding to the PID.
|
||||
*
|
||||
* @return Current value of the encoder either in velocity or position..
|
||||
*/
|
||||
public abstract double get();
|
||||
|
||||
/**
|
||||
* Get the current output.
|
||||
*
|
||||
* @return Output amps.
|
||||
*/
|
||||
public abstract double getAmps();
|
||||
|
||||
/**
|
||||
* Get the current value of the encoder.
|
||||
*
|
||||
* @return Current value of the encoder.
|
||||
*/
|
||||
public abstract double getPosition();
|
||||
|
||||
/**
|
||||
* Set the voltage compensation for the swerve module motor.
|
||||
*
|
||||
* @param nominalVoltage Nominal voltage for operation to output to.
|
||||
*/
|
||||
public abstract void setVoltageCompensation(double nominalVoltage);
|
||||
|
||||
/**
|
||||
* Set the current limit for the swerve drive motor, remember this may cause jumping if used in conjunction with
|
||||
* voltage compensation. This is useful to protect the motor from current spikes.
|
||||
*
|
||||
* @param currentLimit Current limit in AMPS at free speed.
|
||||
*/
|
||||
public abstract void setCurrentLimit(int currentLimit);
|
||||
|
||||
/**
|
||||
* Set the encoder value.
|
||||
*
|
||||
* @param value Value to set the encoder to.
|
||||
*/
|
||||
public abstract void setEncoder(double value);
|
||||
|
||||
/**
|
||||
* Check that the link is good on the swerve module and CAN bus is able to retrieve data.
|
||||
*
|
||||
* @return true on all devices are accessible over CAN.
|
||||
*/
|
||||
public abstract boolean reachable();
|
||||
|
||||
/**
|
||||
* Check if the absolute encoder is used inplace of the integrated encoder.
|
||||
*
|
||||
* @return true, if the absolute encoder is being used as the integrated controller.
|
||||
*/
|
||||
public abstract boolean remoteIntegratedEncoder();
|
||||
|
||||
/**
|
||||
* Optimize the CAN status frames to reduce utilization.
|
||||
*/
|
||||
public abstract void optimizeCANFrames();
|
||||
|
||||
/**
|
||||
* Save configuration data to the motor controller so it is persistent on reboot.
|
||||
*/
|
||||
public abstract void saveConfig();
|
||||
|
||||
/**
|
||||
* Invert the motor.
|
||||
*
|
||||
* @param inverted Set the motor as inverted.
|
||||
*/
|
||||
public abstract void setInverted(boolean inverted);
|
||||
|
||||
/**
|
||||
* Motor type for the swerve drive module
|
||||
*/
|
||||
public enum ModuleMotorType
|
||||
{
|
||||
DRIVE, TURNING
|
||||
}
|
||||
}
|
||||
303
swervelib/SwerveParser.java
Normal file
@@ -0,0 +1,303 @@
|
||||
package frc.robot.subsystems.swervedrive.swerve;
|
||||
|
||||
import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX;
|
||||
import com.ctre.phoenix.sensors.CANCoder;
|
||||
import com.ctre.phoenix.sensors.WPI_Pigeon2;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.revrobotics.CANSparkMax;
|
||||
import com.revrobotics.CANSparkMaxLowLevel.MotorType;
|
||||
import edu.wpi.first.math.geometry.Pose2d;
|
||||
import edu.wpi.first.math.geometry.Rotation2d;
|
||||
import edu.wpi.first.math.util.Units;
|
||||
import edu.wpi.first.wpilibj.AnalogEncoder;
|
||||
import edu.wpi.first.wpilibj.DutyCycleEncoder;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
import frc.robot.subsystems.swervedrive.swerve.SwerveModule.SwerveModuleLocation;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Swerve Drive JSON parser.
|
||||
*/
|
||||
public class SwerveParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Open JSON file.
|
||||
*
|
||||
* @param file JSON File to open.
|
||||
* @return JsonNode of file.
|
||||
*/
|
||||
private static JsonNode openJson(File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ObjectMapper().readTree(file);
|
||||
} catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check directory structure.
|
||||
*
|
||||
* @param directory JSON Configuration Directory
|
||||
*/
|
||||
private static void checkDirectory(File directory)
|
||||
{
|
||||
assert new File(directory, "swerve.json").exists();
|
||||
assert new File(directory, "modules").exists() && new File(directory, "modules").isDirectory();
|
||||
assert new File(directory, "modules/back").exists() && new File(directory, "modules/back").isDirectory();
|
||||
assert new File(directory, "modules/front").exists() && new File(directory, "modules/front").isDirectory();
|
||||
assert new File(directory, "modules/back/left.json").exists();
|
||||
assert new File(directory, "modules/back/right.json").exists();
|
||||
assert new File(directory, "modules/front/left.json").exists();
|
||||
assert new File(directory, "modules/front/right.json").exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the motor controller based off the config.
|
||||
*
|
||||
* @param motorConfig JSON Motor config object.
|
||||
* @return Motor Controller
|
||||
*/
|
||||
private static MotorController createMotor(JsonNode motorConfig)
|
||||
{
|
||||
int motorID = motorConfig.get("ID").asInt();
|
||||
switch (motorConfig.get("Type").asText())
|
||||
{
|
||||
case "Neo":
|
||||
return new CANSparkMax(motorID, MotorType.kBrushless);
|
||||
case "Falcon":
|
||||
return motorConfig.has("CANBus") ? new WPI_TalonFX(motorID, motorConfig.get("CANBus").asText())
|
||||
: new WPI_TalonFX(motorID);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SwerveEncoder from JSON config.
|
||||
*
|
||||
* @param encoderConfig Swerve Encoder JSON config.
|
||||
* @return SwerveEncoder
|
||||
*/
|
||||
private static Object createEncoder(JsonNode encoderConfig)
|
||||
{
|
||||
int encoderID = encoderConfig.get("ID").asInt();
|
||||
switch (encoderConfig.get("Type").asText())
|
||||
{
|
||||
case "CANCoder":
|
||||
return encoderConfig.has("CANBus") ? new CANCoder(encoderID, encoderConfig.get("CANBus").asText())
|
||||
: new CANCoder(encoderID);
|
||||
case "ThriftyBot":
|
||||
case "AnalogEncoder":
|
||||
return new AnalogEncoder(encoderID);
|
||||
case "SRXMagEncoder":
|
||||
case "REVThroughBore":
|
||||
return new DutyCycleEncoder(encoderID); // This probably doesn't work
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the gyro object based off the configuration.
|
||||
*
|
||||
* @param gyroConfig Gyro configuration.
|
||||
* @return Gyro object.
|
||||
*/
|
||||
private static WPI_Pigeon2 createGyro(JsonNode gyroConfig)
|
||||
{
|
||||
int gyroID = gyroConfig.get("ID").asInt();
|
||||
|
||||
if (gyroConfig.get("Type").asText().equals("Pigeon2"))
|
||||
{
|
||||
return gyroConfig.has("CANBus") ? new WPI_Pigeon2(gyroID, gyroConfig.get("CANBus").asText())
|
||||
: new WPI_Pigeon2(gyroID);
|
||||
// case "Pigeon":
|
||||
// return new WPI_PigeonIMU(gyroID);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check module JSON config for existing values.
|
||||
*
|
||||
* @param moduleJson Module JSON object.
|
||||
*/
|
||||
private static void checkModule(JsonNode moduleJson)
|
||||
{
|
||||
assert moduleJson.has("Motor");
|
||||
assert moduleJson.get("Motor").has("Drive");
|
||||
assert moduleJson.get("Motor").get("Drive").has("Type") && moduleJson.get("Motor").get("Drive").has("ID");
|
||||
assert moduleJson.get("Motor").has("Steer");
|
||||
assert moduleJson.get("Motor").get("Steer").has("Type") && moduleJson.get("Motor").get("Steer").has("ID") &&
|
||||
moduleJson.get("Motor").get("Steer").has("FreeSpeedRPM");
|
||||
assert moduleJson.has("AbsoluteEncoder");
|
||||
assert moduleJson.get("AbsoluteEncoder").has("Type") &&
|
||||
moduleJson.get("AbsoluteEncoder").has("ID") &&
|
||||
moduleJson.get("AbsoluteEncoder").has("Offset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the main json for correct attributes.
|
||||
*
|
||||
* @param mainJson Main JSON.
|
||||
*/
|
||||
private static void checkMain(JsonNode mainJson)
|
||||
{
|
||||
assert mainJson.has("WheelBase");
|
||||
assert mainJson.has("TrackWidth");
|
||||
assert mainJson.has("WheelDiameter");
|
||||
assert mainJson.has("Speed");
|
||||
assert mainJson.get("Speed").has("MetersPerSecond") && mainJson.get("Speed").has("RadianPerSecond") &&
|
||||
mainJson.get("Speed").has("PhysicalMetersPerSecond");
|
||||
assert mainJson.has("Acceleration");
|
||||
assert mainJson.get("Acceleration").has("MetersPerSecond") && mainJson.get("Acceleration").has("RadianPerSecond");
|
||||
assert mainJson.has("Drive");
|
||||
assert mainJson.get("Drive").has("Inverted") && mainJson.get("Drive").has("GearRatio") &&
|
||||
mainJson.get("Drive").has("MaxPower");
|
||||
assert mainJson.has("Steer");
|
||||
assert mainJson.get("Steer").has("Inverted") && mainJson.get("Steer").has("GearRatio") &&
|
||||
mainJson.get("Steer").has("MaxPower");
|
||||
assert mainJson.has("Gyro");
|
||||
assert mainJson.get("Gyro").has("Inverted") && mainJson.get("Gyro").has("Type") && mainJson.get("Gyro").has("ID");
|
||||
assert mainJson.has("Initial Pose");
|
||||
assert mainJson.get("Initial Pose").has("X") && mainJson.get("Initial Pose").has("Y") &&
|
||||
mainJson.get("Initial Pose").has("Rotation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SwerveModule from JSON configuration file.
|
||||
*
|
||||
* @param mainJson Main JSON to pull from.
|
||||
* @param moduleFile Module specific JSON data to pull from.
|
||||
* @param moduleLocation Swerve module location that is being created.
|
||||
* @return SwerveModule
|
||||
*/
|
||||
private static SwerveModule<?, ?, ?> createModule(JsonNode mainJson, File moduleFile,
|
||||
SwerveModuleLocation moduleLocation)
|
||||
{
|
||||
JsonNode moduleJson = openJson(moduleFile);
|
||||
checkModule(moduleJson);
|
||||
MotorController driveMotor = createMotor(moduleJson.get("Motor").get("Drive")),
|
||||
steerMotor = createMotor(moduleJson.get("Motor").get("Steer"));
|
||||
Object encoder = createEncoder(moduleJson.get("AbsoluteEncoder"));
|
||||
|
||||
SwerveModule<?, ?, ?> module = new SwerveModule<>(driveMotor,
|
||||
steerMotor,
|
||||
encoder,
|
||||
moduleLocation,
|
||||
mainJson.get("Drive").get("GearRatio").asDouble(),
|
||||
mainJson.get("Steer").get("GearRatio").asDouble(),
|
||||
moduleJson.get("AbsoluteEncoder").get("Offset").asDouble(),
|
||||
Units.inchesToMeters(mainJson.get("WheelDiameter").asDouble()),
|
||||
Units.inchesToMeters(mainJson.get("WheelBase").asDouble()),
|
||||
Units.inchesToMeters(mainJson.get("TrackWidth").asDouble()),
|
||||
moduleJson.get("Motor").get("Steer").get("FreeSpeedRPM")
|
||||
.asDouble(),
|
||||
mainJson.get("Speed").get("MetersPerSecond").asDouble(),
|
||||
mainJson.get("Acceleration").get("MetersPerSecond").asDouble(),
|
||||
mainJson.get("Drive").get("MaxPower").asDouble(),
|
||||
mainJson.get("Steer").get("MaxPower").asDouble(),
|
||||
mainJson.get("Steer").get("Inverted").asBoolean(),
|
||||
mainJson.get("Drive").get("Inverted").asBoolean());
|
||||
|
||||
if (moduleJson.get("Motor").get("Steer").has("PID"))
|
||||
{
|
||||
JsonNode jsonPIDF = moduleJson.get("Motor").get("Steer").get("PID");
|
||||
setModulePIDF(module.turningMotor, jsonPIDF);
|
||||
} else if (mainJson.get("Steer").has("PID"))
|
||||
{
|
||||
JsonNode jsonPIDF = mainJson.get("Steer").get("PID");
|
||||
setModulePIDF(module.turningMotor, jsonPIDF);
|
||||
}
|
||||
|
||||
if (moduleJson.get("Motor").get("Drive").has("PID"))
|
||||
{
|
||||
JsonNode jsonPIDF = moduleJson.get("Motor").get("Drive").get("PID");
|
||||
setModulePIDF(module.driveMotor, jsonPIDF);
|
||||
} else if (mainJson.get("Steer").has("PID"))
|
||||
{
|
||||
JsonNode jsonPIDF = mainJson.get("Drive").get("PID");
|
||||
setModulePIDF(module.turningMotor, jsonPIDF);
|
||||
}
|
||||
|
||||
if (moduleJson.get("Motor").get("Steer").has("CurrentLimit"))
|
||||
{
|
||||
module.turningMotor.setCurrentLimit(moduleJson.get("Motor").get("Steer").get("CurrentLimit")
|
||||
.asInt());
|
||||
} else if (mainJson.get("Steer").has("CurrentLimit"))
|
||||
{
|
||||
module.turningMotor.setCurrentLimit(mainJson.get("Steer").get("CurrentLimit").asInt());
|
||||
}
|
||||
|
||||
if (moduleJson.get("Motor").get("Drive").has("CurrentLimit"))
|
||||
{
|
||||
module.driveMotor.setCurrentLimit(moduleJson.get("Motor").get("Drive").get("CurrentLimit")
|
||||
.asInt());
|
||||
} else if (mainJson.get("Drive").has("CurrentLimit"))
|
||||
{
|
||||
module.turningMotor.setCurrentLimit(mainJson.get("Drive").get("CurrentLimit").asInt());
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PIDF for the module if the given JSON has the paramters.
|
||||
*
|
||||
* @param motor Motor to configure.
|
||||
* @param jsonPIDF JSON ndoe with values.
|
||||
*/
|
||||
private static void setModulePIDF(SwerveMotor motor, JsonNode jsonPIDF)
|
||||
{
|
||||
if (jsonPIDF.has("P") && jsonPIDF.has("I") && jsonPIDF.has("D") &&
|
||||
jsonPIDF.has("F") && jsonPIDF.has("IntegralZone"))
|
||||
{
|
||||
motor.setPIDF(jsonPIDF.get("P").asDouble(),
|
||||
jsonPIDF.get("I").asDouble(),
|
||||
jsonPIDF.get("D").asDouble(),
|
||||
jsonPIDF.get("F").asDouble(),
|
||||
jsonPIDF.get("IntegralZone").asDouble());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SwerveDrive from JSON configuration directory.
|
||||
*
|
||||
* @param directory JSON Configuration directory.
|
||||
* @return SwerveDrive
|
||||
*/
|
||||
public static SwerveDrive fromJSONDirectory(File directory)
|
||||
{
|
||||
checkDirectory(directory);
|
||||
JsonNode swerveJson = openJson(new File(directory, "swerve.json"));
|
||||
checkMain(swerveJson);
|
||||
return new SwerveDrive(createModule(swerveJson,
|
||||
new File(directory, "modules/front/left.json"),
|
||||
SwerveModuleLocation.FrontLeft),
|
||||
createModule(swerveJson,
|
||||
new File(directory, "modules/front/right.json"),
|
||||
SwerveModuleLocation.FrontRight),
|
||||
createModule(swerveJson,
|
||||
new File(directory, "modules/back/left.json"),
|
||||
SwerveModuleLocation.BackLeft),
|
||||
createModule(swerveJson,
|
||||
new File(directory, "modules/back/right.json"),
|
||||
SwerveModuleLocation.BackRight),
|
||||
createGyro(swerveJson.get("Gyro")),
|
||||
swerveJson.get("Speed").get("MetersPerSecond").asDouble(),
|
||||
swerveJson.get("Speed").get("RadianPerSecond").asDouble() * Math.PI,
|
||||
swerveJson.get("Acceleration").get("MetersPerSecond").asDouble(),
|
||||
swerveJson.get("Acceleration").get("RadianPerSecond").asDouble() * Math.PI,
|
||||
swerveJson.get("Speed").get("PhysicalMetersPerSecond").asDouble(),
|
||||
swerveJson.get("Gyro").get("Inverted").asBoolean(),
|
||||
new Pose2d(Units.inchesToMeters(swerveJson.get("Initial Pose").get("X").asDouble()),
|
||||
Units.inchesToMeters(swerveJson.get("Initial Pose").get("Y").asDouble()),
|
||||
Rotation2d.fromDegrees(swerveJson.get("Initial Pose").get("Rotation")
|
||||
.asDouble())));
|
||||
|
||||
}
|
||||
}
|
||||