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

阀门控制系统:开一个阀门到底有多复杂

这是「从零搭建工业控制系统」系列第11篇。上篇聊了权限控制,这篇讲阀门——工业系统里最频繁的操作对象,也是最容易出事的地方。


一个阀门引发的真空事故

第一次写阀门控制的时候,我想得太简单了。开阀门嘛,写个1到寄存器不就完了?

await _modbusService.WriteSingleCoilAsync(slaveAddress, address, true);

结果现场测试时,节流阀还没开到安全角度,闸阀就直接打开了。腔体瞬间从大气压冲到高真空,晶圆飞起来了。

问题出在没有联锁。闸阀打开前应该先确认节流阀已经开到指定角度,但我当时只写了一句WriteSingleCoil就完事。

后来重构了整个阀门执行器,加了预检查、联锁、前导动作、状态等待。一个"开阀门"的操作,背后有六七层逻辑。


阀门执行器架构

ValveCommandExecutor 是所有阀门操作的入口。核心方法 ExecuteAsync 接收阀门ID和动作(开/关):

public class ValveCommandExecutor
{
    public async Task<CommandResult> ExecuteAsync(
        ValveId valveId,
        ValveAction action,
        CancellationToken ct = default)
    {
        var config = _registry.GetValveConfig(valveId);
        if (config == null)
            return CommandResult.Fail("CONFIG_NOT_FOUND", $"未找到阀门配置: {valveId}");

        var commandId = action == ValveAction.Open
            ? config.OpenCommandId
            : config.CloseCommandId;

        // 1. 切换阀互锁检查
        if (action == ValveAction.Open)
        {
            var interlockResult = await CheckChangeoverInterlockAsync(valveId, ct);
            // ...
        }

        // 2. 前置检查
        var preChecks = action == ValveAction.Open
            ? config.OpenPreChecks
            : config.ClosePreChecks;
        var checkResult = await ExecutePreChecksAsync(preChecks, ct);

        // 3. 前导动作
        if (action == ValveAction.Open && config.OpenPreActions?.Count > 0)
        {
            var preActionResult = await ExecutePreActionsAsync(config, ct);
        }

        // 4. 关闭依赖:关阀门前先关其他阀门
        if (action == ValveAction.Close && config.CloseDependencies.Count > 0)
        {
            var depResult = await ExecuteDependenciesAsync(config.CloseDependencies, ct);
        }

        // 5. 执行命令(单阀门或组合命令)
        // 6. 等待状态变化
    }
}

每一步失败都会中断整个流程,返回 CommandResult.Fail

图1:阀门执行器六步流程


预检查:开门之前先看路

预检查(PreCheck)是阀门操作的安全门。比如开闸阀之前要确认腔体盖子关了(状态点位11),开真空阀之前要确认 foreline 状态正常(状态点位20)。

配置文件里这样写:

{
  "valveId": "GateValve",
  "openCommandId": "CMD_OPEN",
  "openPreChecks": [
    {
      "type": "StatusProperty",
      "statusProperty": "SP_01",
      "expectedValue": 1,
      "description": "腔体盖关闭"
    }
  ]
}

执行时逐条检查:

private async Task<CommandResult> ExecutePreChecksAsync(
    List<PreCheck> preChecks, CancellationToken ct)
{
    foreach (var check in preChecks)
    {
        var actualValue = await ReadStatusAsync(check.StatusProperty, ct);
        if (actualValue != check.ExpectedValue)
        {
            return CommandResult.Fail(
                "PRE_CHECK_FAILED",
                $"前置检查失败: {check.Description} (期望{check.ExpectedValue}, 实际{actualValue})");
        }
    }
    return CommandResult.Success();
}

切换阀互锁:不能同时开

系统里有三个切换阀(Changer1、Changer2、Changer3),物理上不能同时打开——同时开会造成气路短路。

互锁逻辑在开阀门前先检查:

图2:切换阀互锁规则

private async Task<CommandResult> CheckChangeoverInterlockAsync(
    ValveId valveId, CancellationToken ct)
{
    // 切换阀1和切换阀2不能同时开
    // 切换阀3和切换阀1/2不能同时开
    if (valveId == ValveId.Changer1)
    {
        var changer2Open = await IsValveOpenAsync(ValveId.Changer2, ct);
        if (changer2Open)
            return CommandResult.Fail("INTERLOCK", "切换阀2已打开,不能开切换阀1");
    }
    // ... 其他互锁组合
}

前导动作:开闸阀之前先开节流阀

最经典的场景:开闸阀之前,节流阀要先开到30度,否则气流冲击太大。

