@@ -8,6 +8,8 @@ import okhttp3.internal.toImmutableList
88import org.strykeforce.healthcheck.Follow
99import org.strykeforce.healthcheck.Position
1010import org.strykeforce.healthcheck.Timed
11+ import org.strykeforce.swerve.SwerveDrive
12+ import org.strykeforce.swerve.TalonSwerveModule
1113import java.lang.reflect.Field
1214
1315private 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+
2529class 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