【问题标题】:State machine and toggling states in a loop循环中的状态机和切换状态
【发布时间】:2017-11-28 22:09:57
【问题描述】:

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

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

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

概括来说,有两种状态,触发是按钮按下,实际上是在两者之间切换。

当前代码是一个无限循环,带有 if ... else 条件,根据状态执行“手动”模式或“自动”模式操作。在循环结束时添加延迟。每当检测到按钮事件时,都会通过中断更新(切换)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)

这个结构类似于我之前用过几次的C equivalent。但我想知道在 Python 中以更面向对象的方式来做同样的事情。

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

我可以想象这两种状态和转换:

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

但是,我无法完全想象在 pytranslations 模型中实现与当前无限循环等效的状态检查的正确方法是什么。

实际上,应在每次循环迭代时处理手动输入,而不仅仅是在从自动转换时完成,反之亦然,以运行自动模式。

我知道状态机应该单独处理,以便更好地分离状态和转换。

这是我无法完全描绘的模型实现:我欢迎任何有关如何以及在何处运行 do_user_input_actions()do_automatic_actions() 的指导。

【问题讨论】:

    标签: python transitions state-machine


    【解决方案1】:

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

    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()
    

    【讨论】:

      猜你喜欢
      • 2020-04-07
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多