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

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

这是「从零搭建工业控制系统」系列第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加蜂鸣器,怎么根据系统状态自动控制。

大气污染是影响公众健康与生态环境的重要问题,精准的空气质量时空预测与污染源贡献度量化是精准治污的关键支撑。针对现有研究源融合不充分、时空关联刻画不足、预测与源解析割裂三方面缺陷,本文设计实现了城市空气质量时空预测与污染源贡献度分析系统,融合监测、气象、工业排放与交通四类数据,构建基于时空注意力的LSTM(STAM-LSTM)预测模型与基于正定矩阵因子分解(PMF)的源解析模型,形成数据融合-特征工程-预测-源解析-可视化闭环。 系统实现四类数据时空对齐与融合,构建时序与空间邻域特征,以普通克里金插值生成1km网格浓度场;STAM-LSTM引入时空注意力自适应学习站点间污染传输时变权重,以72小时输入预测未来24小时逐小时PM2.5浓度;PMF识别交通、工业、燃煤、扬尘与二次生成五个源因子,量化各源全年贡献度并分析时空演变。 实验表明:STAM-LSTM预测RMSE 24.6、MAE 17.8、R² 0.88,相对LSTM基线(30.2)提升18.5%;普通克里金插值误差8.9,优于反距离加权(11.4);源解析显示交通源28.4%、工业源23.1%、燃煤源19.6%为主要贡献源,冬季燃煤源升至27.3%、早高峰交通源达34.8%,下风向工业源贡献高出上风向8~12个百分点;减排情景显示交通源减排20%可使年均PM2.5下降5.7%,与源贡献度排序一致。 系统按五模块14组件实现,功能测试16项用例全部通过,为大气污染预警、源管控与减排政策制定提供了决策依据。 【课程报告内容】 摘要 第1章 绪论 第2章 相关技术与理论 第3章 系统需求分析 第4章 系统总体设计 第5章 系统详细设计与实现 第6章 系统测试与分析 第7章 总结与展望 参考文献 附件-实现指南
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值