从零搭建工业控制系统(十一):阀门控制系统——开一个阀门到底有多复杂

发布时间:2026/8/23 23:49:53
从零搭建工业控制系统(十一):阀门控制系统——开一个阀门到底有多复杂 阀门控制系统开一个阀门到底有多复杂这是「从零搭建工业控制系统」系列第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加蜂鸣器怎么根据系统状态自动控制。