用jxTMS开发智能转运箱(3)
本系列以开发管控类危化品的智能转运箱为例讲述了jxTMS的智能硬件支持下的业务管控体系:
jxTMS目前已打包为docker容器,可以下拉jxTMS的docker镜像并按jxTMS使用示例尝试使用。
逻辑部件
在jxTMS的智能控制编程模型中,分为了三个部分:
-
将输入以文本化的条件的形式识别为事件,控制系统以事件来驱动
-
编程要执行的控制动作
-
用预定义的逻辑部件,加载文本化的控制逻辑描述,成为特定的控制器。以控制器来响应事件,调用动作函数
所秉持的就是IT的降阶思想,将复杂的控制问题转换为几个独立的部分,分别处理;通过提供预制的、可靠的、强有力的形式逻辑工具,将问题转化到通用处理框架下,提高系统设计的可读、可理解性,同时系统实现也更为稳定、可靠。
这一编程模型的轴心自然是各种逻辑部件,根据IT在控制方面的应用,最主要的有两种逻辑部件:
-
IT最基础的形式逻辑的表达:状态机。有限状态自动机是IT对绝大多数问题的抽象,是计算机工作的基础,自然是最重要的逻辑部件
-
模糊推理规则表。IT智能的基础就是产生式,但产生式过于严苛,所以应用范围有限,在引入模糊数学后,就成为模糊推理,就大大扩大了应用范围
本文主要讲解状态机的应用,模糊推理的应用可参考:模糊控制。
状态机的编程
利用状态机这个逻辑部件,智能转运箱的控制部分,就分解为三个部分:
1、输入事件的识别。经过梳理,笔者寻找到如下的事件:
-
箱体开门
-
箱体关门
-
出安全区
-
入安全区阶段1-进入目标安全区边缘
-
入安全区阶段2-和目标基站已非常接近
-
入安全区-通过目标基站已经联系上了服务器
注:入安全区分为三个阶段:扫描到基站发出的wifi信号【进入到安全区边缘】、扫描到基站发出的蓝牙信号强度高于某值【和基站的距离足够的近】、通过wifi登入基站并连接到服务器【入区】
-
命令:开始转运任务
-
命令:返回转运出发点
-
命令:脱离工作状态。由于各种原因,如检修、报废等,暂停转运箱的转运工作
-
命令:检查工作状态。如转运超时、转运中途开门等,转运箱会进入错误状态,并触发高音警号鸣叫,由于120分贝的鸣叫过于刺耳,所以检查工作的先停掉警号的鸣叫
-
命令:复位。检修、检查等工作完成后,需通过复位命令使得转运箱进入工作状态
2、根据这些事件,以及转运箱的任务要求,梳理整个转运任务的状态跃迁图如下:
注:交付前,由于尚不明确客户转运任务的最大用时,所以去掉了sTrans状态时的超时告警
上图中的各状态之间的跃迁线上的标注意为:【输入事件/动作】,即连接两个状态的跃迁线上的标注【如s0到sStart】应做如下的解读:
当前状态为s0时,如发生了cmd:startTask事件,则跃迁至sStart状态,并执行开门、绿灯亮两动作
有了这个状态图,用文本来描述这个状态图就是很简单的事了:
@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
状态机的描述包括两个部分:
1、事件的识别:
event local? 事件名 from 输入源 比较符 值 (and 输入源 比较符 值)*
其中:
local:指示是否为本地设备产生的事件,如果未指定local,则需通过消息系统向目标设备注册需网络广播该输入
输入源:点分格式命名的输入点,其形式为:{设备类型}.{设备名}.{输入类型}.{输入端口}。输入类型可省略
比较符:==、!=、>、>=、<、<=、like等
则
event local eNextTask from 'local.stateMgr:cmd' == 'nextTask' ;
意为:当本地输入【local.stateMgr:cmd】的值是【nextTask】时,识别为事件eNextTask
2、状态跃迁:
trans when 状态名 happen 事件名 to 新状态名 动作列表
其中:
状态名:指示当前状态
事件名:指示发生了何种事件
新状态名:将跃迁到的新的状态
动作列表:需执行的动作,一般都是call 函数名来调用相应的函数,这样可以进一步的降低状态机在描述时的复杂度,具体干什么,到时编程来实现就好
则
trans when s0 happen eStartTask to sStart call funcStart;
意为:当前状态为s0时,如发生了eStartTask事件,则跃迁到sStart状态,并执行funcStart函数
此外,还有一种强制跃迁,即不管在什么状态下,只要发生某事件则必定跃迁处理。如:
trans force eInActive to sError call funcInActive;
即只要发生eInActive事件,就跃迁到sError状态并调用funcInActive函数。
至于各动作函数的编程,被状态机切分后大多是非常简单的,除了蓝牙扫描、wifi扫描和wifi登入,其它一般只有两三行代码,大家根据状态图自己应该就能编写出相应的伪码,笔者就留给大家思考吧。