Skip to content

Drive Commands

DriveCommands holds the drive math for a mecanum or differential drivetrain, so you don’t write another normalized-power denominator by hand. Each function returns an Ivy command that reads the gamepad and writes wheel powers every loop, and each one runs until something cancels it.

var scalar: Double
fun mecanumDrive(
frontLeft: NextMotor, frontRight: NextMotor, backLeft: NextMotor, backRight: NextMotor,
gamepad: Gamepad,
kinematics: MecanumKinematics = MecanumKinematics(),
): Command
fun mecanumDriveFieldCentric(
frontLeft: NextMotor, frontRight: NextMotor, backLeft: NextMotor, backRight: NextMotor,
gamepad: Gamepad,
heading: Supplier<Double>,
kinematics: MecanumKinematics = MecanumKinematics(),
): Command
fun arcadeDrive(
frontLeft: NextMotor, frontRight: NextMotor, backLeft: NextMotor, backRight: NextMotor,
gamepad: Gamepad,
kinematics: TankKinematics = TankKinematics(),
): Command
fun tankDrive(
frontLeft: NextMotor, frontRight: NextMotor, backLeft: NextMotor, backRight: NextMotor,
gamepad: Gamepad,
): Command

The left stick drives and strafes, the right stick turns. Powers are normalized, so a full diagonal plus a full turn stays within motor range instead of clipping.

The optional MecanumKinematics multiplies strafe input by 1.1 by default, which makes up for how much worse mecanum wheels are sideways than forward. Pass MecanumKinematics(1.2) if your robot still strafes short of where you point it.

Same controls, except stick input is rotated by the robot’s heading first, so pushing the stick away from you moves the robot away from you no matter which way it faces.

The extra heading argument supplies the current heading in radians. Pedro Pathing’s follower pose works, and so does an IMU reading, as long as it’s zeroed to the robot’s starting orientation on the field. Get that zero wrong and every direction is wrong.

mecanumDriveFieldCentric(
drivetrain.frontLeft,
drivetrain.frontRight,
drivetrain.backLeft,
drivetrain.backRight,
gamepad1,
{ follower.pose.heading },
).schedule()

Two control schemes for the same differential drivetrain. tankDrive gives the left stick the left wheels and the right stick the right wheels. arcadeDrive drives with the left stick and turns with the right. Both take the same four motors and gamepad as the mecanum functions.

Every drive function multiplies its gamepad input by scalar, which starts at 1.0. It’s read fresh each loop, so a change takes effect on the next one.

scalar = 0.5 // half speed

Slow mode is the usual reason to touch it. Bind an infinite command that lowers the scalar on start and puts it back when cancelled, then hold the bumper to creep:

driver.leftBumper.whileTrue(
Commands.infinite { scalar = 0.4 }.setEnd { scalar = 1.0 },
)

Use instant instead of infinite here and the scalar never goes back up.

Put the four motors on a Mechanism and give it a method that schedules the drive command:

class Drivetrain : Mechanism {
val frontLeft = NextMotor("frontLeft")
val frontRight = NextMotor("frontRight")
val backLeft = NextMotor("backLeft")
val backRight = NextMotor("backRight")
fun startDrive(gamepad: Gamepad) {
mecanumDrive(frontLeft, frontRight, backLeft, backRight, gamepad).schedule()
}
}

Reverse whichever motors need it with each motor’s direction property rather than negating powers yourself, and add the mechanism to your NextRobot’s mechanisms set like any other.

Schedule the command once. It’s infinite, so it keeps reading the gamepad until the OpMode ends:

@NextTeleop
class MyTeleop(val robot: MyRobot) : NextOpMode(robot) {
override fun start() {
robot.drivetrain.startDrive(gamepad1)
}
}