Robots and parts
A robot is a tree of components, each with its name, settings and place in the robot's life.
Parts are components
Every part of the robot is a Component: a subsystem like the intake, or a piece of one, like the shooter’s latch.
A component is created with its parent, and that makes it a part of the parent:
class Shooter(parent: Component) : Component(parent) {
val latch = PositionServo(this, "latch")
val flywheel = Motor(this, "flywheel")
val kV by config(0.00036) // the setting shooter.kV
}
At the top of the tree is the Robot:
class MyRobot(host: RobotHost) : Robot(host) {
val localizer = PinpointLocalizer(this) // created first, so it updates first
val drive = MecanumDrive(this)
val shooter = Shooter(this)
val leftFlywheel = Flywheel(this, "left") // two of the same kind need names
val rightFlywheel = Flywheel(this, "right")
}
The opmode creates the robot (see Opmodes), and with it all of its parts.
Names and paths
A component’s name is its class name, starting with a small letter: a Shooter is shooter. Two parts of the same
parent can’t have the same name, so the second Flywheel needs one of its own, Flywheel(this, "right"). Names may
have letters, digits, _ and -.
The names from the robot down to a part make its path: the shooter’s latch is shooter.latch. The robot’s own name
isn’t part of it, which keeps paths short. Paths show up everywhere:
- Settings of a part start with its path:
val open by config(0.3)in the latch is the settingshooter.latch.open(see Settings). Settings declared in the robot class itself start withrobot. - Hardware parts like motors and servos are named in the robot configuration on the Driver Station by their name,
or by the
devicegiven to them:PositionServo(this, "servo", device = "claw")is the partclaw.servo, and the servoclawin the robot configuration. - Log messages of a part start with its path.
A part’s life
A component takes part in the robot’s life by overriding three functions:
class Intake(parent: Component) : Component(parent) {
val roller = CRServo(this, "roller")
var on = false
override fun init() { /* once, before the first loop: set up what needs the hardware */ }
override fun update() {
// every loop, before robot code runs: read sensors, apply what was asked for
roller.power = if (on) 1.0 else 0.0
}
override fun stop() { /* once, at the end: leave the hardware safe */ }
}
init()runs once, at INIT. Hardware is available from here on, not in constructors: create parts in the constructor, use their devices frominit().update()runs once every loop, before robot code (handlers, actions, everyTick blocks) runs, so robot code sees fresh readings.stop()runs once when the opmode ends. Motors and servos the platform provides stop by themselves.
Parents go before their parts, and siblings in the order they were created: create the localizer first, so everything
that drives by the robot’s pose sees the new one in the same loop. stop() goes the other way around.
Reporting from a part
A component can show lines on the Driver Station, record values for the live graphs, and write to the log (see Telemetry):
override fun update() {
show("Intake", if (on) "on" else "off")
graph("roller power", roller.power)
}
warn("...") reports a problem worth fixing that doesn’t stop the robot, like a doubtful setting: the Driver Station
shows it during INIT, and it goes to the log.
Actions
Most parts can only do one thing at a time: an arm can’t go up and down at once, and a drivetrain can’t drive to two places. Functions that make a part do something over time take it over:
suspend fun Arm.moveTo(angle: Angle) = takeOver("move to $angle") {
target = angle
waitUntil { isAt(angle) }
}
Taking over stops whatever action had the part before, so the latest request wins. isBusy, currentAction and
cancelAction() tell about and stop the action in control. Robot code over time
explains actions in full.
One app for several robots
A team with more than one robot, or a practice bot next to the competition robot, can have one app for all of them. Which robot it is comes from the robot configuration picked on the Driver Station:
object TeamRobots : RobotChoice<TeamRobot>(
"competition" to ::CompetitionRobot,
"practice" to ::PracticeRobot,
)
Opmodes then create whichever robot this one is with val robot by robot(TeamRobots), and the settings file can have a
section for each robot (see Settings). A robot configuration the app
doesn’t know fails INIT, listing the ones it does. An opmode for one of the robots only, like a tuning opmode for a
mechanism the other robot doesn’t have, uses robot(TeamRobots.only<CompetitionRobot>()), which fails INIT on the
other robot, saying so.
TeamRobot is what both robots have in common: a base class with what the shared opmodes need, where each robot
says what its parts are:
abstract class TeamRobot(host: RobotHost) : Robot(host) {
abstract val localizer: Localizer
abstract val drive: OmniDrivetrain
abstract val follower: PathFollower
abstract val intake: Intake
}
class PracticeRobot(host: RobotHost) : TeamRobot(host) {
override val localizer = PinpointLocalizer(this)
override val drive = MecanumDrive(this)
override val follower = PathFollower(this, drive, localizer)
override val intake = RollerIntake(this)
}