Giter Club home page Giter Club logo

pysm's Introduction

pysm - Python State Machine

Versatile and flexible Python State Machine library.

https://travis-ci.org/pgularski/pysm.svg?branch=master https://coveralls.io/repos/github/pgularski/pysm/badge.svg?branch=master https://api.codacy.com/project/badge/Grade/6f18f01639c242a0b83280a52245539d Code Health Documentation Status

Implement simple and complex state machines

It can do simple things like this:

https://cloud.githubusercontent.com/assets/3026621/15031178/bf5efb2a-124e-11e6-9748-0b5a5be60a30.png

Or somewhat more complex like that:

https://cloud.githubusercontent.com/assets/3026621/15031148/ad955f06-124e-11e6-865e-c7e3340f14cb.png

Python State Machine

The State Pattern solves many problems, untangles the code and saves one's sanity. Yet.., it's a bit rigid and doesn't scale. The goal of this library is to give you a close to the State Pattern simplicity with much more flexibility. And, if needed, the full state machine functionality, including FSM, HSM, PDA and other tasty things.

Goals

  • Provide a State Pattern-like behavior with more flexibility (see Documentation for examples)
  • Be explicit and don't add any magic code to objects that use pysm
  • Handle directly any kind of event or input (not only strings) - parsing strings is cool again!
  • Keep it simple, even for someone who's not very familiar with the FSM terminology

Features

  • Finite State Machine (FSM)
  • Hierarchical State Machine (HSM) with Internal/External/Local transitions
  • Pushdown Automaton (PDA)
  • Transition callbacks - action, before, after
  • State hooks - enter, exit, and other event handlers
  • Entry and exit actions are associated with states, not transitions
  • Events may be anything as long as they're hashable
  • States history and transition to previous states
  • Conditional transitions (if/elif/else-like logic)
  • Explicit behaviour (no method or attribute is added to the object containing a state machine)
  • No need to extend a class with State Machine class (composition over inheritance)
  • Fast (even with hundreds of transition rules)
  • Not too many pythonisms, so that it's easily portable to other languages (ie. JavaScript).
  • Micropython support

Installation

Install pysm from PyPI:

pip install pysm

or clone the Github pysm repository:

git clone https://github.com/pgularski/pysm
cd pysm
python setup.py install

Documentation

Read the docs for API documentation and examples - http://pysm.readthedocs.io/

See Unit Tests to see it working and extensively tested.

Micropython support

The library works with pyboards!:

import upip
upip.install('upysm')

Links

pysm's People

Contributors

alhirzel avatar emersonford avatar pgularski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

pysm's Issues

Get the availiable states from a given state

Hello,
Pysm seems to be the "most advanced and versatile" FSM python package but I am looking for a method to get the available transitions from a state.
Did I miss something ? or is there a way to build that without side effects ?

It is very very useful to be able to connect it to a graphical interface without duplication of the states in the GUI.
Available buttons and commands should be mapped/mappable to "current_subject_object.currentState.getPossibleNextStates()"

Thanks a lot for your time.

store/restore of the state stack.

I'm working on an implementation where I store and restore the state stacks so that I can put my Microcontroller to sleep and on boot, reinitialize the state machine, then set the states back to what they were.

I tried to find if any of the states had persistent data, and it doesn't seem to be the case (unless I extend State myself and add it there). So it seems I would only need to restore:

        self.state_stack = Stack(maxlen=StateMachine.STACK_SIZE)
        self.leaf_state_stack = Stack(maxlen=StateMachine.STACK_SIZE)
        self.stack = Stack()
        self._leaf_state = None

The values of those attributes.

Can you please let me know if I'm missing any extra state that is important for restoring the state machine, and further, what is the difference between state_stack, leaf_state_stack and stack?

Not returning to initial state after exit

Consider the following hierarchical state machine

from pysm import State, StateMachine, Event


def info_handler(f, info):
    def wrapper(self, event):
        print(f' >>> {info} state: {self.fully_qualified_name()}')
        return f(self, event)
    return wrapper


class GenericState(StateMachine):
    def __init__(self, name):
        super().__init__(name)

        self.handlers = {
            'enter': info_handler(self.on_enter, 'Enter'),
            'exit': info_handler(self.on_exit, 'Exit')
        }

    def fully_qualified_name(self):
        names = []
        state = self
        while state:
            names.insert(0, state.name)
            state = state.parent
        return ' > '.join(names)

    def on_enter(self, state, event):
        pass

    def on_exit(self, state, event):
        pass

    def send_event(self, name, **kwargs):
        self.root_machine.dispatch(Event(name, **kwargs))


class Move(GenericState):
    def __init__(self, name="move"):
        super().__init__(name)

    def on_enter(self, state, event):
        self.send_event('e_move_done')


class Jump(GenericState):
    def __init__(self, name="jump"):
        super().__init__(name)

    def on_enter(self, state, event):
        self.send_event('e_jump_done')


class MoveAndJump(GenericState):
    def __init__(self, name="move_and_jump"):
        super().__init__(name)

        move = Move()
        jump = Jump()

        self.add_state(move, initial=True)
        self.add_state(jump)
        self.add_transition(move, jump, events=['e_move_done'])


class WaitForResult(GenericState):
    def __init__(self, name="wait_for_result"):
        super().__init__(name)

    def on_enter(self, state, event):
        self.send_event('e_wait_done')


class Turn(GenericState):
    def __init__(self, name="turn"):
        super().__init__(name)
        self.count = 0
        move_and_jump = MoveAndJump()
        wait_for_result = WaitForResult()

        self.add_state(move_and_jump, initial=True)
        self.add_state(wait_for_result)

        self.add_transition(move_and_jump, move_and_jump, events=['e_jump_done'], condition=lambda s, e: self.count < 3, action=self.add)
        self.add_transition(move_and_jump, wait_for_result, events=['e_jump_done'], condition=lambda s, e: self.count == 3, action=self.add)

    def add(self, s, e):
        self.count += 1

    def on_exit(self, s, e):
        self.count = 0


class Idle(GenericState):
    def __init__(self, name="idle"):
        super().__init__(name)

    def on_enter(self, state, event):
        self.root_machine.initialize()


class Main(GenericState):
    def __init__(self, name="main"):
        super().__init__(name)

        idle = Idle()

        turn = Turn()

        self.add_state(idle, initial=True)
        self.add_state(turn)

        self.add_transition(idle, turn, events=['e_turn'])
        self.add_transition(turn, idle, events=['e_wait_done'])

        super().initialize()
        self.send_event('init')


if __name__=='__main__':
    s = Main()
    s.send_event('e_turn')  # Moves correctly through the states
    # At this point turn remains in wait_for_result and does not return to its initial state
    s.send_event('e_turn')  # Does not produce the correct transitions any more

As indicated above, after a first event e_turn, the turn state ends up in wait_for_result instead of its initial state. I can't find a workaround other than calling s.initialize() again to reset the entire state machine. This isn't a solution however as it should be called outside of the state machine.

Is there anything wrong with how I defined the state machine? If not, do you have any pointers on where things might be going wrong in pysm.

Accessing self.sm.leaf_state from a event-handler returns wrong state

Hi

I came across this problem and it is very likely that i'm doing something wrong, however i couldn't figure out what it might be.

After i create an object that has a statemachine with entry-handlers for the states, when i call self.sm.leaf_state from within an entry-handler, it will always return the same state, even if the statemachine changes its state.

It's weird because if i access self.sm.leaf_state from outside the entry-handler, for example inside a test() function, it will return the correct states.

This script demonstrates my problem:

from pysm import StateMachine, State, Event

class TestSM():
    def __init__(self):
        self.sm = self._get_state_machine()

    def _get_state_machine(self):
        state_machine = StateMachine('Test')
        state_1 = State('One')
        state_2 = State('Two')

        state_machine.add_state(state_1, initial=True)
        state_machine.add_state(state_2)
        state_machine.add_transition(state_1, state_2, events=['change_state'])
        state_machine.add_transition(state_2, state_1, events=['change_state'])
        state_1.handlers = {'enter': self.entry_func}
        state_2.handlers = {'enter': self.entry_func}

        state_machine.initialize()
        return state_machine

    def entry_func(self, state, event):
        print('Entered State: ' + str(state.name))
        print(self.sm.leaf_state)

    def test(self):
        self.sm.dispatch(Event('change_state'))
        print(self.sm.leaf_state)
        self.sm.dispatch(Event('change_state'))
        print(self.sm.leaf_state)
        self.sm.dispatch(Event('change_state'))
        print(self.sm.leaf_state)

