Package: efsm
Catalogue/ 目录General/ 总体描述State/ 状态Processing Function/ 处理函数add the smallest unit to fsmconfig the start and endMicro Modehow to useStandard Modehow to useYou could jump to update other fsmYou could create a StateSet before and pass this state_set to addBy the way that StateSet is not a user objectShell Modehow to usebut we could use decorater to make it more simpleuse doc to do the same thinguse doc to a class used EfsmMetademosPython Demo-EfsmMicroPython Demo-micro or StateMachine0 python_module-efsmThis is module to come fsm in python.There are three parts of this module made to deal with different cases: micro, standard and shellThese three modules correspond to three core.py这是为python设计的fsm模块。本模块分为三个部分，分别用于处理不同的情况：Micro, Standard, Shell这三个模块对应三个core.pyinstall:pipinstallefsm1 General/ 总体描述efsm is a one-stage state machine. The smallest unit is composed of severalstateand their correspondingprocessing function.efsm是一个一段式状态机，最小单元是由若干个状态和其对应的处理函数组成的。1.1 State/ 状态Any Python object which can hashable could be used regard as a state. Of course, Python string object is the best choice to represent a stateIn order to create a state, you do not need to do anything more, such as:"this is a string",(1,),lambda:...#  These are acceptable state objects任何可以hash的python对象都可以作为一个状态，当然，python的字符串对象是表示一个状态的最佳选择.为了获得一个状态，您不需要做任何操作，比如:"this is a string",(1,),lambda:...#  这些都是可以接受的状态对象1.2 Processing Function/ 处理函数The processing-function to execute specific tasks and return to the next state according to the current state.An available processing function must contain two parameters: state, o处理函数的作用是根据当前的状态去执行特定的函数任务以及返回下一个状态。一个可用的处理函数必须包含三个参数: state, o, sparam: state:The current state of fsm which is used to let the programmer decide which branch to exec.Note, however, that this function will only be called when the fsm is in a specific state, which is bound to the function in the smallest unit当前的fsm的状态，用于让程序员判断应该执行何种分支。不过请注意，这个函数只会在fsm处于特定状态时才会调用，这些特定状态是和该函数绑定在一个最小单元内的param: o:The attached running environment of the smallest unit is actually an empty object instance.Each smallest unit has a corresponding attached o, in which some data can be stored when the processing function is executed该最小单元的附带的运行环境，实际上是一个空的对象实例。每个最小单元都有一个与之对应的附带o, 处理函数在执行时可以将一些数据储存在这个o中.param: s:The attached running environment of the fsm is actually an empty object instance.Each fsm has a corresponding attached s, in which some data can be stored when the processing function is executed该执行该proccessing function的fsm的运行环境，实际上是一个空的对象实例。每个fsm都有一个与之对应的附带s, 处理函数在执行时可以将一些数据储存在这个s中.return:Jump to the next state by the return value of the processing function. If you return none, it means that there is no state transition通过处理函数的返回值跳转到下一个状态。如果返回None，就意味着不进行状态转移.defupdate(state,o,s):# proccessing function which only handle: idle, move, stop/ 只负责处理idle, move, stop三种状态的函数matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'Then add these smallest units into the FSM object and constantly update these states to come the function of FSM
然后将这些一个个最小单元添加进fsm对象并不断更新这些状态即可实现fsm的功能1.3 add the smallest unit to fsmuseadd(...)to add the smallest unit into your fsmdefadd(*state,fn=None,data=None):"""add a smallest unit to your statemachine:param *state: include some states as a group.:param fn: then assign a proccessing function to only handle this group of states_: None  Note that this is a must param. If you do not pass fn:param data: the 'o' offer to your proccessing function_: None  Mean it will create a empty object instance for this smallest unit.:return : the smallest unit.  list[tuple[*state], fn, data]"""Here are other common api:use remove(...) to remove one state from your fsmdefremove(*state,error=True):"""remove a state.It won't remove the hole group states but only remove the state from the group statesThe smallest unit will be remove only after all of it's group states all being removed.# example.remove('idle').remove('idle', 'move'):param *state: fsm will remove them one by one.:param error: bool If not find, will raise error:return: None"""use find(...) to find a smallest unit in fsmdeffind(state):"""find the smallest unit corresponding to a state:param state: fsm find the state, and return the smallest unit:return: [states, fn, data] or None"""use list(...) to showcase all state-(fn, data) in fsmdeflist():"""showcase stateNote: it do not return list but dict:return: {state: (fn, data)}"""use tolist(...) to get all the smallest units in fsmdeftolist():"""change _groups to list:return: [[(*state), fn, data], ...]"""useis_finish(...)to get whether this fsm is finish.(only work when your fsm been setted 'end')defis_finish():"""get whether the fsm is finish.:return: bool"""use is_prepare(...) to get whether this fsm have executed .step().defis_prepare():"""get whether the fsm is steped.If this statemachine has link to other as target, will check whether the target is finish.:return: bool"""userestart(...)to reset your fsm but keep your config and all the smallest unitsdefrestart():"""reset your fsm but keep your config and all the smallest units:return:"""usestep(...)to update your fsmdefstep(*ex):"""step, mean update once:param *ex: external factor interference. Before formally exec step(if not retarget to other), call these exfunc(sm)->None. If any exfunc return a non-None value will immediate stop 'step' in this time, and 'step' will return the value from that exfunc.:return: bool about statemachine is running-needy or not."""1.4 config the start and endThere are some attrs in your fsm:
.state        # tell you the the state now fsm is.   # default be setted as .start when first call .step W/R
.start        # tell fsm what the first state is when it first .step.    # it is a must-fill attr W/R
.end          # tell fsm should stop and will return False from .step, and when you call is_finish() will return False also. W/R
.end          # tell fsm should stop and will return False from .step, and when you call is_finish() will return False also. W/R
.on_step      # like fn(statemachine:object)->None, will called by fsm automatic before .step return
.on_end       # like fn(statemachine:object)->None, will called by fsm automatic when state==end in .stepAt least, you must config the .start.The statemachine usage is different in these three way:2 Micro ModeIt can deploy on micropython. The simplest fsm in this module only consist of some datum and functions. You could to use this module as a singleton object.该模式可以部署在micropython单片机上。这个模块只包含一些函数和数据，你可以把这个module当作一个单例对象2.1 how to usefromefsmimportmicrodefupdate(state,o,s):matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'micro.add('idle','move','stop',fn=update)# fn is must in micro mode. data is available.micro.start='idle'micro.end='stop'whilemicro.step():...print("finish.")i'm idle, next to move
i'm moving, next to stop
finish.3 Standard ModeIt can deploy on micropython. This module offer a StateMachine class. And you can link their instances to a net.该模式可以部署在micropython单片机上。这个模块提供了一个StateMachine类，并且你可以将它们的实例连接成网3.1 how to usefromefsmimportStateMachinedefupdate(state,o,s):matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'sm=StateMachine('idle','stop')sm.add('idle','move','stop',fn=update)# fn is must in standard mode. data is available.whilesm.step():...print("finish.")i'm idle, next to move
i'm moving, next to stop
finish.3.2 You could jump to update other fsmhow to link statemachine:fromefsmimportStateMachinedefupdate(state,o,s):matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'sm1,sm2=StateMachine('idle','stop'),StateMachine('idle','stop')sm1.add('idle','move','stop',fn=update)sm2.add('idle','move','stop',fn=update)sm1.link('stop',sm2,'idle')# link sm1.stop -> sm2.idle    # when sm1.state come to 'stop', it will start at sm2.idle in next sm1.stepwhilesm1.step():...# note that you only call sm1.step() hereprint("finish.")i'm idle, next to move
i'm moving, next to stop
i'm idle, next to move
i'm moving, next to stop
finish.But careful for the circle link. It will cause infinite loop.3.3 You could create a StateSet before and pass this state_set to addfromefsmimportStateMachine,StateSetdefupdate(state,o,s):matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'sm=StateMachine('idle','stop')ss=StateSet('idle','move','stop',fn=update)# it have the same params as .addsm.add(ss)# fn is must in standard mode. data is available.whilesm.step():...print("finish.")i'm idle, next to move
i'm moving, next to stop
finish.3.4 By the way that StateSet is not a user objectfromefsmimportStateSetss=StateSet('idle','move','stop',fn=lambda*a:...)print(ss)print(type(ss))[('idle', 'move', 'stop'), <function <lambda> at 0x0000028BC8DD76D0>, <efsm.standard.core.Local object at 0x0000028BC8E269B0>]
<class 'list'>4 Shell ModeIt can only deploy on PC python. As the name 'shell' shown, it have all the methods statemachine in standard-mode had, almost like a shell on statemachine in standard-mode. It provide some convienent ways to create fsm.该模式只能部署在电脑端的python上。类如其名，这个'shell'就好像标准模式下的StateMachine下加了一个壳。它提供了一些简单方便的途经去生成一个fsm4.1 how to useShell-mode use Efsm to replace StateMachine:fromefsmimportEfsm,fsm# efsm offer you a default Efsm instance named fsmdefupdate(state):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'# fsm = Efsm()  # 1. use default efsm for showcase  2. Efsm could instance without paramsfsm.add('idle','move','stop',fn=update)# When efsm has not start, it will try to take the first state added at the first time as start# so the start is 'idle'# When efsm has not end, it will try to take the last state added at the first time as end# so the end is 'stop'whilefsm.step():...print("finish")When we take off all the notes:very simplify code, right?fromefsmimportEfsm,fsmdefupdate(state):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'fsm.add('idle','move','stop',fn=update)whilefsm.step():...print("finish")i'm idle, next to move
i'm moving, next to stop
finish.4.2 but we could use decorater to make it more simpleuse decorater to do the same thing:fromefsm.shellimport*ss=StateSet()# create a empty StateSet.  # Only use like this way in shell-mode@ss.idle.move.stop# only use for function. not the class methoddefupdate(state):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'fsm.add(update)whilefsm.step():...print("finish")use @fsm before the @ss.idle... could auto add it to fsmfromefsm.shellimport*ss=StateSet()# create a empty StateSet.  # Only use like this way in shell-mode@fsm@ss.idle.move.stop# only use for function. not the class methoddefupdate(state):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'whilefsm.step():...print("finish")every efsm instance has theirself ss.use fsm.ss replace ss could reach the same effect:fromefsm.shellimport*@fsm.ss.idle.move.stop# only use for function. not the class methoddefupdate(state,o):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'whilefsm.step():...print("finish")i'm idle, next to move
i'm moving, next to stop
finish4.3 use doc to do the same thingyou must add @state or @sta or @s to the function's doc:fromefsm.shellimport*defupdate(state,o,s):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it."""@state idle move stop  # you could use ',' as the separater.  like idle, move stop"""matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'fsm.add(update)# this way need call .addwhilefsm.step():...print("finish")i'm idle, next to move
i'm moving, next to stop
finish4.4 use doc to a class used EfsmMetayou could do the similiar thing to a class which use EfsmMetafromefsm.shellimport*classTest(metaclass=EfsmMeta):# use EfsmMeta to auto add Test._efsm = Efsm()"""@state idle move stop -> update   # do not same as the function doc, you need to use '->' or ':' ro assign a method in the class to these group states"""# __step__ = 'step'  # Auto create a method in this class, used for efsm.step(). Default is 'step', you can overwrite it.# def __bool__(self): ...  # Auto create a __bool__ method if you donot define it, Default is efsm.__bool__, you can overwrite it.defupdate(self,state,o):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'test=Test()whiletest.step():...# test doesnot have any other method in efsm.print("finish")take a compare with the follow example(not use EfsmMeta):fromefsm.shellimport*classTest:"""@state idle move stop -> update  # in this way, the '->' is useless."""def__call__(self,state,o):# in shell-mode, you caould pass 0-3 params, it will auto choose how to call it.matchstate:case'idle':print("i'm idle, next to move")return"move"case'move':print("i'm moving, next to stop")return"stop"return'stop'test=Test()fsm.add(test)whilefsm.step():...print("finish")That's all this python_module-efsm provides.Now i will show you few demos in real project:5 demos5.1 Python Demo -Efsm...5.2 MicroPython Demo -micro or StateMachineto use in micropython. you need to copy the core.py from efsm/standard/core.py or efsm/micro/core.py to your micropython device, and you can rename it to 'efsm.py'