Robot Not a Dev? Pre-order Now
Not a Dev?

Misty Community Forum

A plea for State Machines & Task Queues (or how I struggled with global variables, cross thread serializers, and function pointers)

#1

At the Seattle Hackathon, we were given a challenge – design a beer serving Misty that could be given a beer, travel across the counter, then deliver it to bar stools 1, 2, 3, or 4, and return back to the bartender. Neat, simple but not trivial programming challenge – great choice for a few hour hackathon!

My approach to how to solve this was to combine state machines and task queues. This is based on my previous experience in robotics development which goes back a few more years than I’d care to admit. And I chose to use the “On-Robot Javascript API” because; a) as a personal safety policy, I don’t control moving objects over a wireless network and; b) I love Javascript, for all its foibles because at its heart it’s a powerful and functional programming language with a huge community and a mature ecosystem.

Here’s some sorta-pseudo-code for how I would approach the problem:

var states = { // poor man's enum
    waitForOrder: 'waitForOrder',
    deliverToBarStool1: 'deliverToBarStool1',
    waitForPickup: 'waitForPickup',
    returnToBartender: 'returnToBartender',
    ///...
};

var taskQueue = [];
var state = states.waitingForOrder;

var forward = (ticks) => {
    // ...
};

var turn = (degrees) => {
    // ...
};

var signalWaitForOrder = () => {
    // Set appropriateExpression & LEDs
}

var signalWaitForPickup = () => {
    // Set appropriateExpression & LEDs
}

var changeState = (state) => {

    this.state = state;

}

// Define this action to be interpreted as as 'deliver to bar stool 1' when state.waitingForOrder
misty.onForeheadButtonPress = () => {

    switch(state) {
        
        case states.waitForOrder:

            state = states.deliverToBarStool1;

            // Primitive navigation from Waiter to Bar Stool 1
            taskQueue.push(turn, -90);
            taskQueue.push(forward, 20);
            taskQueue.push(turn, 90);
            taskQueue.push(forward, 3);
            taskQueue.push(signalWaitForPickup);
            taskQueue.push(changeState, states.waitForPickup);

            break;

        case states.waitForPickup:

            state = states.returnToBartender;
            taskQueue.push(turn, -180);
            taskQueue.push(forward, 3);
            taskQueue.push(turn, -90);
            taskQueue.push(forward, 20);
            taskQueue.push(turn, -90);
            taskQueue.push(signalWaitForOrder);
            taskQueue.push(changeState, states.waitForOrder);

            break;

    }

}

The issue I ran into trying this was in the lack of a shared global or static or this pointer between the main function and the event handlers. My preference is not to use globals or this pointers, as they can be tricky and buggy, but to be able to pass (by reference, not serialization) shared state to an event handler. Something like this:

misty.onForeheadButtonPress = ({ state: state, taskQueue: taskQueue }) => { … };

I was unable to do this, so I tried to work around it, using misty.Set(…) and misty.Get(…). I was ‘defeated’ when I couldn’t pass a javascript array [] through this, as it was deserialized as an empty string. If I could have passed a queue, even as simple as:

[ { func: ‘drive’, param: 20 }, { func: ‘turn’, param: -90 } ]

then I would have been able to ‘fix things up’ and make this design approach work.

As I discussed at the hackathon, handling interruptions and the complex system that is a robot in the real world with lots of incoming sensor input causing conflicting priorities and weird emergent behaviors requires programmers to ‘up their game’. They need to have goals (‘deliverToBarStool1’) that can be interrupted by new information (patron passed out on the bar, must avoid) and in my experience the best way to handle this is state machines and task queues.

Async programming through task queues has been around a long time. See Microsoft Robotics Studio and ROS for some examples of this approach (both too complex for my taste unfortunately). One of my favorites from the past is the .NET Task Parallel Library:

https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming

For a Javascript take on this approach, take a look at Caolan Async:

https://caolan.github.io/async/docs.html

Javascript Promises are a moderately good approach to the definition of a Task, but I haven’t played around enough lately to know if there is a good Queue based library for handling sets of Promises.

  • Jay
#2

Reading through this, I realized I didn’t make a specific ask. My asks are:

  • Please allow a context object to be passed into the event handlers, e.g.

    • misty.onForeheadButtonPress = ({ state: state, taskQueue: taskQueue }) => { … };
    • Consider some formalism for this, something like an implicit context object (like you’re already doing with misty.* ). globals/statics tend to introduce bugs imho, but perhaps something like a shared context.* might be more approachable than requiring the context to be passed in as a parameter to every event handler.
    • If you think that passing an object by reference between threads is a violation of your coding principals for some reason (in multithreaded languages like C#, this would be considered to be unthreadsafe, in javascript with its cooperative multitasking this is not as much an issue imho), I’d suggest doing the heavy lifting for the user to pass state machines and task queues around rather than make them jump through deserialization / object-function table call hoops.
  • Consider adding language primitives that support queue-based task execution, like:

  • Consider adding some simple examples that demonstrate ‘goal based state machines’ and ‘command queues’. Navigate from a to b, handling interruptions is a good one. A simplified version of the bartender example is another good one.

3 Likes