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 _logger; private readonly SemaphoreSlim _gate = new(1, 1); private Timer? _timer; private bool _handlingEmergency; private bool _softwareEmergencyActive; private bool _disposed; public SafetyMonitorService( ISafetyDio safetyDio, IPowerSupply power, ILoad load, IOptions options, UiLogSink log, ILogger 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 TriggerSoftwareEmergencyAsync(CancellationToken ct = default) { await _gate.WaitAsync(ct).ConfigureAwait(false); try { _softwareEmergencyActive = true; _handlingEmergency = true; SetStatus(true, IsAlarmOutputOn, "软件急停已触发", "正在执行软件急停安全关断"); _log.Add(UiLogLevel.Error, "软件触发报警急停,开始执行报警灯、电源、负载关断。"); await EnsureSafetyDioConnectedAsync(ct).ConfigureAwait(false); await SetAlarmOutputAsync(true, ct).ConfigureAwait(false); await TryShutdownPowerAsync().ConfigureAwait(false); await TryShutdownLoadAsync().ConfigureAwait(false); SetStatus(true, true, "软件急停已触发", "报警灯已接通,电源输出和负载输入已发送关闭指令"); } catch (Exception ex) { _softwareEmergencyActive = false; _handlingEmergency = false; _logger.LogError(ex, "Software emergency shutdown failed."); _log.Add(UiLogLevel.Error, $"软件急停执行异常:{ex.Message}"); SetStatus(true, IsAlarmOutputOn, "设备故障", ex.Message); throw; } finally { _gate.Release(); } } public async Task ResetAsync(CancellationToken ct = default) { await _gate.WaitAsync(ct).ConfigureAwait(false); try { return await ResetCoreAsync(ct).ConfigureAwait(false); } finally { _gate.Release(); } } private async Task ResetCoreAsync(CancellationToken ct) { var options = _options.Pci2312; await EnsureSafetyDioConnectedAsync(ct).ConfigureAwait(false); if (await IsEmergencyInputActiveAsync(options, ct).ConfigureAwait(false)) { const string message = "硬件急停按钮仍处于触发状态,无法执行状态重置。请先旋转或释放设备上的急停按钮,确认急停输入复位后再重试。"; SetStatus(true, IsAlarmOutputOn, "急停已触发", message); return new SafetyResetResult(false, message); } await SetAlarmOutputAsync(false, ct).ConfigureAwait(false); _softwareEmergencyActive = false; _handlingEmergency = false; SetStatus(false, false, "设备正常", "急停状态已重置,报警灯已关闭,正在监控 PCI2312 输入"); return new SafetyResetResult(true, "急停状态已重置。"); } private void OnTimer(object? state) { if (_disposed) 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 (_softwareEmergencyActive) { SetStatus(true, IsAlarmOutputOn, "软件急停已触发", "等待状态重置,电源和负载不会自动恢复"); } else if (IsEmergencyActive || IsAlarmOutputOn) { SetStatus(true, IsAlarmOutputOn, "急停已触发", $"DI{options.EmergencyInputChannel} 已复位,请点击状态重置关闭报警灯"); } else { SetStatus(false, false, "设备正常", $"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); _handlingEmergency = false; } } 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 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 async Task EnsureSafetyDioConnectedAsync(CancellationToken ct) { var options = _options.Pci2312; if (!options.Enabled) throw new InvalidOperationException("PCI2312 安全输入输出未启用,无法控制报警灯。"); if (!_safetyDio.IsConnected) await _safetyDio.ConnectAsync(ct).ConfigureAwait(false); } 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(); } } public sealed record SafetyResetResult(bool Success, string Message);