从零搭建工业控制系统(十二):灯塔与蜂鸣器——工业界的仪表盘

灯塔与蜂鸣器:工业界的"仪表盘"

这是「从零搭建工业控制系统」系列第12篇。阀门控制聊完了,这篇说灯塔。工厂车间里抬头就能看到的那根三色灯柱,比任何弹窗都管用。


为什么不用弹窗?

做软件开发的人有个本能反应:出问题了弹个MessageBox。但在工厂环境,操作员可能戴着防尘手套,站在2米外的操作台前,根本看不清屏幕上的弹窗。

灯塔不一样。红灯亮了+蜂鸣器响了,隔着车间都能看到听到。不需要操作员盯着屏幕。

项目里用了一根五段式灯塔:蓝、绿、橙、红四个LED加一个蜂鸣器,分别对应五个数字输出点(DO点位号视硬件配置而定)。


灯塔规则定义

先明确每个灯代表什么:

输出点颜色含义触发条件
DO_蓝蓝灯通信连接上位机与下位机连接时亮
DO_绿绿灯运行中Sequence运行时亮(RunState==1)
DO_橙橙灯完成Sequence运行完成时亮(RunState: 1→0)
DO_红红灯报警AlarmState发生报警时亮(AlarmState!=0)
DO_蜂蜂鸣器声音报警橙灯或红灯亮时响5秒

这些规则直接写在 TowerLightService 的注释里,作为需求文档:

图1:五段式灯塔规则定义

/// <summary>
/// 灯塔控制服务
/// 灯塔规则:
/// - 蓝灯: 上位机与下位机连接时亮灯,断开时不亮
/// - 绿灯: Sequence运行时亮灯(RunState==1),其他时候不亮
/// - 橙灯: Sequence运行完成时亮灯(RunState: 1→0),保持到下次运行开始
/// - 红灯: AlarmState发生报警时亮灯(AlarmState!=0),报警解除后熄灭
/// - 蜂鸣器: 橙灯亮或红灯亮时响5秒
/// </summary>

服务架构

TowerLightService 用500毫秒的定时器轮询系统状态,自动控制灯塔:

public partial class TowerLightService : ObservableObject, IDisposable
{
    private const int MONITOR_INTERVAL_MS = 500;
    private const int BUZZER_DURATION_MS = 5000;

    private readonly DeviceCmdService _deviceCmdService;
    private readonly ChamberSubsystemStatus _status;
    private readonly ChamberCoilStatusHelper _coilStatusHelper;
    private readonly Timer _monitorTimer;
    private Timer? _buzzerTimer;

    // 状态追踪
    private int _previousAlarmState;
    private int _previousRunState;
    private bool _sequenceCompleted; // 橙灯保持标志

    // 线圈缓存
    private Models.ModbusCoil? _buzzerCoil;
    private Models.ModbusCoil? _blueLedCoil;
    private Models.ModbusCoil? _greenLedCoil;
    private Models.ModbusCoil? _orangeLedCoil;
    private Models.ModbusCoil? _redLedCoil;
}

每个灯和蜂鸣器都对应一个 ModbusCoil 对象,初始化时通过枚举获取:

private void InitializeCoils()
{
    _buzzerCoil = _deviceCmdService.GetCoilByEnum(DeviceType.DigitalIO, DigitalDOCodes.Buzzer);
    _blueLedCoil = _deviceCmdService.GetCoilByEnum(DeviceType.DigitalIO, DigitalDOCodes.BlueLed);
    _greenLedCoil = _deviceCmdService.GetCoilByEnum(DeviceType.DigitalIO, DigitalDOCodes.GreenLed);
    _orangeLedCoil = _deviceCmdService.GetCoilByEnum(DeviceType.DigitalIO, DigitalDOCodes.OrangeLed);
    _redLedCoil = _deviceCmdService.GetCoilByEnum(DeviceType.DigitalIO, DigitalDOCodes.RedLed);
}

用枚举而不是硬编码地址,换硬件时只改枚举映射,不用改业务代码。

图2:灯塔服务轮询架构


状态更新逻辑

定时器回调里更新所有灯的状态:

private async Task UpdateTowerLightStateAsync()
{
    if (!IsDeviceConnected)
    {
        _logger.Debug("DigitalIO设备未连接,跳过灯塔状态更新");
        return;
    }

    // 1. 蓝灯:连接状态
    var isConnected = _deviceCmdService.DigitalIO?.IsConnected ?? false;
    if (BlueLedOn != isConnected)
    {
        await SetBlueLedAsync(isConnected);
    }

    // 2. 获取当前报警状态
    // 合并硬件报警(DI)与软件追踪报警
    var hwAlarmState = _coilStatusHelper.CalculateTriggerStatus();
    var swAlarmState = _status.TriggerStatus;
    var currentAlarmState = (hwAlarmState == TriggerStatus.Alarm || swAlarmState == TriggerStatus.Alarm)
        ? (int)TriggerStatus.Alarm
        : (int)TriggerStatus.NoAlarm;

    // 3. 红灯:报警状态
    // 4. 绿灯/橙灯:运行状态
    // 5. 蜂鸣器:橙灯或红灯亮时触发
}

