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!
No comments:
Post a Comment