状态机和循环中的切换状态

时间:2017-11-28 22:09:58

标签: python transitions state-machine

我在BeagleBone Black上运行了一个控制某些硬件的应用程序。用户界面包括一个LCD显示屏和一个带集成按钮的旋转编码器开关。

就状态转换而言,应用程序所做的就是对按钮事件做出反应以在它们之间切换:

  • 初始状态:"手册"
  • 推送事件:将状态切换到下一个("自动"),执行自动操作
  • 推送事件:将状态切换到下一步("手动"),执行手动操作

总之,有两种状态,触发器是按钮按钮,它实际上在两者之间切换。

当前代码是一个无限循环,带有if ... else条件,执行"手册"模式或" auto"模式动作取决于状态。在循环结束时添加延迟。只要检测到按钮事件,就会通过中断更新(切换)manual变量。

while True:
    # Toggle mode when the push button event is detected
    # It can also be detected asynchronously outside the loop,
    # but we'll keep it on the loop for a smaller example
    if gpio.event_detected(push_button):
        manual_mode = not manual_mode

    if manual_mode:
        # MANUAL MODE:
        do_user_input_actions()
    else:
        # AUTO mode
        do_automatic_actions()

   time.sleep(0.5)

此构造与<{3}}类似,之前我曾使用过几次。但我想知道在Python中采用更加面向对象的方法。

基本上,似乎逻辑适合使用C equivalent,特别是如果我将来需要添加更多状态,所以我考虑将代码移植到它。

我可以想象两种状态和过渡:

states = ['manual', 'sweep']
transitions = [['handle_manual_input', 'auto', 'manual'],
               ['run_auto_test', 'manual', 'auto']]

但是,我无法直观地了解使用状态检查实现等效当前无限循环的正确方法是在 pytranslations 模型中。

有效地,应该在每次循环迭代时处理手动输入,而不仅仅是从auto转换,反之亦然,以运行自动模式。

我理解状态机应该保持单独处理状态和转换以便更好地进行代码分离。

我可以非常了解模型实施:我欢迎任何有关如何以及在何处运行do_user_input_actions()do_automatic_actions()的指导。

1 个答案:

答案 0 :(得分:0)

如果你愿意(重新)每个周期进入状态,这应该可以解决问题:

from transitions import Machine
from random import choice


class Model(object):

    def on_enter_manual(self):
        # do_user_input_actions()
        print('manual')

    def on_enter_auto(self):
        # do_automatic_actions()
        print('auto')

    def event_detected(self):
        # return gpio.event_detected()
        # For simulation purposes randomise the return value
        return choice([True, False])


states = ['manual', 'auto']
transitions = [{'trigger': 'loop', 'source': ['manual', 'auto'],
                'dest': 'manual', 'conditions': 'event_detected'},
               {'trigger': 'loop', 'source': ['manual', 'auto'],
                'dest': 'auto'}]


model = Model()
machine = Machine(model=model, states=states,
                  transitions=transitions, initial='manual')

for i in range(10):
    model.loop()

transitions按照添加顺序处理可能的转换。这意味着当event_detected返回True时执行第一次转换。如果不是这种情况,将选择第二个转换,因为它没有条件。

对于这个解决方案,可以进行一些调整:

a)您可以将source:[..]替换为source:'*'以允许从所有状态进行循环转换。如果你想在将来添加状态,这可能会有用,但如果你计划使用多个触发器,它也会适得其反。

b)如果do_user_input_actionsgpio.event_detecteddo_automatic_actions是静态方法,您可以使用以下过渡来或多或少地省略模型:

transitions = [{'trigger': 'loop', 'source': ['manual', 'auto'],
                'dest': 'manual', 'conditions': gpio.event_detected,
                'after': do_user_input_actions},
               {'trigger': 'loop', 'source': ['manual', 'auto'],
                'dest': 'auto', 'after': do_automatic_actions}]

machine = Machine(states=states, transitions=transitions, initial='manual')

请注意,我传递了函数引用而不是字符串。字符串被认为是模型函数,而函数引用可以源自任何地方。由于我们的模型或多或少是空的,我们可以使用Machine实例作为模型。仅在行为相当简单时才建议这样做。专用模型使处理复杂配置更易于维护。我将函数回调传递给after,但在这种情况下,它是否在状态转换之前,期间或之后执行无关紧要。

为了完整性而且你明确提到你不想在转换期间处理用户输入,我建议采用Strategy Pattern方法来创建可以以相同方式处理的策略对象,但是不同的任务。

每当状态发生变化时,都会更换策略。这次我们在第二次转换中需要unless才能在未检测到用户输入时进入“自动”模式(unless只是conditions的便利对应物)。我们还使用Machine的关键字finalize_event,它始终执行一个函数,而不管先前尝试过渡的成功与否。我们也可以自己致电Model.execute

from transitions import Machine
from random import choice


class AutoStrategy(object):

    def execute(self):
        # do_automatic_actions()
        print('auto')


class UserInputStrategy(object):

    def execute(self):
        # do_user_input_actions()
        print('manual')


class Model(object):

    def __init__(self):
        self.strategy = UserInputStrategy()

    def execute(self):
        self.strategy.execute()

    def on_enter_manual(self):
        # We could use a singleton here if *Strategy is stateless
        self.strategy = UserInputStrategy()

    def on_enter_strategy(self):
        self.strategy = AutoStrategy()

    def event_detected(self):
        # return gpio.event_detected()
        # For simulation purposes, randomise the return value
        return choice([True, False])


states = ['manual', 'auto']
transitions = [{'trigger': 'loop', 'source': 'auto', 'dest': 'manual',
                'conditions': 'event_detected'},
               {'trigger': 'loop', 'source': 'manual', 'dest': 'auto',
                'unless': 'event_detected'}]


model = Model()
machine = Machine(model=model, states=states, transitions=transitions,
                  initial='manual', finalize_event='execute')

for i in range(10):
    model.loop()