Our team has been using Lua for the past couple years now, and while that has been great for many reasons, our autonomous code in particular is a lot better because of coroutines.
We used command-based programming on our team for about a decade, both WPILib’s old command system and a new custom one based on 254’s. It was ok, but moving our autonomous code to coroutines has been a revelation and we are never going back to commands.
With Python becoming an officially supported language, a lot more teams have access to coroutines now and I strongly encourage you to use them for autonomous. It’s the single most impactful thing we’ve done for our code team in years.
As someone who’s team does something similar. You are effectively just recreating commands in your own style. Also, that autonomous state machine in your blog post isn’t clear what it’s doing, and is arguably more work then the fluent style that command based provides.
Here’s a real, non-article example of a complicated auto routine for driving onto the charge station:
local driveForwardSmartEngage = FancyCoroutine:new(function()
::restart::
print("Starting smart engage")
while Drivetrain:pitchDegrees() < 11 do
print("Driving while waiting for pitch to drop...")
Drivetrain:autoDrive(0.5, 0)
coroutine.yield()
end
print("reached target")
print("Driving 34 inches")
local driveUp = driveNInches(34, 0.4)
while not driveUp.done do
local totalDistance = driveUp:run()
if Drivetrain:pitchDegrees() < 11 and totalDistance < 20 then
goto restart
end
coroutine.yield()
end
Brakes:down()
armBalance:reset()
armBalance:runUntilDone()
end)
You can see that we have logic to revert to an earlier part of the process if we got a spurious sensor reading. You can also see that our driveNInches “command” can return a value on each execute to report how far it’s gone. Commands can only be so “fluent” compared to the control flow constructs of an actual programming language.
Yeah, but now you’re teaching Lua with gotos… (Which, yes, I know, Python is supported now blah blah blah).
We’ve moved to having state enums inside each subsystem and having the subsystem “chase” a target state member variable in periodic() (typically with P/PD loops). The commands exist solely to change target states, and don’t explicitly set any motors/solenoids/etc, and this has worked very well for us. Most commands are just InstantCommands now, which you can static import a short-hand helper if you so desire, or create a class of say ElevatorCommands with static Command generators and do addCommands(ElevatorCommands.goToHeight(12), seq(IntakeCommands.intakeCube(), ... and so on.
Cool thoughts in general here, but I’m not convinced the “this is the way everyone should be doing it period” attitude is the way to go here.
Yeah I agree. I’m not going to “fanboy” that command-based is the end-all-be-all of FRC programming paradigms, but i dont think LUA(or python) with GoTos is the right step forward either. I like @jtrv’s approach and have done something similar myself.
Having subsystems that chase a target state and commands to change the target is a great pattern, and that’s mostly what our auto coroutines are doing too. Coroutines just make the control flow and logic of autonomous easier to express, and remove the need for all the meta-commands like SequentialCommand and ParallelCommand.
(If you’re getting hung up on the use of goto, you’re missing the point - you can use loops or any other control flow you desire.)
I think that an even better step above Coroutines is Rust async/await. Await allows for functions to yield back to the executor without the complicated while loops and yields. The Embassy framework already does this for embedded environments and could be adapted to run on the robot.
These are all sugared with factory functions in modern Command-based. It seems like you’ve only had experience with the legacy version of the library; I suggest you take some time to look at the more recent versions.
I have, and I would still much rather use the control flow constructs of our actual programming language. Why teach the students a whole new proprietary system when they can just use the language as is?
If I had a nickel for every time I saw someone build an entirely custom system around a language rather than use a language better suited for their task I’d have 2 nickels. Which isn’t a lot but it’s weird that it’s happened twice.
(I’m not saying Lua is a better suited language, just referencing a meme)
It’s not proprietary; the source is there for anyone to read.
Coroutines (and Rust async/await…) patterns both require substantially more knowledge of potential edge-cases than does command-based, which is entirely single-threaded and has a very simple underlying semantics.
I would probably be rich. Ever seen a diehard Rust programmer?
I’m failing to see the advantage here. Not only does this look almost identical to command-based, but it also seems to break the loop structure of the RIO. It’s also worth noting that the command-based code written in the article has significant, easy improvements.
Coroutines may have each have their own stack, but only one coroutine is executing at a time. A lot of the difficulties with multithreaded programming don’t apply to Lua coroutines.
In the Java command library, the state of the command is stored in the Command object, and the control is passed when the execute function returns. In the coroutine approach, the state is stored in the coroutine’s stack, and control is passed when coroutine.yield() is called. With this in mind, I don’t think replacing multiple simultaneously running commands with multiple simultaneously running corountines is a big change in complexity (where simultaneously means “within the same robot timestep”).
When teaching FRCFIRST Robotics Competition programming, one of the harder things to get students to realize is that they shouldn’t use any native loops, and that the execute function (or it’s equivalent for functional commands) runs every timestep. I’d imagine that this pattern could be less confusing for new programmers (even though coroutines sound like an advanced concept).
That said, I can also forsee some tricky debugging when people misuse coroutines (for example, calling yield only in some branches and not others, or calling coroutines from a coroutine in a confusing way). I disagree with the presentation in the OP of coroutines as a panacea.
It is a different way of thinking compared to what a student might get taught in a programming class but I found in general they get it.
I’ve had great results covering this paradigm as a state machine (as mentioned earlier) and found students can get their heads around it. I describe it along the lines of “the loop is somewhere else - we’re just writing small functions that get called by it” sort of approach.
In practice I’ve found it to be just fine, for a couple reasons:
For total beginners, you can just say “if you use a loop, always put a coroutine.yield() at the end”. They don’t need to understand why right away but it keeps things ticking.
In many cases we can just use a little runUntilDone utility that does the loop internally. This makes many autonomous routines look like exactly the same simple sequence you’d have in the command-based model (example).
In general, the ways my students have struggled with coroutines are no worse than the ways they struggled with commands, but they are more comfortable diving in and writing code because it feels more familiar. As I said in my article, if you just think of it as a “pause”, it is pretty easy to get started with.
(By the way, I’m not claiming coroutines are a “panacea”, but I think they cut away most of the artificial complexity so the real complexity of autonomous programming is clearer.)
Interesting approach! I see from the code that it’s a very straightforward transformation too.
(Yielding at the top of the loop instead of the bottom is a good idea from a pedagogy perspective…easier for students to see if they’re following the rule. Smart, I like it )