Skip to content

Commit b6e4cf2

Browse files
committed
Merge branch 'pathing-refactor'
# Conflicts: # TeamCode/src/main/kotlin/pioneer/decode/GoalTag.kt
2 parents c734a10 + 6dc0107 commit b6e4cf2

32 files changed

+490
-294
lines changed

TeamCode/src/main/kotlin/pioneer/Bot.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class Bot private constructor(
5656
val follower: Follower by lazy {
5757
Follower(
5858
localizer = pinpoint!!,
59-
mecanumBase = mecanumBase!!,
59+
drive = mecanumBase!!,
6060
)
6161
}
6262

TeamCode/src/main/kotlin/pioneer/Constants.kt

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ object Constants {
6262

6363
// limits
6464
const val MAX_MOTOR_VELOCITY_TPS = 2500.0
65-
const val MAX_FWD_VEL_CMPS = 150.0
66-
const val MAX_STRAFE_VEL_CMPS = 125.0
65+
const val MAX_DRIVE_VEL_CMPS = 150.0
6766
const val DEFAULT_POWER = 0.7
6867

6968
// Feedforward gains using Pose(x,y,theta)
@@ -75,8 +74,8 @@ object Constants {
7574
// @JvmField var kAT = 0.0
7675

7776
val kV = Pose(x = 0.0063, y = 0.0055, theta = -0.147)
78-
val kA = Pose(x = 0.0025, y = 0.001, theta = 0.0)
79-
val kS = Pose(x = 0.0, y = 0.0, theta = 0.0)
77+
val kA = Pose(x = 0.00125, y = 0.001, theta = 0.0)
78+
val kS = Pose(x = 0.11, y = 0.06, theta = 0.0)
8079

8180
val MOTOR_CONFIG =
8281
mapOf(
@@ -104,16 +103,16 @@ object Constants {
104103
@Config
105104
object Follower {
106105
/** The threshold in cm to consider the target reached. */
107-
const val POSITION_THRESHOLD = 0.5
106+
const val POSITION_THRESHOLD = 1.0
108107

109108
/** The threshold in radians to consider the target heading reached. */
110109
const val ROTATION_THRESHOLD = 0.05
111110

112111
/** The maximum drive velocity in cm per second. */
113-
const val MAX_DRIVE_VELOCITY = 100.0
112+
const val MAX_DRIVE_VELOCITY = 110.0
114113

115114
/** The maximum drive acceleration in cm per second squared. */
116-
const val MAX_DRIVE_ACCELERATION = 50.0
115+
const val MAX_DRIVE_ACCELERATION = 30.0
117116

118117
/** The maximum centripetal acceleration that the robot can handle in cm/s^2. */
119118
const val MAX_CENTRIPETAL_ACCELERATION = (70.0 * 70.0) / 25.0
@@ -125,24 +124,18 @@ object Constants {
125124
const val MAX_ANGULAR_ACCELERATION = 10.0
126125

127126
// X-axis PID coefficients for the trajectory follower
128-
@JvmField var X_KP = 10.0
129-
127+
@JvmField var X_KP = 7.5
130128
@JvmField var X_KI = 0.0
131-
132129
@JvmField var X_KD = 0.0
133130

134131
// Y-axis PID coefficients for the trajectory follower
135-
@JvmField var Y_KP = 10.0
136-
132+
@JvmField var Y_KP = 7.5
137133
@JvmField var Y_KI = 0.0
138-
139134
@JvmField var Y_KD = 0.0
140135

141136
// Theta PID coefficients for heading interpolation
142-
@JvmField var THETA_KP = 0.5
143-
137+
@JvmField var THETA_KP = 5.0
144138
@JvmField var THETA_KI = 0.0
145-
146139
@JvmField var THETA_KD = 0.0
147140
}
148141

TeamCode/src/main/kotlin/pioneer/decode/GoalTag.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ enum class GoalTag(
6262
* @property tagId The ID of the goal AprilTag to process.
6363
*/
6464
object GoalTagProcessor {
65-
private val validTags = GoalTag.values().map { it.id }.toSet()
65+
private val validTags = GoalTag.entries.map { it.id }.toSet()
6666

6767
fun isValidGoalTag(aprilTagId: Int): Boolean = aprilTagId in validTags
6868

@@ -97,7 +97,7 @@ object GoalTagProcessor {
9797
fun getRobotFieldPose(detections: List<AprilTagDetection>): Pose? {
9898
val tag =
9999
detections.firstNotNullOfOrNull { detection ->
100-
GoalTag.values().firstOrNull { it.id == detection.id }
100+
GoalTag.entries.firstOrNull { it.id == detection.id }
101101
}
102102

103103
return tag?.pose?.let { tagPose ->
@@ -106,8 +106,8 @@ object GoalTagProcessor {
106106
GoalTag.BLUE.id, GoalTag.RED.id -> {
107107
detection.ftcPose?.let { ftcPose ->
108108
Pose(
109-
x = tagPose.x + MathUtils.inToCM(ftcPose.x.toDouble()),
110-
y = tagPose.y + MathUtils.inToCM(ftcPose.y.toDouble()),
109+
x = tagPose.x + MathUtils.inToCM(ftcPose.x),
110+
y = tagPose.y + MathUtils.inToCM(ftcPose.y),
111111
theta = tagPose.theta + ftcPose.bearing,
112112
)
113113
}

TeamCode/src/main/kotlin/pioneer/decode/Motif.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class Motif(
1616
// Returns the next artifact in the motif's sequence, cycling back to the start if needed
1717
fun getNextArtifact(): Artifact? {
1818
if (artifacts.isEmpty()) return null
19-
2019
val artifact = artifacts[currentIndex]
2120
currentIndex = (currentIndex + 1) % artifacts.size
2221
return artifact

TeamCode/src/main/kotlin/pioneer/decode/Points.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,30 @@ class Points(
3131
fun Pose.T(c: AllianceColor): Pose =
3232
when (c) {
3333
AllianceColor.RED -> this
34-
AllianceColor.BLUE -> Pose(-this.x, this.y, -this.theta)
34+
AllianceColor.BLUE -> Pose(-this.x, this.y, theta=-this.theta)
3535
AllianceColor.NEUTRAL -> this
3636
}
3737

3838
// Key positions on the field
3939
// Written in ACTION_POSITION format
40-
val START_GOAL = Pose(130.0, 137.0, theta = 3 * PI / 4).T(color)
40+
val START_GOAL = Pose(133.0, 134.0, theta = 0.67).T(color)
4141
val START_FAR = Pose(43.0, -157.0, theta = 0.0).T(color)
4242

43-
val SHOOT_GOAL_CLOSE = Pose(60.0, 60.0, theta = 0.0).T(color)
43+
val SHOOT_GOAL_CLOSE = Pose(55.0, 30.0, theta = -PI/2).T(color)
4444
val SHOOT_GOAL_FAR = Pose(43.0, -145.0, theta = 0.0).T(color)
4545

46+
val LEAVE_POSITION = Pose(55.0, 120.0).T(color)
47+
4648
private fun collectY(i: Int) = 30 - (i * 60.0) // 30, -30, -90
4749

48-
private val prepCollectX = 70.0
50+
private val prepCollectX = 75.0
4951
private val collectTheta = -PI / 2 // Point to right
50-
val PREP_COLLECT_GOAL = Pose(prepCollectX, collectY(0), collectTheta).T(color)
51-
val PREP_COLLECT_MID = Pose(prepCollectX, collectY(1), collectTheta).T(color)
52-
val PREP_COLLECT_AUDIENCE = Pose(prepCollectX, collectY(2), collectTheta).T(color)
53-
54-
private val collectX = 130.0
55-
val COLLECT_GOAL = Pose(collectX, collectY(0), collectTheta).T(color)
56-
val COLLECT_MID = Pose(collectX, collectY(1), collectTheta).T(color)
57-
val COLLECT_AUDIENCE = Pose(collectX, collectY(2), collectTheta).T(color)
52+
val PREP_COLLECT_GOAL = Pose(prepCollectX, collectY(0), theta=collectTheta).T(color)
53+
val PREP_COLLECT_MID = Pose(prepCollectX, collectY(1), theta=collectTheta).T(color)
54+
val PREP_COLLECT_AUDIENCE = Pose(prepCollectX, collectY(2), theta=collectTheta).T(color)
55+
56+
private val collectX = 120.0
57+
val COLLECT_GOAL = Pose(collectX, collectY(0), theta=collectTheta).T(color)
58+
val COLLECT_MID = Pose(collectX, collectY(1), theta=collectTheta).T(color)
59+
val COLLECT_AUDIENCE = Pose(collectX, collectY(2), theta=collectTheta).T(color)
5860
}

TeamCode/src/main/kotlin/pioneer/hardware/Launcher.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class Launcher(
1313
private val servoName: String = Constants.HardwareNames.LAUNCH_SERVO,
1414
) : HardwareComponent {
1515
companion object {
16-
private const val SERVO_CYCLE_TIME_MS = 670L
17-
private const val RESET_THRESHOLD_MS = 750.0
16+
private const val SERVO_CYCLE_TIME_MS = 250L
17+
private const val RESET_THRESHOLD_MS = 100.0
1818
}
1919

2020
private lateinit var launchServo: Servo
@@ -23,14 +23,15 @@ class Launcher(
2323
private val isTriggered = AtomicBoolean(false)
2424

2525
val isReset: Boolean
26-
get() = resetTimer.milliseconds() > RESET_THRESHOLD_MS
26+
get() = !isTriggered.get() && resetTimer.milliseconds() > RESET_THRESHOLD_MS
2727

2828
override fun init() {
2929
launchServo = hardwareMap.get(Servo::class.java, servoName)
3030
launchServo.position = Constants.ServoPositions.LAUNCHER_REST
3131
}
3232

3333
fun triggerLaunch() {
34+
if (!isReset) return
3435
if (!isTriggered.compareAndSet(false, true)) return
3536

3637
launchServo.position = Constants.ServoPositions.LAUNCHER_TRIGGERED

TeamCode/src/main/kotlin/pioneer/hardware/MecanumBase.kt

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,23 @@ import com.qualcomm.robotcore.hardware.DcMotorSimple
66
import com.qualcomm.robotcore.hardware.HardwareMap
77
import pioneer.Constants
88
import pioneer.helpers.Pose
9+
import pioneer.pathing.follower.RobotFeedforward
910
import kotlin.math.abs
10-
import kotlin.math.sign
1111

1212
class MecanumBase(
1313
private val hardwareMap: HardwareMap,
1414
private val motorConfig: Map<String, DcMotorSimple.Direction> = Constants.Drive.MOTOR_CONFIG,
1515
) : HardwareComponent {
1616
private lateinit var motors: Map<String, DcMotorEx>
1717

18+
private val feedforward = RobotFeedforward(
19+
Constants.Drive.kV.x, Constants.Drive.kA.x,
20+
Constants.Drive.kV.y, Constants.Drive.kA.y,
21+
Constants.Drive.kV.theta, Constants.Drive.kA.theta,
22+
Constants.Drive.kS.x, Constants.Drive.kS.y,
23+
Constants.Drive.kS.theta,
24+
)
25+
1826
override fun init() {
1927
motors =
2028
motorConfig.mapValues { (name, direction) ->
@@ -61,14 +69,9 @@ class MecanumBase(
6169
* Feedforward control for motion profiling
6270
*/
6371
fun setDriveVA(pose: Pose) {
64-
val ffX = calculateFeedforward(pose.vx, pose.ax, Constants.Drive.kV.x, Constants.Drive.kA.x, Constants.Drive.kS.x)
65-
val ffY = calculateFeedforward(pose.vy, pose.ay, Constants.Drive.kV.y, Constants.Drive.kA.y, Constants.Drive.kS.y)
66-
val ffTheta =
67-
calculateFeedforward(pose.omega, pose.alpha, Constants.Drive.kV.theta, Constants.Drive.kA.theta, Constants.Drive.kS.theta)
68-
69-
// val ffX = calculateFeedforward(pose.vx, pose.ax, Constants.Drive.kV.x, Constants.Drive.kAX, Constants.Drive.kS.x)
70-
// val ffY = calculateFeedforward(pose.vy, pose.ay, Constants.Drive.kV.y, Constants.Drive.kAY, Constants.Drive.kS.y)
71-
// val ffTheta = calculateFeedforward(pose.omega, pose.alpha, Constants.Drive.kV.theta, Constants.Drive.kAT, Constants.Drive.kS.theta)
72+
val ffX = feedforward.calculateX(pose.vx, pose.ax)
73+
val ffY = feedforward.calculateY(pose.vy, pose.ay)
74+
val ffTheta = feedforward.calculateTheta(pose.omega, pose.alpha)
7275

7376
val motorPowers = calculateMotorPowers(Pose(vx = ffX, vy = ffY, omega = ffTheta))
7477
motors.values.forEachIndexed { index, motor ->
@@ -84,14 +87,6 @@ class MecanumBase(
8487
return listOf(leftFrontPower, leftBackPower, rightFrontPower, rightBackPower)
8588
}
8689

87-
private fun calculateFeedforward(
88-
v: Double,
89-
a: Double,
90-
kV: Double,
91-
kA: Double,
92-
kS: Double,
93-
): Double = v * kV + a * kA + if (abs(v) > 1e-3) kS * sign(v) else 0.0
94-
9590
fun stop() {
9691
motors.values.forEach { it.power = 0.0 }
9792
}

TeamCode/src/main/kotlin/pioneer/hardware/Spindexer.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,36 @@ class Spindexer(
7272
MotorPosition.OUTTAKE_3,
7373
)
7474

75+
val closestMotorState: MotorPosition
76+
get() {
77+
var closestPosition = MotorPosition.INTAKE_1
78+
var smallestError = Double.MAX_VALUE
79+
80+
for (position in MotorPosition.entries) {
81+
val targetTicks = (position.radians * ticksPerRadian).toInt()
82+
val error = wrapTicks(targetTicks - currentMotorPosition).toDouble()
83+
if (abs(error) < abs(smallestError)) {
84+
smallestError = error
85+
closestPosition = position
86+
}
87+
}
88+
89+
return closestPosition
90+
}
91+
92+
val reachedOuttakePosition: Boolean
93+
get() {
94+
// Check if motor position matches any outtake position
95+
for (position in outtakePositions) {
96+
val targetTicks = (position.radians * ticksPerRadian).toInt()
97+
val error = wrapTicks(targetTicks - currentMotorPosition)
98+
if (abs(error) < 300.0) {
99+
return true
100+
}
101+
}
102+
return false
103+
}
104+
75105
val isIntakePosition: Boolean
76106
get() =
77107
motorState in
@@ -225,6 +255,14 @@ class Spindexer(
225255
if (isEmpty) moveToNextOpenIntake()
226256
return artifact
227257
}
258+
259+
fun popArtifact(index: Int): Artifact? {
260+
val artifact = _artifacts[index]
261+
_artifacts[index] = null
262+
// Automatically move to intake if empty
263+
if (isEmpty) moveToNextOpenIntake()
264+
return artifact
265+
}
228266

229267
fun cancelLastIntake() {
230268
artifacts[lastStoredIndex] = null
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package pioneer.helpers
2+
3+
/**
4+
* Simple motor feedforward: v + a (+ optionally static)
5+
* output = kV * velocity + kA * acceleration
6+
* Units: velocity in cm/s, acceleration in cm/s^2
7+
*/
8+
class Feedforward(val kV: Double, val kA: Double, val kStatic: Double = 0.0) {
9+
/**
10+
* Compute feedforward for given velocity and acceleration
11+
*/
12+
fun calculate(velocity: Double, acceleration: Double): Double {
13+
val signV = if (velocity >= 0.0) 1.0 else -1.0
14+
return kV * velocity + kA * acceleration + kStatic * signV
15+
}
16+
17+
/**
18+
* Compute feedforward for an array of wheel velocities & accelerations
19+
*/
20+
fun calculate(wheelV: DoubleArray, wheelA: DoubleArray? = null): DoubleArray {
21+
return wheelV.mapIndexed { i, v ->
22+
val a = wheelA?.get(i) ?: 0.0
23+
calculate(v, a)
24+
}.toDoubleArray()
25+
}
26+
}

TeamCode/src/main/kotlin/pioneer/helpers/MathUtils.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,8 @@ object MathUtils {
8989

9090
return YawPitchRollAngles(AngleUnit.RADIANS, yaw, pitch, roll, 0)
9191
}
92+
93+
fun lerp(x1: Double, x2: Double, t: Double) : Double {
94+
return x1 + (x2-x1) * t
95+
}
9296
}

0 commit comments

Comments
 (0)