Docs FTC Integration

SafeDevice

A typed wrapper that routes one device's calls through the hardware thread.

SafeDevice

SafeDevice<T> wraps a single hardware object and routes every operation through the hardware thread. Combined with SafeHardwareMap (below), it is the friendliest way to touch hardware.

SafeDevice<DcMotorEx> intake = safeMap.device(DcMotorEx.class, "intake");

intake.run(m -> m.setPower(0.5));                      // async write
int pos = intake.call(DcMotorEx::getCurrentPosition);  // sync read (blocks caller)
CompletableFuture<Integer> p = intake.callAsync(DcMotorEx::getCurrentPosition);

The API

MethodBlocks?SignatureTypical use
run(action)noConsumer<T>writes — setPower, setPosition, setMode
call(action)yesFunction<T, R>reads you need immediately
callAsync(action)noFunction<T, R>reads consumed later
raw()—returns Tinside hardware-thread code only

run and callAsync return immediately. call blocks the caller until the hardware thread answers and rethrows the device’s exceptions.

raw() — inside hardware-thread code

@RunPeriodically(hz = 50, hardware = true)
public void drive() {
    // Already on the hardware thread: use raw() for immediate access.
    left.raw().setPower(powerFL);
    right.raw().setPower(powerFR);
}

raw() returns the unwrapped device. Only use it from code already running on the hardware thread — hardware = true loops, @OnHardwareThread subscribers, bulk-read readers, or hardware.run(...) bodies. From anywhere else, a raw call is exactly the thread-safety violation Synapse exists to prevent.

Never call(...) from the hardware thread. intake.call(...) inside a hardware = true loop blocks the single hardware thread on itself — the robot hangs. Inside hardware-thread code, use raw() (or submit work with run(...), which just queues onto the thread you’re already on).

SafeHardwareMap — where devices come from

SafeOpMode creates a SafeHardwareMap as safeMap, wrapping the SDK hardwareMap:

SafeDevice<DcMotorEx> frontLeft  = safeMap.device(DcMotorEx.class, "frontLeft");
SafeDevice<Servo>     claw       = safeMap.device(Servo.class, "claw");
SafeDevice<IMU>       imu        = safeMap.device(IMU.class, "imu");

Each device(class, name) performs the same lookup the SDK would, but returns the wrapper, so a device can never be used off-thread by accident. Pass the SafeDevices into your nodes’ constructors — nodes should never see a raw hardwareMap.

Choosing SafeDevice vs HardwareActions

  • One device, one operation → SafeDevice (intake.run(m -> ...)) — reads like a normal method call.
  • A block of code over several devices → hardware.run(() -> { ... }) with raw() inside.
  • Unwrap an already-fetched device → device.raw() inside hardware-thread code.

Next steps