From 7f72e1044bd7b16c5a029331fe0d244aed09f9b3 Mon Sep 17 00:00:00 2001 From: Rohit Shetty <138729620+28shettr@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:23:50 -0500 Subject: [PATCH 1/2] good --- .../dev/nextftc/hardware/actuators/NextCRServo.kt | 4 ++-- .../kotlin/dev/nextftc/hardware/actuators/NextMotor.kt | 4 ++-- .../kotlin/dev/nextftc/hardware/actuators/NextServo.kt | 6 ++---- .../kotlin/dev/nextftc/hardware/util/LazyHardware.kt | 10 ++++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextCRServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextCRServo.kt index 05f4a083..eb222240 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextCRServo.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextCRServo.kt @@ -80,8 +80,8 @@ open class NextCRServo @JvmOverloads constructor( var power: Double by powerCache init { - // a re-created servo does not hold the previously written power - lazyServo.applyAfterInit("powerCache") { powerCache.reset() } + // a skipped write never touches the lazy, so the cache must be dropped on stop, not on re-init + lazyServo.onOpModeStop { powerCache.reset() } } /** 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 a9c569c0..3c63cc60 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextMotor.kt @@ -131,8 +131,8 @@ class NextMotor @JvmOverloads constructor( } init { - // a re-created motor does not hold the previously written power - lazyMotor.applyAfterInit("powerCache") { powerCache.reset() } + // a skipped write never touches the lazy, so the cache must be dropped on stop, not on re-init + lazyMotor.onOpModeStop { powerCache.reset() } } /** diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextServo.kt index a0bb343a..6daa8f8f 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextServo.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/actuators/NextServo.kt @@ -76,8 +76,6 @@ open class NextServo @JvmOverloads constructor( * ``` * // Creates a NextServo with the config name: "armServo" * val servo = NextServo("armServo") - * ``` - * * @param name The configuration name for the servo, usually found on the Driver Station * @param cacheTolerance Tolerance used by the [Caching] delegate for position updates; defaults to 0.01. * @param direction The initial direction of the servo defined by the user @@ -105,8 +103,8 @@ open class NextServo @JvmOverloads constructor( } init { - // a re-created servo does not hold the previously written position - lazyServo.applyAfterInit("positionCache") { positionCache.reset() } + // a skipped write never touches the lazy, so the cache must be dropped on stop, not on re-init + lazyServo.onOpModeStop { positionCache.reset() } } /** diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/util/LazyHardware.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/util/LazyHardware.kt index 96ac34ee..74f1da63 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/util/LazyHardware.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/util/LazyHardware.kt @@ -20,6 +20,7 @@ class LazyHardware(private val initializer: () -> T) : ReadOnlyProperty>() + private val onStop = mutableListOf<() -> Unit>() init { RobotController.register(this) @@ -55,10 +56,19 @@ class LazyHardware(private val initializer: () -> T) : ReadOnlyProperty Unit) { + onStop += block + } + /** * Discards the cached object so the next access re-initializes it. */ internal fun reset() { value = null + onStop.toList().forEach { it() } } } From 4ea53d58cb23e388402b0e77939140b8e4127efb Mon Sep 17 00:00:00 2001 From: 28shettr <28shettr@elmbrookstudents.org> Date: Fri, 6 Nov 2026 18:37:07 -0600 Subject: [PATCH 2/2] Motor Port Fix --- .../kotlin/dev/nextftc/hardware/RobotController.kt | 10 ++++++++-- .../dev/nextftc/hardware/lynx/NextLynxModule.kt | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt index aee2f48d..b1900965 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/RobotController.kt @@ -40,6 +40,10 @@ object RobotController : OnCreateEventLoop, OpModeManagerNotifier.Notifications hardwareObjects += hardware } + private fun resetHardware() { + synchronized(hardwareObjects) { hardwareObjects.toList() }.forEach { it.reset() } + } + /** * Application context captured when the event loop is created. * @@ -145,7 +149,9 @@ object RobotController : OnCreateEventLoop, OpModeManagerNotifier.Notifications ftcEventLoop.opModeManager.registerListener(this) } - override fun onOpModePreInit(opMode: OpMode?) {} + override fun onOpModePreInit(opMode: OpMode?) { + resetHardware() + } override fun onOpModePreStart(opMode: OpMode?) {} @@ -153,6 +159,6 @@ object RobotController : OnCreateEventLoop, OpModeManagerNotifier.Notifications * Discards all cached hardware objects so they are re-initialized in the next op mode. */ override fun onOpModePostStop(opMode: OpMode?) { - synchronized(hardwareObjects) { hardwareObjects.toList() }.forEach { it.reset() } + resetHardware() } } diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/lynx/NextLynxModule.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/lynx/NextLynxModule.kt index 5f7c344e..733a253c 100644 --- a/hardware/src/main/kotlin/dev/nextftc/hardware/lynx/NextLynxModule.kt +++ b/hardware/src/main/kotlin/dev/nextftc/hardware/lynx/NextLynxModule.kt @@ -52,13 +52,19 @@ class NextLynxModule internal constructor(initializer: () -> LynxModule, @JvmFie } /** Tracks used ports. Resets each OpMode. */ - private val usedPorts by LazyHardware { HashSet>() } + private val usedPorts by LazyHardware { HashMap, Throwable>() } /** Throws if this port is already used on this hub. */ private fun claim(kind: PortKind, port: Int) { - check(usedPorts.add(kind to port)) { - "Port conflict on $type: ${kind.name.lowercase()} port $port is used by more than one device" + val key = kind to port + val first = usedPorts[key] + if (first != null) { + throw IllegalStateException( + "Port conflict on $type: ${kind.name.lowercase()} port $port is used by more than one device", + first, + ) } + usedPorts[key] = Throwable("Port first claimed here") } /** Current module temperature. */