pgularski / pysm Goto Github PK
View Code? Open in Web Editor NEWVersatile and flexible Python State Machine library
Home Page: http://pysm.readthedocs.io/
License: MIT License
Versatile and flexible Python State Machine library
Home Page: http://pysm.readthedocs.io/
License: MIT License
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
?
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()
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.
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.
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
.
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".
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.
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'
It would be cool to get support for a micropython.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.