TEST_SM = TestSM()
TEST_SM.test()

The output i get (python 3.5.2):

Entered State: Two
<State One (0x10a4253c8)>
<State Two (0x10a425400)>
Entered State: One
<State One (0x10a4253c8)>
<State One (0x10a4253c8)>
Entered State: Two
<State One (0x10a4253c8)>
<State Two (0x10a425400)>

I would expect for both of the print(self.sm.leaf_state) to be the same. Am i doing something wrong?

Btw thanks again for the great library, i'm using it quite a lot on an ESP32.

Pushing items into an empty stack on micropyhton esp32

Hi,

thanks alot for the effort you put into this great library. It's really awesome you made it available for micropython!

I think there might be a small problem with the way items are added to an empty stack on micropython (esp32):
If i try to make the rpn_calculator.py example work on my esp32 i run into the following problem:

>>> calc.caluculate(' 167 3 2 2 * * * 1 - =')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 38, in caluculate
  File "<stdin>", line 35, in parse
  File "/lib/pysm/pysm.py", line 673, in dispatch
  File "<stdin>", line 44, in start_building_number
  File "/lib/pysm/pysm.py", line 329, in push
  File "/lib/pysm/pysm.py", line 55, in append
TypeError: unsupported types for __gt__: 'NoneType', 'int' 

The problem is that:
self.sm.stack.push(int(digit))
in rpn_calculator.py tries to push into an empty stack, initialized like this:

    def __init__(self, maxlen=None):
        self.deque = deque(maxlen=maxlen)

maxlen ends up as NoneType and therefore can't be compared with > in the append() function in the class deque_maxlen:

        def append(self, item):
            if self.maxlen > 0 and len(self.q) >= self.maxlen:
                self.q.popleft()
            self.q.append(item)

And in the rpn_calculator.py file there is a typo i think it should be calculate() and not caluculate()

State object missing from upysm

Looks like State object is missing from micropython version?

upip.install('upysm')
Installing to: /lib/
Installing upysm 0.3.9 from https://files.pythonhosted.org/packages/4e/6d/c50d0ca1c6cf627830aaf1df619c18aa82519b0bceafa1f7b48a07c3032a/upysm-0.3.9.tar.gz
Installing micropython-collections.deque 0.1.3 from https://micropython.org/pi/collections.deque/collections.deque-0.1.3.tar.gz
Installing micropython-collections.defaultdict 0.3 from https://micropython.org/pi/collections.defaultdict/collections.defaultdict-0.3.tar.gz
Installing micropython-logging 0.3 from https://micropython.org/pi/logging/logging-0.3.tar.gz
from pysm import State
Traceback (most recent call last):
File "", line 1, in
ImportError: no module named 'pysm.State'

Are your recursive PDA's as powerful as a Turing machine? Are regular expression edges supported?

According to this thread:
Computer Science SE thread

two PDA's or also a 2-stack PDA are as powerful as a single Turing machine. So I was wondering, since you've got support for recursivity, then also are yours as powerful as a Turing machine?

Not really, a high-priority question. I'm pretty sure I don't have to worry about it so much and can amend any issues should my system run into lack of expressivity in the future. I'm making a sort of "math state machine" that tries to understand a mixture of English and LaTeX formulae.

I'm using TextBlob to break a user's input into parts-of-speech. How does your library fare with regular expressions on transition edges? I either need that or some equivalent method so that I can effectively handle variable substitutions. So that users can choose whatever variables make sense to a particular context, but then a user can use the statemachine with whatever variables make sense to their "calling context".

Entry event on initialization

I've started using the pysm implementation of HSM and found interesting issue: the 'entry' handler is not called on initialization (for the initial states). According to this doc https://www.state-machine.com/doc/AN_Crash_Course_in_UML_State_Machines.pdf and the Quantum Leaps initialization the entry should be called while state is initial.

Imagine that the "Complex hierarchical state machine" example has entry program for the states s0, s1, and s11 like "print(state)". During the SM initialization we should get the prints "s0", "s1", "s11".

Is it possible to implement this feature? As I understand the initialize method could be slightly modified.

Right now I have to add an additional state like "initial state" and the explicit transition between this state and the proper initial state (e.g. s11) to make the entries work as it should.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.