天天看点

用jxTMS开发智能转运箱(2)

本系列以开发管控类危化品的智能转运箱为例讲述了jxTMS的智能硬件支持下的业务管控体系:

用jxTMS开发智能转运箱(1)

jxTMS目前已打包为docker容器,可以下拉jxTMS的docker镜像并按jxTMS使用示例尝试使用。

设备

在jxTMS的智能控制体系中,所有的设备都是用go实现的。其实现层次如下:

用jxTMS开发智能转运箱(2)

即所有的智能硬件都统一抽象为Dev的接口,python代码根据这个抽象统一对所有前端设备的读取、操作与控制。这样,一个设备在python主代码中只要简单的声明即可:

#智能转运箱中用于控制电锁、指示灯、高音警号的Modbus设备
devBoxControl = jxGoDev('modbus','boxControl')
           

也就是说:一个设备就是一个jxGoDev类的对象,只要在初始化时指定设备类型和设备名即可。

目前已实现的设备类型包括:

  • timer:定时器,一次性或可重复的间隔指定秒数后触发事件
  • cron:unix中cron格式的定时器,按cron格式在符合指定格式的时间点,触发事件
  • local:本地设备,将输入直接馈送到输出,主要用途一是在开发测试阶段用本地设备旁路掉具体的硬件以防止逻辑错误导致损失,同时提高开发测试的效率;二是将用户指令转换为设备的输入输出,从而统一逻辑部件的事件机制
  • modbus:modbus设备,同时支持rtu、tcp等连接方式,以及DIDO【数字型输入输出模块】类型等
  • pi:树莓派的各管脚,目前包括gpio的输入输出和pwd

注:目前的jxTMS的智能控制都是对实时性要求不高的安防、环控等弱电系统的控制,这样的控制系统系统开销极低,而目前卡片式计算机性能升级很快,所以暂不考虑对实时性的要求

设备配置

jxTMS的智能硬件抽象,本质上就是四种能力的集合:输入、动作、配置、设置。

输入显然就是从设备读取数据,而动作、配置、设置其实都是向设备写数据,但这三者在逻辑上是有着明显的区别的:

  • 动作是系统命令设备指定输出【直接对外部产生作用】,设置则是系统命令设备对其自身进行调整【间接的影响到外部】
  • 配置是设备使用前对设备进行初始化,设置则是在设备运行中对设备进行调整

智能硬件的配置同样采取了jxTMS中的文本定义的方式,例如上面声明的devBoxControl设备的配置是:

@devBoxControl.config(vl=localConfig)
def devBoxControlConfig(self):
	'''
	rtu DIDO dev='{rtuDev}',addr={busNo},baudRate={baudRate},readInterval={readInterval},readNum={readNum},writeNum={writeNum};
	'''
	pass
           

即用前面所定义的modbus类型的jxGoDev对象的config函数来修饰devBoxControlConfig,其实就是用定义在该函数的__doc__中的配置文本来执行初始化。

其中的vl是用于关联一个用于归拢配置参数的局部变量:

localConfig = {
	#转运超时时钟超时时间,秒
	'transTimeover_interval' : '3600',
	#四路输入输出设备的master设备名
	'rtuDev' : '/dev/ttyUSB0',
	#四路输入输出设备的总线地址
	'busNo' : '1',
	#rtu的波特率
	'baudRate' : '9600',
	#轮询读的间隔,毫秒
	'readInterval' : '500',
	#输入端口数
	'readNum' : '4',
	#输出线圈数
	'writeNum' : '4',
	#门状态
	'input_doorState' : '0',
	#门锁
	'output_door' : '0',
	#喇叭
	'output_horn' : '1',
	#绿灯
	'output_Green' : '2',
	#黄灯
	'output_Yellow' : '3'
}
           

所以devBoxControl也可按如下的形式进行配置:

@devBoxControl.config(vl=None)
def devBoxControlConfig(self):
	'''
	rtu DIDO dev='/dev/ttyUSB0',addr=1,baudRate=9600,readInterval=500,readNum=4,writeNum=4;
	'''
	pass
           

