statemachine.py

Module containing basic UML State Machine functionality.


Actual states should be instances of the State class. We provide STATE_NONE as a simple example; this can also be used for an concrete initial state.

Instances of the State class are initiated with a set of default hooks for the state machine logic: on_enter(), on_execute(), on_exit(), and on_msg. These do nothing unless overridden as follows:

After calling statename = State(…), the decorator @statename.event is used to replace the default hooks. For example:

STATE_NONE = State('STATE_NONE')
@STATE_NONE.event
def on_enter(agent):
    # This will replace STATE_NONE.on_enter()
    print("WARNING: Agent %s entered STATE_NONE" % agent)

Each enter/execute/exit method must be a callable with a single positional parameter: the agent using this State’s logic.

An on_msg method needs an additional second positional parameter to hold the incoming message. It should True/False to indicate whether the message was succesfully handled; messages that were not handled by the current state are then sent to the global state.

Decorating a function in this way…


The Python object agent can be given state machine functionality as follows:

StateMachine(agent, cur=cur_state, glo=glo_state, pre=pre_state)

Keyword arguments are optional; Statemachine.set_state() can be used to set states at a later time. This sets agent.statemachine to the newly created StateMachine instance, thus state logic and messaging can be done with the respective function calls:

agent.statemachine.update()
agent.statemachine.handle_msg(message)

class statemachine.State(name)

Provides the necessary framework for UML states with trigger events.

Parameters

name (string) – The name of this state.

It is recommended that each instance is a CONSTANT_VALUE with a matching name, such as:

STATE_NONE = State('STATE_NONE')

See the module docstring for full usage and examples.

event(eventhook)

Decorator for assigning event functions to this State.

static print_state_events()

Lists all defined state events/docstrings; mostly for debugging.

statemachine.STATE_NONE = <statemachine.State object>

Used as a default null state by the StateMachine class.

class statemachine.StateMachine(agent, **kwargs)

UML-Style State Machine with event/messaging capability.

Parameters

agent (object) – The agent that will use this StateMachine.

Keyword Arguments
  • cur (State) – Sets the current state.

  • glo (State) – Sets the global state.

  • pre (State) – Sets the previous state.

Instantiating this class will automatically set agent.statemachine to the new instance. Omitting any of the keyword arguments will set their corresponding states to STATE_NONE; these can be assigned/changed later using StateMachine.set_state().

property statename

Get the name of this state machine’s current state.

set_state(*args, **kwargs)

Manually set agent’s states without triggering state change logic.

This method is primarily meant for initialization of the StateMachine. If called with a single parameter, we assume this to be the desired “current” state, and leave the global/previous states unchanged; these default to None when the StateMachine is created.

The “global” and “previous” states can be set using keyword arguments ‘glo’ and ‘pre’, respectively.

start()

Calls on_enter() for global, then current state.

update()

Calls on_execute() for global, then current state.

shutdown()

Calls on_exit() for global, then current state.

change_state(newstate)

Switches to a new state, calling appropriate exit/enter methods.

Parameters

newstate (State) – The StateMachine will switch to this state.

Assuming the current and newstates are both valid, this does:

  • Call on_exit() of the current state.

  • Change to the newstate.

  • Call on_enter() of the newstate.

revert_state()

Reverts agent to its previous state, if one exists.

handle_msg(message)

Lets the StateMachine attempt to handle a received message.

The message is first passed to the current state, which tries to handle it. If the current state’s on_msg() returns False, the message is then passed up to the global state.

There is no particular structure required of the message; it will just be passed along to the on_msg() function of current/global States.

Returns

True if the message was reported as being handled by either the current or global state; False otherwise.

Return type

bool

statemachine.sample_run()

Demo based on the classic two-state turnstile FSM.

Output from sample_run()

Using the @statename.event decorator automatically grabs function names and docstring text, then pairs them with the associated statename, as shown in the first part of the output below. We’ll eventually use this to generate automatic state documentation in a nice format.

=================================
--- STATE_NONE defined events ---
STATE_NONE: on_enter()
    Dummy docstring for discovery by @state.event decorator. 

--- TURNSTILE_LOCKED defined events ---
TURNSTILE_LOCKED : on_msg()
    Acts on the following messages:
        * 'COIN': Change state to TURNSTILE_UNLOCKED.
        * 'PUSH': Access denied; print some text.
         

--- TURNSTILE_UNLOCKED defined events ---
TURNSTILE_UNLOCKED: on_exit()
    Increment turnstile counter. 

TURNSTILE_UNLOCKED : on_msg()
    Acts on the following messages:
        * 'PUSH': Change state to TURNSTILE_LOCKED.
         


Update 1:

Update 2:

Update 3:
Trying to push...
Denied!

Update 4:

Update 5:
Inserting coin...
Coin inserted!

Update 6:
Trying to push...
There it goes!

Update 7:

Update 8:

Update 9:
Trying to push...
Denied!

Update 10:
Inserting coin...
Coin inserted!

Update 11:

Update 12:
Trying to push...
There it goes!

Update 13:

Update 14:

Update 15:
Inserting coin...
Coin inserted!
Trying to push...
There it goes!

Update 16:

Update 17:

Update 18:
Trying to push...
Denied!

Update 19:
Final passthrough count: 3