Skip to content
Work in progress. This library and its docs are new and still changing, and some parts are not yet tested on a real robot. Something wrong or unclear?Tell us (a GitHub issue).

Hardware module

Package: com.crossbotics.ftchelper.hardware

Learn it hands-on: 02 Motors and servos, 06 A fast, clear loop.

Gets motors, servos, sensors and the IMU (the turn sensor in the Control Hub) from the robot configuration and sets them up, each in one readable chain. When a name is wrong, the error says which names do exist. Use it whenever your own code needs a device. The robot in five files does this for its parts, so there you only type the names. Start with tutorial 02.

Class Use it to
Hw get any device, with a helpful error if the name is wrong
MotorSetup get and set up a motor
ServoSetup get and set up a servo
ImuSetup start the IMU and read the heading
BulkReads make your loop faster by reading a hub’s encoders and simple sensors at once

Gets devices by their configuration name (the names you typed in the Driver Station’s Configure Robot screen).

Gets a device, or stops the OpMode with a message that lists the names that DO exist.

// (where to look, what kind of device, its name in the robot configuration)
DcMotorEx intake = Hw.get(hardwareMap, DcMotorEx.class, "intake");
TouchSensor bottom = Hw.get(hardwareMap, TouchSensor.class, "lift_bottom");
Parameter Meaning
hardwareMap the OpMode’s hardwareMap
type the kind of device, e.g. DcMotorEx.class, Servo.class, TouchSensor.class
name the configuration name. Exact: capitals and spaces matter.

Returns the device. If the name is wrong, the Driver Station shows something like:

No DcMotorEx named "intak" in the active robot configuration. Names of this type that do
exist: [front_left, front_right, intake, lift]. Check the spelling and the active
configuration on the Driver Station.

Compare: the SDK’s own hardwareMap.get only says it could not find “intak”.

Same, but returns null instead of stopping when the device is not there. Use it for optional parts (a sensor that is only on the practice robot). Always check for null.

TouchSensor bottom = Hw.tryGet(hardwareMap, TouchSensor.class, "lift_bottom");
// null = not on this robot. bottom::isPressed hands over "how to read it" (a method reference).
if (bottom != null) lift.setLowerSwitch(bottom::isPressed);

Hw.missingMessage(hardwareMap, type, name)

Section titled “Hw.missingMessage(hardwareMap, type, name)”

Gives the error text that get uses. You rarely need it.


Gets a motor and sets it up in one chain. The settings are applied in the order you write them. The encoder is the motor’s sensor that counts how far it has turned; its cable goes to the hub’s encoder port.

DcMotorEx lift = MotorSetup.get(hardwareMap, "lift")
.reversed() // mounted mirrored: positive power should still mean "up"
.brake() // at power 0, hold still instead of letting the lift drop
.resetEncoder() // here counts as 0 (reset BEFORE the run mode)
.runUsingEncoder()
.done(); // finish: power 0, and hand back the motor
Method What it does
MotorSetup.get(hardwareMap, name) Starts the chain. Wrong name = clear error (like Hw.get).
MotorSetup.of(motor) Starts the chain for a motor you already have.
.forward() Positive power turns the normal way (the default).
.reversed() Positive power turns the other way. Use for a mirrored motor.
.reversed(boolean) true = reversed, false = forward. Handy with a setting variable.
.brake() At power 0 the motor holds still. Use for drive wheels, lifts, arms.
.coast() At power 0 the motor spins freely. Use for flywheels.
.resetEncoder() Encoder count becomes 0. Put it before a run mode (see below).
.runUsingEncoder() Power sets a speed; the hub uses the encoder to keep that speed. Needs the encoder cable.
.runWithoutEncoder() Power goes straight to the motor. You can still read the encoder.
.done() Sets power to 0 and returns the DcMotorEx. Always end with this.

Common mistakes

  • .runUsingEncoder().resetEncoder() in that order: the reset puts the motor in STOP_AND_RESET_ENCODER mode, and it will not move until a new run mode is set. Always reset first, then choose the run mode.
  • runUsingEncoder() without an encoder cable: the motor jumps to full speed or does not move. Use runWithoutEncoder() if there is no cable.

Servo claw = ServoSetup.get(hardwareMap, "claw")
.reversed()
.range(0.20, 0.75) // only this part of the travel: it can never hit the frame
.done();
Method What it does
ServoSetup.get(hardwareMap, name) Starts the chain. Wrong name = clear error.
ServoSetup.of(servo) Starts the chain for a servo you already have.
.reversed() Positions 0 and 1 swap ends.
.range(min, max) Uses only part of the travel. After this, 0 means min and 1 means max.
.done() Returns the Servo.

Why use range? Find the positions where the claw is fully open and fully closed (for example 0.20 and 0.75) and put them in range. Now setPosition(0) is “open”, setPosition(1) is “closed”, and no code can ever push the servo into the frame.

Note: setup does not move the servo. It moves the first time you call setPosition.


The IMU is a sensor inside the Control Hub that measures turning. The library uses it for field-centric driving and autonomous turns.

ImuSetup.get(hardwareMap, name, logo, usb)

Section titled “ImuSetup.get(hardwareMap, name, logo, usb)”

Gets the IMU, tells it how the hub is mounted, and sets the heading to 0. If the IMU does not start, it stops the OpMode with a clear message (it does not hand you a broken IMU).

// How the hub sits on the robot: logo pointing UP, USB ports toward the robot's FRONT.
IMU imu = ImuSetup.get(hardwareMap, "imu",
RevHubOrientationOnRobot.LogoFacingDirection.UP,
RevHubOrientationOnRobot.UsbFacingDirection.FORWARD);
Parameter Meaning
name the IMU’s configuration name. It is "imu" unless you changed it.
logo which way the REV logo printed on the hub points: UP, DOWN, FORWARD, BACKWARD, LEFT, RIGHT
usb which way the hub’s USB ports point (same choices)

“Forward” means the front of the robot. If these are wrong, the heading is wrong and field-centric driving goes the wrong way.

How to check: run an OpMode that shows ImuSetup.headingDeg(imu). Turn the robot left by hand. The number must go UP. Turn it right: the number goes DOWN.

The robot’s heading in degrees, from -180 to 180. Turning left makes it bigger. It is 0 where the robot pointed when the IMU was set up or last reset with imu.resetYaw().


Each time your code reads an encoder or sensor, it sends a message to the hub and waits for the answer (about 2 milliseconds). With 6 encoders that is 12 ms each loop. A bulk read gets everything from a hub in one message.

One line in INIT. Safe for everyone. Start with this.

BulkReads.auto(hardwareMap);

In AUTO mode, a new bulk read happens when you read any value a second time. So reading each value once per loop gives the best speed. That is why SafeMotor (so every Lift, Arm and PositionMotor) and the launcher’s Turret read each encoder about once per loop: a reading is used again for 3 ms (so it is at most 3 ms old). EasyTeleOp, EasyAuto and the finders call BulkReads.auto for you. What each part reads every loop: what runs every loop.

BulkReads.manual(hardwareMap) and BulkReads.clear(hubs)

Section titled “BulkReads.manual(hardwareMap) and BulkReads.clear(hubs)”

The fastest way. You clear the saved readings yourself at the top of every loop. If you forget clear, the readings never change and the robot acts very strangely.

// List<LynxModule> = a list of the hubs (a LynxModule is one Control or Expansion Hub)
List<LynxModule> hubs = BulkReads.manual(hardwareMap);
waitForStart();
while (opModeIsActive()) {
BulkReads.clear(hubs); // first line of the loop, every time
...
}

Use LoopTimer (util) to see how much faster your loop gets.