SSPC-Tester/SSPCTester.UI/Services/SafetyMonitorService.cs

193 lines
6.7 KiB
C#

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SSPCTester.Devices.Config;
using SSPCTester.Devices.Interfaces;
namespace SSPCTester.UI.Services;
public sealed class SafetyMonitorService : IDisposable
{
private readonly ISafetyDio _safetyDio;
private readonly IPowerSupply _power;
private readonly ILoad _load;
private readonly DeviceOptions _options;
private readonly UiLogSink _log;
private readonly ILogger<SafetyMonitorService> _logger;
private readonly SemaphoreSlim _gate = new(1, 1);
private Timer? _timer;
private bool _handlingEmergency;
private bool _disposed;
public SafetyMonitorService(
ISafetyDio safetyDio,
IPowerSupply power,
ILoad load,
IOptions<DeviceOptions> options,
UiLogSink log,
ILogger<SafetyMonitorService> logger)
{
_safetyDio = safetyDio;
_power = power;
_load = load;
_options = options.Value;
_log = log;
_logger = logger;
}
public bool IsEmergencyActive { get; private set; }
public bool IsAlarmOutputOn { get; private set; }
public string StatusText { get; private set; } = "未启动";
public string DetailText { get; private set; } = "设备安全监控未启动";
public event EventHandler? StatusChanged;
public void Start()
{
if (_timer is not null) return;
var options = _options.Pci2312;
if (!options.Enabled)
{
SetStatus(false, false, "未启用", "PCI2312 安全监控未启用");
return;
}
int interval = Math.Clamp(options.PollIntervalMs, 50, 10_000);
_timer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(interval));
SetStatus(false, false, "监控中", "正在监控 PCI2312 急停输入");
}
public async Task ResetAsync(CancellationToken ct = default)
{
var options = _options.Pci2312;
if (await IsEmergencyInputActiveAsync(options, ct).ConfigureAwait(false))
{
SetStatus(true, IsAlarmOutputOn, "急停触发", "急停输入仍处于接通状态,无法复位报警灯");
return;
}
await SetAlarmOutputAsync(false, ct).ConfigureAwait(false);
SetStatus(false, false, "监控中", "急停已复位,正在监控 PCI2312 输入");
_handlingEmergency = false;
}
private void OnTimer(object? state)
{
if (_disposed || _handlingEmergency) return;
_ = PollAsync();
}
private async Task PollAsync()
{
if (!await _gate.WaitAsync(0).ConfigureAwait(false)) return;
try
{
var options = _options.Pci2312;
if (!options.Enabled) return;
if (options.AutoConnect && !_safetyDio.IsConnected)
await _safetyDio.ConnectAsync().ConfigureAwait(false);
bool active = await IsEmergencyInputActiveAsync(options, CancellationToken.None).ConfigureAwait(false);
if (active)
await HandleEmergencyAsync().ConfigureAwait(false);
else if (!IsEmergencyActive)
SetStatus(false, IsAlarmOutputOn, "监控中", $"DI{options.EmergencyInputChannel} 未触发");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Safety monitor poll failed.");
SetStatus(IsEmergencyActive, IsAlarmOutputOn, "监控异常", ex.Message);
}
finally
{
_gate.Release();
}
}
private async Task HandleEmergencyAsync()
{
if (_handlingEmergency) return;
_handlingEmergency = true;
SetStatus(true, IsAlarmOutputOn, "急停触发", "正在执行安全关断");
_log.Add(UiLogLevel.Error, "检测到急停输入接通,开始执行报警灯、电源、负载关断。");
try
{
await SetAlarmOutputAsync(true, CancellationToken.None).ConfigureAwait(false);
await TryShutdownPowerAsync().ConfigureAwait(false);
await TryShutdownLoadAsync().ConfigureAwait(false);
SetStatus(true, true, "急停触发", "报警灯已接通,电源输出和负载输入已发送关闭指令");
}
catch (Exception ex)
{
_logger.LogError(ex, "Emergency shutdown failed.");
_log.Add(UiLogLevel.Error, $"急停关断执行异常:{ex.Message}");
SetStatus(true, IsAlarmOutputOn, "急停异常", ex.Message);
}
}
private async Task TryShutdownPowerAsync()
{
try
{
if (!_power.IsConnected && _options.PowerSupply.AutoConnect)
await _power.ConnectAsync().ConfigureAwait(false);
if (_power.IsConnected)
await _power.OutputAsync(false).ConfigureAwait(false);
_log.Add(UiLogLevel.Warning, "UDP5080 电源输出已发送关闭指令。");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Power shutdown failed.");
_log.Add(UiLogLevel.Error, $"UDP5080 电源关闭失败:{ex.Message}");
}
}
private async Task TryShutdownLoadAsync()
{
try
{
if (!_load.IsConnected && _options.Load.AutoConnect)
await _load.ConnectAsync().ConfigureAwait(false);
if (_load.IsConnected)
await _load.InputAsync(false).ConfigureAwait(false);
_log.Add(UiLogLevel.Warning, "IT8702P 负载输入已发送关闭指令。");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Load shutdown failed.");
_log.Add(UiLogLevel.Error, $"IT8702P 负载关闭失败:{ex.Message}");
}
}
private async Task<bool> IsEmergencyInputActiveAsync(Pci2312Options options, CancellationToken ct)
{
bool value = await _safetyDio.ReadInputAsync(options.EmergencyInputChannel, ct).ConfigureAwait(false);
return options.EmergencyInputActiveHigh ? value : !value;
}
private async Task SetAlarmOutputAsync(bool on, CancellationToken ct)
{
var options = _options.Pci2312;
bool outputValue = options.AlarmOutputActiveHigh ? on : !on;
await _safetyDio.WriteOutputAsync(options.AlarmOutputChannel, outputValue, ct).ConfigureAwait(false);
IsAlarmOutputOn = on;
}
private void SetStatus(bool emergencyActive, bool alarmOutputOn, string statusText, string detailText)
{
IsEmergencyActive = emergencyActive;
IsAlarmOutputOn = alarmOutputOn;
StatusText = statusText;
DetailText = detailText;
StatusChanged?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
_disposed = true;
_timer?.Dispose();
_gate.Dispose();
}
}