Getting Started
Trying it out is simple and doesn’t interfere with your robot code. If you have a mecanum chassis, just fork or clone the starter repository, update configuration values for your motor names in code and give it a go! All the code you see here is already present on the started repository.
Instructions can be found on the Setup guide.
Introduction
This page you guide you step-by-step through the Pond starter classes, while explaining their basic functionality.
Sample Operation Drive
File: OperationDrive
This class extends the OperationBase base class from the Pond integrations and defines 4 methods:
Method | When it Executes | What it Does |
---|---|---|
onInit | Called on Drive Hub operation Init |
Initializes robot components (no movement is allowed) |
onStart | Called on Drive Hub operation Start |
Creates commands that allow for robot control |
onPeriodic | Called periodically after onStart completes | Allows for any custom recurring code to be executed. Please favor using commands or subsystems instead adding code in this method. |
onStop | Called on Drive Hub operation Stop |
Allows for custom code to be executed when the drive operation ends. |
onInit
Creates a HardwareMapAccessor
(See Pond Integration) and then add the robot subsystems. Subsystems are independent robot components.
@Override
protected void onInit() {
HardwareMapAccessor hardwareMapAccessor = new HardwareMapAccessor(hardwareMap);
robotSubsystems.add(new DeadWheelsLocalizer(hardwareMapAccessor, logger));
robotSubsystems.add(new ArmController(hardwareMapAccessor, logger));
robotSubsystems.add(new MecanumDrive(hardwareMapAccessor, logger));
}
Once the subsystems are added, they are ready to be operated. Since no movement is allowed until the Drive Operation starts, commands are only added on the onStart
method.
If you robot doesn’t have dead wheels and an arm, just go ahead and delete the initialization and movement code.
onStart
Creates the required commands to operate the robot during drive operation.
@Override
protected void onStart(){
// Setup driving commands.
IDrivetrain drivetrain = robotSubsystems.findFirst(IDrivetrain.class);
ArmController armController = robotSubsystems.findFirst(ArmController.class);
// Chassis movement
Command chassisMovement =
Commands.dynamic()
.when(CommandConditionBuilder::Always)
.execute(() -> drivetrain.setPower(new Pose2D(-gamepad1.left_stick_y, -gamepad1.left_stick_x, -gamepad1.right_stick_x)))
.build("JoystickCommand");
commandScheduler.runPeriodically(chassisMovement);
// Arm Movement
Command armMovement =
Commands.anyOf("ArmControl",
Commands.dynamic()
.when(c -> c.buttonWasJustPressed(() -> gamepad1.a))
.execute(() -> armController.moveToAngle(Math.toRadians(90.0), 0.8))
.build("RaiseArm"),
Commands.dynamic()
.when(c -> c.buttonWasJustPressed(() -> gamepad1.b))
.execute(() -> armController.moveToAngle(Math.toRadians(0), 0.4))
.build("LowerArm"));
commandScheduler.runPeriodically(armMovement);
}
At the beginning of the method, the required subsystems are retrieved from the robot, so they can be referenced by commands.
The chassisMovement
command executes continuously, and at every cycle, it reads the joystick values and sets the power targets on the drive train accordingly. This is what controls robot movement on the field.
The armMovement
command monitors buttons and when a button is pressed it executes the intended movement on the arm. In this case button A
raises the robot arm and button B
lowers the arm. Note that the commands are wrapped by an anyOf
command, which will execute one command of the set at a time.
Notice that there are no loops in this method! The commands that control the robot are created and they will continuously do their job until the Drive Operation end. Isn’t that neat 😀
onPeriodic
This method is called on every processing cycle. If you are making good use of Subsystems and Commands, you should not have any code in this method.
onStop
Any final code that you may want to run when the robot is stopped.
Sample Configuration
File: Configuration
All robots need configurations values, and most likely, lots of them! While Pond does not require FTC Dashboard, it’s defined in a way to make it easy for the required configuration values to be compatible with its use.
If you are using FTC Dashboard, once you deploy the code, connect your computer to the robot wifi and open http://192.168.43.1:8080/dash for dynamically changing the configuration without having to re-deploy your code.
public class Configuration {
/**
* Chassis configurations.
*/
@Config
public static class Chassis {
// Motors
public static MotorParams MotorFrontLeft = new MotorParams("front_left", MotorParams.Mode.RUN_WITHOUT_ENCODER, false);
public static MotorParams MotorFrontRight = new MotorParams("front_right", MotorParams.Mode.RUN_WITHOUT_ENCODER, true);
public static MotorParams MotorRearLeft = new MotorParams("rear_left", MotorParams.Mode.RUN_WITHOUT_ENCODER, false);
public static MotorParams MotorRearRight = new MotorParams("rear_right", MotorParams.Mode.RUN_WITHOUT_ENCODER, true);
// Dead wheels parameters
public static DeadWheelsKinematicsParams DeadWheels = new DeadWheelsKinematicsParams(
63,
196,
2 * Math.PI * 18.688524,
8192); // https://www.revrobotics.com/rev-11-1271/
}
/**
* Arm configuration
*/
@Config
public static class Arm {
public static AngularActuatorParams PivotActuation = new AngularActuatorParams(
StandardGearRatios.GOBILDA_223_RPM,
5.0);
public static MotorParams MotorArm = new MotorParams("slide_pivot", MotorParams.Mode.RUN_TO_POSITION, true);
}
// ...
}
The Configuration
class holds all robot parameters that may need to be adjusted (either because you are a new team using the repository, or because the the team is trying out different settings).
Each subsection is defined as a child static
class. This also allows for each one of the sections to be published to FTC Dashboard through the @Config
attribute (If you don’t want to use FTC Dashboard, simply remove the @Config
attributes).
The Chassis
subsection holds all the configuration values for your drive train. It has configurations for each motor, including the hardware names configured in your robot through the Drive Hub. It has also parameters for the odometry through dead wheels.
IMPORTANT: Make sure that the motor names match your robot hardware configuration! Also, pay attention the the boolean (true or false) values in the end of each configuration as they indicate whether or not the direction of the motor needs to be inverted.
Sample Subsystems
Mecanum Drive
File: subsystems/MecanumDrive
The constructor uses the hardware map accessor to get the motors, apply their configurations, and store their references within the class
public class MecanumDrive extends SubsystemBase implements IDrivetrain {
private final DcMotor frontLeft;
private final DcMotor rearLeft;
private final DcMotor frontRight;
private final DcMotor rearRight;
private final Pose2D targetPower = new Pose2D();
public MecanumDrive(HardwareMapAccessor hardwareMap, ITelemetryLogger logger) {
super(logger);
this.frontLeft = hardwareMap.getAndConfigureMotor(Configuration.Chassis.MotorFrontLeft);
this.frontRight = hardwareMap.getAndConfigureMotor(Configuration.Chassis.MotorFrontRight);
this.rearLeft = hardwareMap.getAndConfigureMotor(Configuration.Chassis.MotorRearLeft);
this.rearRight = hardwareMap.getAndConfigureMotor(Configuration.Chassis.MotorRearRight);
}
MecanumDrive contains two other methods setPower
and periodic
, the setPower
method stores the new target power and logs it:
@Override
public void setPower(Pose2D power) {
this.logger.logDebug(getLogTag(), "TargetPower=%s", power);
this.targetPower.copyFrom(power);
}
Once the target power is stored, the periodic
method calculates the power for each wheel, and applies it in the robot’s processing cycle:
@Override
public void periodic() {
MecanumWheelKinematicsData<Double> wheelsData = MecanumInverseKinematics.calculate(this.targetPower);
this.frontLeft.setPower(wheelsData.frontLeft);
this.frontRight.setPower(wheelsData.frontRight);
this.rearLeft.setPower(wheelsData.rearLeft);
this.rearRight.setPower(wheelsData.rearRight);
logger.logDebug(getLogTag(),"Mecanum Power: %s", wheelsData);
}
This method makes use of the MecanumInverseKinematics
class to calculate the power given to each wheel.
Remember, Pond assists in writing high-performance robot code by making sure that all processing happens in the “periodic” method of Commands and Subsystem. For this to work correctly, you should never block the execution in your robot code (either by having infinite loops or calling sleep method).
Dead Wheels Localizer
File: subsystems/DeadWheelsLocalizer
DeadWheelLocalizer
is a sample subsystem intended on providing a localization of the robot based on the encoded dead wheels of a robot. It is also a real time subsystem since it implements the ILocalizer
interface. This example relies on 3 dead wheels (left, right, and center), and makes use of the DeadWheelsKinematics
class to get the positions of our dead wheels on the field.
The constructor, similar to the MecanumDrive
subsystem, uses the HardwareMapAccessor
to get the encoders of our dead wheels and set them to the member variables. It also uses the setMode method to reset the encoders. You can also see below how the new DeadWheelsKinematics instance uses the dead wheels defined in the configuration for the chassis.
public class DeadWheelsLocalizer extends SubsystemBase implements ILocalizer {
private final DeadWheelsKinematics deadWheelsKinematics;
private final DcMotor deadWheelRight;
private final DcMotor deadWheelLeft;
private final DcMotor deadWheelCenter;
public DeadWheelsLocalizer(HardwareMapAccessor hardwareMap, ITelemetryLogger logger) {
super(logger);
this.deadWheelsKinematics = new DeadWheelsKinematics(logger, Configuration.Chassis.DeadWheels);
this.deadWheelLeft = hardwareMap.getMotor(Configuration.Chassis.MotorRearLeft);
this.deadWheelRight = hardwareMap.getMotor(Configuration.Chassis.MotorRearRight);
this.deadWheelCenter = hardwareMap.getMotor(Configuration.Chassis.MotorFrontRight);
this.deadWheelLeft.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
this.deadWheelRight.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
this.deadWheelCenter.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
}
The methods within this class are getLocalization
, setLocalization
, and periodic
.
getLocalization
uses the DeadWheelsKinematics
class to return the robots current pose and velocity.
@Override
public PoseAndVelocity getLocalization() {
return this.deadWheelsKinematics.getPositionVelocity();
}
setLocalization
sets the position of the dead wheels to the current robot position and the velocity to zero using the updateAndReset
method (see kinematics for more information).
@Override
public void setLocalization(Pose2D pose) {
this.deadWheelsKinematics.updateAndReset(
deadWheelLeft.getCurrentPosition(),
deadWheelRight.getCurrentPosition(),
deadWheelCenter.getCurrentPosition(),
pose);
}
periodic
iterates in every robot processing cycle, it updates the position of the robot using the dead wheels current position.
@Override
public void periodic() {
this.deadWheelsKinematics.update(
-deadWheelLeft.getCurrentPosition(),
-deadWheelRight.getCurrentPosition(),
deadWheelCenter.getCurrentPosition());
this.logger.displayData("Localization", getLocalization().pose);
}
Arm Controller
Coming soon!
Sample Autonomous
OperationAutonomous
is an Autonomous OpMode that defines how the robot behaves during the autonomous portion of an FTC match. It extends the OperationBase class
, which provides lifecycle methods (onInit
, onStart
, onPeriodic
, onStop
).
This OpMode makes use of several subsystems (DeadWheelsLocalizer
, ArmController
, MecanumDrive
, and HolonomicController
) and leverages Road Runner to generate and follow trajectories.
@Override
protected void onInit() {
HardwareMapAccessor hardwareMapAccessor = new HardwareMapAccessor(hardwareMap);
robotSubsystems.add(new DeadWheelsLocalizer(hardwareMapAccessor, logger));
robotSubsystems.add(new ArmController(hardwareMapAccessor, logger));
robotSubsystems.add(new MecanumDrive(hardwareMapAccessor, logger));
// Auto Only
robotSubsystems.add(new HolonomicController(Configuration.Autonomous.HolonomicConfig, logger));
}
The onStart() method defines the robot’s initial pose and sets up a trajectory to follow.
@Override
protected void onStart() {
Pose2D initialPose = new Pose2D(0, 0, 0);
FollowTrajectoryCommandParams trajectoryParams = Configuration.Autonomous.TrajectoryParams;
ITimedTrajectory trajectory = RoadRunner.createTrajectory(initialPose, b -> b
.lineToX(600)
.build());
Command command = Commands.followTrajectory("Test", trajectory, trajectoryParams, robotSubsystems);
commandScheduler.runOnce(command);
}
The onPeriodic() method runs continuously during autonomous, similar to FTC’s loop(). It can be used for logging, sensor checks, or updating subsystems.
@Override
protected void onPeriodic() {
}
The onStop() method runs once when autonomous ends. It allows for cleanup.
@Override
protected void onStop() {
}