Closed Panos26 closed 4 years ago
Hello, I was wondering, why do you have to check the possible transitions instead of triggering it. I mean, it sounds to me that you are doing yourself the state machine there instead of using it. If your trigger is coming from message, then, you just to have a mapping between the original input and the triggers, and then pass the string the the fsm.
i.e.: with something like that: the choice of command is random, but it works and does not crash the program. I've disable the conditions since I wanted to keep it short (kinda).
from transitions import Machine, MachineError
import random
class MyRobot:
states = ["Off", "Manual", "Ready"]
transitions = [
{
"trigger": "off_2_manual",
"source": "Off",
"dest": "Manual",
# "conditions": "trans_1",
},
{
"trigger": "manual_2_off",
"source": "Manual",
"dest": "Off",
# "conditions": "trans_2",
},
{
"trigger": "off_2_ready",
"source": "Off",
"dest": "Ready",
# "conditions": "trans_3",
},
{
"trigger": "ready_2_off",
"source": "Ready",
"dest": "Off",
# "conditions": "trans_4",
},
]
def __init__(self):
self.machine = Machine(
model=self,
states=self.states,
transitions=self.transitions,
initial="Off",
)
def process_input(self, input_data):
map_cmd = {
'foo': 'off_2_manual',
'bar': 'manual_2_off',
'crux': 'off_2_ready',
'baz': 'ready_2_off'
}
try:
self.trigger(map_cmd[input_data])
except (MachineError, KeyError):
print("Nope, that's an invalid command")
if __name__ == "__main__":
walle = MyRobot()
cmds = ['foo', 'bar', 'crux', 'baz']
for cpt in range(10):
walle.process_input(random.choice(cmds))
print(walle.state)
# a last one that does not exist at all
walle.process_input('grow')
I hope it helps, and I don't miss the point.
You could use the get transitions from the machine but it is busted if you do not specify a trigger.
states=['solid', 'liquid', 'gas', 'plasma']
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
{ 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
{ 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
{ 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]
machine = Machine(lump, states=states, transitions=transitions, initial='liquid')
From the Machine
def get_transitions(self, trigger="", source="*", dest="*"):
""" Return the transitions from the Machine.
Args:
trigger (str): Trigger name of the transition.
source (str): Limits removal to transitions from a certain state.
dest (str): Limits removal to transitions to a certain state.
"""
machine.get_transitions(source='liquid')
You would expect to see Transition('luquid', 'gas')
But the extending and chaining of transitions are causing non-sense results
machine.get_transitions(source='liquid')
[<Transition('liquid', 'solid')@4584687888>, <Transition('liquid', 'liquid')@4584688912>, <Transition('liquid', 'gas')@4584776912>, <Transition('liquid', 'plasma')@4584777616>, <Transition('liquid', 'gas')@4584777936>]
now all of a sudden we can transform from liquid
-> solid
:man_shrugging: :confused:
I might code up a little extension to find the available states from a given state with simple statements, better than triggering things to 'test'
I work with enums so my method is a bit longer but since you are working with strings this should work with one line python magic.
transitions = [
['a2b', 'a', 'b'],
['b2c', 'b', 'c'],
['c2d', 'c', 'd'],
['restart', ['a', 'b', 'c'], 'a'],
['shortcut', 'a', 'd'],
]
def get_available_transitions(self, src) -> []:
return [i[0] for i in list(filter(lambda x: src in x[1], self.transitions))]
running
print(ob.get_available_transitions('a'))
['a2b', 'restart', 'shortcut']
print(ob.get_available_transitions('b'))
['b2c', 'restart']
print(ob.get_available_transitions('d'))
[]
from the documentation btw
If you need to know which transitions are valid from a certain state, you can use _gettriggers:
I want it to check every possible transition every time it gets new data ... I tried using the get_triggers but since it returns a list of strings I can't use the possible transitions from there.
why not? you could get the trigger with getattr
from your model (or more convenient: lump.trigger(<trigger_name>)
. This method is also added to the model. It seems like this hasn't been added to the Readme.md yet) and call it:
from transitions import Machine
class Lump:
pass
states = ['solid', 'liquid', 'gas', 'plasma']
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
{ 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
{ 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
{ 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]
lump = Lump()
machine = Machine(lump, states=states, transitions=transitions, initial='liquid')
triggers = machine.get_triggers(lump.state)
print('possible transitions: {}'.format(triggers)) # >>> possible transitions: ['to_solid', 'to_liquid', 'to_gas', 'to_plasma', 'evaporate']
getattr(lump, triggers[0])()
# or lump.trigger(triggers[0])
print(lump.state) # >>> solid
... You would expect to see Transition('luquid', 'gas') ... But the extending and chaining of transitions are causing non-sense results
I am not completely certain what you refer to with 'chaining and extending' but the result is fine. As you see in the example above auto transitions are also valid transitions which will be returned by get_transitions
since they are valid (see Stackoverflow).
So I would expect 1 (evaporate) plus 4 (auto transitions) equals 5 transitions:
trans_list = machine.get_transitions(source=lump.state)
print('There are {} possible transitions'.format(len(trans_list))) # >>> There are 5 possible transitions
If you do not want to use auto transitions you can disable them by passing auto_transitions=False
to Machine
. Furthermore, get_triggers
will not evaluate conditions. If you want to check whether a trigger is actually possible, you might want to have a look here.
Since there is no feedback, I assume this is solved or not relevant anylonger. I will close this issue for now. Feel free to comment though. If a feature request or bug report arises, I will gladly reopen the issue.
I am using the transitions module in ROS environment where the "inputs" will be from messages so I am not the one that controls the machine. I want it to check every possible transition every time it gets new data. I tried using the
get_triggers
but since it returns a list of strings I can't use the possible transitions from there. The solution I've come up with is this:Is there a better way? *PS VScode keeps underlining
self.manyal_2_off
andself._transition_
saying instance of class has no such member but it works fine...is this a problem?Thanks for the great package