美文网首页
python状态机transitions库的学习小结(1)--状

python状态机transitions库的学习小结(1)--状

作者: 阿罗_ca27 | 来源:发表于2020-01-18 01:28 被阅读0次

    一、学习背景

    (一)为什么要用状态机

    在用python做一个比较复杂的小项目,需要根据不同的输入,控制摄像头采集执行不同的任务。虽然用流程方式实现了,但阅读起来费劲,还容易出错。所以就用了状态机。至于状态机是什么,度娘上一大把。

    (二)为什么用transition

    一是懒,懒得自己写一个状态机。
    二是已经有现成且成熟的库了,为什么不用呢。况且这个库还在持续维护。

    (三)transition怎么来

    按github上的手册安装就行了,https://github.com/pytransitions/transitions#threading
    我的情况,直接用 pip install transitions可以装上,但import的时候说没有这个库。所以我用第二个方法装了,即克隆下来后用python setup.py install进行安装。

    (四)我要做成什么样的模型

    前面说了,我需要根据不同的输入,控制摄像头采集执行不同的任务。实际任务总共有3个:
    1.摄像头采集一定数量的视频帧
    2.对所采集到的帧进行一些分析和计算(本文不会对怎样做计算展开,仅以把帧保存为图片文件代替)
    3.持续实时采集视频,分析当前帧与第2步计算的结果进行比对(本文不会对怎样做分析比对展开,仅以把当前帧保存为图片文件代替)。

    二、对目标模型的思考

    (一)有多少个状态

    针对需求的3个功能,至少应该有3个状态。即:采集状态,基准计算状态和实时跟踪比对状态。我把这3个状态分别命名为:sample,locate和trace。
    实际应用中,还有可能让这个系统空转,什么也不干。等有下一步输入的时候再重新开始,因此多设计一个空闲状态:idle。这样,总共就有4个状态了。归结如下:

    状态名称 功能
    idle 空闲状态,啥都不干
    sample 图像采集
    locate 基准定位计算
    trace 实时追踪比对

    (二)如何触发状态切换

    根据我的需求,所要实现的目标是:
    1.一开始应处于idle状态。
    2.当输入接收到start的时候,进行idle->sample切换
    3.sample执行完采样后,自动进入locate状态,即进行sample->locate切换
    4.locate计算完后,自动进入trace状态,实时追踪比对。除非收到stop、restart信号,否则一直运行。
    5.当收到restart信号时回到sample状态,接着进入locate状态和trace状态。即trace->sample->locate->trace。
    6.当任何时候收到stop信号时,切换到idle状态。

    根据上述需求,显示触发信号和状态切换的对应关系如下:

    触发信号 原状态 目标状态
    start idle sample
    restart 任何状态 sample
    stop 任何状态 idle

    sample->locate->trace间的状态切换应为自动切换。

    我将一步步实现从手工触发使其切换,到某些状态间自动切换。

    三、上状态机

    先上定义状态机源码,然后再做小结分析。

    import transitions
    class tracer_model(object): # 先定义一个类,把它作为基础模型
        pass
    tracer = tracer_model() #生成一个实例,这时候它和普通的实例没有任何区别
    
    # 定义所有状态的列表
    states_lst = [
                  'idle',
                  'sample',
                  'locate',
                  'trace'
    ]
    
    # 定义状态切换器
    # 也就是当发生什么时从哪个状态转换到哪个状态
    transitions_lst = [
        ['start','*','sample'],
        ['cal_pos', 'sample', 'locate'],
        ['live_trace', 'locate', 'trace'],
        ['stop', '*', 'idle'],
        ['restart', '*', 'sample']
    ]
    
    # 生成一个状态机控制器
    machine = transitions.Machine(model=tracer, # 控制哪个模型
                                  states=states_lst, # 载入模型可能有的状态
                                  transitions=transitions_lst, # 载入状态切换器
                                  initial='idle' # 这个状态机初始状态是什么
                                  )
    

    transitions库把一个完整的状态机分为执行器控制器2部分。
    执行器:就是在指定状态下分别干什么,各种算法都将装在此处
    控制器:就是通过外界的动作出发来切换不同的状态。达到想让程序干啥就干啥的目的。状态切换并非状态1->状态2这么简单,还涉及到触发切换后准备阶段、退出旧状态阶段、进入新状态阶段、处于新状态阶段等等,这个放在后面再说。
    刚才的代码中:

    class tracer_model(object): # 先定义一个类,把它作为基础模型
        pass
    tracer = tracer_model() 
    

    相当于定义了执行器,只不过现在定义的这个执行器啥都不干。
    而这段代码则是定义了控制器:

    
    # 定义所有状态的列表
    states_lst = ['idle',
              'sample',
              'locate',
              'trace'
    ]
    
    # 定义状态切换器
    # 也就是当发生什么时从哪个状态转换到哪个状态
    transitions_lst = [
        ['start','*','sample'],
        ['cal_pos', 'sample', 'locate'],
        ['live_trace', 'locate', 'trace'],
        ['stop', '*', 'idle'],
        ['restart', '*', 'sample']
    ]
    
    # 生成一个状态机控制器
    machine = transitions.Machine(model=tracer, 
                                  states=states_lst, 
                                  transitions=transitions_lst, 
                                  initial='idle' 
                                  )
    

    我的理解,一个状态机控制器最起码应包括几个内容:
    1.控制器要控制哪个执行器 model=tracer
    2.整个状态机都有哪些状态states=states_lst
    3.状态间切换的触发条件transitions=transitions_lst

    这里对状态切换器做个简单介绍。
    首先transitions可以是一个列表(更多的方式请看github),列表中的每一个元素就是怎么切换。以['cal_pos', 'sample', 'locate']为例,第一位表示触发切换的触发器(怎么用,后面有说),第二位表示从哪个状态切换出去,第三位表示要切换到哪个状态。起始状态和目标状态都需要事先在状态列表中定义,否则实际执行时会出错。

    那为什么['stop', '*', 'idle']中第二位是*号呢?这是transitions库其中一个牛逼的地方,这表示可以从任何当前状态切换到idle状态。

    四、状态切换

    (一)触发器切换法

    通过激活触发器实现状态切换。在上面代码的最后加入以下:

    tracer.start() # 激活start触发器
    print(tracer.state)
    
    tracer.cal_pos() # 激活cal_pos触发器
    print(tracer.state)
    

    可以看到出现这样的结果:

    sample
    locate
    

    是不是和我之前的定义一致:
    当start触发器被触发时,不论当前处于什么状态(此时状态机处于idle状态)都切换到sample状态。此处对应的切换条件为:['start','*','sample']

    当前处于sample状态下时,若cal_pos触发器被触发,则切换到locate状态。此处对应的切换条件为['cal_pos', 'sample', 'locate']

    不得不说这是transitions库另一个牛逼之处,直接把字符串型定义的触发器转化成执行器的一个方法。

    问题来了,如果触发器被触发了,但当前所处状态又不是定义中的原状态,出现什么结果呢?把

    #tracer.start() 
    #print(tracer.state)
    

    注释掉试一下就会发现出错了:transitions.core.MachineError: "Can't trigger event cal_pos from state idle!"

    道理很简单,因为cal_pos触发器被触发时是要从sample->locate的,而状态机运行后初始状态为idle,当然出错啦。

    (二)目标状态切换法

    transitions同时还支持直接切换到目标状态的切换方式。

    1、用执行器进行切换

    把刚才的触发器切换法语句删掉,用这几句替换,看看是什么效果。

    tracer.to_locate()
    print(tracer.state)
    
    locate
    

    你可以看到,不论当前处于什么状态,状态机切换到了locate状态。transitions库根据状态定义,在初始化状态机的时候定义了为执行器tracer增加了to_locate()方法。注意观察可以看到所增加的方法为“to_<状态名>”

    2、用控制器进行切换

    我们还可以用machine.set_state('locate')强行切换状态。这也是不论当前处于什么状态,都将切换到你想要的目标状态。

    这两种分别通过执行器和控制器进行强行切换的方法很重要,在后面的实践中将发挥作用。

    本节主要总结了状态机的定义和切换,但仅仅是切换了状态而已,实际上并没有做什么卵事,充其量就是个前戏。下一节将真的来干一炮,也就是对执行器如何执行任务展开讨论。

    相关文章

      网友评论

          本文标题:python状态机transitions库的学习小结(1)--状

          本文链接:https://www.haomeiwen.com/subject/qfvhzctx.html