本文讲解4.4版jxTMS中新的有限状态自动机,整个系列的文章请查看:docker版jxTMS使用指南:4.4版升级内容
docker版本的使用,请查看:docker版jxTMS使用指南
4.0版jxTMS的说明,请查看:4.0版升级内容
4.2版jxTMS的说明,请查看:4.2版升级内容
4.4版jxTMS中新的有限状态自动机是重新写的,只是尽可能的利用了jxLocalStateMachine模块中的基础设施,尤其是复用了SMInstance,这可以大大降低从jxLocalStateMachine迁移的代价。
重写一个新的有限状态自动机的原因,主要就是由于上文所介绍的本地数据总线和文本化定义这两者的影响。
对于状态机,笔者更喜欢以文本的方式来进行定义,更清晰简洁,而且容易和相关各方沟通。所以当获得了python中的文本构造分析能力后【基于antlr】,笔者第一个想法就是将其用于对状态机的改造。
考虑到改造的变动很大,所以干脆还是重写一个比较好。
引用:
from jx.jxLocalStateMachine import stateMachine
使用stateMachine来定义一个状态机就简单多了:
#先声明一个状态机
sm1 = stateMachine('sm1')
#用该状态机的define来修饰一个函数即完成了状态机的定义
@sm1.define
def smDefind():
'''
初始状态 s0
状态为 s0 时发生 event_1 跃迁到 s1
状态为 s1 时发生 event_2 跃迁到 s1 执行 a1,时钟 dt1 延时 1 秒 执行 a2
状态为 s1 时发生未期望事件跌落到 s0 执行 ae
'''
pass
状态机的定义放到了函数的__doc__之中,再用状态机的define进行修饰即可。
看过python服务之内置自动机一文的就知道,状态机的定义就三种:
-
初始状态,smDefind中的第一行就指定了sm1的初始状态为s0
-
跃迁,smDefind中的第二行、第三行,分别定义了s0时发生事件event_1、s1时发生事件event_2的跃迁
-
跌落,smDefind中的第四行定义了s1时当发生未期望事件时的跌落
stateMachine另外增加通过本地数据总线来抄收数据作为事件的定义,这部分功能下一篇文章中进行介绍。
stateMachine使用了中文作为关键字,也就是说smDefind的__doc__中,凡是中文的都必须原封不动的使用【也不能加空格等】。
状态机语法
1、初始状态:
'初始状态' VARIABLE
就是关键字【初始状态】后跟一个变量。
注:变量是字符【英文字符、中文字符或英文下划线】开头,后跟字符或数字的组合
2、跃迁:
'状态为' VARIABLE '时发生' VARIABLE '跃迁到' VARIABLE activeStatements?
第一个变量是当前状态,第二个变量是事件,第三个变量是跃迁到的状态,最后是动作语句组,因为跟了一个问号,所以动作语句组是可选的,如smDefind中的第二行s0状态遇event_1的跃迁,就没有定义动作语句组,也就是跃迁时只修改当前状态但不做任何动作。
3、跌落:
'状态为' VARIABLE '时发生未期望事件跌落到' VARIABLE activeStatements?
第一个变量是当前状态,第二个变量是跌落到的状态,最后是可选的动作语句组。
动作语句组是由逗号分隔的多个动作:
activeStatement (Comma activeStatement)*
而动作有五种:
#执行本地函数,可延时执行
delayCall? '执行' VARIABLE
#关闭前述的延时时钟
'关闭时钟' VARIABLE
#设置状态机实例的参数【需状态机实例支持】
'设置' VARIABLE value
#通过自定义协议包向远程的现场数据采集卡下达命令
'下达命令' VARIABLE VARIABLE VARIABLE value
#通过自定义协议包向远程的现场数据采集卡触发事件
'触发事件' VARIABLE VARIABLE VARIABLE value
delayCall的语法为:
'时钟' VARIABLE '延时' time_C
而time_C的语法为:
NUMBER '秒'
NUMBER '分'
NUMBER '小时'
NUMBER '小时' NUMBER '分'
NUMBER '分' NUMBER '秒'
NUMBER '小时' NUMBER '分' NUMBER '秒'
所以smDefind中的【状态为 s1 时发生 event_2 跃迁到 s1 执行 a1,时钟 dt1 延时 1 秒 执行 a2】的动作就是有两个:
-
立刻执行a1函数
-
延时1秒后执行a2函数,在延时期间,如果将dt1关闭,则a2函数不会执行
本地函数
上面提到本地执行的函数,要先关联给状态机:
@sm1.active
def ae(smi,params):
print(f'sm[{smi._name}] call ae')
#smi.closeTimer('dt1')
@sm1.active
def a1(smi,params):
print(f'sm[{smi._name}] call a1')
@sm1.active
def a2(smi,params):
print(f'sm[{smi._name}] call a2')
只要用状态机的active给相应的函数修饰一下即可将该函数注册为状态机中的同名本地动作。
本地动作函数的签名应为:
def loaclActive(smi,params):
#smi:调用happen的状态机实例
#params:调用happen时送入的参数
状态机实例
状态机不能直接使用,只能使用状态机的实例:
from jx.jxLocalStateMachine import SMInstance
生成实例:
smi1 = SMInstance(sm1,name='smi1')
状态机实例的对象函数有:
debug(self, bv)
设置状态机实例的调试状态
参数:
bv:True打开调试模式,False关闭调试模式
返回值:
无
说明:
调试模式时会对整个状态机的运行过程进行跟踪,sm.py实例文件中调试模式就是打开的,大家可以执行了看一些效果:
python3 sm.py
currentState(self)
查询状态机实例的当前状态
closeTimer(self, tn)
关闭状态机实例的时钟
参数:
tn:时钟名
返回值:
无
说明:
时钟名是延迟调用时指定的时钟名,如果该时钟尚未创建则忽略
set(self, paramName, paramValue)
set的active的赋值接口
参数:
paramName:参数名
paramValue:参数值
返回值:
无
说明:
状态机实例的set函数只是一个空接口,有需要的状态机示例需要自己来实现,如device等
happen(self, event,param=None)
触发一个事件
参数:
event:事件
param:参数值
状态机调试
1、语法检查
大家看一下sm.py文件,可以修改一下其中的汉字,如在【时发生未期望事件跌落到】前加几个英文字符,如【dd时发生未期望事件跌落到】,保存后执行:
python3 sm.py
就会看到jxTMS报语法错误,一大段错误跟踪的最后两行是:
File "dataDual.py", line 224, in dataDual.jxErrorListener.syntaxError
Exception: [状态为 s1 dd时发生未期望事件跌落到 s0 执行 ae]syntaxError::line:1/column:7/msg:no viable alternative at input '状态为s1dd时发生未期望事件跌落到'
Exception: 后的方括号中指出了出现语法错误的语句,column指出了错误的位置,也就是第7个【从0开始,汉字只算一个字符】字符【d】处错了。
2、逻辑处理
可以如sm.py所示意,先将状态机实例的debug打开,然后将各动作函数全部用print代替,然后手动触发,然后执行sm.py跟踪状态机的运行过程。
参考资料:
下面的系列文章讲述了如何用jxTMS开发一个实用的业务功能:
下面的系列文章讲述了jxTMS的一些基本开发能力: