Skip to content

Commit 535eadb

Browse files
authored
Merge pull request #37 from Pioneer-Robotics/bot-refactor
Bot refactor
2 parents a394ce2 + 9176cba commit 535eadb

26 files changed

+345
-335
lines changed
Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,89 @@
11
package pioneer
22

33
import com.qualcomm.robotcore.hardware.HardwareMap
4-
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor
5-
import pioneer.constants.Drive
6-
import pioneer.hardware.AprilTag
74
import pioneer.hardware.BatteryMonitor
85
import pioneer.hardware.Camera
96
import pioneer.hardware.Flywheel
7+
import pioneer.hardware.HardwareComponent
108
import pioneer.hardware.LaunchServos
119
import pioneer.hardware.MecanumBase
12-
import pioneer.hardware.MockHardware
13-
import pioneer.localization.Localizer
14-
import pioneer.localization.localizers.LocalizerMock
1510
import pioneer.localization.localizers.Pinpoint
1611
import pioneer.pathing.follower.Follower
17-
import pioneer.constants.Camera as CameraConstants
1812

19-
enum class BotType(
20-
val supportsLocalizer: Boolean,
21-
) {
22-
BASIC_MECANUM_BOT(false),
23-
MECANUM_BOT(true),
24-
GOBILDA_STARTER_BOT(true),
13+
enum class BotType {
14+
MECANUM_BOT,
15+
GOBILDA_STARTER_BOT,
16+
CUSTOM,
2517
}
2618

27-
class Bot(
28-
val botType: BotType,
29-
hardwareMap: HardwareMap,
19+
class Bot private constructor(
20+
val type: BotType,
21+
private val hardwareComponents: Map<Class<out HardwareComponent>, HardwareComponent>,
3022
) {
31-
// Basic hardware components
32-
var mecanumBase: MecanumBase = MecanumBase(MockHardware())
33-
var localizer: Localizer = LocalizerMock()
34-
var batteryMonitor: BatteryMonitor = BatteryMonitor(MockHardware())
23+
// Type-safe access
24+
internal inline fun <reified T : HardwareComponent> get(): T? = hardwareComponents[T::class.java] as? T
3525

36-
// GoBilda starter bot specific components
37-
var flywheel: Flywheel = Flywheel(MockHardware())
38-
var launchServos: LaunchServos = LaunchServos(MockHardware())
26+
// Check if a component is present
27+
internal inline fun <reified T : HardwareComponent> has(): Boolean = hardwareComponents.containsKey(T::class.java)
3928

40-
// Other hardware components
41-
var aprilTagProcessor: AprilTagProcessor = AprilTag().processor
42-
var camera: Camera = Camera(MockHardware())
29+
fun initAll() {
30+
hardwareComponents.values.forEach { it.init() }
31+
}
4332

44-
// Path follower
45-
var follower = Follower(this)
33+
// Property-style access for known components
34+
val mecanumBase get() = get<MecanumBase>()
35+
val pinpoint get() = get<Pinpoint>()
36+
val launchServos get() = get<LaunchServos>()
37+
val flywheel get() = get<Flywheel>()
38+
val camera get() = get<Camera>()
39+
val batteryMonitor get() = get<BatteryMonitor>()
4640

47-
init {
48-
when (botType) {
49-
BotType.BASIC_MECANUM_BOT -> {
50-
// Initialize hardware components for Basic Mecanum Bot
51-
mecanumBase = MecanumBase(hardwareMap)
52-
batteryMonitor = BatteryMonitor(hardwareMap)
53-
}
41+
// Follower is lazily initialized (only if accessed)
42+
// and will error if localizer or mecanumBase is missing
43+
val follower: Follower by lazy {
44+
Follower(
45+
localizer = get<Pinpoint>()!!,
46+
mecanumBase = get<MecanumBase>()!!,
47+
)
48+
}
5449

55-
BotType.MECANUM_BOT -> {
56-
// Initialize hardware components for Basic Mecanum Bot
57-
mecanumBase = MecanumBase(hardwareMap, Drive.MOTOR_CONFIG)
58-
localizer = Pinpoint(hardwareMap)
59-
batteryMonitor = BatteryMonitor(hardwareMap)
60-
}
50+
// Companion for builder and fromType
51+
companion object {
52+
fun builder() = Builder()
6153

62-
BotType.GOBILDA_STARTER_BOT -> {
63-
// Initialize hardware components for GoBilda Starter Bot
64-
mecanumBase = MecanumBase(hardwareMap)
65-
localizer = Pinpoint(hardwareMap)
66-
batteryMonitor = BatteryMonitor(hardwareMap)
67-
flywheel = Flywheel(hardwareMap)
68-
launchServos = LaunchServos(hardwareMap)
69-
aprilTagProcessor =
70-
AprilTag(
71-
CameraConstants.POSITION_CM,
72-
CameraConstants.ORIENTATION_RAD,
73-
).processor
74-
camera =
75-
Camera(
76-
hardwareMap,
77-
processors = arrayOf(aprilTagProcessor),
78-
)
54+
fun fromType(
55+
type: BotType,
56+
hardwareMap: HardwareMap,
57+
): Bot =
58+
when (type) {
59+
BotType.MECANUM_BOT ->
60+
builder()
61+
.add(MecanumBase(hardwareMap))
62+
.add(Pinpoint(hardwareMap))
63+
.add(BatteryMonitor(hardwareMap))
64+
.build()
65+
BotType.GOBILDA_STARTER_BOT ->
66+
builder()
67+
.add(MecanumBase(hardwareMap))
68+
.add(Pinpoint(hardwareMap))
69+
.add(LaunchServos(hardwareMap))
70+
.add(Flywheel(hardwareMap))
71+
.add(Camera(hardwareMap, processors = arrayOf(Camera.createAprilTagProcessor())))
72+
.add(BatteryMonitor(hardwareMap))
73+
.build()
74+
BotType.CUSTOM -> throw IllegalArgumentException("Use Bot.builder() to create a custom bot")
7975
}
76+
}
77+
78+
class Builder {
79+
private val components = mutableMapOf<Class<out HardwareComponent>, HardwareComponent>()
80+
81+
fun <T : HardwareComponent> add(component: T): Builder {
82+
// TODO: Possibly allow interfaces such as Localizer vs Pinpoint
83+
components[component::class.java] = component
84+
return this
8085
}
86+
87+
fun build(): Bot = Bot(BotType.CUSTOM, components.toMap())
8188
}
8289
}

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

Lines changed: 0 additions & 27 deletions
This file was deleted.

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

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,60 @@ import com.qualcomm.robotcore.hardware.VoltageSensor
88
* Provides battery voltage information for power management and telemetry.
99
*/
1010
class BatteryMonitor(
11-
hardwareMap: HardwareMap,
12-
) {
13-
private val voltageSensors: List<VoltageSensor> = hardwareMap.voltageSensor.toList()
11+
private val hardwareMap: HardwareMap,
12+
) : HardwareComponent {
13+
override val name = "BatteryMonitor"
14+
15+
private lateinit var voltageSensors: List<VoltageSensor>
16+
17+
override fun init() {
18+
voltageSensors = hardwareMap.voltageSensor.toList()
19+
}
1420

1521
/**
16-
* Gets the lowest voltage reading from all available sensors.
17-
* @return Minimum voltage in volts, or 0.0 if no valid readings
22+
* Minimum voltage in volts, or 0.0 if no valid readings
1823
*/
19-
fun getVoltage(): Double =
20-
voltageSensors
21-
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
22-
.minOrNull() ?: 0.0
24+
val voltage: Double
25+
get() {
26+
return voltageSensors
27+
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
28+
.minOrNull() ?: 0.0
29+
}
2330

2431
/**
25-
* Gets the highest voltage reading from all available sensors.
26-
* @return Maximum voltage in volts, or 0.0 if no valid readings
32+
* Maximum voltage in volts, or 0.0 if no valid readings
2733
*/
28-
fun getMaxVoltage(): Double =
29-
voltageSensors
30-
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
31-
.maxOrNull() ?: 0.0
34+
val maxVoltage: Double
35+
get() {
36+
return voltageSensors
37+
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
38+
.maxOrNull() ?: 0.0
39+
}
3240

3341
/**
34-
* Gets average voltage from all valid sensors.
35-
* @return Average voltage in volts, or 0.0 if no valid readings
42+
* Average voltage in volts, or 0.0 if no valid readings
3643
*/
37-
fun getAverageVoltage(): Double {
38-
val validVoltages =
39-
voltageSensors
40-
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
44+
val averageVoltage: Double
45+
get() {
46+
val validVoltages =
47+
voltageSensors
48+
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
49+
return if (validVoltages.isNotEmpty()) validVoltages.average() else 0.0
50+
}
4151

42-
return if (validVoltages.isNotEmpty()) {
43-
validVoltages.average()
44-
} else {
45-
0.0
52+
/**
53+
* List of valid voltage readings
54+
*/
55+
val allVoltages: List<Double>
56+
get() {
57+
return voltageSensors
58+
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
4659
}
47-
}
4860

4961
/**
5062
* Checks if battery voltage is critically low.
5163
* @param threshold Minimum acceptable voltage (default: 11.0V)
5264
* @return True if voltage is below threshold
5365
*/
54-
fun isVoltageLow(threshold: Double): Boolean = getVoltage() < threshold
55-
56-
/**
57-
* Gets all valid voltage readings for debugging.
58-
* @return List of valid voltage readings
59-
*/
60-
fun getAllVoltages(): List<Double> =
61-
voltageSensors
62-
.mapNotNull { sensor -> sensor.voltage.takeIf { it > 0.0 } }
66+
fun isVoltageLow(threshold: Double): Boolean = voltage < threshold
6367
}

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

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,58 @@ package pioneer.hardware
22

33
import com.qualcomm.robotcore.hardware.HardwareMap
44
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName
5+
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit
6+
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit
7+
import org.firstinspires.ftc.robotcore.external.navigation.Position
8+
import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles
59
import org.firstinspires.ftc.vision.VisionPortal
610
import org.firstinspires.ftc.vision.VisionProcessor
11+
import org.firstinspires.ftc.vision.apriltag.AprilTagGameDatabase
12+
import org.firstinspires.ftc.vision.apriltag.AprilTagLibrary
13+
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor
714
import pioneer.constants.HardwareNames
815
import kotlin.jvm.java
916

1017
class Camera(
11-
hardwareMap: HardwareMap,
12-
name: String = HardwareNames.WEBCAM,
13-
processors: Array<VisionProcessor> = emptyArray(),
14-
) {
15-
val portal: VisionPortal =
16-
VisionPortal
17-
.Builder()
18-
.setCamera(hardwareMap.get(WebcamName::class.java, name))
19-
.apply {
20-
if (processors.isNotEmpty()) {
21-
addProcessors(*processors)
22-
}
23-
}.build()
18+
private val hardwareMap: HardwareMap,
19+
private val cameraName: String = HardwareNames.WEBCAM,
20+
private val processors: Array<VisionProcessor> = emptyArray(),
21+
) : HardwareComponent {
22+
override val name = "Camera"
23+
24+
private lateinit var portal: VisionPortal
25+
26+
override fun init() {
27+
portal =
28+
VisionPortal
29+
.Builder()
30+
.setCamera(hardwareMap.get(WebcamName::class.java, cameraName))
31+
.apply {
32+
if (processors.isNotEmpty()) {
33+
addProcessors(*processors)
34+
}
35+
}.build()
36+
}
37+
38+
companion object {
39+
fun createAprilTagProcessor(
40+
position: Position = Position(DistanceUnit.CM, 0.0, 0.0, 0.0, 0),
41+
orientation: YawPitchRollAngles = YawPitchRollAngles(AngleUnit.RADIANS, 0.0, 0.0, 0.0, 0),
42+
): VisionProcessor {
43+
val library: AprilTagLibrary =
44+
AprilTagLibrary
45+
.Builder()
46+
.addTags(AprilTagGameDatabase.getCurrentGameTagLibrary())
47+
.build()
48+
49+
val processor: AprilTagProcessor =
50+
AprilTagProcessor
51+
.Builder()
52+
.setTagLibrary(library)
53+
.setCameraPose(position, orientation)
54+
.build()
55+
56+
return processor
57+
}
58+
}
2459
}

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,24 @@ import com.qualcomm.robotcore.hardware.HardwareMap
77
import pioneer.constants.HardwareNames
88

99
class Flywheel(
10-
hardwareMap: HardwareMap,
11-
name: String = HardwareNames.FLYWHEEL,
12-
) {
13-
private val flywheel: DcMotorEx = hardwareMap.get(DcMotorEx::class.java, name)
10+
private val hardwareMap: HardwareMap,
11+
private val motorName: String = HardwareNames.FLYWHEEL,
12+
) : HardwareComponent {
13+
override val name = "Flywheel"
1414

15-
val velocity
16-
get() = flywheel.getVelocity()
15+
private lateinit var flywheel: DcMotorEx
1716

18-
init {
17+
var velocity
18+
get() = flywheel.velocity
19+
set(value) {
20+
flywheel.velocity = value
21+
}
22+
23+
override fun init() {
24+
flywheel = hardwareMap.get(DcMotorEx::class.java, motorName)
1925
flywheel.mode = DcMotor.RunMode.STOP_AND_RESET_ENCODER
2026
flywheel.mode = DcMotor.RunMode.RUN_USING_ENCODER
2127
flywheel.zeroPowerBehavior = DcMotor.ZeroPowerBehavior.FLOAT
2228
flywheel.direction = DcMotorSimple.Direction.FORWARD
2329
}
24-
25-
fun setSpeed(velocity: Double) {
26-
flywheel.setVelocity(velocity)
27-
}
2830
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package pioneer.hardware
2+
3+
interface HardwareComponent {
4+
val name: String
5+
6+
fun init()
7+
}

0 commit comments

Comments
 (0)