这两种形式在功能上没有区别,只是第一种形式将所有的配置参数集中了起来,如果对设备比较熟悉,则在设备定义好后,就不再需要查找定义配置代码,在调试时较为便利;而第二种形式则是看到代码就可知道设备的具体定义,这样在设备较多的时候,就非常直观。即第一种形式侧重于调试,尤其是多种设备的联合调试;第二种形式则侧重于代码理解,让代码阅读起来更为直观。

编程模型

输入是从设备的某输入端口,读取一个值。通常的处理方法是直接读,然后对读到的值处理条件判断来调用对应的处理函数。但这种处理方式把应用逻辑和编程逻辑混在了一起,对开发者的要求较高,程序质量依赖于开发者的技术水平。

所以jxTMS采取的是三层处理模型:

  • 输入层,用文本化的条件等式将输入识别为事件
  • 逻辑层,go中预定义好的、功能强大的、可靠而稳定的逻辑部件,用户用易于理解的文本来描述控制逻辑,两者相结合就成为高度稳定、可靠的、定制的控制器
  • 执行层,用户用python编写的控制动作

也就是说,jxTMS的智能控制编程模型是:

用jxTMS开发智能转运箱(2)

即逻辑部件加载描述控制逻辑的文本成为满足用户需要的特定的控制器,输入被条件识别为事件,事件驱动控制器工作来筛选符合控制逻辑的动作加以执行。

这样一来,开发者所做的事情就分为了三个部分:

1、输入转换为事件,如:

event local eNextTask from 'local.stateMgr:cmd' == 'nextTask' ;
           

意为:当本地输入【local.stateMgr:cmd】的值是【nextTask】时,识别为事件eNextTask。

注:事件的识别一般都是逻辑部件的一部分,所以这一布其实是合并到逻辑部件的控件逻辑描述中的

2、控制逻辑描述。详见第三部分

3、动作编程。具体的控制动作,利用python丰富的而可靠的第三方库来大幅度提高代码质量、提高开发速度。如智能转运箱中实现扫描蓝牙、测距以识别是否进入到安全区中:

def _beaconScan(self):
	while self.scanBT:
		#反复扫描蓝牙基站
		bs = BeaconService()
		devices = bs.scan(3)
		self._log('my uuid:{}/rssi:{}'.format(self.uuid,self.rssi))
		for address, data in list(devices.items()):
			self._log('scan uuid:{}/rssi:{}'.format(data[0],data[4]))
			#扫描到目的基站且基站的rssi强于指定值
			if data[0] == self.uuid  and self.rssi <= data[4]:
				#判定和目标基站的距离接近到足够近的距离
				self.event('scanBT')
				return
		time.sleep(10)
           

由于实现控制逻辑的逻辑部件是预先实现、良好测试过的通用化工具,所以系统的稳定性与可靠性大幅度的提升;同时各动作被逻辑部件所分割只需编程实现非常明确的动作要求,所以代码的复杂度显著下降,代码的开发成本自然也会显著下降。

此外,控制逻辑不是用代码来实现的,而是用符合语法要求的文本进行描述,这样就可以避免由不熟悉控制需要的代码开发人员来开发控制逻辑,而是由更熟悉现场、熟悉业务、熟悉需求的人来讨论恰当的控制逻辑。所以控制逻辑会更符合实际情况与需求、更全面、更可行。

依据这一思路,jxTMS中的输入到事件的转换过程是:

  • 轮询读【如modbus设置】或触发读【支持硬件读事件的设备,如jxTMS所实现的时钟设备、本地设备等】
  • 将读到的值依次发送给注册到该端口上的监听器
  • 各监听器用注册时给定的条件表达式对该值进行判断,使得条件表达式结果为真的,或未指定表达式的,识别为触发
  • 监听器触发,则广播一个注册该监听器时关联的事件并输入值;为触发则丢弃
  • 对此事件感兴趣的控制器,监听该事件,接收到此事件广播后送入逻辑部件中,根据逻辑部件的当前逻辑,如需动作则执行相应的动作

上述过程的主体都为jxTMS实现,用户需要做的是:

  • 根据需要定义条件表达式来筛选合适的输入值
  • 定义接收事件的控制逻辑描述
  • 对要执行的动作进行编程

从而大大减少了对编程的需要,可以让更大的人员参与到开发中来,同时动作编程部分也较为单一且要求明确,这同样也降低了对编程的需要。