package com.aaravlabs.synapse.ftc; import com.aaravlabs.synapse.Orchestrator; import com.qualcomm.robotcore.eventloop.opmode.OpMode; import com.qualcomm.robotcore.eventloop.opmode.TeleOp; /** * Base class for {@link OpMode} subclasses that use the Synapse pub/sub bus * with hardware-thread safety. Extends the FTC SDK's {@code OpMode} and * provides: * *
To use, extend this class instead of {@code OpMode}: * *
{@code
* @TeleOp(name = "My OpMode")
* public class MyOpMode extends SafeOpMode {
* @Override protected void onSafeInit() {
* // hardwareMap, hardware, orchestrator, safeMap are all initialized for you.
* SafeDevice intake = safeMap.device(DcMotorEx.class, "intake");
* orchestrator.registerNode("intake", new IntakeNode(orchestrator, intake));
* }
*
* @Override protected void onSafeLoop() {
* // Default loop body. You may also override loop() yourself if you
* // need full control, but then call super.loop() to get the
* // thread assertion + tick.
* orchestrator.publish("tick", System.nanoTime());
* }
* }
* }
*
* Lifecycle mapping (SDK method → your hook): * *
| SDK method | Your hook | Notes |
|---|---|---|
| {@code init()} | {@link #onSafeInit()} | Required. Orchestrator is created first. |
| {@code init_loop()} | {@link #onSafeInitLoop()} | Between init and start. |
| {@code start()} | {@link #onSafeStart()} | When Start is pressed. |
| {@code loop()} | {@link #onSafeLoop()} | Thread assertion + tick run first. |
| {@code stop()} | {@link #onSafeStop()} | Runs before the orchestrator is closed. |
Defaulted in {@link #init()} based on the OpMode type: *
Subclasses may override this field in {@link #onSafeInit()} before * calling {@link #attachGamepad} to opt in (auto) or opt out (teleop). */ protected boolean publishGamepads; @Override @SuppressWarnings("deprecation") public final void init() { orchestrator = FtcOrchestrator.create(); orch = orchestrator; hardware = orchestrator.hardware(); safeMap = new SafeHardwareMap(hardwareMap, hardware); // Gamepad publishing is on by default for TeleOp, off for Autonomous/other. publishGamepads = getClass().isAnnotationPresent(TeleOp.class); onSafeInit(); } @Override public void init_loop() { onSafeInitLoop(); } @Override public void loop() { hardware.assertNotHardwareThread(); hardware.tick(); onSafeLoop(); } @Override public void start() { onSafeStart(); } @Override public void stop() { onSafeStop(); orchestrator.close(); } // ---- user-overridable hooks ---------------------------------------- /** Required: build your nodes and register them here. */ protected abstract void onSafeInit(); /** Called every iteration after start(); defaults to no-op. */ protected void onSafeLoop() {} /** Called repeatedly between init and start; defaults to no-op. */ protected void onSafeInitLoop() {} /** Called when the OpMode starts. Defaults to no-op. */ protected void onSafeStart() {} /** Called when the OpMode stops, before orchestrator is closed. Defaults to no-op. */ protected void onSafeStop() {} // ---- gamepad attachment --------------------------------------------- /** * Attach a {@link GamepadAdaptor} for {@code gamepad} only if * {@link #publishGamepads} is true. Returns the registered node name * (e.g. {@code "GamepadAdaptor:g1"}) or {@code null} when gamepad * publishing is disabled for this OpMode. * *
Use this instead of calling {@link GamepadAdaptor#attach} directly so * the auto/teleop default is respected. Example: * *
{@code
* @Autonomous(name = "My Auto")
* public class MyAuto extends SafeOpMode {
* @Override protected void onSafeInit() {
* // publishGamepads defaults to false for Autonomous.
* attachGamepad(gamepad1, "g1"); // no-op: gamepads not published
* publishGamepads = true; // opt in
* attachGamepad(gamepad1, "g1"); // now attaches
* }
* }
* }
*/
protected String attachGamepad(Object gamepad, String parentTopic) {
if (!publishGamepads) {
if (orchestrator != null) {
orchestrator.log("GamepadAdaptor: skipping '" + parentTopic + "' — gamepad publishing disabled");
}
return null;
}
return GamepadAdaptor.attach(orchestrator, gamepad, parentTopic);
}
}