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): * * * * * * * * * *
Lifecycle hooks
SDK methodYour hookNotes
{@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.
*/ public abstract class SafeOpMode extends OpMode { /** The pub/sub bus, created in {@code init()} and closed in {@code stop()}. */ protected Orchestrator orchestrator; /** * @deprecated use {@link #orchestrator}. Retained as a deprecated alias so * existing FTC team code that still references {@code this.orch} * keeps compiling with a deprecation warning. */ @Deprecated protected Orchestrator orch; /** Facade over the dedicated hardware thread. See {@link HardwareActions}. */ protected HardwareActions hardware; /** Wraps {@code hardwareMap} so device lookups return {@link SafeDevice}s. */ protected SafeHardwareMap safeMap; /** * Whether {@link #attachGamepad(Object, String)} should actually attach a * {@link GamepadAdaptor} for this OpMode. * *

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); } }