glyph / automat

Self-service finite-state machines for the programmer on the go.
MIT License
591 stars 65 forks source link

Internal conditional state transition #155

Closed ETYFun closed 2 months ago

ETYFun commented 7 months ago

I'm a novice trying to create an internally triggered state transition but was unsuccessful. Is there any way to trigger a state transition within the state machine instead of relying on external commands?

Here's my example timer code that should ring after 5 seconds:

import time

class Counter(object):
    machine = MethodicalMachine()

    def __init__(self):
        self.timer = 5

    @machine.state(initial=True)
    def _dormant(self):
        "not counting"
        self.reset()

    @machine.state()
    def _counting(self):
        "counting"
        print('in counting mode')
        self.countdown()

    @machine.input()
    def press_button(self):
        "press button to start count down"

    @machine.input()
    def stop_timer(self):
        "stop timer"

    @machine.output()
    def _click(self):
        "button clicks"
        # print('CLICKKKK')
        return 'CLICKKKK'

    @machine.output()
    def _ring(self):
        "ring once timer is zero"
        # print('RINGGGGGGGGG')
        return 'RINGGGGGGGGG'

    @machine.output()
    def countdown(self):
        "counting down"
        while self.timer - 0 >= 0:
            print(f'Timer: {self.timer} seconds')
            time.sleep(1)
            self.timer -= 1

        self.stop_timer()
        print('The timer should have stopped and reset')

    def _reset(self):
        "reset timer"
        self.timer = 5

    _dormant.upon(press_button, enter=_counting, outputs=[countdown, _click], collector=itemgetter(0))
    _counting.upon(stop_timer, enter=_dormant, outputs=[_ring])

# main
counter = Counter()
counter.press_button()

This code does not trigger a state transition to _dormant state (by _stop_timer). Please show me a way to correct this.

Thank you

glyph commented 7 months ago

I think this is a duplicate of #41 . This example seems rather contrived though, can you explain what you're actually trying to do here? You can't trigger a state transition in an output, but then, this only provides one external method call (the press_button at the bottom) so I'm not sure why a state machine would be helpful to you at all for this example.

ETYFun commented 7 months ago

@glyph I'm trying to trigger a state transition based on internal timer or any conditional triggers. Not sure if the automat was originally developed for finite state machines or agents.

glyph commented 7 months ago

A timer cannot be "internal" to the automat state machine, unless you're literally talking about time.sleep.

The usual solution here is a private input method with a public method that does the transition around it, but I think I would need a much more comprehensive picture of the actual application you are trying to build here, not just "internal state transition" or "conditional trigger". You can implement a conditional trigger in Python as an if statement so at that level it's not clear how that fits in to the state machine itself.

glyph commented 2 months ago

Similar to #72 this should now be addressed by https://automat.readthedocs.io/en/latest/tutorial.html#conditional-state-transitions