fakehardware¶
org.horizon36596.simloop.fakehardware
Fake devices implement FTC SDK 12.0 interfaces. Your robot code resolves them from a
FakeHardwareMap by the same config name it uses on the robot. Code that uses the supported SDK
methods can run unchanged; the models and unsupported calls are described below.
What you use¶
| Type | Stands in for | Notes |
|---|---|---|
FakeHardwareMap |
HardwareMap |
Hand this to your init(). Register each fake under its config name. Files sensors into the SDK's typed maps the way the SDK's own configuration parser does. |
FakeMotor |
DcMotorEx |
Modes, encoders, power, velocity, current, direction, and the hub's closed loops: RUN_USING_ENCODER, setVelocity and RUN_TO_POSITION. |
FakeExternalEncoder |
a separate encoder on a motor port | For a mechanism whose encoder is not on its own motor. |
FakeServo |
Servo |
Positional. Honours scaleRange the way the SDK does. |
FakeCRServo |
CRServo |
Continuous rotation. Starts stopped. |
FakeImu |
IMU |
The universal IMU. Every IMU method runs through the SDK's own helper class. |
FakeGoBildaPinpoint |
GoBildaPinpointDriver |
Is the SDK 12.0 driver, running on an emulated register file. |
FakeSparkFunOTOS |
SparkFunOTOS |
Is the SDK 12.0 driver, running on an emulated register file. |
FakeLimelight3A |
Limelight3A |
Results built from the AprilTags the sim robot can see, parsed by the SDK's own LLResult. |
FakeTouchSensor |
TouchSensor |
A REV Touch Sensor. |
FakeDistanceSensor |
DistanceSensor |
A REV 2m distance sensor. |
FakeRevColorSensorV3 |
RevColorSensorV3 |
Also a ColorRangeSensor, ColorSensor, NormalizedColorSensor and DistanceSensor. Fed raw ColorCounts; the SDK's own driver does the arithmetic. |
ColorCounts |
— | Raw red, green, blue photodiode counts, 0..65535 each. |
FakeAnalogInput |
AnalogInput |
A voltage you set. |
FakeDigitalChannel |
DigitalChannel |
Input or output, with the SDK's mode rules. |
SwitchPolarity |
— | Puts a switch's raw line level on a FakeDigitalChannel (PRESSED_IS_LOW for a REV Touch Sensor). |
FakeVoltageSensor |
VoltageSensor |
12.0 V by default; setVoltage for brown-out tests. |
FakeI2cDeviceSynchSimple |
the I2C bus client inside a device driver | Bus config only. See below. |
FakeDevice |
— | The interface every fake implements: update(deltaTime). |
AbstractFakeDevice |
— | Base class supplying the HardwareDevice boilerplate. |
How it is used¶
FakeMotor slideMotor = new FakeMotor(2000.0, 8.0); // max ticks/s, accel constant (1/s)
FakeHardwareMap hardwareMap = new FakeHardwareMap();
hardwareMap.register("slide", slideMotor); // the name in YOUR robot configuration
// ... your robot code does hardwareMap.get(DcMotorEx.class, "slide") and never knows ...
hardwareMap.updateAll(deltaTime); // once per tick, from the scenario body
Units and frames¶
- Motor position: encoder ticks. Velocity: ticks per second.
- Motor power: unitless,
[-1, 1]. - Servo position: unitless,
[0, 1], beforescaleRangeis applied. - Voltage: volts.
- Odometry seam (
FakeGoBildaPinpoint,FakeSparkFunOTOS): millimetres and radians. A drive plant reports inches, so multiply by 25.4. FakeImu.setChassisHeading: radians, counter-clockwise, not wrapped.FakeLimelight3A.setRobotPose: field frame, inches, heading radians CCW from+X.update(deltaTime):deltaTimein seconds, always the scenario's fixed tick.
Motors in closed loop¶
Robot code that uses RUN_TO_POSITION, RUN_USING_ENCODER or setVelocity works unchanged. The SDK side
is exact; the hub firmware's loops are a labelled approximation. The one thing the fake needs from you is
the load, as a share of stall torque, every tick:
RUN_TO_POSITION in the sim has the full table of what is exact and what is approximate, and what to measure on a real hub.
Odometry: Pinpoint and OTOS¶
Both fakes are the SDK 12.0 driver classes, so robot code gets them from hardwareMap.get(...) and
runs unchanged. Wire them in the sim harness, never in robot code:
FakeGoBildaPinpoint pinpoint = new FakeGoBildaPinpoint();
pinpoint.setPhysicalPodMount(xOffsetMm, yOffsetMm, xDirection, yDirection, ticksPerMm); // how the pods REALLY sit
hardwareMap.register("pinpoint", pinpoint);
pinpoint.attachChassisAt(plant.getX() * 25.4, plant.getY() * 25.4, plant.getHeading());
// every tick, after the drive plant has integrated, then updateAll:
pinpoint.moveChassisTo(plant.getX() * 25.4, plant.getY() * 25.4, plant.getHeading());
FakeSparkFunOTOS otos = new FakeSparkFunOTOS();
otos.setPhysicalMount(forwardMm, leftMm, headingRad); // where the sensor REALLY sits
hardwareMap.register("otos", otos);
otos.attachChassisAt(plant.getX() * 25.4, plant.getY() * 25.4, plant.getHeading());
// every tick, after the drive plant has integrated, then updateAll:
otos.moveChassisTo(plant.getX() * 25.4, plant.getY() * 25.4, plant.getHeading());
Physical against configured. The setPhysical... call is the truth; what your robot code writes
(offsets, directions, resolution, scalars) is what it believes. If they disagree, the reading is wrong the
same way a mis-configured real sensor is wrong. So a robot that writes its real offsets needs its sim
wiring to call setPhysical... with the same values.
IMU¶
FakeImu imu = new FakeImu(new RevHubOrientationOnRobot(logo, usb)); // how the hub is REALLY mounted
hardwareMap.register("imu", imu);
// every tick, after the drive plant has integrated:
imu.setChassisHeading(drivePlant.getHeading()); // radians, CCW, not wrapped
The constructor says how the hub is really mounted; your imu.initialize(...) says what the robot code
believes. Yaw is zeroed once, at construction, as a real hub zeroes at power-on.
Your test JVM needs unitTests.returnDefaultValues = true. The SDK's orientation types call an
Android matrix method that otherwise throws "not mocked". See Installing it.
Limelight 3A¶
AprilTagLayout layout = AprilTagLayout.builder()
.addTag(1, 70.0, 30.0, 6.0, Math.PI, 4.0) // id, x, y, z (in), facing (rad), size (in): YOUR numbers
.build();
FakeLimelight3A limelight = FakeLimelight3A.builder(layout,
new CameraMount(7.0, 0.0, 9.0, 0.0, Math.toRadians(-15.0)), 54.5, 42.0) // mount, H/V FOV (deg)
.rangeInches(6.0, 120.0)
.build();
hardwareMap.register("limelight", limelight);
// every tick, before hardwareMap.updateAll(dt):
limelight.setRobotPose(fieldX, fieldY, fieldHeading); // or setRobotPoseFromLocalizer(...)
Robot code calls start(), pipelineSwitch(i), updateRobotOrientation(yawDeg) and getLatestResult()
as usual and gets botpose, getBotpose_MT2() and fiducial results. Staleness and timestamps run on sim
time. Optional on the builder: seeded noise (and the matching getStddevMt1/Mt2), seeded dropouts,
latency, a frame rate, lens distortion (distortion(k1, k2, p1, p2, k3), which moves corner and centre
pixels) and the robot's own body as boxes (robotBody(...)). The geometry is vision's
SimTagCamera, so tilted tags in the layout work too.
Your test JVM needs the real org.json: testRuntimeOnly "org.json:json:20250517". Android's copy
is a stub.
Touch, distance and colour sensors¶
FakeTouchSensor touch = new FakeTouchSensor(); // hardwareMap.get(TouchSensor.class, "liftDown")
FakeDistanceSensor range = new FakeDistanceSensor(); // get(DistanceSensor.class, "frontRange"), a REV 2m
FakeRevColorSensorV3 color = new FakeRevColorSensorV3(); // RevColorSensorV3, ColorRangeSensor, ColorSensor,
hardwareMap.register("liftDown", touch); // NormalizedColorSensor or DistanceSensor
hardwareMap.register("frontRange", range);
hardwareMap.register("intakeColor", color);
Three ways to drive them:
- Scripted:
touch.setPressed(true),range.setDistance(DistanceUnit.INCH, 12),color.setRawCounts(new ColorCounts(r, g, b)). They take effect at once. - From a source:
touch.setPressedSource(() -> tracker.getHeldCount() > 0)and the otherset...Sourcemethods are asked once perhardwareMap.updateAll(dt). - From the field:
sensorworks out what a sensor on the robot sees or touches.
A REV 2m reports 8190 mm with nothing in range. A Color Sensor V3 never reads farther than 6 in. Colour counts for a game piece must be measured on the real sensor.
Two things worth knowing¶
Getters do not advance the model. Motion and source-driven sensor readings advance in update.
Commands and scripted setters can change state immediately: for example, setPressed(true) changes
the touch reading without waiting for another tick.
Sim-only accessors are marked as such. Methods like FakeMotor.getPhysicalPositionTicks() and
FakeCRServo.getPhysicalPower() report the simulation's truth rather than what the device would report.
They are useful in a test and do not exist on real hardware, so never wire season code to one.
What this package will not do¶
- A motor shaft is not a whole mechanism.
FakeMotorintegrates its own shaft speed and encoder position when updated, even without a plant. Useplantto model a mechanism's motion, geometry and load rather than treating the unloaded shaft as the mechanism. - No generic I2C peripherals.
FakeI2cDeviceSynchSimplemodels the bus configuration a driver's constructor performs. Every read and write throws, on purpose: there is no peripheral behind the bus. The Pinpoint and OTOS fakes emulate their own register files instead. - No sensor noise or drift on the IMU, Pinpoint, OTOS, distance or colour fakes. The Limelight has optional seeded noise and dropouts.
- No webcams. A
WebcamNamelookup fails ininit()as on a robot without one. - No Limelight pipelines but AprilTags. Network-only calls (field map upload, snapshots, Python, calibration) throw.
- No legacy
BNO055IMU. Only the universalIMU. - No USB, no Lynx module, no firmware behaviour beyond the motor loops.
isArmed()is always false.
Every model's limits, and which of its numbers are PLACEHOLDER or NEEDS MEASUREMENT, are in each class's javadoc.