PID-To-Point Action
The PIDToPoint
class is an action that uses the SquID Controller
to move a drivetrain to a specified point.
It is designed to work with RoadRunner's MecanumDrive
class,
present in the roadrunner-quickstart
repository.
The PIDToPoint action has not been tested in a real-world scenario, so it may not work as expected. If you encounter any issues, please let us know by creating an issue on the Expressway GitHub repository, or by contacting us on our Discord server.
Constructing PIDToPoint
The PIDToPoint
constructor is the following:
private val pose: Supplier<Pose2d>,
private val vel: Supplier<PoseVelocity2d>,
private val target: Pose2d,
private val powerUpdater: Consumer<PoseVelocity2d>,
axialCoefs: PIDFController.PIDCoefficients,
lateralCoefs: PIDFController.PIDCoefficients,
headingCoefs: PIDFController.PIDCoefficients,
A lot, right? Let's break it down.
Pose
and Vel
The pose
and vel
suppliers are functions that return the current pose and velocity of the robot, respectively.
The reason they are suppliers is that the pose and velocity of the robot change over time,
and we need to get the current pose and velocity every time the action is updated.
They are designed to work with RoadRunner's MecanumDrive
class, present in the roadrunner-quickstart
repository.
However, in order to update the pose
variable of the MecanumDrive
class, you need to call the updatePoseEstimate
method,
so we recommend creating a function in your MecanumDrive
class (or any subclass of it) called getPose
that updates the pose estimate and returns the pose.
public Pose2d getPoseEstiamte() {
updatePoseEstimate();
return pose;
}
Target
The target
is the point that the robot is moving to.
PowerUpdater
The powerUpdater
consumer is a function that sets the power of the drivetrain motors based on the output of the Squid Controller.
It is designed to work with MecanumDrive.setDrivePowers
, present in the roadrunner-quickstart
repository.
Coefficients
The axialCoefs
, lateralCoefs
, and headingCoefs
are the PID coefficients for the Squid Controller,
discussed in the PID Controller section.
Conveniently, the values for your coefficients are actually tuned during the RoadRunner tuning process.
Because most of these parameters are obtained from the MecanumDrive
class,
we recommend creating a function in your MecanumDrive
class (or any subclass of it)
that returns a PIDToPoint
action object with the correct parameters.
Implementing PIDToPoint
Here is how you could create a function to return a PIDToPoint
object in your MecanumDrive
class.
public PIDToPoint pidToPointAction(Pose2d target) {
return new PIDToPoint(
(this::getPoseEstimate)
(this::updatePoseEstimate) // updatePoseEstimate, in addition to updating the pose property, returns the velocity of the robot
target, // the target pose
(this::setDrivePowers), // setDrivePowers uses inverse kinematics to set the powers of the drivetrain motors
new PIDFController.PIDCoefficients(PARAMS.axialGain, 0, PARAMS.axialVelGain), // the axial PID coefficients
new PIDFController.PIDCoefficients(PARAMS.lateralGain, 0, PARAMS.lateralVelGain), // the lateral PID coefficients
new PIDFController.PIDCoefficients(PARAMS.headingGain, 0, PARAMS.headingVelGain) // the heading PID coefficients
);
}
Or in Kotlin (even though the RoadRunner quickstart is in Java, you can put Kotlin code in your project):
fun pidfToPointAction(target: Pose2d): PIDToPoint = PIDToPoint(
this::getPoseEstimate,
this::linearVel,
target,
this::setDrivePowers,
PIDFController.PIDCoefficients(RR_PARAMS.axialGain, 0.0, RR_PARAMS.axialVelGain), // the axial PID coefficients
PIDFController.PIDCoefficients(RR_PARAMS.lateralGain, 0.0, RR_PARAMS.lateralVelGain), // the lateral PID coefficients
PIDFController.PIDCoefficients(RR_PARAMS.headingGain, 0.0, RR_PARAMS.headingVelGain) // the heading PID coefficients
)
The ::
operator in both Java and Kotlin can be used to reference a method or function,
so if a class method has the same signature as a function type (like Supplier
or Consumer
),
you can use the ::
operator to reference the method.
In Kotlin, you can also use the ::
operator to reference a top-level function, or a class property.
For example, the updatePoseEstimate
method in the MecanumDrive
class has the same signature as a Supplier<Pose2d>
,
so you can use this::updatePoseEstimate
to reference the method and pass it to the PIDToPoint
constructor.
Alternatively, since other Action types are classes,
you could create a PIDToPointAction class that extends the PIDToPoint class
as a subclass of MecanumDrive
that simply calls the constructor of the superclass with the correct parameters.
class PIDToPointAction(target: Pose2d) : PIDToPoint(
this::getPoseEstimate,
this::linearVel,
target,
this::setDrivePowers,
new PIDFController.PIDCoefficients(PARAMS.axialGain, 0, PARAMS.axialVelGain),
new PIDFController.PIDCoefficients(PARAMS.lateralGain, 0, PARAMS.lateralVelGain),
new PIDFController.PIDCoefficients(PARAMS.headingGain, 0, PARAMS.headingVelGain)
)
Using PIDToPoint
Let's assume you added the pidToPointAction
function to your MecanumDrive
class.
Here is an example of how you might use the PIDToPoint
action in an OpMode:
class MyOpMode : ActionOpMode {
private val drive by lazy { MecanumDrive(hardwareMap, Pose2d(0.0, 0.0, 0.0)) }
private val target = Pose2d(24.0, 24.0, 0.0)
private val action = drive.pidToPointAction(target)
override fun init() {
drive.updatePoseEstimate()
}
override fun start() {
runner.runAction(action)
}
override fun loop() {
runner.updateAsync()
}
}
In this example, the robot will move to the point (24, 24, 0) until it reaches the target.
runAsync is only called during start
so that the robot does not move during initialization.
The updateAsync
method is called every loop to update the action.