Closed omagarwal25 closed 2 years ago
Joos does not currently have support for differential swerve out of the box, so you would have to write the odometry and kinematics yourself. It might end up looking something like this:
/**
* @param trackWidth the distance between the two modules
*/
class DifferentialSwerveDrive(
private val leftMotorA: Motor, private val leftMotorB: Motor,
private val rightMotorA: Motor, private val rightMotorB: Motor,
private val trackWidth: Double,
private val imu: Imu? = null
) : Drive(), Component {
/*
Note that a motor A corresponds to the motor that spins the same direction as the wheel,
and motor B spins the opposite direction.
*/
/**
* The gear ratio from one of the motors to the wheel shaft.
*/
val gearRatio: Double = 1.0
/**
* The ticks per rev of each motor.
*/
val ticksPerRev: Double = 100.0
/**
* The radius of each wheel.
*/
val wheelRadius: Double = 2.0
/**
* This is for controlling the orientation of each module. You'll have to tune this.
*/
val modulePID = PIDCoefficients(4.0, 0.0, 0.1)
override var localizer: Localizer = DifferentialSwerveLocalizer(
::getWheelPositions, ::getWheelVelocities, ::getModuleOrientations,
trackWidth, this, imu != null
)
override val rawExternalHeading: Double = imu.heading ?: 0.0
private val leftModuleController = PIDFController(modulePID)
private val rightModuleController = PIDFController(modulePID)
private val motors = listOf(leftMotorA, leftMotorB, rightMotorA, rightMotorB)
init {
leftModuleController.setInputBounds(-PI, PI)
rightModuleController.setInputBounds(-PI, PI)
motors.forEach {
it.distancePerRev = 2 * PI * wheelRadius * gearRatio
//TODO: tune feedforward for motors
it.feedforwardCoefficients == FeedforwardCoefficients()
}
}
private var leftVel = 0.0
private var leftAccel = 0.0
private var rightVel = 0.0
private var rightAccel = 0.0
private fun getWheelPositions(): Pair<Double, Double> =
(leftMotorA.distance - leftMotorB.distance) * 0.5 to (rightMotorA.distance - rightMotorB.distance) * 0.5
private fun getWheelVelocities(): Pair<Double, Double> =
(leftMotorA.distanceVelocity - leftMotorB.distanceVelocity) * 0.5 to (rightMotorA.distanceVelocity - rightMotorB.distanceVelocity) * 0.5
private fun getModuleOrientations(): Pair<Double, Double> =
(leftMotorA.currentPosition + leftMotorB.currentPosition) / ticksPerRev * PI to (rightMotorA.currentPosition + rightMotorB) / ticksPerRev * PI
override fun setDrivePower(drivePower: Pose2d) {
val leftVector = Vector2d(drivePower.x, drivePower.y + drivePower.heading)
val rightVector = Vector2d(drivePower.x, drivePower.y - drivePower.heading)
leftModuleController.setTarget(leftVector.angle())
rightModuleController.setTarget(rightVector.angle())
leftVel = leftVector.norm() * leftMotorA.maxRPM
leftAccel = 0.0
rightVel = rightVector.norm() * rightMotorA.maxRPM
rightAccel = 0.0
}
override fun update() {
val moduleOrientations = getModuleOrientations()
val leftControl = leftModuleController.update(
moduleOrientations.first
)
leftMotorA.setSpeed(leftVel + leftControl, leftAccel)
leftMotorB.setSpeed(-leftVel + leftControl, -leftAccel)
val rightControl = rightModuleController.update(
moduleOrientations.second
)
rightMotorA.setSpeed(rightVel + rightControl, rightAccel)
rightMotorB.setSpeed(-rightVel + rightControl, -rightAccel)
motors.forEach { it.update() }
}
override fun setDriveSignal(driveSignal: DriveSignal) {
val leftVelVector = Vector2d(driveSignal.vel.x, driveSignal.vel.y + driveSignal.vel.heading)
val rightVelVector = Vector2d(driveSignal.vel.x, driveSignal.vel.y - driveSignal.vel.heading)
val leftAccelVector = Vector2d(driveSignal.accel.x, driveSignal.accel.y + driveSignal.accel.heading)
val rightAccelVector = Vector2d(driveSignal.accel.x, driveSignal.accel.y - driveSignal.accel.heading)
leftModuleController.setTarget(leftVelVector.angle())
rightModuleController.setTarget(rightVelVector.angle())
val divisor = (2 * PI * wheelRadius * gearRatio)
leftVel = leftVelVector.norm() / divisor
leftAccel = leftAccelVector.norm() / divisor
rightVel = rightVelVector.norm() / divisor
rightAccel = rightAccelVector.norm() / divisor
}
override fun getExternalHeadingVelocity(): Double? = imu?.headingVelocity
}
class DifferentialSwerveLocalizer(
private val wheelPositions: () -> Pair<Double, Double>,
private val wheelVelocities: () -> Pair<Double, Double>? = { null },
private val moduleOrientations: () -> Pair<Double, Double>,
private val trackWidth: Double,
private val drive: Drive,
private val useExternalHeading: Boolean = true
) : Localizer {
private var _poseEstimate: Pose2d = Pose2d()
override var poseEstimate: Pose2d
get() = _poseEstimate
set(value) {
lastWheelPositions = null
lastExtHeading = null
if (useExternalHeading) drive.externalHeading = value.heading
_poseEstimate = value
}
private var lastWheelPositions: Pair<Double, Double>? = null
private var lastExtHeading: Double? = null
override var poseVelocity: Pose2d? = null
private set
override fun update() {
val wheelPositions = wheelPositions()
val moduleOrientations = moduleOrientations()
val externalHeading = if (useExternalHeading) drive.externalHeading else null
lastWheelPositions?.let { lastWheelPositions ->
val leftDelta = wheelPositions.first - lastWheelPositions.first
val rightDelta = wheelPositions.second - lastWheelPositions.second
val robotPoseDelta = Pose2d(
(leftDelta * cos(moduleOrientations.first) + rightDelta * cos(moduleOrientations.second)) * 0.5,
(leftDelta * sin(moduleOrientations.first) + rightDelta * sin(moduleOrientations.second)) * 0.5,
lastExtHeading?.let { externalHeading?.minus(it) }
?: ((rightDelta * sin(moduleOrientations.second) - leftDelta * sin(moduleOrientations.first)) / trackWidth)
)
_poseEstimate = Kinematics.relativeOdometryUpdate(
_poseEstimate, robotPoseDelta
)
}
val wheelVelocities = wheelVelocities()
val extHeadingVel = if (useExternalHeading) drive.getExternalHeadingVelocity() else null
poseVelocity =
if (wheelVelocities != null)
Pose2d(
(wheelVelocities.first * cos(moduleOrientations.first) + wheelVelocities.second * cos(
moduleOrientations.second
)) * 0.5,
(wheelVelocities.first * sin(moduleOrientations.first) + wheelVelocities.second * sin(
moduleOrientations.second
)) * 0.5,
extHeadingVel
?: ((wheelVelocities.second * sin(moduleOrientations.second) - wheelVelocities.first * sin(
moduleOrientations.first
)) / trackWidth)
)
else null
lastWheelPositions = wheelPositions
lastExtHeading = externalHeading
}
}
Note that this may or may not work, but should give you an idea of what to do.
Hey, I appreciate you taking the time to write this amazing code. Although I have been programming for a long time, concepts such as kinematics and odometry are completely new to me. Our team is a rookie FTC team (we technically participated in 2020 but that was remote.) but we have seasoned FRC veterans. So as a challenge we decided to build a Differential Swerve. This is some awesome stuff. We use regular swerve for our FRC robot and the code for that so much easier. I wish you and your team good luck from here.
FTC is mainly a post-season project for us as our season is pretty much over as we got knocked our last weekend in Orlando (We were on Alliance Two). If your team ever does FRC do let me know, I can put you in contact with the team. FTC is actually relatively new to the UK, so it's been a fun challenge.
PS: I see that you team is called the Griffins and that's a fun coincidence as our FRC team (1884) is also called the Griffins
Also @amarcolini am I meant to be using the GUI package yet? Or do I just use the FTC dashboard? I don't need to do any config for that right? I should just be able to open it up in my browser?
The GUI does not currently work properly due to some radians/degrees thing, but after the next release, it should be good to go. If you're familiar with MeepMeep, it works very similar to that. I'll provide some instructions with the next release as well.
As for the kinematics and odometry, there's a great explanation here. It's what I used to derive the differential swerve kinematics.
What I meant with the GUI is whenever I uncomment the dependency for the GUI I get a ton of duplicate class errors.
I'm not sure what would have caused that. How are you importing it?
I add it to build.dependencies.gradle. Here is what the file looks like
It builds fine without the GUI, but as soon as I add it back in I start to get these errors on build.
Duplicate class javafx.util.converter.DefaultStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.DoubleStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.FloatStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.FormatStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.IntegerStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.LocalDateStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.LocalDateTimeStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.LocalDateTimeStringConverter$LdtConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.LocalTimeStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.LongStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.NumberStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.PercentageStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.ShortStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2) Duplicate class javafx.util.converter.TimeStringConverter found in the following modules: jetified-javafx-base-11.0-linux.2-linux (org.openjfx:javafx-base:11.0.2), jetified-javafx-base-11.0-mac.2-mac (org.openjfx:javafx-base:11.0.2) and jetified-javafx-base-11.0-win.2-win (org.openjfx:javafx-base:11.0.2)
I think I've found the problem. It has to do with one of the GUI's dependencies. It'll hopefully be cleared up in the next release.
How would I go about implementing a custom drivetrain? We have a differential 2 moduled swerve and I was wondering how to create it so that I can use all the odometry and kinematics.