Infinite loop means no control

Our regional finished up yesterday. Python worked out very well for us, but I just now realized that the autonomous code I wrote was causing us a serious problem.

Thursday and Friday, everything was working fine. On Saturday, one of our mechanisms that was used during autonomous mode broke down. In order to keep our thrower motor from running continuously during the match we just tied down the limit switch that normally stopped and started the motor.

The problem was that the autonomous code watched for that switch to be toggled twice – once for each ball thrown. No toggle meant that the autonomous code just kept on running in a loop waiting for the switch to toggle.

The moral of the story is:

You absolutely must check which mode you are in during every loop. Failure to do so may mean that your program will not change modes when the field changes mode.

I think this is an issue no matter which programming method is used, but I’m not really sure. I know it caught me in python.

I think what I am going to do is wrap wpilib.Wait in Python.

Loops are already required to call Wait regularly, so why not check for mode changes there?

What it could do is check and save the current mode and throw an exception any time the mode changes. That exception could then be caught at the top level mode dispatch tree.

In fact, I’d recommend something like this as the best way to go to insure that the correct section of code is always running.

Some people might say that you should not use exceptions to control program flow, but I think this will simplify programs significantly. I haven’t tested anything yet, though, so I don’t know what problems may come up.

This is not a problem specific to python, this could happen with any programming language. If your code doesn’t yield, then it will never be able to switch modes. We always program our robot in a loop that looks like this:


while self.IsEnabled() and self.IsOperatorControl():
    .. do stuff here

In particular, if you’re using wpilib.Wait() for anything other than a delay at the end of the main loop so you’re not using 100% CPU, or if you’re in a loop waiting for some switch or condition to change without doing anything else… you’re probably doing it wrong. Once again, this goes for any programming language, not just python. There are many other ways to wait for things to happen, and by blocking on a wait statement you cannot do anything else on that thread while waiting, which is generally bad.

You might try looking at the Commands/Events stuff that WPILib introduced this year, there’s support for it in Python despite a lack of documentation (in all languages, really).

I will admit, there are a variety of examples that come with RobotPy, and I don’t really like many of them. This might be part of the problem. Hopefully I (or someone) will find some time to clean them up at some point.

I’m not a LabVIEW guru, but I was under the impression that “getting stuck in autonomous” is not an issue with the FRC LabVIEW framework, because it automatically shuts down autonomous at the end of the autonomous period.

Could a knowledgeable LabVIEW person please comment?

But it made the autonomous code so simple. And it worked great … until the mechanism broke down.

I could do:

while not throwerToReady():
    Wait(0.01)

throw():

If you have to go through the whole loop each time, you must have to create some kind of state machine.

By tooling Wait to watch for mode changes the simple style could work.

From my reading of the LabVIEW framework, this is right. LabVIEW starts the Autonomous VI as a separate task, and then kills it when autonomous mode ends. This is possible with C++/Python/Java too, but most teams opt to avoid the confusion of threading and just stick with single-threaded code. In fact, my team had an issue with LabVIEW last year that we traced to a race condition happening around the transition into autonomous.

all teams should have a method for exiting auto mode manually in case you get stuck in auto and can’t switch to manual.
just get the state of a button in an if statement and have a variable turn true. add


or myVariable:

to the end of your loops and if’s in auto and your all set.

Or even better, just make sure you don’t ever use loops in your robot code. :stuck_out_tongue:

I implemented my plan to throw an exception on every mode change and it is working pretty well.

I wrapped wpilib.Wait

from mode import check_mode

def wait():
    check_mode()
    wpilib.Wait(0.01)

and mode.py looks like this:

MODE_DIS = 0 # Disabled
MODE_AUTO = 1 # Autonomous
MODE_TELE = 2 # Teleoperated
mode = None


class ModeChange(Exception):
    'Exception raised when mode changes.'
    pass


def check_mode():
    'Check for a change in mode. Raise an exception if changed.'

    global mode

    if wpilib.IsDisabled():
        if mode != MODE_DIS:
            mode = MODE_DIS
            raise ModeChange
        return

    if wpilib.IsAutonomous():
        if mode != MODE_AUTO:
            mode = MODE_AUTO
            raise ModeChange

    if wpilib.IsOperatorControl():
        if mode != MODE_TELE:
            mode = MODE_TELE
            raise ModeChange

As long as any looping code calls this custom wait function, as soon as the mode changes an exception will be thrown.

In robot.py I catch the mode change and any other exception that happens to be thrown.

def run():
    while True:

        try:
            if wpilib.IsDisabled():
                print('Running disabled()')
                while wpilib.IsDisabled():
                    disabled()
                    wait()

            elif wpilib.IsAutonomous():
                print('Running autonomous()')
                while wpilib.IsAutonomous() and wpilib.IsEnabled():
                    autonomous()
                    wait()

            elif wpilib.IsOperatorControl():
                print('Running teleop()')
                while wpilib.IsOperatorControl() and wpilib.IsEnabled():
                    teleop()
                    wait()

            wait()

        except ModeChange:
            pass

        except KeyboardInterrupt:
            raise

        except:
            disable()
            print('ERROR')
            import traceback
            traceback.print_exc()

Seems to be working well, but if anyone sees a potential problem, I’d be glad to hear about it.

I like the idea about having an emergency autonomous escape button. With the code I have, it could just be a button that raises an exception.