jxTMS:低成本快速定制的业务开发平台。
智能控制之状态机
笔者之前曾开发过危化品的智能转运箱系统,因为前不久完成了前文的目录服务,所以也正好趁机对其进行了一次升级。
该系统将防护严密的中转库和各实验室视为安全堡垒区,则转运箱出区自动增加保险,入区自动解除保险,出区以前文的失联为触发条件,入区则通过对标定安全堡垒区的基站的wifi信号进行扫描、然后检测基站蓝牙信标的RSSI值为触发条件。整个转运及回库的闭环流程的全状态跃迁图如下:
上图中的各状态之间的跃迁线上的标注意为:【输入事件/动作】,则连接两个状态的跃迁线上的标注【如s0到sStart】应做如下的解读:
当前状态为s0时,如发生了cmd:startTask事件,则跃迁至sStart状态,并执行开门、绿灯亮两动作
注:其中的cmd:startTask等以cmd:开头的输入事件来自如实验室管理系统等外部的上级管理系统,而jxTMS目前提供rest和webSocket两种接口供上级系统勾连。
这个状态跃迁图其实可实现三种转运模式:
-
单趟转运:从s0走【cmd:startTask】这条线再回到s0结束,这就是从某个安全堡垒区转运到另一个安全堡垒区,这种转运模式在危化品管理中应该主要是用于两实验室之间的危化品调度,但应用频次应该非常低
-
闭环转运:从s0走【cmd:startTask】这条线再回到s0,然后再从s0走【cmd:nextTask】这条线再回到s0结束,这就是从中转库出发转运到某实验室进行投料,然后回库的闭环转运流程,这是危化品转运的主要模式
-
多目的地集中转运:从s0走【cmd:startTask】这条线再回到s0,然后反复从s0走【cmd:nextTask】这条线再回到s0,最后从s0走【cmd:nextTask】这条线再回到s0结束,这就是从中转库一次性向多个实验室沿某个规划好的路线进行转运投料,最后回库的闭环转运流程。这种模式虽然看起来效率很高,但安全风险太大
整个箱子的硬件信号的接入与馈出都采用RTU的modbus设备进行控制,相关的转运流程处理、闭环处理等,都不是本文的重点,就不展开讲了。我们主要是看如何用状态机做控制这部分的代码:
#系统的配置,因为下面的代码要用到,为了避免大家看后面的代码疑惑,所以才列到这个地方,知道什么样就好
localConfig = {
#转运超时时钟超时时间,秒
'transTimeover_interval' : '3600',
#四路输入输出设备的master设备名
'rtuDev' : '/dev/ttyUSB0',
#四路输入输出设备的总线地址
'busNo' : '1',
#rtu的波特率
'baudRate' : '38400',
#轮询读的间隔,毫秒
'readInterval' : '250',
#输入端口数
'readNum' : '4',
#输出线圈数
'writeNum' : '4',
#门状态
'input_doorState' : '0',
#门锁
'output_door' : '0',
#喇叭
'output_horn' : '1',
#绿灯
'output_Green' : '2',
#黄灯
'output_Yellow' : '3'
}
#上面的状态跃迁图所对应的状态机的定义
#目前所有的硬件控制,都继承自基于前文目录服务中所描述的基础模块开发的专用于硬件控制的python类
#而状态机的定义是用@修饰符对本类的某个对象函数的修饰
#因为状态机的定义中可能需要用本地配置中的值进行替代,所以在定义时需要给出一个配置对象
@smTransBox.define(vl=localConfig)
def smTransBoxDef(self):
'''
initState s0 ;
//本状态机所接收的输入事件定义
//本状态机一共从三个设备接收信号并识别为不同的输入事件:
// 1、modbus.boxControl设备,是本机作为master所管理的总线地址为1的四路输入输出模块
// 2、local.stateMgr设备,一个父类中定义的本地设备,用于简化本机的各种消息通知,其会将接收到的消息直接作为输入转发出来
// 目前定义了四个消息端口:
// - 'state':用于设备管控状态机的状态通知,这里定义的状态机是业务状态机,但父类中还定义了一个设备管控状态机用于设备管理
// - 'network':用于设备和目录服务的连接状态的通知
// - 'cmd':用于接收到的外部的命令通知
// - 'event':用于触发事件
// 3、timer.boxTimer设备,一个本地的定时器,当转运开始后启动,转运结束后停止
// 初始设计中如果超时触发,会触发高音喇叭啸叫,但目前考虑到用户的管理水平,所以不做实现
//
//本状态机的所有事件都是本地事件,即不通过网络来接收其它设备发送的信号,都是上面这三个本地设备的信号
//服务器下达任务工单
event local eStartTask from 'local.stateMgr:cmd' == 'startTask' ;
//服务器下达下一步转运命令
event local eNextTask from 'local.stateMgr:cmd' == 'nextTask' ;
//门开了【总线号.线圈号】,花括号中的变量名会被转换为localConfig中对应的配置
event local eDoorOpen from 'modbus.boxControl:{busNo}.{input_doorState}' == 1 ;
//门关了
event local eDoorClosed from 'modbus.boxControl:{busNo}.{input_doorState}' == 0 ;
//出区【中心库和实验室】后和服务器失去联系
event local eDisConnected from 'local.stateMgr:network' == 'disConnected' ;
//入区【中心库和实验室】后和服务器取得联系
event local eConnected from 'local.stateMgr:network' == 'connected' ;
//扫描到了目标的wifi信号
event local eScanWifi from 'local.stateMgr:event' == 'scanWifi' ;
//扫描到了目标的蓝牙信标【即距离已经足够的近了】
event local eScanBlueTooth from 'local.stateMgr:event' == 'scanBT' ;
//转运超时
event local eTimeover from 'timer.boxTimer:transTimeover.tick' == '*' ;
//状态跃迁的定义
//根据上面的跃迁图一一对应就好;如果最后带有call funcName的,就是在跃迁后会执行名为funcName的本地函数去实现标注中要求实现的动作
trans when s0 happen eStartTask to sStart call funcStart;
trans when s0 happen eNextTask to sWaitLose call funcNextTask;
trans when sStart happen eDoorOpen to sWaitCloseDoor ;
trans when sWaitCloseDoor happen eDoorClosed to sWaitLose call funcWaitLose;
trans when sWaitLose happen eDisConnected to sTrans call funcTrans;
trans when sTrans happen eScanWifi to sWaitBT call funcWaitBT;
trans when sTrans happen eDoorOpen to sError call funcError;
trans when sWaitBT happen eScanBlueTooth to sWaitConnected call funcWaitConnected;
trans when sWaitConnected happen eConnected to sWaitOpenDoor call funcConnected;
trans when sWaitOpenDoor happen eDoorOpen to sWaitCloseDoor2 ;
trans when sWaitCloseDoor2 happen eDoorClosed to s0 call funcOver;
//联动设备管控,即当将设备停用后,本状态机也应停止工作;将设备投产时,本状态机也应能进入工作状态
event local eInActive from 'local.stateMgr:state' = 'inActive' ;
event local eCheck from 'local.stateMgr:state' = 'check' ;
event local eActive from 'local.stateMgr:state' = 'active' ;
//trans when是当前状态为某状态时才会捕获某事件而trans force是不管什么状态下只要捕获到某事件就会触发
//不管什么原因,本机停用了
trans force eInActive to sError call funcInActive;
//由于sError发生时,可能是在转运过程中出现的问题,导致状态不对不执行funcWaitConnected来连接网络
//所以当出现问题后,如果重启也不能解决问题,就需要将箱子连接网线后,从服务器发送setState命令来手动触发eCheck
trans force eCheck to sWaitCheck call funcWaitCheck;
//由于箱子里存放的是危化品,所以当出现故障后,必须手工设置check状态,检查完后再手工设置激活状态,本状态机才会复位到s0状态
trans when sWaitCheck happen eActive to s0 call funcOK;
'''
pass
各跃迁后的动作函数就不复赘述了。以笔者的经验,用状态机执行控制任务的最大好处就是高稳定性、高可靠性:只要状态图画清楚了,实现起来既非常简单又极为可靠。但状态机的最大问题也就是要画清楚状态图,如果由于各种原因导致状态和外部事件对不起来,状态机就会非常干净利落的死到那了:)
所以呢,状态机是将开发重心从编程转为对问题的认识与分析的可靠手段。
目前,jxTMS已经打包为云服务器镜像,开发者开箱即用:
jxTMS-腾讯云市场