diff --git a/UIShare/GlobalVariable/DeviceHealthMonitor.cs b/UIShare/GlobalVariable/DeviceHealthMonitor.cs
new file mode 100644
index 0000000..01b1513
--- /dev/null
+++ b/UIShare/GlobalVariable/DeviceHealthMonitor.cs
@@ -0,0 +1,229 @@
+using DeviceCommand.Base;
+using Logger;
+using Model.Models;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace UIShare.GlobalVariable
+{
+ ///
+ /// 设备健康监控器:独立于监控采样的心跳重连机制。
+ ///
+ /// 设计原则——与检测值零冲突:
+ ///
+ /// - 健康检查平时只读 (纯本地属性,零网络开销,不碰通信锁)
+ /// - 仅在 IsConnected==false 时才获取 _commLock 执行重连,与监控采样天然串行化
+ /// - ModbusTcp 的 ConnectAsync 是幂等的(已连接时直接返回),不会中断正在进行的监控
+ /// - TCP 的 ConnectAsync 会重置连接,但监控的 catch 容忍单次失败,下次 tick 自动恢复
+ ///
+ ///
+ ///
+ public class DeviceHealthMonitor : IDisposable
+ {
+ private readonly IDictionary _deviceMap;
+ private readonly SystemConfig _systemConfig;
+ private readonly string _scopeName;
+
+ /// 健康检查定时器
+ private Timer? _healthCheckTimer;
+
+ /// 每个设备的连续失败计数
+ private readonly ConcurrentDictionary _failureCounts = new();
+
+ /// 连续失败次数阈值,达到后触发重连
+ private const int FailureThreshold = 3;
+
+ /// 健康检查间隔(毫秒)
+ private readonly int _checkIntervalMs;
+
+ private bool _disposed;
+ private readonly object _startStopLock = new();
+
+ ///
+ /// 创建健康监控器实例。
+ ///
+ /// 当前作用域的设备字典
+ /// 当前作用域的系统配置(用于更新 DeviceInfoVM.IsConnected)
+ /// 作用域名称(日志标识)
+ /// 健康检查间隔,默认 5000ms
+ public DeviceHealthMonitor(
+ IDictionary deviceMap,
+ SystemConfig systemConfig,
+ string scopeName,
+ int checkIntervalMs = 5000)
+ {
+ _deviceMap = deviceMap;
+ _systemConfig = systemConfig;
+ _scopeName = scopeName;
+ _checkIntervalMs = checkIntervalMs;
+ }
+
+ /// 启动健康监控(幂等:多次调用只启动一次)
+ public void Start()
+ {
+ lock (_startStopLock)
+ {
+ if (_disposed || _healthCheckTimer != null) return;
+ _healthCheckTimer = new Timer(
+ OnHealthCheckTick,
+ null,
+ TimeSpan.FromSeconds(10), // 首次检查延迟 10 秒,避免启动时设备尚未连接完成
+ TimeSpan.FromMilliseconds(_checkIntervalMs));
+ LoggerHelper.Info($"[{_scopeName}] 设备健康监控已启动,检查间隔={_checkIntervalMs}ms,重连阈值={FailureThreshold}次");
+ }
+ }
+
+ /// 停止健康监控
+ public void Stop()
+ {
+ lock (_startStopLock)
+ {
+ _healthCheckTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+ _healthCheckTimer?.Dispose();
+ _healthCheckTimer = null;
+ _failureCounts.Clear();
+ }
+ }
+
+ ///
+ /// 健康检查核心逻辑:遍历设备,检查连接状态,失败计数超阈值则重连。
+ /// TCP 设备额外进行主动探活(*IDN?),以检测死连接(对端崩溃但 TCP 未收到 FIN)。
+ ///
+ private async void OnHealthCheckTick(object? state)
+ {
+ if (_disposed || _deviceMap.Count == 0) return;
+
+ // 快照避免枚举期间字典被修改
+ var snapshot = _deviceMap.ToArray();
+
+ foreach (var kvp in snapshot)
+ {
+ if (_disposed) return;
+
+ string deviceName = kvp.Key;
+ var device = kvp.Value;
+
+ try
+ {
+ bool alive = device.IsConnected;
+
+ // TCP 设备主动探活:IsConnected 只反映上次操作状态,无法检测死连接
+ if (alive && device is Tcp tcpDevice)
+ {
+ alive = await ProbeTcpDeviceAsync(tcpDevice);
+ }
+
+ if (alive)
+ {
+ // 连接正常:清零失败计数
+ _failureCounts.TryRemove(deviceName, out _);
+ continue;
+ }
+
+ // 连接断开:累加失败计数
+ int failures = _failureCounts.AddOrUpdate(deviceName, 1, (_, count) => count + 1);
+
+ if (failures < FailureThreshold)
+ {
+ LoggerHelper.Warn(
+ $"[{_scopeName}] 设备 [{deviceName}] 连接断开,等待重连中 ({failures}/{FailureThreshold})");
+ continue;
+ }
+
+ // 达到阈值:执行重连
+ await ReconnectDeviceAsync(deviceName, device);
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.Error($"[{_scopeName}] 设备 [{deviceName}] 健康检查异常:{ex.Message}");
+ }
+ }
+ }
+
+ ///
+ /// 对 TCP 设备执行轻量级主动探活(SCPI *IDN?),2 秒超时。
+ /// 通过 WriteReadAsync 内部获取 _commLock,与监控采样天然串行化。
+ ///
+ /// true: 探活成功(连接确实存活); false: 探活失败(死连接)
+ private async Task ProbeTcpDeviceAsync(Tcp tcpDevice)
+ {
+ try
+ {
+ using var probeCts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
+ string resp = await tcpDevice.WriteReadAsync("*IDN?\n", "\n", probeCts.Token);
+ return !string.IsNullOrWhiteSpace(resp);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// 重连单个设备。
+ ///
+ /// 冲突避免机制:ConnectAsync 内部获取设备的 _commLock,
+ /// 如果此时监控采样正在通信,重连会等待锁释放后再执行,
+ /// 保证同一时刻只有一个操作在使用通信链路。
+ ///
+ ///
+ private async Task ReconnectDeviceAsync(string deviceName, IBaseInterface device)
+ {
+ try
+ {
+ LoggerHelper.Info($"[{_scopeName}] 设备 [{deviceName}] 连续 {FailureThreshold} 次检测断连,开始重连...");
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+ bool ok = await device.ConnectAsync(cts.Token);
+
+ // 同步更新 DeviceInfoVM 的 UI 状态
+ UpdateDeviceInfoState(deviceName, ok);
+
+ if (ok)
+ {
+ _failureCounts.TryRemove(deviceName, out _);
+ LoggerHelper.Info($"[{_scopeName}] 设备 [{deviceName}] 重连成功");
+ }
+ else
+ {
+ LoggerHelper.Warn($"[{_scopeName}] 设备 [{deviceName}] 重连失败,将在下次检查时重试");
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ UpdateDeviceInfoState(deviceName, false);
+ LoggerHelper.Warn($"[{_scopeName}] 设备 [{deviceName}] 重连超时(10s),将在下次检查时重试");
+ }
+ catch (Exception ex)
+ {
+ UpdateDeviceInfoState(deviceName, false);
+ LoggerHelper.Error($"[{_scopeName}] 设备 [{deviceName}] 重连异常:{ex.Message}");
+ }
+ }
+
+ ///
+ /// 同步更新 SystemConfig.DeviceList 中对应设备的 IsConnected 状态(驱动 UI 刷新)
+ ///
+ private void UpdateDeviceInfoState(string deviceName, bool isConnected)
+ {
+ var info = _systemConfig?.DeviceList?
+ .FirstOrDefault(d => d != null &&
+ string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
+ if (info != null)
+ {
+ info.IsConnected = isConnected;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ Stop();
+ }
+ }
+}