Command-Based with persistent command?

We’ve replaced the IterativeRobot code for our 2018 'bot and built a new command-based version of the code for our (quickly-approaching!) off-season competition and we’ve come a long way but have one tough glitch…

We have a subsystem that needs constant attention and updating. We have commands to control and move it, but when those commands end, the updating stops. In case that’s not clear… in autonomous, we issue an “adjust subsystem” command and then to a sequential “drive this distance” command, but when the “adjust subsystem” command ends, so does active control of that subsystem.

This subsystem needs to be actively controlled 100% of the time. In Auto and Teleop, we will be adjusting its position.

Where and how would this “background” updating need to happen? Do we need to move the babysitting outside of the command structure to the underlying TimedRobot framework? We’re programming in C++ in case someone wants to give a specific example.

Thanks in advance!

You can either use a default command, or else put code in the subsystem’s “periodic” block. I advise defaulting to the former, and only using the latter if doing the former is either not possible or results in unconscionably ugly code, since the more things you have being done outside of commands, the harder the control flow of your code is to follow.

If I understand you correctly, I concur that the default command is the way to do this. Here’s what we did with the default command with our summer boot camp rewrite of our 2017 gear handler. It’s in Java, not C++.

1 subsystem: Scooch
3 commands:

  • CalibrateScoosh: Run at startup or if we lose calibration.
  • HangGear: Push the gear out onto the peg
  • LoadHoldGear: Put the scoosh in position to load a gear if one is not detected, or in the “hold” position if a gear is detected in the gear pocket.

LoadHoldGear was the default command. The idea was that CalibrateScoosh and HangGear each took a few seconds to run. When one of these terminated, the command-based monitor noticed that the Scoosh subsystem had no commands running, and automatically started LoadHoldGear. When the operator pushed the buttons to start HangGear or CalibrateScoosh, the existing command (usually LoadHoldGear) would be terminated, and the new command would start.

In your case, set up adjustSubsystem as the default command. When driveThisDistance starts, adjustSubsystem would be killed because only one running command can require a subsystem at a time. When driveThisDistance finishes, adjustSubsystem would automatically restart. If you want the “adjustSubsystem” logic to work **while **executing a driveThisDistance, the best solution is likely to move that logic to the subsystem itself, and call it from both the commands.

I agree.

Subsystem

Command

Thanks for the replies, that was what we were missing. Now our subsystem babysitting is done in the default command and our specific commands only set target values for the babysitter to work with.

Now, we have this working great in teleop, we can cycle the subsystem to each position over and over with button presses. We are outputting to the console so we can see the transitions back and forth between specific and default commands.

BUT… our subsystem is not picking up the default command in autonomous. The specific command runs and exits, but the default command doesn’t start back up. All of the commands are the same in teleop (i.e. SetSubSystem and MaintainSubSystem), with our SetSubSystem being triggered from a button in teleop.

Any ideas why our subsystem is not picking up the default command after a specific command runs?

A bit of a guess, but are you using CommandGroups in Autonomous? I know that one of the comments in the Commands/CommandGroups templates says that CommandGroups “Require” the sum of their sub-command’s "Require"s. If this is the case, then the CommandGroup would still be requiring the subsystem, even though the sub-command that specifically uses that subsystem has finished.

I will do a bit of digging on this though. If this is the case, I don’t immediately have any easy solutions, but perhaps one will present itself in research.

There are two ways to specify the default command. The preferred is to have an initDefaultCommand function defined as shown here. An alternate way is to call setDefaultCommand() as shown in the example, most commonly in one of the initialization routines. IIRC, calling setDefaultCommand() later will override the initial value of the default command, but this is a better way of creating a problem (by confusing yourself and most people who would try to help you) than of solving a problem.

Back to your question: my guess is that you either did not create initDefaultCommand(), or did not actually call setDefaultCommand(). It is also possible that you later overrode the default command with an effectively null command. If it appears you did one of these, posting your code* so people could have a look at it would help.

  • Snippets are rarely helpful, because too often the problem is somewhere other than where you’re staring - posting a link to a github or similar repository allows people to dig into the less obvious problems.

If your command is just setting a position value, then it wouldn’t need to require the subsystem and you can leave the default command running all the time and have nothing require the subsystem except for things like manual over rides. that is what we do for our arm control code. That allows the setting commands to be put into command groups without problems.

Gustave is correct about how requires behaves for command groups and that is likely your issue.

I have worked with a couple of teams that have successfully used the technique described by Allen:

If that is not the issue, one other possibility is to break your autonomous command group up into several commands or command groups and have one start the next before it terminates. You can even have a command choose to start a different command(s) based on conditions and thereby implement an autonomous state machine. The default command for the subsystem would then execute when any autonomous state command is running that does not require the subsystem.

Try the easier approach Allen suggests first, this state machine command thing can get messy. For example, making sure you cancel the right thing(s) in teleopInit can get a bit dicey.

Steve

Yes, we’re using a command group for our auto routines - as makes sense, as we have several steps to accomplish in each routine - is there another and/or preferred way? This seemed to be much of the reason of investing in the command-based methodology. How strange that the “requires” are additive like that.

Considering that the same exact commands are being called in teleop and auto, but only failing in auto makes me think that the command-group complication is the root-cause.

@GeeTwo - we are specifying the default command in the subsystem InitDefaultCommand method.

Code is at: https://github.com/Robodox-599/Emma_CommandBased

@AllenGregoryIV, good point about being able to just change the variable on the fly and not require the subsystem… is that compatible with a command that has to wait until the subsystem achieves it’s commanded position? For example, if we need to “raise the lift” and only then “move forward” - can we still keep the command from completing until the subsystem is done? I’m trying to think if there will be some repercussions that are not obvious or if we have to take some care not to require the subsystem prematurely. Is there a way to ask a subsystem if it’s currently in a non-default command and hold of on taking that subsystem over?

Wow, thanks for the replies!

I’m certain that the problem lies in the apparent “grabby” nature of the command group since we are using exactly the same commands in teleop and it works flawlessly. We’ll try to do the on-the-fly parameter tweak without the “requires” and failing that we’ll break up our command group, ugly as that feels.

We’re going to be coding some more this afternoon and we’ll give some of these ideas a go.

Alternatively you can use the subsystem periodic block; for stuff that’s constantly done in the background it is totally sensible. You just don’t want to use it for anything particularly stateful.

Another possibility would be to simply call your default command explicitly, inside the command group but after the temporary subcommand completed. Perhaps not the neatest way, but simple.

Thanks again everyone for the replies and the help!

We ended up going with the “run the default command all the time” and only changing the subsystem target value in our auto routines. We were able to compete well at our off-season event, Beach Blitz, making it to the semi-finals.

Our autonomous routines, other than a couple of minor bugs worked very well. We are quite pleased with our transition to command-based programming.