From afb3bb4a0778effe52af59529575ac83d34f57d8 Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Wed, 13 May 2026 19:41:39 -0500
Subject: [PATCH 01/12] Add NextCRServo wrapper for continuous-rotation servos
---
.../nextftc/hardware/servos/NextCRServo.kt | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
new file mode 100644
index 0000000..eb03c85
--- /dev/null
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2026 NextFTC Team
+ *
+ * Use of this source code is governed by an BSD-3-clause
+ * license that can be found in the LICENSE.md file at the root of this repository or at
+ * https://opensource.org/license/bsd-3-clause.
+ */
+
+package dev.nextftc.hardware.servos
+
+import com.qualcomm.robotcore.hardware.CRServoImplEx
+import com.qualcomm.robotcore.hardware.DcMotorSimple
+import dev.nextftc.hardware.Caching
+import dev.nextftc.hardware.LazyHardware
+import dev.nextftc.hardware.RobotController
+
+/**
+ * Lightweight wrapper around a [CRServoImplEx] that provides a more user-friendly
+ * interface for controlling continuous-rotation servo power and direction.
+ *
+ * Example:
+ * val crServo = NextCRServo("intakeServo")
+ * crServo.power = 0.75
+ * crServo.direction = DcMotorSimple.Direction.REVERSE
+ *
+ * @param initializer A function returning the backing [CRServoImplEx]. It will be
+ * invoked lazily the first time the servo is accessed.
+ * @param cacheTolerance Tolerance used by the [Caching] delegate for
+ * power updates; defaults to 0.01.
+ */
+class NextCRServo(initializer: () -> CRServoImplEx, val cacheTolerance: Double = 0.01) {
+ @JvmOverloads constructor(name: String, cacheTolerance: Double = 0.01) : this(
+ { RobotController.hardwareMap[name] as CRServoImplEx },
+ cacheTolerance,
+ )
+
+ private val servo by LazyHardware(initializer)
+
+ /**
+ * Power applied to the servo, in the range [-1.0, 1.0].
+ */
+ val power: Double by Caching(cacheTolerance) {
+ if (it != null) {
+ servo.power = it
+ }
+ }
+
+ /**
+ * Direction of the servo. Setting this to [DcMotorSimple.Direction.REVERSE]
+ * causes positive [power] values to spin the servo the opposite way,
+ * and vice versa.
+ */
+ var direction: DcMotorSimple.Direction
+ get() = servo.direction
+ set(value) {
+ servo.direction = value
+ }
+
+ /**
+ * Sets the servo's direction to [DcMotorSimple.Direction.REVERSE], causing
+ * positive power values to spin the servo the opposite way.
+ */
+ fun reverse() = apply {
+ direction = DcMotorSimple.Direction.REVERSE
+ }
+
+ /**
+ * Enables the PWM output of the associated servo.
+ */
+ fun enable() {
+ servo.setPwmEnable()
+ }
+
+ /**
+ * Disables the PWM output of the associated servo.
+ */
+ fun disable() {
+ servo.setPwmDisable()
+ }
+}
\ No newline at end of file
From 6b2dbe6ab4aaccf1aa9cf9e0f7360fed0ab71fdf Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Wed, 13 May 2026 21:03:24 -0500
Subject: [PATCH 02/12] Next CR Servo now is an open file
---
.../src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
index eb03c85..daad905 100644
--- a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
@@ -28,7 +28,7 @@ import dev.nextftc.hardware.RobotController
* @param cacheTolerance Tolerance used by the [Caching] delegate for
* power updates; defaults to 0.01.
*/
-class NextCRServo(initializer: () -> CRServoImplEx, val cacheTolerance: Double = 0.01) {
+open class NextCRServo(initializer: () -> CRServoImplEx, val cacheTolerance: Double = 0.01) {
@JvmOverloads constructor(name: String, cacheTolerance: Double = 0.01) : this(
{ RobotController.hardwareMap[name] as CRServoImplEx },
cacheTolerance,
From 1d5ec6c8df2a8851348b9e6f0b7b12fa4213f290 Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Wed, 13 May 2026 21:05:08 -0500
Subject: [PATCH 03/12] Add NextFeedbackServo and NextFeedbackCRServo for
analog feedback
---
.../hardware/servos/NextFeedbackCRServo.kt | 44 +++++++++++++++++
.../hardware/servos/NextFeedbackServo.kt | 48 +++++++++++++++++++
2 files changed, 92 insertions(+)
create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackCRServo.kt
create mode 100644 hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackCRServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackCRServo.kt
new file mode 100644
index 0000000..03714b7
--- /dev/null
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackCRServo.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2026 NextFTC Team
+ *
+ * Use of this source code is governed by an BSD-3-clause
+ * license that can be found in the LICENSE.md file at the root of this repository or at
+ * https://opensource.org/license/bsd-3-clause.
+ */
+
+package dev.nextftc.hardware.servos
+
+import com.qualcomm.robotcore.hardware.AnalogInput
+import dev.nextftc.hardware.AnalogFeedback
+import dev.nextftc.hardware.LazyHardware
+import dev.nextftc.hardware.RobotController
+
+/**
+ * A [NextCRServo] paired with an analog feedback input for reading the servo's
+ * actual angle. Useful for continuous-rotation servos with feedback wires
+ * (e.g. Axon CR) where you want to know how far the servo has rotated.
+ *
+ * Inherits everything from [NextCRServo] — `power`, `direction`, `reverse()`,
+ * `enable()`, `disable()` — and adds [angleInRadians] or [angleInDegrees] for reading the physical angle in
+ * radians or degrees respectively from the feedback input.
+ *
+ * @param servoName Hardware map name of the servo.
+ * @param feedbackName Hardware map name of the analog input.
+ * @param cacheTolerance Tolerance for the [NextCRServo] power caching delegate.
+ */
+class NextFeedbackCRServo(
+ servoName: String,
+ feedbackName: String,
+ cacheTolerance: Double = 0.01,
+ ) : NextCRServo(servoName, cacheTolerance) {
+
+ private val analogInput by LazyHardware {
+ RobotController.hardwareMap[feedbackName] as AnalogInput
+ }
+
+ /** Actual angle of the servo, in RADIANS. */
+ val angleInRadians: Double by AnalogFeedback { analogInput.voltage }
+
+ /** Actual angle of the servo, in DEGREES. */
+ val angleInDegrees: Double get() = Math.toDegrees(angleInRadians)
+}
\ No newline at end of file
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt
new file mode 100644
index 0000000..8329708
--- /dev/null
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 NextFTC Team
+ *
+ * Use of this source code is governed by an BSD-3-clause
+ * license that can be found in the LICENSE.md file at the root of this repository or at
+ * https://opensource.org/license/bsd-3-clause.
+ */
+
+package dev.nextftc.hardware.servos
+import com.qualcomm.robotcore.hardware.AnalogInput
+import dev.nextftc.hardware.AnalogFeedback
+import dev.nextftc.hardware.LazyHardware
+import dev.nextftc.hardware.RobotController
+
+/**
+ * A [NextServo] paired with an analog feedback input for reading the servo's
+ * actual angle. Useful for servos with a feedback wire (e.g. Axon).
+ *
+ * Inherits everything from [NextServo] — `position`, `pwmRange`, `enable()`,
+ * `disable()` — and adds [angleInRadians] or [angleInDegrees] for reading the physical angle in
+ * radians or degrees respectively from the feedback input.
+ *
+ * Example:
+ * val arm = NextFeedbackServo("armServo", "armEncoder")
+ * arm.position = 0.5
+ * val angle = arm.angleInRadians // where it actually is, in RADIANS
+ *
+ * @param servoName Hardware map name of the servo.
+ * @param feedbackName Hardware map name of the analog input.
+ * @param cacheTolerance Tolerance for the [NextServo] position caching delegate.
+ */
+class NextFeedbackServo(
+ servoName: String,
+ feedbackName: String,
+ cacheTolerance: Double = 0.01,
+ ) : NextServo(servoName, cacheTolerance) {
+
+ private val analogInput by LazyHardware {
+ RobotController.hardwareMap[feedbackName] as AnalogInput
+ }
+
+ /** Actual angle of the servo, in RADIANS. */
+ val angleInRadians: Double by AnalogFeedback { analogInput.voltage }
+
+ /** Actual angle of the servo, in DEGREES. */
+ val angleInDegrees: Double get() = Math.toDegrees(angleInRadians)
+
+}
\ No newline at end of file
From 99bf47bc38946ae301864724bf639a7f4cfffe44 Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Tue, 19 May 2026 18:47:03 -0500
Subject: [PATCH 04/12] commit
---
.../src/main/kotlin/dev/nextftc/hardware/servos/NextServo.kt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextServo.kt
index 5622ce9..44c0dce 100644
--- a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextServo.kt
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextServo.kt
@@ -20,6 +20,7 @@ import dev.nextftc.hardware.RobotController
* controls how sensitive the [position] caching delegate is to small changes.
*
* Example:
+ *
* val servo = NextServo("armServo")
* servo.position = 0.5
* servo.setPwmRange(500.0, 2500.0)
@@ -29,7 +30,7 @@ import dev.nextftc.hardware.RobotController
* @param cacheTolerance Tolerance used by the [Caching] delegate for
* position updates; defaults to 0.01.
*/
-class NextServo(initializer: () -> ServoImplEx, val cacheTolerance: Double = 0.01) {
+open class NextServo(initializer: () -> ServoImplEx, val cacheTolerance: Double = 0.01) {
@JvmOverloads constructor(name: String, cacheTolerance: Double = 0.01) : this(
{ RobotController.hardwareMap[name] as ServoImplEx },
cacheTolerance,
From 208bfc96db75062581892d4920a63d8f75d96adf Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Tue, 19 May 2026 18:47:18 -0500
Subject: [PATCH 05/12] cleaned up docs
---
.../src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt | 3 +++
.../kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt | 2 ++
2 files changed, 5 insertions(+)
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
index daad905..9cf7b00 100644
--- a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextCRServo.kt
@@ -19,9 +19,12 @@ import dev.nextftc.hardware.RobotController
* interface for controlling continuous-rotation servo power and direction.
*
* Example:
+ *
+ *```
* val crServo = NextCRServo("intakeServo")
* crServo.power = 0.75
* crServo.direction = DcMotorSimple.Direction.REVERSE
+ *```
*
* @param initializer A function returning the backing [CRServoImplEx]. It will be
* invoked lazily the first time the servo is accessed.
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt
index 8329708..5122aac 100644
--- a/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/servos/NextFeedbackServo.kt
@@ -21,9 +21,11 @@ import dev.nextftc.hardware.RobotController
* radians or degrees respectively from the feedback input.
*
* Example:
+ * ```
* val arm = NextFeedbackServo("armServo", "armEncoder")
* arm.position = 0.5
* val angle = arm.angleInRadians // where it actually is, in RADIANS
+ *```
*
* @param servoName Hardware map name of the servo.
* @param feedbackName Hardware map name of the analog input.
From 55b4e6fb9170b5b4c7861e1a2a5603d8f09d17a7 Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Tue, 19 May 2026 18:59:22 -0500
Subject: [PATCH 06/12] commit
---
.idea/caches/deviceStreaming.xml | 1773 ++++++++++++++++++++++++++++++
.idea/codeStyles/Project.xml | 157 +++
.idea/markdown.xml | 8 +
.idea/migrations.xml | 10 +
.idea/runConfigurations.xml | 17 +
5 files changed, 1965 insertions(+)
create mode 100644 .idea/caches/deviceStreaming.xml
create mode 100644 .idea/codeStyles/Project.xml
create mode 100644 .idea/markdown.xml
create mode 100644 .idea/migrations.xml
create mode 100644 .idea/runConfigurations.xml
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
new file mode 100644
index 0000000..2d334f0
--- /dev/null
+++ b/.idea/caches/deviceStreaming.xml
@@ -0,0 +1,1773 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..9c3d95a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index a55e7a1..6e6eec1 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 732832c..07468d9 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,7 +4,9 @@
+
+
\ No newline at end of file
From 5ca36f9dee246855f2003f33859d8831a30ccf34 Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Sat, 23 May 2026 10:31:30 -0500
Subject: [PATCH 10/12] Added debug method
---
.../dev/nextftc/hardware/sensors/NextDigitalSensor.kt | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt
index 7fb0d9d..2338cbb 100644
--- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt
@@ -34,6 +34,7 @@ import dev.nextftc.hardware.RobotController
* @author 28shettr
*/
class NextDigitalSensor(initializer: () -> DigitalChannel, private val triggeredOnLow: Boolean = true) {
+ /** @param name Hardware map name of the digital channel. @param activeLow See [triggeredOnLow]. */
@JvmOverloads
constructor(name: String, activeLow: Boolean = true) : this(
{
@@ -46,7 +47,7 @@ class NextDigitalSensor(initializer: () -> DigitalChannel, private val triggered
it.applyAfterInit { channel -> channel.mode = DigitalChannel.Mode.INPUT }
}
- /** Raw state of the digital channel*/
+ /** Raw state of the digital channel */
val rawState: Boolean
get() = sensor.state
@@ -57,4 +58,8 @@ class NextDigitalSensor(initializer: () -> DigitalChannel, private val triggered
} else {
sensor.state
}
+
+ /** Returns a string of the sensor's current state for telemetry or logging. */
+ fun debug(): String =
+ "Sensor State: $isTriggered, Raw State: $rawState, Triggered On Low: $triggeredOnLow"
}
From ac0037299f19142800cab3a5028305c5f764abdd Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Sat, 23 May 2026 10:45:51 -0500
Subject: [PATCH 11/12] switch some variable names, more readable
---
.../hardware/sensors/NextDigitalSensor.kt | 30 +++++++++++--------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt
index 2338cbb..cc54591 100644
--- a/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt
+++ b/hardware/src/main/kotlin/dev/nextftc/hardware/sensors/NextDigitalSensor.kt
@@ -18,29 +18,36 @@ import dev.nextftc.hardware.RobotController
*
* Most digital sensors are "active low" — they read `false` when triggered
* (switch pressed, magnet present, beam broken) and `true` when idle. This
- * wrapper handles that inversion via [triggeredOnLow] so [isTriggered] always
+ * wrapper handles that inversion via [inverted] so [isTriggered] always
* means what you'd expect.
*
* Example:
* ```
* val beamBreak = NextDigitalSensor("beamBreak")
* if (beamBreak.isTriggered) { stopMotor() }
- *```
+ * ```
*
* @param initializer Lazily resolves the backing [DigitalChannel].
- * @param triggeredOnLow If true, [isTriggered] returns the inverse of the raw
- * sensor state. Defaults to true (matches most FTC digital sensors).
+ * @param inverted If true, [isTriggered] returns the opposite of the raw
+ * sensor state — i.e. triggered when the channel reads low. For example, a
+ * touch sensor reads `false` while it's being pressed, so inverting makes
+ * [isTriggered] read `true` when pressed, which is what you'd expect.
+ * Defaults to true, matching most FTC digital sensors, which are active-low.
*
* @author 28shettr
*/
-class NextDigitalSensor(initializer: () -> DigitalChannel, private val triggeredOnLow: Boolean = true) {
- /** @param name Hardware map name of the digital channel. @param activeLow See [triggeredOnLow]. */
+
+class NextDigitalSensor(initializer: () -> DigitalChannel, private val inverted: Boolean = true) {
+ /**
+ * @param name Hardware map name to resolve the [DigitalChannel] from.
+ * @param inverted If true, [isTriggered] is the opposite of the raw state. Defaults to true.
+ */
@JvmOverloads
- constructor(name: String, activeLow: Boolean = true) : this(
+ constructor(name: String, inverted: Boolean = true) : this(
{
RobotController.hardwareMap[name] as DigitalChannel
},
- activeLow,
+ inverted,
)
private val sensor by LazyHardware(initializer).also {
@@ -51,15 +58,14 @@ class NextDigitalSensor(initializer: () -> DigitalChannel, private val triggered
val rawState: Boolean
get() = sensor.state
- /** True if the sensor is currently triggered (accounting for [triggeredOnLow]). */
+ /** True if the sensor is currently triggered (accounting for [inverted]). */
val isTriggered: Boolean
- get() = if (triggeredOnLow) {
+ get() = if (inverted) {
!sensor.state
} else {
sensor.state
}
/** Returns a string of the sensor's current state for telemetry or logging. */
- fun debug(): String =
- "Sensor State: $isTriggered, Raw State: $rawState, Triggered On Low: $triggeredOnLow"
+ fun debug(): String = "Sensor State: $isTriggered, Raw State: $rawState, Inverted: $inverted"
}
From eb08fa0f7527bd956421c8f9af6bf52432f13432 Mon Sep 17 00:00:00 2001
From: Rohit Shetty <138729620+28shettr@users.noreply.github.com>
Date: Wed, 27 May 2026 20:39:01 -0500
Subject: [PATCH 12/12] spotless apply
---
.idea/caches/deviceStreaming.xml | 36 +++++++++++++++++++
.../hardware/actuators/NextFeedbackCRServo.kt | 8 ++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index 2d334f0..f432cf1 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -677,6 +677,18 @@