Standardized State-Based Robot Control Vendor Dep

A bit of background first. My name is Malachi Bergman. I joined FIRST as a freshman in high school in 2016 on team 3534. After graduating high school in 2019, I continued my involvement as a programming mentor.

During my time programming FRC robots, I have went from Command-Based to a custom state based stucture that is not common to any other team. The reason for state-based in the first place is to have more control, but it comes with a lot of freedom, freedom that can easily cause disaster if you’re not careful.

But I started thinking to myself. What if I built a structure that allowed students to put their thoughts into code like Command-Based, but with the positives of statebased. I started thinking more broadly about the concept more than the actual code implementation.

My end goal became to get my hands off the actual code for the year. Allow students to program our robots entirely, there had to be a stucture that made that possible.

I wanted incoming freshman to be able to program with even more intuition than Command-Based.

I hope everyone can see the potential in this project that I’ve made a very rough vendor dependecy, but I did it none the less. I would really appreciate if some people that are state-based could come along side me and help me make this into something more.

Vendor Dep: StateBasedControl
2022 Example: StateBasedExample

I really think I have something here that could change programming difficulty for many teams. Please feel free to contact me. I am trying to get testers. If you would like to do so, I am very intersted in getting feedback.

2 Likes

Neat. Reminds me of magicbot, but without DI and with explicit phase switch statements + tables instead of being driven by annotations.

It’d be nice if there was a README showing a bare minimum class/state and how it’s used.

I can’t think of what you mean by DI.

EDIT: So I would think without DI makes better sense for students just learning programming. Annotations are nice though. I might have to consider annotations or something to make it less verbose.

Let me get back to you soon on that one.

I looked at magicbot. I am not familiar with it but I happend upon it before. I hope to put a README together quickly so the use and features of this project are clear.

Thank you for the valuable feedback!

Dependency injection - it’s a design pattern in object oriented languages where the objects a class depends on are passed in at construction time.

1 Like

Yeah, I agree, I’ve always had problems getting my students to really understand the DI aspects of using Magicbot.

I really like your thoughts here… I wonder if there is an hybrid approach.

When I’m working with a student they get the basics… Shooter is a subsystem… Shoot is a command. Check, check.

When it comes to implementing the Shoot command in the execute block doing something like…

public void execute() {
   Shooter.shoot();
}

Makes sense… No problem.

Well, don’t we need to tell the indexer to advance the balls (aka Cargo) into the Shooter? Again, no problem.

public void execute() {
   Shooter.shoot();
   Indexer.shoot();
}

Don’t we need to check that the turret is aimed at the target?
That the RPM on the shooter is within error tolerance?
What about the hood angle?
What about the 2nd cargo?
Do we even have a 2nd cargo on board?
Has the shooter RPM recovered from the first shot?
Ummm… think the robot is moving?

Mind blown.

The state framework could be very powerful in establishing all of these preconditions and making it understandable.

Otherwise we fall back on hacks like …

public void execute() {
        // pause briefly to allow turret to center
        // allow shooter to spin up
        if (!m_timer.hasElapsed(0.2)) {
            return;
        }
        DataLogManager.log("ShootLow Shot1.");
        RobotContainer.m_Indexer.shoot();

        if (!m_timer.hasElapsed(0.5)) {
            DataLogManager.log("ShootLow Shot2.");
            RobotContainer.m_Intake.start();
        }
    }

This example would be better decomposed into seperate commands, perhaps a shooter spin up command, an indexer advance command, and an intake command, with a command group sequencing them.

5 Likes

I’m bringing the following up to make sure the OP is aware of prior art in state machine APIs, since context and prior art is important to consider when doing rewrites.

In case the OP doesn’t already know, command-based is a wrapper around state machines. Commands are states, and command groups are hierarchical state machines. The main limitation is you can’t have cycles in your state transition diagram.

The most common cycle in FRC code is a subsystem sitting in an idle state until told to do something by an event. Command-based handles that by scheduling a command group with the Trigger class; the command group being created and scheduled is analogous to the cyclic state machine transitioning out of the idle state.

For better or worse, the actual state transition logic is abstracted away by inline functions that build the state machine. For example:

new RunCommand(() -> { do something; })
  .withTimeout(5.0)
  .andThen(() -> { do something else; })
  .until(() -> { some condition returns true; });

StartEndCommand would be your goto for doing something when you enter and exit a state; you give it lambdas to execute on start and end.

3 Likes

Totally agree on how to do it better with the current framework. I’m agreeing with the OP that it gets complicated quickly and ability to make it more clear and easier for a novice programmer is a worthwhile endeavor.

The concept of the execute() being called 20x a second is a tough to get into their brains. The decomposition suggested is spot on… why it is important to a novice? not obvious.

Although I would note that while making separate commands is the correct way, you shouldn’t actually make classes for them. The fluent API is a better way to express that intended decomposition.

The main thing students need to know is what the different built-in commands do (see Javadocs for classes in the command package) and what all the decorator functions are (see Command class Javadoc).

5 Likes