红灯的逻辑有个细节:硬件报警和软件追踪报警要合并。任何一个触发,红灯都亮。这俩来源不同——硬件报警来自DI输入,软件追踪报警来自 TraceMonitorService 写入的状态。

图4:硬件报警与软件报警合并


橙灯的保持逻辑

橙灯表示"完成",有个特殊逻辑:Sequence从运行状态(RunState1)变回空闲(RunState0)时亮,一直保持到下次运行开始。

// 检测RunState从1→0的跳变
if (_previousRunState == (int)RunStatus.Running 
    && currentRunState == (int)RunStatus.Idle)
{
    _sequenceCompleted = true;
    await SetOrangeLedAsync(true);
}

// 下次运行开始时熄灭橙灯
if (currentRunState == (int)RunStatus.Running)
{
    _sequenceCompleted = false;
    await SetOrangeLedAsync(false);
}

_sequenceCompleted 标志位追踪状态,避免重复设置。

图3:橙灯保持逻辑


蜂鸣器定时关闭

蜂鸣器不能一直响,响5秒自动关。用一个单独的定时器:

public async Task TriggerBuzzerAsync(int durationMs = 5000)
{
    await SetBuzzerAsync(true);
    _buzzerTimer?.Dispose();
    _buzzerTimer = new Timer(async _ =>
    {
        await SetBuzzerAsync(false);
    }, null, durationMs, Timeout.Infinite);
}

蜂鸣器持续时间从配置文件读取:

private void LoadConfiguration()
{
    var iniConfig = ConfigReader.Instance.ReadConfig();
    IsEnabled = iniConfig.TowerLightServiceEnable;
    BuzzerDurationSeconds = iniConfig.BuzzerDurationSeconds > 0 
        ? iniConfig.BuzzerDurationSeconds : 5;
}

Config.ini 里的配置:

[TowerLight]
TowerLightServiceEnable = True
BuzzerDurationSeconds = 5

UI状态绑定

所有灯的状态都是 [ObservableProperty],直接绑定到UI:

[ObservableProperty] private bool blueLedOn;
[ObservableProperty] private bool greenLedOn;
[ObservableProperty] private bool orangeLedOn;
[ObservableProperty] private bool redLedOn;
[ObservableProperty] private bool buzzerOn;

界面上用圆形图标表示灯塔状态,操作员一眼就能看到当前系统处于什么状态。


手动控制

除了自动模式,还支持手动控制,方便测试:

public async Task ManualControlAsync(bool blue, bool green, bool orange, bool red, bool buzzer)
{
    await SetBlueLedAsync(blue);
    await SetGreenLedAsync(green);
    await SetOrangeLedAsync(orange);
    await SetRedLedAsync(red);
    await SetBuzzerAsync(buzzer);
}

停止时全部关闭

服务停止时关掉所有灯和蜂鸣器,避免灯一直亮着误导操作员:

public void StopMonitoring()
{
    _monitorTimer.Change(Timeout.Infinite, Timeout.Infinite);
    _buzzerTimer?.Change(Timeout.Infinite, Timeout.Infinite);
    _ = TurnOffAllAsync();
    IsRunning = false;
}

灯塔踩坑清单

现象解决
初始化误触发启动时红灯闪一下用安全默认值初始化_previousAlarmState
蜂鸣器一直响没有关闭定时器单独Timer定时关闭
橙灯不保持完成后马上灭_sequenceCompleted标志位
设备断开还在写写入失败刷日志IsDeviceConnected检查
硬件软件报警没合并软件报警红灯不亮hwAlarmState和swAlarmState取或

本篇小结

知识点关键做法
五路输出蜂鸣器 + 四色LED(蓝绿橙红)
轮询监控500ms Timer定时更新
状态合并硬件报警 + 软件追踪报警取或
橙灯保持标志位追踪,下次运行才灭
蜂鸣器定时单独Timer 5秒后自动关闭
配置化Config.ini控制启用和蜂鸣时长

灯塔的价值在于:不需要操作员看屏幕,一眼就知道系统状态。


下期预告

第13篇:安全监控服务

灯塔是"显示"安全状态,下篇讲"执行"安全动作——压力超标自动关阀门、断电自动启动保护。

