diff --git a/Docs/开发文档.md b/Docs/开发文档.md index 8475c3f..b8e62da 100644 --- a/Docs/开发文档.md +++ b/Docs/开发文档.md @@ -81,9 +81,9 @@ - 硬件连接:`PCI2312` 通过厂商 `PCI2312_64.dll` 以 P/Invoke 方式接入,使用 DI 通道读取急停状态,使用 DO 通道控制报警灯。 - 后台监控:`SafetyMonitorService.Start()` 在主窗口初始化后启动,按 `Devices:Pci2312:PollIntervalMs` 周期轮询急停输入。 -- 硬件急停:当配置的 DI 通道达到有效电平时,程序写入报警 DO,并依次执行 SSPC 全通道关断、电源输出关闭、负载输入关闭。 -- 软件急停:主窗口顶部“报警急停”按钮调用 `TriggerSoftwareEmergencyAsync()`,走同一套报警灯和设备关断链路。 -- 状态重置:报警灯点亮后按钮变为“状态重置”。若硬件急停 DI 仍有效,重置会被拒绝;DI 复位后才关闭报警输出并恢复监控状态。 +- 硬件急停:当配置的 DI 通道达到有效电平时,程序锁存硬件急停状态,写入报警 DO,并依次执行 SSPC 全通道关断、电源输出关闭、负载输入关闭。 +- 软件急停:主窗口顶部“报警急停”按钮调用 `TriggerSoftwareEmergencyAsync()`,走同一套报警灯和设备关断链路;纯软件急停需要通过“状态重置”解除。 +- 状态重置与硬件解除:报警灯点亮后按钮变为“状态重置”。若硬件急停 DI 仍有效,手动重置会被拒绝;硬件急停曾触发后,DI 复位会自动关闭报警输出、清除硬件急停锁存和软件急停状态,并刷新 UI 回到“报警急停”。 控制链路如下: @@ -518,10 +518,16 @@ Timer(PollIntervalMs,代码中限制 50~10000ms) -> 自动连接 PCI2312(若启用 AutoConnect) -> ReadInputAsync(EmergencyInputChannel) -> 按 EmergencyInputActiveHigh 解释有效电平 --> 触发 HandleEmergencyAsync() +-> DI 有效时锁存硬件急停并触发 HandleEmergencyAsync() +-> 硬件急停锁存后 DI 复位时触发 ClearHardwareEmergencyAsync() ``` -急停处理具有防重入保护:`_gate` 和 `_handlingEmergency` 避免重复并发关断。每个设备的关断失败会单独记录日志,不阻断后续设备的关断尝试。 +急停处理具有防重入保护:`_gate` 和 `_handlingEmergency` 避免重复并发关断;`_hardwareEmergencyLatched` 用于区分“硬件急停曾触发后解除”和“纯软件急停等待手动重置”。每个设备的关断失败会单独记录日志,不阻断后续设备的关断尝试。 + +解除行为: + +- 硬件急停触发后,DI 复位会自动关闭报警 DO,清除 `_handlingEmergency`、`_softwareEmergencyActive`、`_hardwareEmergencyLatched`,并刷新 UI 按钮回到“报警急停”。 +- 纯软件急停不会因为硬件 DI 处于未触发状态而被轮询自动解除,仍需要点击“状态重置”;若此时硬件 DI 仍有效,重置会被拒绝。 主窗口顶部状态区会展示: @@ -669,7 +675,7 @@ PowerShell -ExecutionPolicy Bypass -File .\publish.ps1 - PDF / Word 报告生成 - 真/假设备可按设备粒度切换 - 实际 SSPC / 电源 / 负载 / PCIe8586 / PCI2312 驱动接入代码 -- PCI2312 急停输入轮询、报警输出、软件急停、状态重置和设备关断联动 +- PCI2312 急停输入轮询、报警输出、软件急停、硬件解除联动、状态重置和设备关断联动 ### 10.2 待统一 / 待完善 @@ -687,7 +693,7 @@ PowerShell -ExecutionPolicy Bypass -File .\publish.ps1 3. **电源与负载真实驱动已接入,但控制命令支持度需按实物验证**。 4. **PCI2312 急停报警链路依赖现场接线和 DLL 部署**:DI/DO 通道号、高/低有效配置、报警灯供电、`PCI2312_64.dll` 位数和搜索路径都必须逐项核实。 5. **软件安全链路不能替代硬件安全回路**:`SafetyMonitorService` 通过轮询和软件命令下发关断,适合作为上位机联动保护;最终急停/互锁仍应由硬件电气回路兜底。 -6. **状态重置必须确认急停输入已复位**:当前代码在 DI 仍有效时拒绝关闭报警灯,避免误复位。 +6. **状态重置与硬件解除都必须确认急停输入已复位**:当前代码在 DI 仍有效时拒绝手动重置;硬件急停锁存后只有检测到 DI 复位才会自动关闭报警灯并清除软件急停状态,避免误复位。 7. **所有长流程应贯穿 `CancellationToken`**,中止后优先保证设备回到安全状态。 8. **示波器不纳入软件控制**,仅作为线下校准基准。 9. **任何涉及数据模型持久化格式的修改,都要考虑旧项目兼容**。 diff --git a/SSPCTester.UI/Services/SafetyMonitorService.cs b/SSPCTester.UI/Services/SafetyMonitorService.cs index e6afb0c..76cf6bd 100644 --- a/SSPCTester.UI/Services/SafetyMonitorService.cs +++ b/SSPCTester.UI/Services/SafetyMonitorService.cs @@ -18,6 +18,7 @@ public sealed class SafetyMonitorService : IDisposable private Timer? _timer; private bool _handlingEmergency; private bool _softwareEmergencyActive; + private bool _hardwareEmergencyLatched; private bool _disposed; public SafetyMonitorService( @@ -55,7 +56,7 @@ public sealed class SafetyMonitorService : IDisposable return; } - int interval = Math.Clamp(options.PollIntervalMs, 50, 10_000); + int interval = Math.Clamp(options.PollIntervalMs, 50, 10000); _timer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(interval)); SetStatus(false, false, "设备正常", "正在监控 PCI2312 急停输入"); } @@ -118,6 +119,7 @@ public sealed class SafetyMonitorService : IDisposable await SetAlarmOutputAsync(false, ct).ConfigureAwait(false); _softwareEmergencyActive = false; + _hardwareEmergencyLatched = false; _handlingEmergency = false; SetStatus(false, false, "设备正常", "急停状态已重置,报警灯已关闭,正在监控 PCI2312 输入"); return new SafetyResetResult(true, "急停状态已重置。"); @@ -143,15 +145,20 @@ public sealed class SafetyMonitorService : IDisposable bool active = await IsEmergencyInputActiveAsync(options, CancellationToken.None).ConfigureAwait(false); if (active) { + _hardwareEmergencyLatched = true; await HandleEmergencyAsync().ConfigureAwait(false); } + else if (_hardwareEmergencyLatched) + { + await ClearHardwareEmergencyAsync(options).ConfigureAwait(false); + } else if (_softwareEmergencyActive) { SetStatus(true, IsAlarmOutputOn, "软件急停已触发", "等待状态重置,电源和负载不会自动恢复"); } else if (IsEmergencyActive || IsAlarmOutputOn) { - SetStatus(true, IsAlarmOutputOn, "急停已触发", $"DI{options.EmergencyInputChannel} 已复位,请点击状态重置关闭报警灯"); + await ClearHardwareEmergencyAsync(options).ConfigureAwait(false); } else { @@ -193,6 +200,18 @@ public sealed class SafetyMonitorService : IDisposable } } + private async Task ClearHardwareEmergencyAsync(Pci2312Options options) + { + if (IsAlarmOutputOn) + await SetAlarmOutputAsync(false, CancellationToken.None).ConfigureAwait(false); + + _handlingEmergency = false; + _softwareEmergencyActive = false; + _hardwareEmergencyLatched = false; + SetStatus(false, false, "设备正常", $"DI{options.EmergencyInputChannel} 已复位,硬件急停已解除,报警灯已关闭,设备输出不会自动恢复"); + _log.Add(UiLogLevel.Info, "硬件急停输入已复位,软件急停状态和报警灯已自动解除。"); + } + private async Task TryShutdownSspcAsync() { try