Monday, January 5, 2015

Hierarchical State Machines in Python

Recently I've been messing around with Python for some project at work.  I thought it was a good time to dig up some of my old interests on Hierarchical State Machines using Python.  I've wrote this last year just for experiments, but I decided to try integrating it with a project at work.

So what exactly is a Hierarchical State Machine (HSM) anyway?  It is a super set of a Finite State Machines(FSM).  And like all FSM, they are basically event filters that only react to an event if the state handles it.  For a given state, certain events can invoke an action or cause a transition to another state.  This is very common design technique to model behavior in an embedded systems.  But one of the problems with realizing the state machine in code is that you end up with too many states and too many transitions that makes it complicated and unmanageable for non trivial systems.

Enter the HSM which basically allows states to be nested.  The advantages of nesting a state into another state is that the child state doesn't need to handle every event/signal.  If the current state doesn't have a handler for it, then it can pass the event to its parent and see if this has a handler for the event/signal.  Otherwise that state can continue passing that event up to the state hierarchy.  You can think of this as a sort of class inheritance.  This offers some big advantages such as:
  1. More opportunities for refactoring code - similar behavior can be pushed up to the parent
  2. Less event handlers - Common events can be pushed up to the parent instead of every child state
  3. Less transitions - Each state doesn't need to handle the transition
  4. Consistent setup and teardown - Each state handles specific operations, while parent handle common operations
Anyhow, there are many resources about HSM that you can google or find here.

From my perspective, I only care about being able to draw a HSM state machine using some UML diagramming tool and then map it directly into some code.  So here is my python implementation of HSM @ https://github.com/howard-chan/HSM.git

Of course for this to be useful, I need an example to demonstrate its capabilities.  So using Visual Paradigm as my UML drawing tool, I created this simple HSM model of a camera.

Here we have a camera that really has 4 states (Off, OnShoot, OnDispPlay, OnDispMenu) which are nested into the parent states (On, OnDisp).

Here we see that any state will handle the PWR event.  If we are in any one of the child On states (OnShoot, OnDispPlay, OnDispMenu), we see that each child doesn't directly handle the PWR event so it pushes the event up to the parent which does handle the PWR event.  In this case, its the On state which will handle the PWR event and cause a transition to the Off state.  As the HSM transitions from say the "OnDispMenu" to "Off" state, each of the "exit" event handling are invoked to clean up the state, til it enter the "Off" state which triggers the "entry" even handling to setup the state.

So for the following event sequence fed to the Camera:
    # Instantiate Camera
    basic = Camera("Canon")
    # Turn on the Power
    basic.Run(evt.PWR)
    # Take a picture
    basic.Run(evt.RELEASE)
    # Take another picture
    basic.Run(evt.RELEASE)
    # Playback the photo
    basic.Run(evt.MODE)
    # Oops, pushed the release button by accident
    basic.Run(evt.RELEASE)
    # Go to menu settings
    basic.Run(evt.MODE)
    # Uh oh, low battery
    basic.Run(evt.LOWBATT)
    # Time to turn it off
    basic.Run(evt.PWR)

We get the following output:
Run  Canon[Off](evt:PWR)
Tran Canon[Off -> On]
  Canon[Off](EXIT)
Exit Low Power Mode
  Canon[On](ENTRY)
Open Lens
  Canon[On](INIT)
Tran Canon[On -> On.Shoot]
  Canon[On.Shoot](ENTRY)
Enable Sensor
  Canon[On.Shoot](INIT)
Run  Canon[On.Shoot](evt:RELEASE)
CLICK!, save photo
Run  Canon[On.Shoot](evt:RELEASE)
CLICK!, save photo
Run  Canon[On.Shoot](evt:MODE)
Tran Canon[On.Shoot -> On.Disp.Play]
  Canon[On.Shoot](EXIT)
Disable Sensor
  Canon[On.Disp](ENTRY)
Turn on LCD
  Canon[On.Disp.Play](ENTRY)
Display Pictures
  Canon[On.Disp.Play](INIT)
Run  Canon[On.Disp.Play](evt:RELEASE)
  evt:RELEASE unhandled, passing to Canon[On.Disp]
  evt:RELEASE unhandled, passing to Canon[On]
  evt:RELEASE unhandled, passing to Canon[:root:]
Unhandled event:RELEASE Canon[<hsm.State instance at 0x00000000058EA708>]
Run  Canon[On.Disp.Play](evt:MODE)
Tran Canon[On.Disp.Play -> On.Disp.Menu]
  Canon[On.Disp.Play](EXIT)
  Canon[On.Disp.Menu](ENTRY)
Display Menu
  Canon[On.Disp.Menu](INIT)
Run  Canon[On.Disp.Menu](evt:LOWBATT)
  evt:LOWBATT unhandled, passing to Canon[On.Disp]
  evt:LOWBATT unhandled, passing to Canon[On]
Beep low battery warning
Run  Canon[On.Disp.Menu](evt:PWR)
  evt:PWR unhandled, passing to Canon[On.Disp]
  evt:PWR unhandled, passing to Canon[On]
Tran Canon[On.Disp.Menu -> Off]
  Canon[On.Disp.Menu](EXIT)
  Canon[On.Disp](EXIT)
Turn off LCD
  Canon[On](EXIT)
Close Lens
  Canon[Off](ENTRY)
Enter Low Power Mode
  Canon[Off](INIT)

Well that is it for now, I'll probably elaborate on how the HSM.py works on my next entry

No comments:

Post a Comment