There’s more to it. If I want to perform an overall task (a sequence), in command-based, this is separated out into many separate command that are HARDWARE specific.

This structure abstracts the process away from the hardware (mostly, besides some logical checks, but no control directly from a sequence)

In this case, I can tell what the components in the subsystem are doing based on the state the subsystem is in, not necessary having to know what sequences are running, although those can be determined as well.

Thank you for your background information on Command-Based. I’ve said it before and I’ll say it again, I’ve tried to emulate many of the benefits of command-based into this alternative.

Not necessarily. Hardware dependencies can be injected into commands if you want - 449 used to do this to reuse commands across different subsystem implementations.

2 Likes

It’s probably worth bringing up that I used to be a state machine evangelist.
I went through a phase from 2015 to 2019 where I tried to throw hierarchnical state machines (HSMs) and the actor model at everything. Here’s my custom state machine API from back then:

And here’s a climb procedure based on the actor model:

They were both a reaction to command-based’s verbosity at the time (“A new class for every command? Ew”). If I were architecting that 2019 climb again, I’d have a central state machine (or command group factory) in the main robot class that uses command groups to glue subsystem function calls together. Modern command-based is essentially a way to string behavior together in a minimal-boilerplate way based on predicates. All the hardware-specific business logic lives in the subsystem. I’ll talk about that subsystem business logic abstraction next.

That’s one way to do it, but it’s not the only way. You can do subsystem hardware abstractions in command-based by adding factory functions to the subsystem class that return command sequences for things you want it to do (e.g., public CommandGroup shoot()). Those can be composed outside the subsystem without knowledge of the subsystem’s internal hardware.

You can’t know where the scheduler is at in the execution of that command group directly, but you can still provide getters on the subsystem that return useful information. That can include “am I at setpoint yet?” or “what is the current position?” for confirming you’ve reached a specific place.

If I understand you correctly, an example of what you’re saying would be the ability to check if a flywheel is idle, spinning up, or at setpoint from outside the subsystem for the purposes of coordination by the autonomous mode.

A state enum getter is a valid way to do that, but I think it’s actually easier to check the condition you care about directly. For example, a subsystem function that returns true if the flywheel is at the setpoint.

4 Likes

I’d like to see an implementation of that, I guess if nothing more to prove out feasibility for a newer programmer to use it. Also, see dependency injection used for robotics (I am quite ignorant to DI obviously)

Dependency injection is a fancy term for doing this:

public class Subsystem {
  private Thing m_thing;

  public Subsystem(Thing thing) {
    m_thing = thing;
  }
}

Basically, passing an object Subsystem needs in its constructor instead of having Subsystem access global variables.

4 Likes

I will have to look through your implementation more, but one improvement is the phases and states can only be set to the enums defined as opposed to strings. This allows students to use auto completion and they are not able to compile code that has hard coded strings as states or enums.

Like “self” in python?

Is it possible to have logic to switch back and forth and not linearly through a command group based on logic that is build into the library? To my knowledge, this is not possible and is one of the reasons to deviate from command-based. This is quite easy with alternate structures.

This makes it extremely difficult to debug code in my experience. Now, I haven’t use command-based in several years (hence my ignorance to several pieces), but I haven’t had anyone convince me to return yet, especially when I can’t pin point what the code is doing at any given time.

Ideally, a few changes could be made to command-based programming and my concerns would be resolved. That is:

  • a command (or command group if you’d like) itself has a “state” or “phase” that can change that is built in and mandatory for operation
  • a command group has logic for not just ending one command and going to the next, but also logic for going to a different command of choice given a logical expression
  • the hardware is abstracted by default
  • the order for command calls and subsystem processes can be controlled for debugging purposes

I’ll add more requests later.
End Goal: Make a solution whether it be command-based or an alternative that has these qualities by default. Something that forces students into designing code without much reference to the hardware (something no implementation, including this one), seems to currently accomplish

Feedback is appreciated and this will definitely aid in my continued development of this project.

All of these are supported features in the current command-based library.

1 Like

I was mainly trying to point out that I’ve been down this road before, and I didn’t like where it ended. Even if you assume enums are used instead of strings, there’s still the framework training issue and verbosity of defining the transition logic.

For Python member functions, yes.

Not directly, but I’d need a more concrete example to show how the code would be restructured to achieve the same goal. This is dangerously close to XY problem territory.

You can view the execution status of existing commands on Shuffleboard by publishing the command scheduler and/or commands, if I recall correctly.

Could you please elaborate?

I would be willing to help improve the documentation for it then. To the ignorant observer, these operations are not possible.

  1. You can define a state enum on your subsystem and have commands modify it; if you really want you can write an implementation of Command or subclass of CommandBase that does this for you in a generic manner.

  2. See ConditionalCommand and related documentation

  3. This is why Subsystem exists and why the standard examples inject subsystems into commands.

  4. Ordering is deterministic in the current implementation, so I’m not sure what you mean here. The standard group implementations could expose more of their runtime state as telemetry data through Sendable, I suppose.

1 Like