Alright. Thank you!
Subsystems can appropriately be a bit smarter than that. As an example for last year, some of the methods for a lift that took in game pieces and also carried a scoring mechanism might have been:
- gotoCargoIntakeHeight()
- gotoCargoShipHeight()
- gotoCargoRocketL1Height()
- gotoCargoRocketL2Height()
- gotoCargoRocketL3Height()
- gotoHPIntake+L1Height()
- gotoHPRocketL2Height()
- gotoHPRocketL3Height()
These methods would move the lift to the appropriate height, then terminate. It would still be up to commands to call these methods to move the lift, and composite commands to then do the scoring.
Your post articulates perfectly what I believe the design should be, in fact that is exactly how we do it.
The Subsystem owns all the components that are needed to safely do it’s job (the sensors and actuators), but it’s up to the Commands to ask the subsystem to do things, and to report back the status of said request.
Commands are the objects out there that are performing the actions, and by that I mean, the subsystem pieces (Arm, Intake, whatever) aren’t actually doing much on their own without a command asking it to do the work.
Nothing inherently wrong with doing this, but, typically in Command-Based, especially with the paradigm shifts with the new rewrite, these would be Commands, not methods in the subsystem. The Subsystem would just have a generic goToHeight(double height) type of method with logic to validate input (check allowed ranges and such), and you’d set the setpoint in Commands for the various positions you wanted.
You’d likely do this either by creating a command for each height, or creating one Command that also takes in a height to the constructor. In either case probably pulling the height setpoints themselves from the RobotMap or Constants Class so they are labelled and in one place to be quickly referenced and changed. I’d also argue that the former method would be more common for the prior Command Based framework, with the latter being preferable with the new framework.
Of course, you are the conductor of your own robo-Orchestra!
What I try to teach my students is readable code. So in our piece, we’d have a a Command called CargoRocketHeight3, that called subsystem.gotoCargoRocketL3Height(), which would act as a wrapper function around a private subsystem method that would call goToHeight(double height).
The school system where I help doesn’t have really strong computer scientists or programmers teaching the programming and computer science related courses. So we focus on simplicity and basics such that any code written by one student MUST be written in a way that is easily understood and extended by any other student on the team.
I agree with what you said though, do what works for you. When I teach my kids what is the “right” way to do things, there are two criteria: 1) Is it readable code? 2) Does it get the job done?
Nothing else really (greatly) matters for small scale software like these robots.
Like I said, nothing inherently wrong with the approach. More highlighting the approach the framework tends to lean towards, particularly in many of its provided examples.
I will also admit that my goals and the students I mentor’s goals aren’t the same. Theirs is mostly to program a robot and have fun while doing it. Mine is to teach the bare basics of programming, and computer science to prepare them to understand if they want to pursue it as a career or not, and have fun while doing it. If that means Robotics, well the fun part just gets a bit easier! So I do tend to teach from a much broader context than just robotics, with a lot more why and context behind ideas than most. Gotta go make things harder on myself than I need to and all that, right?
For example, I had a blast teaching the basics of Boolean logic diagrams, and how we could use them to mock up Command Trigger conditions from JoystickButtons and other sensor states passed into Trigger/Button objects. Then, translate that directly into our ButtonBindings through the and() or() and negate() methods provided by the Trigger class. Necessary for robots, no, but for a greater understanding of what Computer Science is, absolutely.
OK, I think it’s a language difference. To me, moving to a predefined position using an encoder is a “smart” action. I certainly agree that the subsystem shouldn’t do anything except initialize and possibly maintain itself unless directed by a command or interrupt. As I look at it, the subsystem should manage the “how do we do this basic task” and the commands should tell the subsystem when to do them and build them into composite tasks.
In that situation, I would definitely not have separate methods; the subsystem could expose a setHeight(double) method, and the command could accept an ElevatorHeight enum for readability.
The one confusion I have seen when commands appear to be little more than wrappers is that it obfuscates the difference between a command and a subsystem method.
e.g. if I have a subsystem method elevator.goToRocketLevel3(), and a command GoToRocketLevel3 which just calls elevator.goToRocketLevel3(), students start wondering what the purpose of the Commands are. It seems convoluted and redundant initially. It takes some of them a while to realize the other benefits such as the scheduling and the ability to do CommandGroups and such. (I have some students that just learned Java this fall, and it is a rapid acceleration from “hello world” on your PC to an implementation of the command pattern run via periodic function calls on a real-time processor!)
A Command called GoToRocketLevel3 which calls a subsystem elevator.GoToHeight(x) would make that difference more evident. Though I don’t necessarily argue that one approach vs the other is more correct.
It took me fifteen years to get used to methods without arguments, and now arguments are in fashion.
But I do like @Oblarg’s idea about using enums (or numeric constants defined by the class).
I’m still trying to figure out all these new-fangled Java expressions. Templates? Lambdas??
Lambdas were added in Java 8 in March 2014 - it’s been nearly 6 years since their introduction. Generics were added in Java 5 in September 2005 - that’s older than some students competing on FRC teams this season!
oof. Guess I need to bring out the textbooks.
That also explains it somewhat - at my current job, I’ve been working with Java since 2014, but we’re using Java 7 due to <insert_irrelevant_reasoning_here>.
The large amount of boilerplate required to write a simple command wrapper on a subsystem method was a driving motivation behind the new command framework’s emphasis on inline-able commands (e.g. InstantCommand, StartEndCommand, FunctionalCommand, etc).
For something like goToHeight, if the command does not add any code, it is probably a good idea to simply inline it with InstantCommand. In fact, one often doesn’t even need to define the command:
button.whenPressed(() -> elevator.goToHeight(Elevator.kMaxHeight), elevator);
BASH is my favorite language!.. ugh…
I too will agree with this, I like the idea of an enum instead of just the static values in the Constants class.
This method is really good for fetching errors. We currently use it to get motor faults and check if limit switches are pressed, update LED states, etc. It could be used for state-machines as well, but we haven’t really felt like there is an advantage for us to go that way just yet.
I used to try to emulate procedural robot code (what I had done in VEX) in FRC Command Subsystem. It (technically) worked, but I spent hours fighting the implications that Command Subsystem would have on my routines. If you are new to FRC, I might check out going with one of the other templates until you can learn Command Subsystem in the offseason. In my experience, structuring in Command Subsystem is very different from other languages that I was used to, so the adjustment time was fairly significant.
Since it is Week 6, I would try a simple project in some of the other ones, and do whatever you are most comfortable with. I have learned the hard way that trying to restructure your entire robot program this late into the season results in bad long-term effects, unless you are able to spent tons of time working on the project. We are having people on my team what they feel most comfortable with, and then I, along with two other students, are going to adopt it for Command Subsystem since there isn’t enough time for everyone to rethink how robot code is written.
I agree that enums are the way to go for things like various scoring heights. However, I would also write the subsystem API in terms of the enum. That is:
setHeight(ElevatorHeight.ScoreHi)
rather than:
setHeight(double)
The height (in inches, encoder clicks… whatever) is a property of the subsystem and may need to adjust multiple times during development. The subsystem should define the enum to encapsulate these values and the rest of the system should just know the enum values.
Steve
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.