代码下载链接: https://pan.quark.cn/s/a4b39357ea24 在本文中,我们将详细阐述利用Oracle VM VirtualBox在非Apple设备上安装“黑苹果”系统的方法,即实现在非macOS原装硬件上执行macOS操作系统。这一过程需要具备相应的技术能力并投入一定的耐心,然而,只要严格遵循以下详尽的步骤,您将能够顺利完成安装工作。在开始之前,请确认您已经获取了Oracle VM VirtualBox,这是一款免费且开源的虚拟化软件,能够让您在一台计算机上同时运行多种不同的操作系统。此外,请确保您的主机系统符合macOS的最低硬件配置要求,其中包括至少4GB的内存容量以及充足的硬盘存储空间。 1. **虚拟机的建立**: - 启动VirtualBox应用程序,并点击“新建”按钮以创建一个新的虚拟机实例。 - 为虚拟机指定一个名称,例如“BlackApple”,并设定操作系统类型为“Mac OS X”或选择“其他”。 - 分配合理的内存资源,通常4GB是基本需求,但8GB或更多将有助于提升运行效率。 - 创建一个新的虚拟硬盘文件,并选择VDI(VirtualBox动态分配)格式,这种方式能够更高效地利用存储资源。 2. **虚拟机的设置**: - 在“系统”配置选项中,确保处理器的核心数至少为2个,如果条件允许,选择4个或更多核心将更有利于系统性能。 - 启用“IO APIC”功能,这对于macOS的稳定运行具有关键作用。 - 在“显示”配置中,开启3D加速功能,并将显存设置为最大值,这将显著改善图形处理能力,使用户界面更加流畅。 - 在“存储...
源码下载地址: https://pan.quark.cn/s/de26074cf420 CadLib4.0被定位为一个功能丰富的.NET CAD类库,它为开发人员提供了在C#或其它.NET编程语言环境中嵌入CAD功能的可能性,从而简化了DWG和DXF文件的构建修改过程。这个压缩文件内含了必要的DLL组件以及一个基于WinForms的应用实例,该实例清晰展示了在Visual Studio 2010开发环境中如何进行CAD文件的读取和处理,特别是对于AutoCAD 2014所支持的最新文件格式具备良好的兼容性。 1. **CadLib**:CadLib作为核心的类库,为AutoCAD的DWG和DXF文件进行交互提供了接口和实现机制。它通过封装CAD数据结构和相关操作,让开发人员无需深入探究底层CAD格式细节,即可便捷地完成CAD文件的输入输出操作。 2. **WW.Cad.dll**:此DLL文件被视为CadLib的核心构成部分,其中汇集了所有CAD操作直接关联的类和函数。例如,开发人员可借助此库来初始化新的图纸,向其中添加各类几何元素(比如直线、圆形、多段线等),或是提取已有图纸中的数据信息。 3. **WW.dll**:该DLL可能扮演着CadLib的辅助角色,里面存放了通用的工具函数和类,它们为CadLib各项功能的实现提供了支持。这些功能可能涵盖数据转换、异常管理或图形的视觉呈现等方面。 4. **WW.Pdf.dll**:此文件或许具备将CAD图纸内容转换为PDF文档的能力。开发者可利用这一特性,将设计成果导出为PDF格式,方便进行打印或在线传播,而无需借助AutoCAD软件。 5. **WW.GL.dll**:从其命名推断,该文...
源码下载地址: https://pan.quark.cn/s/5479f7d1ce58 在MATLAB平台中,Lyapunov指数被视为一种评估动力系统稳定性的核心手段,尤其在混沌理论领域展现出广泛的应用价值。Chen系统作为一个典型的三阶混沌系统,由Liu Chen于1998年构建,其数学表达式通常以以下三个非线性微分方程来呈现: \[ \frac{dx}{dt} = a(y - x) \] \[ \frac{dy}{dt} = x - (a + b)xz - y \] \[ \frac{dz}{dt} = xy - bz \] 其中,\(a\) 和 \(b\) 代表系统的关键参数。Lyapunov指数是判定Chen系统是否呈现混沌状态的核心依据,一旦存在一个正值Lyapunov指数,表明该系统可能表现出混沌现象。 计算Lyapunov指数的主要途径包括定义法以及数值积分法等。在此描述的“定义法”是指通过量化相邻轨迹线间的分离速度来推算Lyapunov指数。具体实施步骤如下: 1. **初始设定**:挑选两条起始位置极为接近的轨迹,标记为\(x_0\)和\(x_0+\delta x_0\),其中\(\delta x_0\)为初始扰动向量。 2. **数值求解**:对Chen系统的微分方程实施数值求解,分别获取两条轨迹\(x(t)\)\(x(t)+\delta x(t)\)。 3. **扰动向量修正**:在每一个时间间隔内,依据当前轨迹的位置修正扰动向量\(\delta x(t)\),确保其原轨迹的夹角恒定不变。 4. **Lyapunov指数估算**:计算每时段的局部Lyapunov指数\(\lambda_t\),该指数反映了扰动向量在该时刻的增长速率,即...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值