Skip to content

Control Module

The control module is a WPILib-style control theory library: feedback controllers, feedforward models, signal filters, motion profiling, typed 2D geometry, and a type-safe units system. It has no dependency on the FTC SDK, so it can be used entirely on its own, even outside of a robot project.

PIDController implements a standard PID loop, with optional continuous input for wrapping angles.

val pid = PIDController(PIDCoefficients(kP = 0.01, kI = 0.0, kD = 0.001))
val output = pid.calculate(error = setpoint - currentPosition)

SimpleFeedforward models a mechanism with static friction, velocity, and acceleration terms. ElevatorFeedforward and ArmFeedforward add a gravity term for mechanisms that fight gravity.

val feedforward = ArmFeedforward(GravityFeedforwardParameters(kG = 0.3, kV = 0.01))
val output = feedforward.calculate(position = armAngle, velocity = targetVelocity)

Pose2d, Vector2d, and Rotation2d model robot position and orientation, adapted from RoadRunner and WPILib. They support the full range of operators you’d expect: adding a Twist2d to a Pose2d, composing rotations, taking the relative transform between two poses, and more.

val pose = Pose2d(x = 12.0, y = 24.0, heading = 90.0.degrees)
val relative = otherPose.relativeTo(pose)

TrapezoidProfile generates a smooth acceleration/cruise/deceleration profile between two MotionStates, respecting maximum velocity and acceleration constraints.

val profile = TrapezoidProfile(TrapezoidProfileConstraints.linear(maxVelocity = 30.0, maxAcceleration = 60.0))
val state = profile.calculate(current = currentState, goal = goalState)

MecanumKinematics and TankKinematics convert raw driver input (forward, strafe, and rotation) into per-wheel power for their respective drivetrains.

val kinematics = MecanumKinematics()
val powers = kinematics.calculate(DriveInput(x = gamepad.leftStickX.value, y = -gamepad.leftStickY.value, rx = gamepad.rightStickX.value))

EMAFilter and SlewRateLimiter smooth out noisy or jumpy signals, Debouncer filters chattery boolean input, and KalmanFilter fuses multiple noisy measurements into a single best estimate of state.

Every measurement in the control module is typed, using extension properties instead of bare doubles:

val distance = 5.0.meters
val speed = distance / 2.0.seconds // a LinearVelocity, not a raw Double