{
  "openPreActions": [
    {
      "type": "Command",
      "commandId": "CMD_THROTTLE_30",
      "description": "节流阀开到30度"
    },
    {
      "type": "Delay",
      "milliseconds": 10000
    }
  ]
}

执行顺序:下发节流阀30度指令 → 等待10秒 → 开闸阀 → 等闸阀状态确认 → 节流阀全开指令。

图3:前导动作执行时序

if (action == ValveAction.Open && config.OpenPreActions?.Count > 0)
{
    var preActionResult = await ExecutePreActionsAsync(config, ct);
    if (!preActionResult.IsSuccess)
        return preActionResult;
}

两种执行模式:命令模式 vs 寄存器模式

阀门配置里有两种寄存器模式:

private async Task<CommandResult> ExecuteSingleValveCommandAsync(
    ValveConfig config, ValveAction action,
    Stopwatch sw, CancellationToken ct)
{
    if (config.Register.IsCommandMode)
    {
        // 命令模式:通过命令索引执行
        var cmdIndex = action == ValveAction.Open
            ? config.Register.OpenCmdIndex
            : config.Register.CloseCmdIndex;
        await ExecuteCommandByIndexAsync(config.Register.Type, cmdIndex.Value);
    }
    else
    {
        // 寄存器模式:直接写入寄存器
        var writeValue = action == ValveAction.Open
            ? config.Register.OpenValue
            : config.Register.CloseValue;
        await WriteRegisterAsync(config.Register, writeValue);
    }

    // 等待状态变化
    var expectedStatus = (int)action;
    var waitResult = await WaitForStatusAsync(
        config.GetStatusProperty(),
        expectedStatus,
        config.TimeoutMs,
        ct);

    if (waitResult)
        return CommandResult.Success();
    else
        return CommandResult.Timeout($"阀门 {config.ValveId} 状态变化超时");
}

命令模式通过下发命令索引让下位机执行预设动作,寄存器模式直接写线圈值。前者更安全(下位机有自己的逻辑),后者更快。

图4:命令模式 vs 寄存器模式


状态等待:写了不代表成功

写完寄存器不代表阀门真的动了。必须等待状态反馈:

var waitResult = await WaitForStatusAsync(
    config.GetStatusProperty(),  // SP_01, SP_02...
    expectedStatus,               // 1=开, 0=关
    config.TimeoutMs,             // 超时时间
    ct);

WaitForStatusAsync 内部轮询读取状态寄存器,直到值匹配或超时。超时就返回 CommandResult.Timeout

调试模式下有个特殊处理——节流阀打开需要额外延迟:

if (Global.IsDevDebug)
{
    if (config.ValveId == ValveId.ThrottleValve && action == ValveAction.Open)
    {
        _logger.Debug("调试模式,节流阀打开需要延迟1秒,否则会报:阀门状态变化超时");
        await Task.Delay(1000, ct);
    }
}

模拟器响应比真实硬件快,反而导致状态读取时序错位。这种坑只有踩过才知道。


关闭依赖:关我之前先关你

有些阀门关闭前需要先关闭下游阀门。比如关切换阀之前要先关所有依赖它的阀门:

if (action == ValveAction.Close && config.CloseDependencies.Count > 0)
{
    var depResult = await ExecuteDependenciesAsync(config.CloseDependencies, ct);
    if (!depResult.IsSuccess)
        return depResult;
}

还有 CloseParents——关完当前阀门后顺带关父阀门:

if (singleResult.IsSuccess && action == ValveAction.Close 
    && config.CloseParents?.Count > 0)
{
    await ExecuteCloseParentsAsync(config.CloseParents, ct);
}

阀门踩坑清单

现象解决
不做联锁切换阀同时开气路短路CheckChangeoverInterlockAsync
不等状态写了1但阀门没动WaitForStatusAsync轮询
不做前导闸阀直接开气流冲击PreActions先开节流阀到30度
模拟器时序节流阀状态变化超时调试模式加1秒延迟
不关依赖关切换阀后下游还通着CloseDependencies先关下游

本篇小结

知识点关键做法
执行流程互锁→预检查→前导动作→执行→状态等待
两种模式命令模式(下位机执行) vs 寄存器模式(直接写)
联锁切换阀互斥,不能同时开
前导动作开闸阀前先开节流阀到30度,等10秒
状态等待轮询状态寄存器直到匹配或超时
关闭依赖关阀门前先关下游阀门

阀门控制的核心不是"写值",而是"写值之前确认安全,写值之后确认生效"。


下期预告

第12篇:灯塔与蜂鸣器控制

阀门说完了,下篇看灯塔——蓝绿橙红四色LED加蜂鸣器,怎么根据系统状态自动控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值