That’s a fair criticism. The state machine stuff was originally created for autonomous modes (and honestly, that’s really where I find it useful, I’m still working on making it more useful in teleop), so the state machine behaves as if someone was constantly calling engage() until done is called. That behavior is closer to what one expects.
In teleop, my philosophy is that “nothing should happen unless I tell it to happen”, and so the state machine follows that. If you don’t call engage, then the state machine won’t do it’s things. In my opinion this generally makes it easier to reason about what’s happening when debugging. Often the most confusing types of bugs occur when there’s some kind of stale state left in the system and you’re trying to figure out why it is still doing that thing.
it ran through the state machine nicely, but when it reached the end state, it reset to the start state and went through the state machine again. This doesn’t seem useful, if I wanted my state machine to loop, I’d build the loop into the state machine.
Hm. I see how that could happen. The looping is arguably buggy behavior. Please file an issue on github (or even better, make a pull request with a fix).
My expectations for a state machine api:
execute - run the state machine for a single cycle unless it is in the end state, otherwise do nothing
is_finished - true iff in the end state
reset - set the current state to the start state
Compared to that, it seems engage is a combination of execute and reset, and there is no is_finished.
execute is one of the functions required by the magicbot component definitions (and the component API was defined before the state machine stuff I think), so it is always called every loop, thus it can’t be used in the way that you specify. Instead, it manages the actual execution of the states if the machine is engaged.
engage is similar to what you want in execute, with the exception that the state machine effectively turns itself off if you don’t call it. If the state machine is ‘turned off’, then the function with the @default_state decorator is called.
reset could be implemented, it would be equivalent to:
def reset(self):
first_state = look up the first state()
self.next(first_state)
Because the state machine can branch in arbitrary paths because you can call the next() function to move to a named state, technically, there’s no end state, so is_finished isn’t really meaningful. It would be vaguely equivalent to ‘not my_state_machine.is_executing’.
The RobotPy project is intended to be a community project, so if it doesn’t do what you want then I’m certainly open to improvements. Pull requests on github are especially encouraged.