Skip to content

Commit 6d0eb57

Browse files
authored
Merge pull request #101 from strykeforce/swervedrive-healthcheck
Refactor swerve drive health check builder
2 parents c96037d + bb9fdd1 commit 6d0eb57

File tree

2 files changed

+98
-16
lines changed

2 files changed

+98
-16
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ plugins {
1111
}
1212

1313
group = "org.strykeforce"
14-
version = "23.1.0"
14+
version = "23.1.1"
1515

1616
sourceCompatibility = JavaVersion.VERSION_17
1717
targetCompatibility = JavaVersion.VERSION_17

src/main/kotlin/org/strykeforce/healthcheck/internal/Builders.kt

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import okhttp3.internal.toImmutableList
88
import org.strykeforce.healthcheck.Follow
99
import org.strykeforce.healthcheck.Position
1010
import org.strykeforce.healthcheck.Timed
11+
import org.strykeforce.swerve.SwerveDrive
12+
import org.strykeforce.swerve.TalonSwerveModule
1113
import java.lang.reflect.Field
1214

1315
private val logger = KotlinLogging.logger {}
@@ -22,52 +24,132 @@ class RobotHealthCheckBuilder(vararg subsystems: Subsystem) {
2224
}
2325
}
2426

27+
private val kHealthCheckAnnotationClass = org.strykeforce.healthcheck.HealthCheck::class.java
28+
2529
class SubsystemHealthCheckBuilder(private val subsystem: Subsystem) {
2630

2731
fun build(): SubsystemHealthCheck {
28-
val talonHealthChecks =
29-
subsystem.javaClass.declaredFields.filter { it.isAnnotationPresent(org.strykeforce.healthcheck.HealthCheck::class.java) }
30-
.map { TalonHealthCheckBuilder(subsystem, it).build() }.toMutableList()
3132

32-
if (talonHealthChecks.isEmpty()) logger.error { "SubsystemHealthCheckBuilder: no health checks found: $subsystem" }
33+
// get all fields from this subsystem that are annotated with HealthCheck
34+
val healthCheckFields =
35+
subsystem.javaClass.declaredFields.filter { it.isAnnotationPresent(kHealthCheckAnnotationClass) }
36+
.toMutableList()
37+
38+
// will remove non-talon field(s), only expect SwerveDrive field
39+
val fieldsToRemove = mutableSetOf<Field>()
40+
41+
// make each HealthCheck annotated field accessible or remove it if unable
42+
healthCheckFields.forEach {
43+
if (!it.trySetAccessible()) {
44+
logger.error { "$subsystem: trySetAccessible() failed for ${it.name}, removing!" }
45+
fieldsToRemove.add(it)
46+
}
47+
}
48+
healthCheckFields.removeAll(fieldsToRemove)
49+
50+
val talonHealthChecks = mutableListOf<TalonHealthCheck>()
51+
52+
// if a SwerveDrive field is annotated with HealthCheck,
53+
// create a TalonHealthCheck for each azimuth and drive talon and add to talonHealthChecks list
54+
// queue the SwerveDrive field for removal in fieldsToRemove
55+
healthCheckFields.forEach {
56+
if (it.type == SwerveDrive::class.java) {
57+
talonHealthChecks.addAll(SwerveDriveHealthCheckBuilder(subsystem, it).build())
58+
fieldsToRemove.add(it)
59+
}
60+
}
3361

34-
// process any follower talon health checks by adding their talons to be measured to the
35-
// associated leader health checks
62+
// remove all non-talon fields and create TalonHealthChecks from the remaining talon fields
63+
healthCheckFields.removeAll(fieldsToRemove)
64+
talonHealthChecks.addAll(healthCheckFields.map { TalonHealthCheckBuilder(subsystem, it).build() })
65+
if (talonHealthChecks.isEmpty()) logger.warn { "$subsystem: no health checks found" }
66+
67+
// find any follower talon health checks and
68+
// add their talon to be measured to their associated leader health check
69+
// remove the follower health check since they will be run during leader health check
3670
val followerHealthChecks = talonHealthChecks.mapNotNull { it as? TalonFollowerHealthCheck }
3771
for (fhc in followerHealthChecks) {
3872
val lhc = talonHealthChecks.find { it.talon.deviceID == fhc.leaderId }
3973
if (lhc == null) {
4074
logger.error { "$subsystem: no leader (id = ${fhc.leaderId}) found for follower (id = ${fhc.talon.deviceID})" }
4175
continue
4276
}
77+
// leader health check is a TalonTimedHealthCheck or TalonPositionHealthCheck
78+
// add follower talon to each of its child cases
4379
lhc.healthChecks.map { it as TalonHealthCheckCase }.forEach { it.addFollowerTalon(fhc.talon) }
4480
}
45-
// follower health checks are removed since they don't run anything
4681
talonHealthChecks.removeAll(followerHealthChecks)
4782

4883
val name = (subsystem as? SubsystemBase)?.name ?: subsystem.toString()
4984
return SubsystemHealthCheck(name, talonHealthChecks.toImmutableList())
5085
}
5186
}
5287

