Using the ChassisControllerBuilder
The ChassisControllerBuilder is a versatile and simple way to make a ChassisController. This tutorial will give an overview of how to use it and customize it for your robot.
Please read the preface on where to use builders: Where to use builders
Configuring motors
You must configure the motors. Skid-steer, x-drive, and h-drive configurations are supported.
Two motor skid-steer:
ChassisControllerBuilder() .withMotors(1, -2) // Left motor is 1, right motor is 2 (reversed)
Four motor skid-steer:
You may also use MotorGroups. You are not required to have an equal number of motors per side. There is no limit on the number of motors per side.
ChassisControllerBuilder() .withMotors( {-1, -2}, // Left motors are 1 & 2 (reversed) {3, 4} // Right motors are 3 & 4 )
X-Drive:
You may also use MotorGroups.
ChassisControllerBuilder() .withMotors( 1, // Top left -2, // Top right (reversed) -3, // Bottom right (reversed) 4 // Bottom left )
H-Drive:
You may also use MotorGroups.
ChassisControllerBuilder() .withMotors( 1, // Left side -2, // Right side (reversed) 3 // Middle )
Configuring your gearset and robot dimensions
You must configure the gearset and chassis dimensions to ensure that the gearsets in the motors are correct and to enable commanding the robot in real-life units (inches, degrees, etc.). If you specified non-integrated encoders using withSensors
then withDimensions
will refer to the wheels those encoders are attached to. Otherwise, withDimensions
will refer to the driven wheels.
Gearset and dimensions:
ChassisControllerBuilder() // Green gearset, 4 inch wheel diameter, 11.5 inch wheel track .withDimensions(AbstractMotor::gearset::green, {{4_in, 11.5_in}, imev5GreenTPR})
Gearset with an external ratio and dimensions:
ChassisControllerBuilder() // Green gearset, external ratio of (36.0 / 60.0), 4 inch wheel diameter, 11.5 inch wheel track .withDimensions({AbstractMotor::gearset::green, (36.0 / 60.0)}, {{4_in, 11.5_in}, imev5GreenTPR})
Gearset and raw scales:
ChassisControllerBuilder() // Green gearset, straight scale of 1127.8696, turn scale of 2.875 .withDimensions(AbstractMotor::gearset::green, {{1127.8696, 2.875}, imev5GreenTPR})
Configuring your sensors
If you do not use the motors' built-in encoders (e.g., you might use ADI encoders or rotation sensors), then you will need to pass those in as well. These sensors will not affect the ChassisControllerIntegrated controls because it uses the integrated control (and therefore, the encoders built-in to the motors).
Rotation Sensors
ChassisControllerBuilder() .withSensors( RotationSensor{1}, // Left encoder in V5 port 1 RotationSensor{2, true} // Right encoder in V5 port 2 (reversed) )
ADI Encoders
ChassisControllerBuilder() .withSensors( ADIEncoder{'A', 'B'}, // Left encoder in ADI ports A & B ADIEncoder{'C', 'D', true} // Right encoder in ADI ports C & D (reversed) )
Configuring odometry
OkapiLib supports odometry for all chassis configurations. For more information about odometry, read the Odometry Tutorial.
Odometry using integrated encoders:
If you don't have external sensors, don't pass an extra ChassisScales to withOdometry.
ChassisControllerBuilder() .withMotors(1, -2) // Left motor is 1, right motor is 2 (reversed) // Green gearset, 4 inch wheel diameter, 11.5 inch wheel track .withDimensions(AbstractMotor::gearset::green, {{4_in, 11.5_in}, imev5GreenTPR}) .withOdometry() // Use the same scales as the chassis (above) .buildOdometry()
Odometry using external encoders:
If you have external sensors, you need to pass an extra ChassisScales to withOdometry to specify the dimensions for the tracking wheels. If you are using a ChassisControllerPID, these dimensions will be the same as the ones given to withDimensions, so you do not need to pass any dimensions to withOdometry. If you are using a ChassisControllerIntegrated, these dimensions will be different than the ones given to withDimensions.
ChassisControllerBuilder() .withMotors(1, -2) // Left motor is 1, right motor is 2 (reversed) // Green gearset, 4 inch wheel diameter, 11.5 inch wheel track .withDimensions(AbstractMotor::gearset::green, {{4_in, 11.5_in}, imev5GreenTPR}) .withSensors( ADIEncoder{'A', 'B'}, // Left encoder in ADI ports A & B ADIEncoder{'C', 'D', true} // Right encoder in ADI ports C & D (reversed) ) // Specify the tracking wheels diam (2.75 in), track (7 in), and TPR (360) .withOdometry({{2.75_in, 7_in}, quadEncoderTPR}) .buildOdometry()
Configuring PID gains
If you want to use OkapiLib's PID control instead of the built-in control, you need to pass in two or three sets of PID gains.
Two sets:
ChassisControllerBuilder() .withGains( {0.001, 0, 0.0001}, // Distance controller gains {0.001, 0, 0.0001} // Turn controller gains )
Three sets:
ChassisControllerBuilder() .withGains( {0.001, 0, 0.0001}, // Distance controller gains {0.001, 0, 0.0001}, // Turn controller gains {0.001, 0, 0.0001} // Angle controller gains (helps drive straight) )
Configuring derivative filters
If you are using OkapiLib's PID control instead of the built-in control, you can pass in derivative term filters. These are applied to the PID controllers' derivative terms to smooth them. If you use OkapiLib's PID control but do not specify any derivative filters, the default filter is a PassthroughFilter.
One filter:
ChassisControllerBuilder() .withDerivativeFilters( std::make_unique<AverageFilter<3>>() // Distance controller filter )
Two filters:
ChassisControllerBuilder() .withDerivativeFilters( std::make_unique<AverageFilter<3>>(), // Distance controller filter std::make_unique<AverageFilter<3>>() // Turn controller filter )
Three filters:
ChassisControllerBuilder() .withDerivativeFilters( std::make_unique<AverageFilter<3>>(), // Distance controller filter std::make_unique<AverageFilter<3>>(), // Turn controller filter std::make_unique<AverageFilter<3>>() // Angle controller filter )
Configuring maximum velocity and voltage
You can change the default maximum velocity or voltage.
Max velocity:
The default max velocity depends on the gearset.
ChassisControllerBuilder() .withMaxVelocity(100)
Max voltage:
The default max voltage is 12000
.
ChassisControllerBuilder() .withMaxVoltage(10000)
Configuring the controller settling behavior
You can change the SettledUtil that a ClosedLoopController gets when it is created by the builder, in order to change the settling behavior of the chassis.
ChassisControllerBuilder() .withClosedLoopControllerTimeUtil(50, 5, 250_ms)
Configuring the Logger
If you want the ChassisController and the classes it creates to log what they are doing, either for debugging or other purposes, you can supply a Logger. You can also enable logging globally.
Log to the PROS terminal:
ChassisControllerBuilder() .withLogger( std::make_shared<Logger>( TimeUtilFactory::createDefault().getTimer(), // It needs a Timer "/ser/sout", // Output to the PROS terminal Logger::LogLevel::debug // Most verbose log level ) )
Log to the SD card:
ChassisControllerBuilder() .withLogger( std::make_shared<Logger>( TimeUtilFactory::createDefault().getTimer(), // It needs a Timer "/usd/test_logging.txt", // Output to a file on the SD card Logger::LogLevel::debug // Most verbose log level ) )