6. RobotPy Patterns
Three parts of Python that normal tutorials skip, but our code uses everywhere: lambdas, button decorators, and yield.
Summary
Lambdas: read the value again later
Button(lambda: self.robot.rumble_d1)
lambda: expr is a small function without a name. It is the same as:
def nameless():
return self.robot.rumble_d1
Why the lambda? Button(self.robot.rumble_d1) would freeze the startup value forever. Button(lambda: self.robot.rumble_d1) hands over a function, and the button calls it every frame. It always reads the current value.
Decorators: register a function
@self.driver1.A.whenPressed
def _():
self.robot.poseEstimator.set_yaw(0.0)
A line @x above a function runs once at startup: it calls x(your_function) right away. whenPressed stores your function. The command scheduler calls it later, when the driver presses A.
Two details you will see in oi.py:
- The function is named
_on purpose. It means: only the decorator uses this name. whenHeldruns the function every frame while the button is down.whenReleasedruns once when it comes up.
Coroutines: a command as a timeline
A function with yield inside is a generator. Its lifecycle:
- You call it. Nothing runs yet. You get a generator object.
- Each time the scheduler steps it, the function runs up to the next
yield, then pauses there. - When the function returns, the scheduler removes the command.
@self.rumble_button_d1.whenPressed
def _():
timer = Timer()
timer.start()
self.driver1.setRumble(1)
while not timer.hasElapsed(0.5):
yield # stop here, continue next frame
self.driver1.setRumble(0)
“Rumble for half a second” reads top to bottom, and the robot keeps driving during the wait. Many of our command bodies are while True: ... yield: do some work, wait a frame, repeat forever (the drivetrain default command is built this way).
Do not use time.sleep() in robot code. Sleep freezes the whole robot loop. Use yield to wait one frame at a time.
Try it
Run the code. Then add a second decorated function that prints something else, and call a_button.press() again.
The code operates in your browser. The page sends nothing to a server and saves your work on this computer. Press Ctrl+Enter to run it.
Exercises
6.1 Lambdas read the live value
A lambda re-reads its variables every time it is called. That is why Button(lambda: ...) works: the button asks a fresh question every frame.
PART A: make a lambda status. It returns 'spinning' when motor_speed > 0.1, else 'stopped'.
PART B: make a lambda healthy. It returns True when -60 < motor_speed < 60, else False.
The code operates in your browser. The page sends nothing to a server and saves your work on this computer. Press Ctrl+Enter to run it.
6.2 Step a generator until it ends
A function with yield is a generator. The scheduler advances it one yield per frame with next(), and it ends with StopIteration.
Write count_frames(gen). Step the generator until it ends, and return how many times next() succeeded.
The code operates in your browser. The page sends nothing to a server and saves your work on this computer. Press Ctrl+Enter to run it.
6.3 Predict, then measure
This is the while ... yield pattern from the command bodies in oi.py.
PART A: change intake_until_full so the hopper fills in 100 frames instead of 5.
PART B: set prediction to the number of frames a frame counter will report. Make the prediction before you run the checks.
The code operates in your browser. The page sends nothing to a server and saves your work on this computer. Press Ctrl+Enter to run it.
After the lessons
Open robot/oi.py with a mentor. Trace one button binding from the press to the motor command. You now know every Python tool it uses.