Friday, March 8, 2013

State Machines in Python

State machines are a pretty popular design pattern for designing reliable systems.  The general concept is that you have a system which is in one particular state at a time. The state a system is in should completely describe the system; i.e. a system should not have storage that isn't represented by the state.

State transitions occur in response to events. The system can only change states when an event occurs that makes it change. After the transition, the system will remain in the new state until an event occurs that causes it to move to another state.

The nice thing about state machines is that you can very easily make a diagram explaining the complete logic of your system, and then implement this diagram in code without making errors. And you have an easy way to break down the logic and look for errors. It's easy to instrument events and state transitions.

In C, it is very common to make the states and events be enums. Then your state transition logic can be handled by switch/case constructs.

so you'd likely have:
typedef enum State{
INIT = 0;
GO_FORWARD = 1;
GO_BACK =2;
TURN_LEFT = 3;
TURN_RIGHT =4;
} State;

typedef enum Event{
None  = -1;
Init_Done =0;
Hit_Wall =1;
Left_Key = 2;
Right_Key =3;
Timer_Done= 4} Event;
And then your state machine would be something like:
while(1)
event = checkForEvents();
switch state{
case INIT:
if (event == Init_Done)
    state = enterState(GO_FORWARD);
break;
case GO_FORWARD:
And so on. There are other ways to organize it--you could switch on the event for example.


Now of course Python doesn't have switch and it doesn't have enum. There are a lot of kludges for both, most contained within stackexchange reponses to frustrated C programmers. We could of course combine a kludgy enum with a kludgy switch.

If you're a shitty programmer like me, this is what you end up with at first cut:

class StateEnum():
    _vals=['no_change','Init','WaitLong','Acquired','WaitL','WaitR','ScanL','ScanR']
    def __init__(self,val):
        if self._vals.__contains__(val):
            self.val = val
        else:
            raise ValueError
    def __str__(self):
        return self.val
    def __eq__(self,y):
        return self.val == y


And down in our loop:

       while(self.runSM.isSet()):
            if self.state == StateEnum('Init'):
                event = EventEnum('done_init')
            elif self.state == StateEnum('WaitLong'):
                if self.hasFace:
                    event = EventEnum('Acquired')
                if self.timer[0]:
                    if self.timer[1] < time.time():
                        event=EventEnum('DoneTimer')
                else:
                    RuntimeError
            elif self.state == StateEnum('Acquired'):
                if self.hasFace:
                    cx = self.pidx.update(self.errx)
                    self.yaw = self.yaw - cx
                    self.servo.setyaw(self.yaw)
                    self.errx_last = self.errx
                    cy = self.pidy.update(self.erry)
                    self.pitch = self.pitch - cy
                    self.servo.setpitch(self.pitch)
                    self.erry_last = self.erry
                   else:
                    if self.errx_last <= 0:
                        event=EventEnum('LostL')
                    if self.errx_last > 0:
                        event=EventEnum('LostR')
            elif (self.state == StateEnum('WaitL')) or (self.state == StateEnum('WaitR')):
                self.yaw = self.yaw - cx
                self.servo.setyaw(self.yaw)
                cx=cx/2
                self.pitch = self.pitch - cy
                self.servo.setpitch(self.pitch)
                cy=cy/2
                if self.hasFace:
                    event = EventEnum('Acquired')
                if self.timer[0]:
                    if self.timer[1] < time.time():
                        event=EventEnum('DoneTimer')
                else:
                    RuntimeError
# (and so on)

Yeah, this is pretty horrible. Note that that i'm combining event checking and state transition logic, and that I have to use string compares to compare states, and that really everything is just a giant mess of elifs.


Next: A better way to do things!

2-D works (as of last week)

Haven't really  done much lately, but I'm back at it for the weekend. Anyway, got it working in 2-d:


Pretty sweet?



Code is a little messy, especially the state machine implementation. Also, there's an infrequent glitch in comms, and for some reason they don't work at higher baud rates--this is probably due to the interrupt being called for every character.

First thing is probably rewriting the state machine. I think I have a way to use inheritance to REALLY clean things up (will cover in another post).

After that comms. Adding a check byte for sure, and extending to add some new features.  Kind of toying with adding some features like slew, report position, etc.

Friday, March 1, 2013

Face Tracker working pretty well (in 1-d)


Ended up using Python. Pyqt4 + opencv.  Cool thing is that the code is 100% cross-platform so far. I don't even have to check what platform I'm on.

I guess the biggest problem is the latency through the camera and the image-processing pipeline.  I'm thinking of some ways to feed that into the PID controller.