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

2020-01-18  本文已影响0人  阿罗_ca27

一、学习背景

(一)为什么要用状态机

在用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')强行切换状态。这也是不论当前处于什么状态,都将切换到你想要的目标状态。

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

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

上一篇下一篇

猜你喜欢

热点阅读