控制系統(tǒng)(十一):閥門控制系統(tǒng)——開一個閥門到底有多復(fù)雜)
閥門控制系統(tǒng)開一個閥門到底有多復(fù)雜這是「從零搭建工業(yè)控制系統(tǒng)」系列第11篇。上篇聊了權(quán)限控制這篇講閥門——工業(yè)系統(tǒng)里最頻繁的操作對象也是最容易出事的地方。一個閥門引發(fā)的真空事故第一次寫閥門控制的時候我想得太簡單了。開閥門嘛寫個1到寄存器不就完了await_modbusService.WriteSingleCoilAsync(slaveAddress,address,true);結(jié)果現(xiàn)場測試時節(jié)流閥還沒開到安全角度閘閥就直接打開了。腔體瞬間從大氣壓沖到高真空晶圓飛起來了。問題出在沒有聯(lián)鎖。閘閥打開前應(yīng)該先確認節(jié)流閥已經(jīng)開到指定角度但我當(dāng)時只寫了一句WriteSingleCoil就完事。后來重構(gòu)了整個閥門執(zhí)行器加了預(yù)檢查、聯(lián)鎖、前導(dǎo)動作、狀態(tài)等待。一個開閥門的操作背后有六七層邏輯。閥門執(zhí)行器架構(gòu)ValveCommandExecutor是所有閥門操作的入口。核心方法ExecuteAsync接收閥門ID和動作開/關(guān)publicclassValveCommandExecutor{publicasyncTaskCommandResultExecuteAsync(ValveIdvalveId,ValveActionaction,CancellationTokenctdefault){varconfig_registry.GetValveConfig(valveId);if(confignull)returnCommandResult.Fail(CONFIG_NOT_FOUND,$未找到閥門配置:{valveId});varcommandIdactionValveAction.Open?config.OpenCommandId:config.CloseCommandId;// 1. 切換閥互鎖檢查if(actionValveAction.Open){varinterlockResultawaitCheckChangeoverInterlockAsync(valveId,ct);// ...}// 2. 前置檢查varpreChecksactionValveAction.Open?config.OpenPreChecks:config.ClosePreChecks;varcheckResultawaitExecutePreChecksAsync(preChecks,ct);// 3. 前導(dǎo)動作if(actionValveAction.Openconfig.OpenPreActions?.Count0){varpreActionResultawaitExecutePreActionsAsync(config,ct);}// 4. 關(guān)閉依賴關(guān)閥門前先關(guān)其他閥門if(actionValveAction.Closeconfig.CloseDependencies.Count0){vardepResultawaitExecuteDependenciesAsync(config.CloseDependencies,ct);}// 5. 執(zhí)行命令單閥門或組合命令// 6. 等待狀態(tài)變化}}每一步失敗都會中斷整個流程返回CommandResult.Fail。預(yù)檢查開門之前先看路預(yù)檢查PreCheck是閥門操作的安全門。比如開閘閥之前要確認腔體蓋子關(guān)了狀態(tài)點位11開真空閥之前要確認 foreline 狀態(tài)正常狀態(tài)點位20。配置文件里這樣寫{valveId:GateValve,openCommandId:CMD_OPEN,openPreChecks:[{type:StatusProperty,statusProperty:SP_01,expectedValue:1,description:腔體蓋關(guān)閉}]}執(zhí)行時逐條檢查privateasyncTaskCommandResultExecutePreChecksAsync(ListPreCheckpreChecks,CancellationTokenct){foreach(varcheckinpreChecks){varactualValueawaitReadStatusAsync(check.StatusProperty,ct);if(actualValue!check.ExpectedValue){returnCommandResult.Fail(PRE_CHECK_FAILED,$前置檢查失敗:{check.Description}(期望{check.ExpectedValue}, 實際{actualValue}));}}returnCommandResult.Success();}切換閥互鎖不能同時開系統(tǒng)里有三個切換閥Changer1、Changer2、Changer3物理上不能同時打開——同時開會造成氣路短路?;ユi邏輯在開閥門前先檢查privateasyncTaskCommandResultCheckChangeoverInterlockAsync(ValveIdvalveId,CancellationTokenct){// 切換閥1和切換閥2不能同時開// 切換閥3和切換閥1/2不能同時開if(valveIdValveId.Changer1){varchanger2OpenawaitIsValveOpenAsync(ValveId.Changer2,ct);if(changer2Open)returnCommandResult.Fail(INTERLOCK,切換閥2已打開不能開切換閥1);}// ... 其他互鎖組合}前導(dǎo)動作開閘閥之前先開節(jié)流閥最經(jīng)典的場景開閘閥之前節(jié)流閥要先開到30度否則氣流沖擊太大。{openPreActions:[{type:Command,commandId:CMD_THROTTLE_30,description:節(jié)流閥開到30度},{type:Delay,milliseconds:10000}]}執(zhí)行順序下發(fā)節(jié)流閥30度指令 → 等待10秒 → 開閘閥 → 等閘閥狀態(tài)確認 → 節(jié)流閥全開指令。if(actionValveAction.Openconfig.OpenPreActions?.Count0){varpreActionResultawaitExecutePreActionsAsync(config,ct);if(!preActionResult.IsSuccess)returnpreActionResult;}兩種執(zhí)行模式命令模式 vs 寄存器模式閥門配置里有兩種寄存器模式privateasyncTaskCommandResultExecuteSingleValveCommandAsync(ValveConfigconfig,ValveActionaction,Stopwatchsw,CancellationTokenct){if(config.Register.IsCommandMode){// 命令模式通過命令索引執(zhí)行varcmdIndexactionValveAction.Open?config.Register.OpenCmdIndex:config.Register.CloseCmdIndex;awaitExecuteCommandByIndexAsync(config.Register.Type,cmdIndex.Value);}else{// 寄存器模式直接寫入寄存器varwriteValueactionValveAction.Open?config.Register.OpenValue:config.Register.CloseValue;awaitWriteRegisterAsync(config.Register,writeValue);}// 等待狀態(tài)變化varexpectedStatus(int)action;varwaitResultawaitWaitForStatusAsync(config.GetStatusProperty(),expectedStatus,config.TimeoutMs,ct);if(waitResult)returnCommandResult.Success();elsereturnCommandResult.Timeout($閥門{config.ValveId}狀態(tài)變化超時);}命令模式通過下發(fā)命令索引讓下位機執(zhí)行預(yù)設(shè)動作寄存器模式直接寫線圈值。前者更安全下位機有自己的邏輯后者更快。狀態(tài)等待寫了不代表成功寫完寄存器不代表閥門真的動了。必須等待狀態(tài)反饋varwaitResultawaitWaitForStatusAsync(config.GetStatusProperty(),// SP_01, SP_02...expectedStatus,// 1開, 0關(guān)config.TimeoutMs,// 超時時間ct);WaitForStatusAsync內(nèi)部輪詢讀取狀態(tài)寄存器直到值匹配或超時。超時就返回CommandResult.Timeout。調(diào)試模式下有個特殊處理——節(jié)流閥打開需要額外延遲if(Global.IsDevDebug){if(config.ValveIdValveId.ThrottleValveactionValveAction.Open){_logger.Debug(調(diào)試模式節(jié)流閥打開需要延遲1秒否則會報閥門狀態(tài)變化超時);awaitTask.Delay(1000,ct);}}模擬器響應(yīng)比真實硬件快反而導(dǎo)致狀態(tài)讀取時序錯位。這種坑只有踩過才知道。關(guān)閉依賴關(guān)我之前先關(guān)你有些閥門關(guān)閉前需要先關(guān)閉下游閥門。比如關(guān)切換閥之前要先關(guān)所有依賴它的閥門if(actionValveAction.Closeconfig.CloseDependencies.Count0){vardepResultawaitExecuteDependenciesAsync(config.CloseDependencies,ct);if(!depResult.IsSuccess)returndepResult;}還有CloseParents——關(guān)完當(dāng)前閥門后順帶關(guān)父閥門if(singleResult.IsSuccessactionValveAction.Closeconfig.CloseParents?.Count0){awaitExecuteCloseParentsAsync(config.CloseParents,ct);}閥門踩坑清單坑現(xiàn)象解決不做聯(lián)鎖切換閥同時開氣路短路CheckChangeoverInterlockAsync不等狀態(tài)寫了1但閥門沒動WaitForStatusAsync輪詢不做前導(dǎo)閘閥直接開氣流沖擊PreActions先開節(jié)流閥到30度模擬器時序節(jié)流閥狀態(tài)變化超時調(diào)試模式加1秒延遲不關(guān)依賴關(guān)切換閥后下游還通著CloseDependencies先關(guān)下游本篇小結(jié)知識點關(guān)鍵做法執(zhí)行流程互鎖→預(yù)檢查→前導(dǎo)動作→執(zhí)行→狀態(tài)等待兩種模式命令模式(下位機執(zhí)行) vs 寄存器模式(直接寫)聯(lián)鎖切換閥互斥不能同時開前導(dǎo)動作開閘閥前先開節(jié)流閥到30度等10秒狀態(tài)等待輪詢狀態(tài)寄存器直到匹配或超時關(guān)閉依賴關(guān)閥門前先關(guān)下游閥門閥門控制的核心不是寫值而是寫值之前確認安全寫值之后確認生效。下期預(yù)告第12篇燈塔與蜂鳴器控制閥門說完了下篇看燈塔——藍綠橙紅四色LED加蜂鳴器怎么根據(jù)系統(tǒng)狀態(tài)自動控制。