From 47256de4a556b3d688c27d1f5f6b479d3e9a2d5d Mon Sep 17 00:00:00 2001 From: owen Date: Mon, 14 Sep 2026 23:29:10 -0400 Subject: [PATCH 1/8] Blaze Attempt --- gradle/libs.versions.toml | 1 + hardware/build.gradle.kts | 1 + .../dev/nextftc/hardware/RobotController.kt | 6 ++++ .../nextftc/hardware/actuators/NextMotor.kt | 19 ++++++++++++- robot/build.gradle.kts | 1 + .../dev/nextftc/robot/opmode/NextOpMode.kt | 23 +++++++++++++++ .../dev/nextftc/robot/opmode/OpModeHook.kt | 28 +++++++++++++++++++ settings.gradle.kts | 1 + 8 files changed, 79 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f9ac4c1b..5aa4f477 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,7 @@ ftc-common = { group = "org.firstinspires.ftc", name = "FtcCommon", version.ref sloth = { module = "dev.frozenmilk.sinister:Sloth", version.ref = "sloth" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } ivy = { module = "com.pedropathing.ivy:core", version.ref = "ivy" } +blaze = { module = "dev.anygeneric:blazeftc", version = "0.1.61"} [bundles] kotest = ["kotest-runner", "kotest-assertations", "kotest-property", "kotest-data-driven"] diff --git a/hardware/build.gradle.kts b/hardware/build.gradle.kts index 25c8e860..9f79b175 100644 --- a/hardware/build.gradle.kts +++ b/hardware/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { api(libs.functional.interfaces) compileOnly(libs.bundles.ftc) implementation(libs.sloth) + compileOnly(libs.blaze) testImplementation(libs.bundles.kotest) testImplementation(libs.mockk) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt index 8a278d67..2b6ed20f 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt @@ -114,6 +114,12 @@ object RobotController : OnCreateEventLoop { val auxiliaryVoltage: Voltage get() = controlHub.auxiliaryVoltage + /** + * Blaze has been enabled + */ + @JvmStatic + var blazeEnabled: Boolean = false + /** * Lifecycle callback used to initialize runtime context for hardware access. * diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt index ff0b6f0f..d472f953 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt @@ -12,6 +12,7 @@ import com.qualcomm.robotcore.hardware.DcMotor import com.qualcomm.robotcore.hardware.DcMotorImplEx import com.qualcomm.robotcore.hardware.DcMotorSimple import com.qualcomm.robotcore.hardware.Servo +import dev.anygeneric.blazeftc.BlazeFTC import dev.nextftc.control.feedback.PIDCoefficients import dev.nextftc.control.feedback.PIDController import dev.nextftc.control.feedforward.SimpleFFCoefficients @@ -151,6 +152,17 @@ class NextMotor @JvmOverloads constructor( var controlType: ControlType = ControlType.Throttle(0.0) private set + private var hubId = -1 + /** I apologize for how awful this is, this is how I was doing it in Blaze. TODO replace */ + private fun getHubId() : Int { + if (hubId == -1) { + val match1 = "(?<=module )[0-9]*".toRegex() + val hub1 = motor.controller.connectionInfo + hubId = match1.find(hub1)!!.value.toInt() + } + return hubId + } + /** * Raw motor power (throttle) in the range [-1.0, 1.0]. * @@ -159,7 +171,12 @@ class NextMotor @JvmOverloads constructor( */ private var power by Caching(cacheTolerance) { if (it != null) { - motor.power = it + if (RobotController.blazeEnabled) { + val port = this.motor.portNumber + BlazeFTC.setMotorPower(getHubId(), port, it) + } else { + motor.power = it + } } } diff --git a/robot/build.gradle.kts b/robot/build.gradle.kts index d3e7122c..db808f92 100644 --- a/robot/build.gradle.kts +++ b/robot/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { compileOnly(libs.bundles.ftc) implementation(libs.sloth) implementation(libs.kotlin.reflect) + compileOnly(libs.blaze) testImplementation(libs.bundles.kotest) testImplementation(kotlin("test")) diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index 77d7cc99..4836c974 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -3,6 +3,10 @@ package dev.nextftc.robot.opmode import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode import com.qualcomm.robotcore.hardware.Gamepad import com.qualcomm.robotcore.hardware.HardwareMap +import dev.anygeneric.blazeftc.BlazeDummyPlug +import dev.anygeneric.blazeftc.BlazeFTC +import dev.anygeneric.blazeftc.DummyPlugOpMode +import dev.nextftc.hardware.RobotController import dev.nextftc.robot.NextFTCException import dev.nextftc.robot.NextRobot import dev.nextftc.robot.RobotLog @@ -54,6 +58,15 @@ abstract class NextOpMode internal constructor(internal val hooks: MutableList NextOpMode) : Linear try { opMode = opModeConstructor() + RobotController.blazeEnabled = RobotController.blazeEnabled || opMode.enableBlaze() + if (RobotController.blazeEnabled) + BlazeDummyPlug.initializeBlazeFTC(hardwareMap) + + opMode.hooks.forEach { it.withNextOpMode(opMode) } opMode.hooks.forEach(OpModeHook::afterConstruction) while (opModeInInit()) { opMode.hooks.forEach(OpModeHook::beforeDisabled) @@ -89,6 +107,8 @@ internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode) : Linear opMode.hooks.forEach(OpModeHook::afterDisabled) } waitForStart() + if (RobotController.blazeEnabled) + BlazeFTC.run(0) opMode.hooks.forEach(OpModeHook::beforeStart) opMode.start() opMode.hooks.forEach(OpModeHook::afterStart) @@ -114,6 +134,9 @@ internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode) : Linear NextOpMode.activeGamepad2 = null NextOpMode.activeTelemetry = null NextOpMode.activeHardwareMap = null + + if (RobotController.blazeEnabled) + BlazeDummyPlug.closeBlazeFTC() } } } diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index ed471085..61c076a1 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -10,8 +10,11 @@ package dev.nextftc.robot.opmode import com.pedropathing.ivy.Scheduler import com.qualcomm.hardware.lynx.LynxModule +import dev.anygeneric.blazeftc.BlazeDummyPlug +import dev.anygeneric.blazeftc.Hub import dev.nextftc.hardware.RobotController import dev.nextftc.hardware.actuators.NextMotor +import dev.nextftc.hardware.lynx.NextLynxModule import dev.nextftc.robot.Mechanism import dev.nextftc.robot.NextRobot import dev.nextftc.robot.Telemetry @@ -25,6 +28,12 @@ import dev.nextftc.robot.triggers.Trigger */ interface OpModeHook { + /** + * Called immediately after the OpMode's onInit phase. + * Hands a reference to the NextOpMode to the Hook in case it needs to call methods. + */ + fun withNextOpMode(opMode: NextOpMode) {} + /** Called immediately after the OpMode's onInit phase. */ fun afterConstruction() {} @@ -136,3 +145,22 @@ object BulkReadHook : OpModeHook { lynxHubs.forEach { it.clearBulkCache() } } } + +class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : OpModeHook { + override fun withNextOpMode(opMode: NextOpMode) { + if (RobotController.blazeEnabled) { + val hw = RobotController.hardwareMap + val hub = if (hub == NextLynxModule.Type.CONTROL_HUB) { + Hub.CtrlHub + } else { Hub.ExHub } + val packets = if (fastMode) 2 else 1 + BlazeDummyPlug.engageBulkReadAcceleration(hw, hub, packets) { + if (hub == Hub.CtrlHub) { + opMode.onControlHubBulkData() + } else { + opMode.onExpansionHubBulkData() + } + } + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 5174cdfd..4f411ec8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,6 +21,7 @@ dependencyResolutionManagement { google() maven("https://repo.dairy.foundation/releases") maven("https://repo.dairy.foundation/snapshots") + maven("https://maven.anygeneric.dev/") mavenLocal() } } From d8fd7e724ff4f9b089483ee25acb28c64b7ee100 Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 18 Sep 2026 09:06:40 -0400 Subject: [PATCH 2/8] Cleanup --- .../dev/nextftc/hardware/RobotController.kt | 1 + .../nextftc/hardware/actuators/NextMotor.kt | 22 +++++++------ .../dev/nextftc/robot/opmode/NextOpMode.kt | 6 ++-- .../dev/nextftc/robot/opmode/OpModeHook.kt | 31 ++++++++++++------- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt index 2b6ed20f..de90c487 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt @@ -119,6 +119,7 @@ object RobotController : OnCreateEventLoop { */ @JvmStatic var blazeEnabled: Boolean = false + set(value) {field = field || value} /** * Lifecycle callback used to initialize runtime context for hardware access. diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt index d472f953..bca06211 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt @@ -8,6 +8,8 @@ package dev.nextftc.hardware.actuators +import com.qualcomm.hardware.lynx.LynxController +import com.qualcomm.hardware.lynx.LynxModule import com.qualcomm.robotcore.hardware.DcMotor import com.qualcomm.robotcore.hardware.DcMotorImplEx import com.qualcomm.robotcore.hardware.DcMotorSimple @@ -152,16 +154,16 @@ class NextMotor @JvmOverloads constructor( var controlType: ControlType = ControlType.Throttle(0.0) private set - private var hubId = -1 - /** I apologize for how awful this is, this is how I was doing it in Blaze. TODO replace */ - private fun getHubId() : Int { - if (hubId == -1) { - val match1 = "(?<=module )[0-9]*".toRegex() - val hub1 = motor.controller.connectionInfo - hubId = match1.find(hub1)!!.value.toInt() + private var hubId: Int = -1 + get() = if (field == -1) { + val lcField = LynxController::class.java.getField("module") + lcField.isAccessible = true + val module = lcField.get(motor.controller) as LynxModule + hubId = module.moduleAddress + field + } else { + field } - return hubId - } /** * Raw motor power (throttle) in the range [-1.0, 1.0]. @@ -173,7 +175,7 @@ class NextMotor @JvmOverloads constructor( if (it != null) { if (RobotController.blazeEnabled) { val port = this.motor.portNumber - BlazeFTC.setMotorPower(getHubId(), port, it) + BlazeFTC.setMotorPower(hubId, port, it) } else { motor.power = it } diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index 4836c974..eaff5e38 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -5,7 +5,6 @@ import com.qualcomm.robotcore.hardware.Gamepad import com.qualcomm.robotcore.hardware.HardwareMap import dev.anygeneric.blazeftc.BlazeDummyPlug import dev.anygeneric.blazeftc.BlazeFTC -import dev.anygeneric.blazeftc.DummyPlugOpMode import dev.nextftc.hardware.RobotController import dev.nextftc.robot.NextFTCException import dev.nextftc.robot.NextRobot @@ -67,6 +66,9 @@ abstract class NextOpMode internal constructor(internal val hooks: MutableList NextOpMode) : Linear try { opMode = opModeConstructor() - RobotController.blazeEnabled = RobotController.blazeEnabled || opMode.enableBlaze() + RobotController.blazeEnabled = opMode.enableBlaze() if (RobotController.blazeEnabled) BlazeDummyPlug.initializeBlazeFTC(hardwareMap) diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index 61c076a1..470243d1 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -146,20 +146,27 @@ object BulkReadHook : OpModeHook { } } +/** + * Hook to enable Blaze control of Bulk Reads on a single hub. Do not run this with BulkReadHook. + * fastMode controls the level of concurrency. false will run at ~500 hz and is very stable. + * true is stable but if you enable it and then do too many writes, it could become unstable. + * Do not put two hooks on the same hub. This hook does nothing if Blaze wasn't enabled. + */ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : OpModeHook { override fun withNextOpMode(opMode: NextOpMode) { - if (RobotController.blazeEnabled) { - val hw = RobotController.hardwareMap - val hub = if (hub == NextLynxModule.Type.CONTROL_HUB) { - Hub.CtrlHub - } else { Hub.ExHub } - val packets = if (fastMode) 2 else 1 - BlazeDummyPlug.engageBulkReadAcceleration(hw, hub, packets) { - if (hub == Hub.CtrlHub) { - opMode.onControlHubBulkData() - } else { - opMode.onExpansionHubBulkData() - } + if (!RobotController.blazeEnabled) { + return + } + val hw = RobotController.hardwareMap + val hub = if (hub == NextLynxModule.Type.CONTROL_HUB) { + Hub.CtrlHub + } else { Hub.ExHub } + val packets = if (fastMode) 2 else 1 + BlazeDummyPlug.engageBulkReadAcceleration(hw, hub, packets) { + if (hub == Hub.CtrlHub) { + opMode.onControlHubBulkData() + } else { + opMode.onExpansionHubBulkData() } } } From 755ed70c4fb4e9071754c0b389af6eab7c09f4b8 Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 18 Sep 2026 13:33:37 -0400 Subject: [PATCH 3/8] Blaze as hooks/annotation --- .../robot/opmode/NextFTCOpModeScanner.kt | 3 ++- .../dev/nextftc/robot/opmode/NextOpMode.kt | 22 +++++-------------- .../nextftc/robot/opmode/OpModeAnnotations.kt | 5 +++++ .../dev/nextftc/robot/opmode/OpModeHook.kt | 20 +++++++++++++++++ 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt index d31f6616..fa93fff1 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt @@ -44,13 +44,14 @@ object NextFTCOpModeScanner : OpModeScanner() { RobotLog.info("Skipping disabled NextFTC OpMode class: $kcls") return } + val enableBlaze = kcls.hasAnnotation() when (val metaResult = opModeMetaFromClass(kcls)) { is OpModeMetaCheckResult.FoundAnnotation -> { when (val constructorResult = opModeConstructorFromClass(kcls)) { is OpModeConstructorCheckResult.FoundConstructor -> { RobotLog.info("Found NextFTC OpMode class: $cls") - registrationHelper.register(metaResult.meta) { BoundNextOpMode(constructorResult.constructor) } + registrationHelper.register(metaResult.meta) { BoundNextOpMode(constructorResult.constructor, enableBlaze) } } is OpModeConstructorCheckResult.NoConstructorFound -> { RobotLog.Global.addWarning( diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index eaff5e38..2cd50cbf 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -3,8 +3,6 @@ package dev.nextftc.robot.opmode import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode import com.qualcomm.robotcore.hardware.Gamepad import com.qualcomm.robotcore.hardware.HardwareMap -import dev.anygeneric.blazeftc.BlazeDummyPlug -import dev.anygeneric.blazeftc.BlazeFTC import dev.nextftc.hardware.RobotController import dev.nextftc.robot.NextFTCException import dev.nextftc.robot.NextRobot @@ -27,10 +25,12 @@ abstract class NextOpMode internal constructor(internal val hooks: MutableList NextOpMode) : LinearOpMode() { +internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode, private val enableBlaze: Boolean) : LinearOpMode() { override fun runOpMode() { NextOpMode.activeGamepad1 = this.gamepad1 NextOpMode.activeGamepad2 = this.gamepad2 NextOpMode.activeTelemetry = this.telemetry NextOpMode.activeHardwareMap = this.hardwareMap + RobotController.blazeEnabled = enableBlaze + var opMode: NextOpMode? = null try { opMode = opModeConstructor() - RobotController.blazeEnabled = opMode.enableBlaze() - if (RobotController.blazeEnabled) - BlazeDummyPlug.initializeBlazeFTC(hardwareMap) - - opMode.hooks.forEach { it.withNextOpMode(opMode) } opMode.hooks.forEach(OpModeHook::afterConstruction) + opMode.hooks.forEach { it.withNextOpMode(opMode) } while (opModeInInit()) { opMode.hooks.forEach(OpModeHook::beforeDisabled) opMode.disabledPeriodic() opMode.hooks.forEach(OpModeHook::afterDisabled) } waitForStart() - if (RobotController.blazeEnabled) - BlazeFTC.run(0) opMode.hooks.forEach(OpModeHook::beforeStart) opMode.start() opMode.hooks.forEach(OpModeHook::afterStart) @@ -136,9 +129,6 @@ internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode) : Linear NextOpMode.activeGamepad2 = null NextOpMode.activeTelemetry = null NextOpMode.activeHardwareMap = null - - if (RobotController.blazeEnabled) - BlazeDummyPlug.closeBlazeFTC() } } } diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt index 7c2d1391..a65296e4 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt @@ -18,3 +18,8 @@ annotation class NextTeleop(val name: String = "", val group: String = "") @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented annotation class NextUtility(val name: String = "", val description: String = "") + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +annotation class BlazeEnabled diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index 470243d1..989cc65d 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -11,6 +11,7 @@ package dev.nextftc.robot.opmode import com.pedropathing.ivy.Scheduler import com.qualcomm.hardware.lynx.LynxModule import dev.anygeneric.blazeftc.BlazeDummyPlug +import dev.anygeneric.blazeftc.BlazeFTC import dev.anygeneric.blazeftc.Hub import dev.nextftc.hardware.RobotController import dev.nextftc.hardware.actuators.NextMotor @@ -157,6 +158,7 @@ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : O if (!RobotController.blazeEnabled) { return } + BlazeFTC.load() val hw = RobotController.hardwareMap val hub = if (hub == NextLynxModule.Type.CONTROL_HUB) { Hub.CtrlHub @@ -170,4 +172,22 @@ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : O } } } +} + +object BlazeStartHook : OpModeHook { + override fun afterConstruction() { + if (RobotController.blazeEnabled) + BlazeDummyPlug.initializeBlazeFTC(RobotController.hardwareMap) + } + + override fun beforeStart() { + if (RobotController.blazeEnabled) + BlazeFTC.run(0) + } +} +object BlazeEndHook : OpModeHook { + override fun afterEnd() { + if (RobotController.blazeEnabled) + BlazeDummyPlug.closeBlazeFTC() + } } \ No newline at end of file From c02887c5e8db8ed6877499a71cc0f4bd626db446 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 22 Sep 2026 20:37:55 -0400 Subject: [PATCH 4/8] Tried to follow instructions --- .../robot/opmode/NextFTCOpModeScanner.kt | 7 ++- .../dev/nextftc/robot/opmode/NextOpMode.kt | 63 ++++++++++++++++--- .../dev/nextftc/robot/opmode/OpModeHook.kt | 28 +-------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt index fa93fff1..7aa3b297 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt @@ -51,7 +51,12 @@ object NextFTCOpModeScanner : OpModeScanner() { when (val constructorResult = opModeConstructorFromClass(kcls)) { is OpModeConstructorCheckResult.FoundConstructor -> { RobotLog.info("Found NextFTC OpMode class: $cls") - registrationHelper.register(metaResult.meta) { BoundNextOpMode(constructorResult.constructor, enableBlaze) } + registrationHelper.register(metaResult.meta) { + if (enableBlaze) + BlazeBoundOpMode(constructorResult.constructor) + else + BoundNextOpMode(constructorResult.constructor) + } } is OpModeConstructorCheckResult.NoConstructorFound -> { RobotLog.Global.addWarning( diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index 2cd50cbf..574590c0 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -3,6 +3,8 @@ package dev.nextftc.robot.opmode import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode import com.qualcomm.robotcore.hardware.Gamepad import com.qualcomm.robotcore.hardware.HardwareMap +import dev.anygeneric.blazeftc.BlazeDummyPlug +import dev.anygeneric.blazeftc.BlazeFTC import dev.nextftc.hardware.RobotController import dev.nextftc.robot.NextFTCException import dev.nextftc.robot.NextRobot @@ -25,12 +27,10 @@ abstract class NextOpMode internal constructor(internal val hooks: MutableList NextOpMode, private val enableBlaze: Boolean) : LinearOpMode() { +internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode) : LinearOpMode() { override fun runOpMode() { NextOpMode.activeGamepad1 = this.gamepad1 NextOpMode.activeGamepad2 = this.gamepad2 NextOpMode.activeTelemetry = this.telemetry NextOpMode.activeHardwareMap = this.hardwareMap - RobotController.blazeEnabled = enableBlaze + var opMode: NextOpMode? = null + + try { + opMode = opModeConstructor() + opMode.hooks.forEach { it.afterConstruction(opMode) } + while (opModeInInit()) { + opMode.hooks.forEach(OpModeHook::beforeDisabled) + opMode.disabledPeriodic() + opMode.hooks.forEach(OpModeHook::afterDisabled) + } + waitForStart() + opMode.hooks.forEach(OpModeHook::beforeStart) + opMode.start() + opMode.hooks.forEach(OpModeHook::afterStart) + while (opModeIsActive()) { + opMode.hooks.forEach(OpModeHook::beforePeriodic) + opMode.periodic() + opMode.hooks.forEach(OpModeHook::afterPeriodic) + } + opMode.hooks.forEach(OpModeHook::beforeEnd) + opMode.end() + } finally { + // even if the OpMode didn't finish execution we still want to clean up the hooks + // and clear the static references to the SDK objects + opMode?.hooks?.forEach { hook -> + try { + hook.afterEnd() + } catch (throwable: Throwable) { + RobotLog.warn("${hook::class.displayName} threw while cleaning up after the OpMode.", throwable) + } + } + + NextOpMode.activeGamepad1 = null + NextOpMode.activeGamepad2 = null + NextOpMode.activeTelemetry = null + NextOpMode.activeHardwareMap = null + } + } +} + +internal class BlazeBoundOpMode(val opModeConstructor: () -> NextOpMode) : LinearOpMode() { + override fun runOpMode() { + NextOpMode.activeGamepad1 = this.gamepad1 + NextOpMode.activeGamepad2 = this.gamepad2 + NextOpMode.activeTelemetry = this.telemetry + NextOpMode.activeHardwareMap = this.hardwareMap + + RobotController.blazeEnabled = true var opMode: NextOpMode? = null try { opMode = opModeConstructor() - opMode.hooks.forEach(OpModeHook::afterConstruction) - opMode.hooks.forEach { it.withNextOpMode(opMode) } + BlazeDummyPlug.initializeBlazeFTC(RobotController.hardwareMap) + opMode.hooks.forEach { it.afterConstruction(opMode) } while (opModeInInit()) { opMode.hooks.forEach(OpModeHook::beforeDisabled) opMode.disabledPeriodic() opMode.hooks.forEach(OpModeHook::afterDisabled) } waitForStart() + BlazeFTC.run(0) opMode.hooks.forEach(OpModeHook::beforeStart) opMode.start() opMode.hooks.forEach(OpModeHook::afterStart) @@ -124,6 +172,7 @@ internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode, private RobotLog.warn("${hook::class.displayName} threw while cleaning up after the OpMode.", throwable) } } + BlazeDummyPlug.closeBlazeFTC() NextOpMode.activeGamepad1 = null NextOpMode.activeGamepad2 = null @@ -131,4 +180,4 @@ internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode, private NextOpMode.activeHardwareMap = null } } -} +} \ No newline at end of file diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index 989cc65d..6feb7896 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -29,14 +29,8 @@ import dev.nextftc.robot.triggers.Trigger */ interface OpModeHook { - /** - * Called immediately after the OpMode's onInit phase. - * Hands a reference to the NextOpMode to the Hook in case it needs to call methods. - */ - fun withNextOpMode(opMode: NextOpMode) {} - /** Called immediately after the OpMode's onInit phase. */ - fun afterConstruction() {} + fun afterConstruction(opMode: NextOpMode) {} /** Called immediately before the OpMode's disabledPeriodic (init_loop) phase. */ fun beforeDisabled() {} @@ -154,7 +148,7 @@ object BulkReadHook : OpModeHook { * Do not put two hooks on the same hub. This hook does nothing if Blaze wasn't enabled. */ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : OpModeHook { - override fun withNextOpMode(opMode: NextOpMode) { + override fun afterConstruction(opMode: NextOpMode) { if (!RobotController.blazeEnabled) { return } @@ -172,22 +166,4 @@ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : O } } } -} - -object BlazeStartHook : OpModeHook { - override fun afterConstruction() { - if (RobotController.blazeEnabled) - BlazeDummyPlug.initializeBlazeFTC(RobotController.hardwareMap) - } - - override fun beforeStart() { - if (RobotController.blazeEnabled) - BlazeFTC.run(0) - } -} -object BlazeEndHook : OpModeHook { - override fun afterEnd() { - if (RobotController.blazeEnabled) - BlazeDummyPlug.closeBlazeFTC() - } } \ No newline at end of file From 089fec958319645ab2f5d98b230837a0fa6d5488 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 22 Sep 2026 20:40:06 -0400 Subject: [PATCH 5/8] bump blaze --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb268ddf..6280a4e9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ ftc-common = { group = "org.firstinspires.ftc", name = "FtcCommon", version.ref sloth = { module = "dev.frozenmilk.sinister:Sloth", version.ref = "sloth" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } ivy = { module = "com.pedropathing.ivy:core", version.ref = "ivy" } -blaze = { module = "dev.anygeneric:blazeftc", version = "0.1.61"} +blaze = { module = "dev.anygeneric:blazeftc", version = "0.1.62"} [bundles] kotest = ["kotest-runner", "kotest-assertations", "kotest-property", "kotest-data-driven"] From 61d9a9319ae2af5473d78041824f855d3627ad5f Mon Sep 17 00:00:00 2001 From: Zach Harel Date: Tue, 22 Sep 2026 21:41:14 -0400 Subject: [PATCH 6/8] make blaze a hook Signed-off-by: Zach Harel --- .../robot/opmode/NextFTCOpModeScanner.kt | 5 +- .../dev/nextftc/robot/opmode/NextOpMode.kt | 68 +++---------------- .../dev/nextftc/robot/opmode/OpModeHook.kt | 20 ++++++ 3 files changed, 32 insertions(+), 61 deletions(-) diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt index 7aa3b297..b68ced9e 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt @@ -52,10 +52,7 @@ object NextFTCOpModeScanner : OpModeScanner() { is OpModeConstructorCheckResult.FoundConstructor -> { RobotLog.info("Found NextFTC OpMode class: $cls") registrationHelper.register(metaResult.meta) { - if (enableBlaze) - BlazeBoundOpMode(constructorResult.constructor) - else - BoundNextOpMode(constructorResult.constructor) + BoundNextOpMode(constructorResult.constructor, enableBlaze) } } is OpModeConstructorCheckResult.NoConstructorFound -> { diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index 574590c0..96ec9884 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -3,8 +3,6 @@ package dev.nextftc.robot.opmode import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode import com.qualcomm.robotcore.hardware.Gamepad import com.qualcomm.robotcore.hardware.HardwareMap -import dev.anygeneric.blazeftc.BlazeDummyPlug -import dev.anygeneric.blazeftc.BlazeFTC import dev.nextftc.hardware.RobotController import dev.nextftc.robot.NextFTCException import dev.nextftc.robot.NextRobot @@ -82,68 +80,23 @@ abstract class NextOpMode internal constructor(internal val hooks: MutableList NextOpMode) : LinearOpMode() { +internal class BoundNextOpMode( + val opModeConstructor: () -> NextOpMode, + val blaze: Boolean = false, +) : LinearOpMode() { override fun runOpMode() { NextOpMode.activeGamepad1 = this.gamepad1 NextOpMode.activeGamepad2 = this.gamepad2 NextOpMode.activeTelemetry = this.telemetry NextOpMode.activeHardwareMap = this.hardwareMap - var opMode: NextOpMode? = null - - try { - opMode = opModeConstructor() - opMode.hooks.forEach { it.afterConstruction(opMode) } - while (opModeInInit()) { - opMode.hooks.forEach(OpModeHook::beforeDisabled) - opMode.disabledPeriodic() - opMode.hooks.forEach(OpModeHook::afterDisabled) - } - waitForStart() - opMode.hooks.forEach(OpModeHook::beforeStart) - opMode.start() - opMode.hooks.forEach(OpModeHook::afterStart) - while (opModeIsActive()) { - opMode.hooks.forEach(OpModeHook::beforePeriodic) - opMode.periodic() - opMode.hooks.forEach(OpModeHook::afterPeriodic) - } - opMode.hooks.forEach(OpModeHook::beforeEnd) - opMode.end() - } finally { - // even if the OpMode didn't finish execution we still want to clean up the hooks - // and clear the static references to the SDK objects - opMode?.hooks?.forEach { hook -> - try { - hook.afterEnd() - } catch (throwable: Throwable) { - RobotLog.warn("${hook::class.displayName} threw while cleaning up after the OpMode.", throwable) - } - } - - NextOpMode.activeGamepad1 = null - NextOpMode.activeGamepad2 = null - NextOpMode.activeTelemetry = null - NextOpMode.activeHardwareMap = null - } - } -} - -internal class BlazeBoundOpMode(val opModeConstructor: () -> NextOpMode) : LinearOpMode() { - override fun runOpMode() { - NextOpMode.activeGamepad1 = this.gamepad1 - NextOpMode.activeGamepad2 = this.gamepad2 - NextOpMode.activeTelemetry = this.telemetry - NextOpMode.activeHardwareMap = this.hardwareMap - - RobotController.blazeEnabled = true + if (blaze) RobotController.blazeEnabled = true var opMode: NextOpMode? = null try { opMode = opModeConstructor() - - BlazeDummyPlug.initializeBlazeFTC(RobotController.hardwareMap) + if (blaze) opMode.hooks.add(0, BlazeHook) opMode.hooks.forEach { it.afterConstruction(opMode) } while (opModeInInit()) { opMode.hooks.forEach(OpModeHook::beforeDisabled) @@ -151,7 +104,6 @@ internal class BlazeBoundOpMode(val opModeConstructor: () -> NextOpMode) : Linea opMode.hooks.forEach(OpModeHook::afterDisabled) } waitForStart() - BlazeFTC.run(0) opMode.hooks.forEach(OpModeHook::beforeStart) opMode.start() opMode.hooks.forEach(OpModeHook::afterStart) @@ -164,15 +116,17 @@ internal class BlazeBoundOpMode(val opModeConstructor: () -> NextOpMode) : Linea opMode.end() } finally { // even if the OpMode didn't finish execution we still want to clean up the hooks - // and clear the static references to the SDK objects - opMode?.hooks?.forEach { hook -> + // and clear the static references to the SDK objects. + // hooks are torn down in reverse so hooks set up first are torn down last + opMode?.hooks?.asReversed()?.forEach { hook -> try { hook.afterEnd() } catch (throwable: Throwable) { RobotLog.warn("${hook::class.displayName} threw while cleaning up after the OpMode.", throwable) } } - BlazeDummyPlug.closeBlazeFTC() + + RobotController.blazeEnabled = false NextOpMode.activeGamepad1 = null NextOpMode.activeGamepad2 = null diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index 6feb7896..e8eab6b6 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -141,6 +141,26 @@ object BulkReadHook : OpModeHook { } } +/** + * Internal hook responsible for the Blaze lifecycle. Added to the front of the hook list by + * [BoundNextOpMode] for OpModes annotated with [BlazeEnabled], so it is set up before and torn + * down after every other hook. + */ +internal object BlazeHook : OpModeHook { + override fun afterConstruction(opMode: NextOpMode) { + BlazeDummyPlug.initializeBlazeFTC(RobotController.hardwareMap) + } + + override fun beforeStart() { + BlazeFTC.run(0) + } + + override fun afterEnd() { + BlazeDummyPlug.closeBlazeFTC() + RobotController.blazeEnabled = false + } +} + /** * Hook to enable Blaze control of Bulk Reads on a single hub. Do not run this with BulkReadHook. * fastMode controls the level of concurrency. false will run at ~500 hz and is very stable. From 9c0ff615954b061afc6b339082425b666e22899a Mon Sep 17 00:00:00 2001 From: Zach Harel Date: Tue, 22 Sep 2026 21:45:01 -0400 Subject: [PATCH 7/8] make separate blazeopmode api type Signed-off-by: Zach Harel --- .../robot/opmode/NextFTCOpModeScanner.kt | 2 +- .../dev/nextftc/robot/opmode/NextOpMode.kt | 29 +++++++++++++------ .../nextftc/robot/opmode/OpModeAnnotations.kt | 5 ---- .../dev/nextftc/robot/opmode/OpModeHook.kt | 6 ++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt index b68ced9e..4f9fdf6b 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextFTCOpModeScanner.kt @@ -44,7 +44,7 @@ object NextFTCOpModeScanner : OpModeScanner() { RobotLog.info("Skipping disabled NextFTC OpMode class: $kcls") return } - val enableBlaze = kcls.hasAnnotation() + val enableBlaze = BlazeOpMode::class.java.isAssignableFrom(cls) when (val metaResult = opModeMetaFromClass(kcls)) { is OpModeMetaCheckResult.FoundAnnotation -> { diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index 96ec9884..41773999 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -55,15 +55,6 @@ abstract class NextOpMode internal constructor(internal val hooks: MutableList NextOpMode, val blaze: Boolean = false, diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt index a65296e4..7c2d1391 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeAnnotations.kt @@ -18,8 +18,3 @@ annotation class NextTeleop(val name: String = "", val group: String = "") @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented annotation class NextUtility(val name: String = "", val description: String = "") - -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class BlazeEnabled diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index e8eab6b6..17e15508 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -143,7 +143,7 @@ object BulkReadHook : OpModeHook { /** * Internal hook responsible for the Blaze lifecycle. Added to the front of the hook list by - * [BoundNextOpMode] for OpModes annotated with [BlazeEnabled], so it is set up before and torn + * [BoundNextOpMode] for [BlazeOpMode]s, so it is set up before and torn * down after every other hook. */ internal object BlazeHook : OpModeHook { @@ -165,11 +165,11 @@ internal object BlazeHook : OpModeHook { * Hook to enable Blaze control of Bulk Reads on a single hub. Do not run this with BulkReadHook. * fastMode controls the level of concurrency. false will run at ~500 hz and is very stable. * true is stable but if you enable it and then do too many writes, it could become unstable. - * Do not put two hooks on the same hub. This hook does nothing if Blaze wasn't enabled. + * Do not put two hooks on the same hub. This hook does nothing if the OpMode isn't a [BlazeOpMode]. */ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : OpModeHook { override fun afterConstruction(opMode: NextOpMode) { - if (!RobotController.blazeEnabled) { + if (opMode !is BlazeOpMode) { return } BlazeFTC.load() From 4692332cfe73cf54e5f99dfc23e7ff7f0fc02346 Mon Sep 17 00:00:00 2001 From: Zach Harel Date: Tue, 22 Sep 2026 21:46:16 -0400 Subject: [PATCH 8/8] run spotless Signed-off-by: Zach Harel --- .../main/kotlin/dev/nextftc/hardware/RobotController.kt | 1 - .../main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt | 8 +++----- .../main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt | 6 ++++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt index de90c487..2b6ed20f 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt @@ -119,7 +119,6 @@ object RobotController : OnCreateEventLoop { */ @JvmStatic var blazeEnabled: Boolean = false - set(value) {field = field || value} /** * Lifecycle callback used to initialize runtime context for hardware access. diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt index 41773999..2740af68 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/NextOpMode.kt @@ -91,10 +91,8 @@ abstract class BlazeOpMode(robot: NextRobot, vararg hooks: OpModeHook) : NextOpM open fun onLocalizationData() {} } -internal class BoundNextOpMode( - val opModeConstructor: () -> NextOpMode, - val blaze: Boolean = false, -) : LinearOpMode() { +internal class BoundNextOpMode(val opModeConstructor: () -> NextOpMode, val blaze: Boolean = false) : + LinearOpMode() { override fun runOpMode() { NextOpMode.activeGamepad1 = this.gamepad1 NextOpMode.activeGamepad2 = this.gamepad2 @@ -145,4 +143,4 @@ internal class BoundNextOpMode( NextOpMode.activeHardwareMap = null } } -} \ No newline at end of file +} diff --git a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt index 17e15508..aca223aa 100644 --- a/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt +++ b/robot/src/main/kotlin/dev/nextftc/robot/opmode/OpModeHook.kt @@ -176,7 +176,9 @@ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : O val hw = RobotController.hardwareMap val hub = if (hub == NextLynxModule.Type.CONTROL_HUB) { Hub.CtrlHub - } else { Hub.ExHub } + } else { + Hub.ExHub + } val packets = if (fastMode) 2 else 1 BlazeDummyPlug.engageBulkReadAcceleration(hw, hub, packets) { if (hub == Hub.CtrlHub) { @@ -186,4 +188,4 @@ class BlazeBulkReadHook(val hub: NextLynxModule.Type, val fastMode: Boolean) : O } } } -} \ No newline at end of file +}