88+
private const val AZIMUTH_LEADER_ID = 0
89+
private const val DRIVE_LEADER_ID = 10
90+
91+
class SwerveDriveHealthCheckBuilder(private val subsystem: Subsystem, private val field: Field) {
92+
93+
private val swerveDrive = field.get(subsystem) as? SwerveDrive
94+
?: throw IllegalArgumentException("$subsystem: field '${field.name}' is not a SwerveDrive")
95+
96+
fun build(): List<TalonHealthCheck> {
97+
val talonHealthChecks = mutableListOf<TalonHealthCheck>()
98+
99+
var azimuthLeader: BaseTalon? = null
100+
var driveLeader: BaseTalon? = null
101+
val azimuthFollowers = mutableListOf<BaseTalon>()
102+
val driveFollowers = mutableListOf<BaseTalon>()
103+
104+
// extract talons from swerve drive modules and
105+
// create TalonTimedHealthChecks for leader azimuth and drive talons
106+
// create TalonFollowerHealthCheck for remainder
107+
// follow associated leaders
108+
swerveDrive.swerveModules.forEach {
109+
val module =
110+
it as? TalonSwerveModule ?: throw IllegalArgumentException("$subsystem: $it is not TalonSwerveModule")
111+
val azimuth = module.azimuthTalon
112+
val drive = module.driveTalon
113+
114+
if (azimuth.deviceID == AZIMUTH_LEADER_ID) {
115+
talonHealthChecks.add(TalonTimedHealthCheckBuilder(azimuth).build())
116+
azimuthLeader = azimuth
117+
} else {
118+
talonHealthChecks.add(TalonFollowerHealthCheck(azimuth, AZIMUTH_LEADER_ID))
119+
azimuthFollowers.add(azimuth)
120+
}
121+
122+
if (drive.deviceID == DRIVE_LEADER_ID) {
123+
talonHealthChecks.add(TalonTimedHealthCheckBuilder(drive).build())
124+
driveLeader = drive
125+
} else {
126+
talonHealthChecks.add(TalonFollowerHealthCheck(drive, DRIVE_LEADER_ID))
127+
driveFollowers.add(drive)
128+
}
129+
}
130+
131+
requireNotNull(azimuthLeader) { "$subsystem: swerve azimuth talon with id $AZIMUTH_LEADER_ID not found" }
132+
requireNotNull(driveLeader) { "$subsystem: swerve drive talon with id $DRIVE_LEADER_ID not found" }
53133

54-
class TalonHealthCheckBuilder(val subsystem: Subsystem, val field: Field) {
134+
azimuthFollowers.forEach { it.follow(azimuthLeader) }
135+
driveFollowers.forEach { it.follow(driveLeader) }
55136

56-
init {
57-
if (!field.trySetAccessible())
58-
logger.error { "trySetAccessible() failed for $subsystem: ${field.name}" }
137+
return talonHealthChecks
59138
}
139+
}
140+
141+
class TalonHealthCheckBuilder(private val subsystem: Subsystem, private val field: Field) {
60142

61143
val talon = field.get(subsystem) as? BaseTalon
62-
?: throw IllegalArgumentException("Subsystem $subsystem field '${field.name}' is not a subclass of BaseTalon")
144+
?: throw IllegalArgumentException("$subsystem: field '${field.name}' is not a subclass of BaseTalon")
63145

64146
fun build(): TalonHealthCheck {
65147
val timedAnnotation = field.getAnnotation(Timed::class.java)
66148
val positionAnnotation = field.getAnnotation(Position::class.java)
67149
val followAnnotation = field.getAnnotation(Follow::class.java)
68150

69151
if (arrayListOf(timedAnnotation, positionAnnotation, followAnnotation).filterNotNull().count() > 1)
70-
throw IllegalArgumentException("Only one of @Timed, @Position, or @Follow may be specified.")
152+
throw IllegalArgumentException("$subsystem: only one of @Timed, @Position, or @Follow may be specified.")
71153

72154
if (timedAnnotation != null) {
73155
val builder = TalonTimedHealthCheckBuilder(talon)
@@ -107,7 +189,7 @@ class TalonTimedHealthCheckBuilder(val talon: BaseTalon) {
107189
}
108190
}
109191

110-
return TalonTimedHealthCheck(talon, cases, percentOutput, /*duration, doubleArrayOf()*/)
192+
return TalonTimedHealthCheck(talon, cases, percentOutput)
111193
}
112194
}
113195

@@ -129,6 +211,6 @@ class TalonPositionHealthCheckBuilder(val talon: BaseTalon) {
129211
}
130212
}
131213

132-
return TalonPositionHealthCheck(talon, cases, percentOutput, /*encoderChange, limits*/)
214+
return TalonPositionHealthCheck(talon, cases, percentOutput)
133215
}
134216
}

0 commit comments

Comments
 (0)