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