This project contains several test fakes of hardware devices common to FTC projects. Currently implemented test doubles include the following:
ColorRangeSensorCRServoImplExDcMotorControllerExDcMotorImplExDigitalChannelControllerDigitalChannelImplDistanceSensorLEDLynxModuleLynxModule(partial, see "Known Issues")LynxUsbDeviceImplServoControllerExServoImplEx
Along with these, the following classes are planned to be implemented:
AnalogInputGamepadHardwareMapIMUTelemetryTouchSensor
The goal of these fakes is to ultimately be able to test and entire opmode completely offline (separate from a robot) and so have faster, more reliable development.
- Copy the HardwareFaker directory into your project, and add the following
line to the top-level
settings.gradle:
include ":HardwareFaker" // Or whatever you rename the directory to.- Include the following code in your project's
build.gradle(most commonly in the:TeamCodesubproject):
dependencies {
// ... other statements
implementation project(':HardwareFaker') // Or whatever you rename the directory to.
// ...
}- Import the desired test double. All fakes are located in the
clarson.ftc.fakerpackage and have the class name formula of${HardwareDevice}Fake. For example, to import the test double forDcMotorEx, place the following import statement at the beginning of your code:
import clarson.ftc.faker.DcMotorExFake;-
Construct the fake.
DcMotorExFakeis constructed with an RPM and number of ticks per revolution, in that order. Specifics for other constructors vary. -
Use the fake wherever you would use the ordinary object! All fakes implement the corresponding interfaces;
DcMotorExFakeimplements theDcMotorExinterface fromcom.qualcomm.robotcore.hardware, and so the following code is completely valid:
DcMotorImplEx motor = new DcMotorImplExFake(312, 576.6);
motor.setMode(DcMotorEx.RunMode.RUN_USING_ENCODER);
motor.getMode(); // Returns DcMotorEx.RunMode.RUN_USING_ENCODER
motor.setPower(0.33); // The motor's current position will begin to move
motor.setMode(DcMotorEx.RunMode.STOP_AND_RESET_ENCODER); // Resets getCurrentPosition()NOTE: If you wanted the test double of DcMotor, you would simply use
the DcMotorImplExFake constructor as normal but use the field/variable as
DcMotor. In other words, line 1 in the previous example becomes
DcMotor motor = new DcMotorImplExFake(312, 576.6);
// Previously was "DcMotorEx motor = new DcMotorImplExFake(312, 576.6)" This also applies for other interfaces, such as Servo and CRServo; use
the fake with "Ex" in its name. For Servo, use ServoImplExFake, and with
CRServo use CRServoImplExFake.
In real opmodes with real hardware, methods that send commands to the Lynx board
(a part of Control and Expansion Hubs) take time, upwards of 2 milliseconds. As
a result, such hardware calls are responsible for both latency but also- and
more importantly- the movement of actuators and updating of sensors after each
hardware call. This effect can be simulated by manually calling update() on
fakes implementing the Updateable interface, or by registering such
Updateables with a ModularUpdater object.
Updateables can be registered using the ModularUpdater.register method.
All ModularUpdater-related classes are in the clarson.ftc.faker.updater
package. This includes ModularUpdater and other classes, listed below:
| Symbol Name | Synopsis |
|---|---|
ModularUpdater |
Concrete subclass of the Updater interface |
Rotateable |
Interface for fakes able to have external rotation applied |
SimulateDelay |
Annotates a method as causing automatic updates |
Updateable |
Interface for fakes able to be registered with Updaters |
Updater |
Interface for ModularUpdater |
UpdatesWhen |
Argument of SimulateDelay |
TwoWayUpdateable |
An Updateable that can remember registered Updaters |
Automatic updating is done whenever a registered Updateable calls a method
whose real life counterpart would send a command to the Lynx board. Such methods
include setPower(), getMode(), setPositon(), getDistance(), etc. This
calls the update() method on all Updateables registered on the same Updater.
@SimulateDelay is used to document a method as causing an automatic update to
all Updaters it is registered in. The meaning of each enum constant is listed
below:
UpdatesWhen Constant |
Description |
|---|---|
ALWAYS |
Always automatically updates all reigstered Updaters. |
CONDITIONAL |
Depends on a conditon that is not related to the BulkData cache |
NEVER |
Never causes an automatic update |
ON_BULK_READ |
Automatically updates whenever a reigstered ModularUpdater has an outdated LynxModule cache |
Although methods without this annotation can be assumed to never cause an
automatic update, some methods may be explicitly annotated with
@SimulateDelay(UpdatesWhen.NEVER).
NOTE: @SimulateDelay annotation is only retained in source, and cannot be
used at runtime to verify a method causes automatic updates.
Each automatic update has a delay length of 2.5 milliseconds unless the source
is an I2C command, in which the delay is 7.5 ms. Manual updates must specify
delay length in seconds using the update() method's parameter deltaSec.
NOTE: "Delay length" refers to the length of time simulated, not actually
elapsing. In other words, update() does not block the calling thread for
2.5 milliseconds; instead, the real-life hardware the updated fake represents
would block all threads for such time, causing all actuators to move and sensors
to update.
All classes capable of automatic updates implement the TwoWayUpdateable interface.
-
ServoControllerExFake.setServoType()is provided for compatability and does not affect how the servo interacts -
DcMotorControllerExFake.setMotorType()only respects theorientationfield on the given configuration, but changing this from CW to CCW is untested and expected to cause unintended behavior. -
All actuators assume acceleration and friction are negligible. This is most notable with
DcMotorExFake.setVelocity(), which instantly changes the velocity. -
All PID(F) coefficient-setting methods in
DcMotorExFakedo nothing as neitherRUN_TO_POSITIONnorRUN_USING_ENCODERare implemented with PID(F) controllers. -
Framing length in
PwmController.PwmRangeobjects is ignored for all servo fakes, continuous or positional. -
Not all
LynxMessages are supported to be transmitted toLynxUsbDeviceImplFake. For maintainers and advanced users, unsupported messages are sentLynxNackswith error code 254 (COMMAND_ROUTING_ERROR). Nothing is done except have an error be recieved. The following codes are supported:LynxGetBulkInputDataCommand,LynxGetMotorEncoderPositionCommand(for bulk data retrieval),LynxIsMotorAtTargetCommand(for bulk data retrieval),LynxKeepAliveCommand,LynxQueryInterfaceCommand.