天天看點

智能控制之狀态機

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-騰訊雲市場​