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
| Method | Blocks? | Signature | Typical use |
|---|---|---|---|
run(action) | no | Consumer<T> | writes — setPower, setPosition, setMode |
call(action) | yes | Function<T, R> | reads you need immediately |
callAsync(action) | no | Function<T, R> | reads consumed later |
raw() | — | returns T | inside 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 ahardware = trueloop blocks the single hardware thread on itself — the robot hangs. Inside hardware-thread code, useraw()(or submit work withrun(...), 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(() -> { ... })withraw()inside. - Unwrap an already-fetched device →
device.raw()inside hardware-thread code.
Next steps
- HardwareActions — the facade these wrappers submit to.
- Mecanum drive — SafeDevice usage on a real drivetrain.