Files
ACP/UIShare/GlobalVariable/DeviceManager.cs
T

607 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using DeviceCommand.Base;
using DeviceCommand.Devices;
using Logger;
using Model.Models;
using Prism.Events;
using Prism.Ioc;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Reflection;
using TSMasterCAN;
using UIShare.PubEvent;
using UIShare.UIViewModel;
namespace UIShare.GlobalVariable
{
/// <summary>
/// 设备管理器:根据 <see cref="SystemConfig.DeviceList"/> 反射实例化所有启用的设备,
/// 通过 <see cref="IBaseInterface"/> 多态统一管理,避免为每种设备单独硬编码字段。
/// </summary>
public class DeviceManager:IDisposable
{
private object _lockObj = new object();
public SystemConfig _systemConfig { get; set; }
public CANMonitoringService _CANMonitoringService { get; set; }
private readonly GlobalInfo _globalInfo;
private readonly string _scopeName;
private readonly IEventAggregator _eventAggregator;
/// <summary>设备健康监控器:独立心跳检测 + 自动重连,与监控采样互不冲突</summary>
private DeviceHealthMonitor? _healthMonitor;
/// <summary>按 DeviceName 索引的设备字典,便于业务层按名取实例。</summary>
public IDictionary<string, IBaseInterface> DeviceMap { get; private set; }
= new Dictionary<string, IBaseInterface>(StringComparer.OrdinalIgnoreCase);
/// <summary>类名 → Type 的反射缓存(仅扫描一次)。</summary>
private static readonly IReadOnlyDictionary<string, Type> _deviceTypeMap = BuildDeviceTypeMap();
public IOBoardGroup IOGroup { get; set; }
public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo, IEventAggregator eventAggregator)
{
_systemConfig = systemConfig;
_globalInfo = globalInfo;
_eventAggregator = eventAggregator;
_CANMonitoringService=new CANMonitoringService(_systemConfig);
// 用 SystemConfig.Title 作为作用域唯一标识,无需反查 ConfigDic
_scopeName = _systemConfig.Title;
InitDevices();
InitCAN();
}
public void InitCAN()
{
var re1 = _CANMonitoringService.Init();
}
/// <summary>
/// 根据设备配置提取唯一的硬件指纹字符串。
/// <para>Tcp → "Tcp:IP:Port"Serial → "Serial:PortName";无法识别则返回空字符串。</para>
/// </summary>
public static string ExtractHardwareFingerprint(DeviceInfoVM config)
{
if (string.Equals(config.ConnectionType, "Tcp", StringComparison.OrdinalIgnoreCase)
&& config.TcpConfig != null)
{
return $"Tcp:{config.TcpConfig.IPAddress}:{config.TcpConfig.Port}";
}
if (string.Equals(config.ConnectionType, "Serial", StringComparison.OrdinalIgnoreCase)
&& config.SerialPortConfig != null)
{
return $"Serial:{config.SerialPortConfig.PortName}";
}
if (string.Equals(config.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase)
&& config.CANConfig != null)
{
return $"CAN:{config.CANConfig.DeviceIndex}";
}
return string.Empty;
}
/// <summary>
/// 获取当前作用域配置的 CAN 设备硬件指纹(与 GlobalInfo.CanPool 的 Key 一致)。
/// 如 "CAN:0",其中 0 是 CANConfig.DeviceIndex。
/// </summary>
public string GetCanDeviceFingerprint()
{
if (_systemConfig?.DeviceList == null) return string.Empty;
var canConfig = _systemConfig.DeviceList.FirstOrDefault(d =>
d != null && d.IsEnabled &&
string.Equals(d.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase));
return canConfig != null ? ExtractHardwareFingerprint(canConfig) : string.Empty;
}
private void InitDevices()
{
DeviceMap = new Dictionary<string, IBaseInterface>(StringComparer.OrdinalIgnoreCase);
if (_systemConfig?.DeviceList == null) return;
foreach (var config in _systemConfig.DeviceList)
{
if (config == null || !config.IsEnabled) continue;
if (string.IsNullOrWhiteSpace(config.DeviceType) ||
!_deviceTypeMap.TryGetValue(config.DeviceType, out var deviceType))
{
LoggerHelper.Warn($"未识别的设备类型 [{config.DeviceType}],已跳过 [{config.DeviceName}]。");
continue;
}
try
{
// 第一步:提取硬件指纹,为空则跳过
var fingerprint = ExtractHardwareFingerprint(config);
if (string.IsNullOrEmpty(fingerprint))
{
LoggerHelper.Warn($"设备 [{config.DeviceName}] 无法提取硬件指纹(连接方式={config.ConnectionType}),已跳过。");
continue;
}
// 第二步:原子化获取或添加 Lazy 包装盒,确保同一指纹全局只创建一个实例
var lazy = _globalInfo.HardwarePool.GetOrAdd(fingerprint, key => new Lazy<IBaseInterface>(() =>
{
return config.ConnectionType switch
{
"Tcp" => CreateTcpDevice(deviceType, config.TcpConfig)!,
"Serial" => CreateSerialDevice(deviceType, config.SerialPortConfig)!,
_ => null!
};
}));
// 第三步:安全拆盒,Lazy 内部线程锁保证只实例化一次
var instance = lazy.Value;
if (instance == null)
{
LoggerHelper.Warn($"设备 [{config.DeviceName}] 连接方式 [{config.ConnectionType}] 不支持,已跳过。");
continue;
}
// 第四步:绑定逻辑名 → 同一物理设备可被多个台架名称映射
if (!string.IsNullOrWhiteSpace(config.DeviceName))
{
DeviceMap[config.DeviceName] = instance;
}
// 第五步:将当前作用域注册到指纹的作用域列表,用于引用计数与安全销毁
if (!string.IsNullOrEmpty(_scopeName))
{
var scopeList = _globalInfo.DeviceAndScopeDic.GetOrAdd(fingerprint,
_ => new Lazy<List<string>>(() => new List<string>())).Value;
lock (scopeList)
{
if (!scopeList.Contains(_scopeName))
scopeList.Add(_scopeName);
}
}
LoggerHelper.Info($"已加载设备 [{config.DeviceName} / {config.DeviceType} / {config.ConnectionType}] 指纹={fingerprint}");
}
catch (Exception ex)
{
var inner = ex.InnerException?.Message ?? ex.Message;
LoggerHelper.ErrorWithNotify(_scopeName, $"设备 [{config.DeviceName}] 实例化失败:{inner}");
}
}
// IOGroup 初始化:从已实例化的设备中取出前两个 IOBoard 实例组建 IOBoardGroup
try
{
var ioBoards = DeviceMap.Values.OfType<IOBoard>().Take(3).ToList();
if (ioBoards.Count == 3)
{
IOGroup = new IOBoardGroup(ioBoards[0], ioBoards[1], ioBoards[2]);
LoggerHelper.Info($"IOBoardGroup 已初始化,Board1={ioBoards[0].GetType().Name}, Board2={ioBoards[1].GetType().Name}, Board3={ioBoards[2].GetType().Name}。");
}
else
{
LoggerHelper.Warn($"IOBoardGroup 初始化跳过:需要 3 个 IOBoard 实例,当前仅找到 {ioBoards.Count} 个。");
}
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(_scopeName, $"IOBoardGroup 初始化失败:{ex.Message}");
}
}
public async Task ConnectAllDevices(CancellationToken ct = default)
{
if (_systemConfig?.DeviceList == null) return;
var tasks = new List<Task>();
foreach (var info in _systemConfig.DeviceList)
{
if (info == null || !info.IsEnabled) continue;
if (string.IsNullOrWhiteSpace(info.DeviceName)) continue;
// CAN 设备:直接打开 CAN 卡
if (string.Equals(info.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
{
tasks.Add(ConnectCanAsync(info));
continue;
}
if (!DeviceMap.TryGetValue(info.DeviceName, out var device)) continue;
tasks.Add(ConnectInternalAsync(info, device, ct));
}
await Task.WhenAll(tasks);
// 所有设备连接完成后,启动健康监控(心跳重连)
StartHealthMonitor();
}
/// <summary>
/// 启动设备健康监控器:周期性检查设备连接状态,断连自动重连。
/// 与 HardwareDataBroadcaster 的监控采样互不干扰。
/// </summary>
private void StartHealthMonitor()
{
if (_healthMonitor != null || DeviceMap.Count == 0) return;
_healthMonitor = new DeviceHealthMonitor(DeviceMap, _systemConfig, _scopeName);
_healthMonitor.Start();
LoggerHelper.Info($"[{_scopeName}] 心跳重连机制已激活");
}
/// <summary>停止设备健康监控器</summary>
private void StopHealthMonitor()
{
_healthMonitor?.Stop();
_healthMonitor?.Dispose();
_healthMonitor = null;
}
public async Task ConnectSpecifiedDevice(string deviceName, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(deviceName))
{
LoggerHelper.Warn("ConnectSpecifiedDevice:设备名为空。");
return;
}
var info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
if (info == null)
{
LoggerHelper.Warn($"ConnectSpecifiedDevice:未找到设备配置 [{deviceName}]。");
return;
}
// CAN 设备:直接打开 CAN 卡
if (string.Equals(info.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
{
await ConnectCanAsync(info);
return;
}
if (!DeviceMap.TryGetValue(deviceName, out var device))
{
LoggerHelper.Warn($"ConnectSpecifiedDevice:未找到设备 [{deviceName}]。");
return;
}
await ConnectInternalAsync(info, device, ct);
}
/// <summary>
/// 异步关闭指定设备,释放底层连接并更新 UI 状态
/// </summary>
public async Task CloseDeviceAsync(string deviceName)
{
if (string.IsNullOrWhiteSpace(deviceName)) return;
var info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
// CAN 设备:直接关闭 CAN 卡
if (info != null && string.Equals(info.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
{
CloseCan();
return;
}
IBaseInterface? device;
lock (_lockObj)
{
if (!DeviceMap.TryGetValue(deviceName, out device)) return;
}
await CloseInternalAsync(info, device);
}
/// <summary>
/// 异步关闭所有设备
/// </summary>
public async Task CloseAllDevicesAsync()
{
// 先停止健康监控,避免重连定时器与关闭操作冲突
StopHealthMonitor();
List<Task> tasks = new List<Task>();
lock (_lockObj)
{
if (DeviceMap.Count == 0 ) return;
foreach (var kvp in DeviceMap)
{
string deviceName = kvp.Key;
var device = kvp.Value;
var info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
tasks.Add(CloseInternalAsync(info, device));
}
}
CAN.DisConnect();
await Task.WhenAll(tasks);
LoggerHelper.Info("所有设备已执行关闭操作。");
}
#region 辅助方法
private async Task CloseInternalAsync(DeviceInfoVM? info, IBaseInterface device)
{
string name = info?.DeviceName ?? device.GetType().Name;
string conn = info?.ConnectionType ?? "?";
try
{
// 如果设备本身已经是断开状态,直接更新 UI 并返回
if (!device.IsConnected)
{
if (info != null) info.IsConnected = false;
LoggerHelper.Info($"设备 [{name}] 本就处于断开状态。");
return;
}
await Task.Run(() => device.Close());
LoggerHelper.Info($"设备 [{name}/{conn}] 已成功关闭连接。");
}
catch (Exception ex)
{
var inner = ex.InnerException?.Message ?? ex.Message;
LoggerHelper.Error($"设备 [{name}/{conn}] 关闭连接时出现异常: {inner}");
}
finally
{
// 无论关闭时是否抛出异常,均强制同步 UI 状态为未连接
if (info != null)
{
info.IsConnected = false;
}
}
}
private async Task ConnectInternalAsync(DeviceInfoVM? info, IBaseInterface device, CancellationToken ct)
{
string name = info?.DeviceName ?? device.GetType().Name;
string conn = info?.ConnectionType ?? "?";
try
{
if (device.IsConnected)
{
if (info != null) info.IsConnected = true;
LoggerHelper.Info($"设备 [{name}] 已连接,跳过。");
return;
}
bool ok = conn switch
{
"Tcp" => await ConnectTcpAsync(name, device, ct),
"Serial" => await ConnectSerialAsync(name, device, ct),
_ => false
};
if (info != null) info.IsConnected = ok;
if (ok)
LoggerHelper.Info($"设备 [{name}/{conn}] 连接成功。");
else
LoggerHelper.Warn($"设备 [{name}/{conn}] 连接失败。");
}
catch (OperationCanceledException)
{
if (info != null) info.IsConnected = false;
LoggerHelper.Warn($"设备 [{name}/{conn}] 连接已取消。");
}
catch (Exception ex)
{
if (info != null) info.IsConnected = false;
var inner = ex.InnerException?.Message ?? ex.Message;
LoggerHelper.ErrorWithNotify(_scopeName, $"设备 [{name}/{conn}] 连接异常:{inner}");
}
}
private static async Task<bool> ConnectTcpAsync(string name, IBaseInterface device, CancellationToken ct)
{
if (device is ITcp tcp)
{
return await tcp.ConnectAsync(ct);
}
if (device is IModbusDevice modbusTCP)
{
return await modbusTCP.ConnectAsync(ct);
}
LoggerHelper.Warn($"设备 [{name}] 配置为 Tcp 但未实现 ITcp,实际类型为 {device.GetType().Name}。");
return false;
}
private static async Task<bool> ConnectSerialAsync(string name, IBaseInterface device, CancellationToken ct)
{
if (device is not ISerialPort sp)
{
LoggerHelper.Warn($"设备 [{name}] 配置为 Serial 但未实现 ISerialPort,实际类型为 {device.GetType().Name}。");
return false;
}
return await sp.ConnectAsync(ct);
}
/// <summary>
/// 打开 CAN 卡:打开设备 + 初始化并启动所有通道 + 自动加载 DBC + 启动信号广播
/// </summary>
private async Task<bool> ConnectCanAsync(DeviceInfoVM info)
{
try
{
bool ok = await Task.Run(() =>
{
// 自动加载 DBC 文件
if (_systemConfig?.DBCAutoLoadList != null)
{
foreach (var item in _systemConfig.DBCAutoLoadList)
{
if (item.DBCChannel < 0 || item.DBCChannel >= 4) continue;
if (string.IsNullOrWhiteSpace(item.DBCFilePath)) continue;
if (!File.Exists(item.DBCFilePath))
{
// 发布 DBC 卸载事件,通知监控系统清除对应信号
_eventAggregator.GetEvent<DBCUnloadedEvent>().Publish(new DBCUnloadedArgs
{
Channel = (uint)item.DBCChannel,
Scope = _scopeName
});
LoggerHelper.Warn($"CAN 通道 {item.DBCChannel} 自动加载 DBC 失败:文件不存在 [{item.DBCFilePath}]");
continue;
}
bool loadOk = CAN.LoadDBC(item.DBCFilePath,new int[] { item.DBCChannel },out _ )==0;
if (loadOk)
{
LoggerHelper.Info($"CAN 通道 {item.DBCChannel} 已自动加载 DBC{item.DBCFilePath}");
// 发布 DBC 加载完成事件,通知监控系统刷新信号列表
_eventAggregator.GetEvent<DBCLoadedEvent>().Publish(new DBCLoadedArgs
{
Channel = (uint)item.DBCChannel,
Scope = _scopeName
});
}
else
{
LoggerHelper.Warn($"CAN 通道 {item.DBCChannel} 自动加载 DBC 失败:{item.DBCFilePath}");
_eventAggregator.GetEvent<DBCUnloadedEvent>().Publish(new DBCUnloadedArgs
{
Channel = (uint)item.DBCChannel,
Scope = _scopeName
});
}
}
}
if (CAN.Connect() != 0) return false;
return true;
});
info.IsConnected = ok;
if (ok)
LoggerHelper.Info($"CAN 设备 连接成功");
else
LoggerHelper.Warn($"CAN 设备连接失败。");
return ok;
}
catch (Exception ex)
{
info.IsConnected = false;
var inner = ex.InnerException?.Message ?? ex.Message;
LoggerHelper.ErrorWithNotify(_scopeName, $"CAN 设备 连接异常:{inner}");
return false;
}
}
/// <summary>
/// 关闭 CAN 卡
/// </summary>
private void CloseCan()
{
CAN.DisConnect();
}
private static IReadOnlyDictionary<string, Type> BuildDeviceTypeMap()
{
try
{
return typeof(IBaseInterface).Assembly
.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && typeof(IBaseInterface).IsAssignableFrom(t))
.ToDictionary(t => t.Name, t => t, StringComparer.OrdinalIgnoreCase);
}
catch (ReflectionTypeLoadException ex)
{
LoggerHelper.Error($"扫描设备类型失败:{ex.Message}");
return new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
}
}
private static IBaseInterface? CreateTcpDevice(Type type, TcpConfigVM? vm)
{
vm ??= new TcpConfigVM();
var cfg = new TcpConfig
{
IPAddress = vm.IPAddress,
Port = vm.Port,
SendTimeout = vm.SendTimeout,
ReceiveTimeout = vm.ReceiveTimeout
};
return Activator.CreateInstance(type, cfg) as IBaseInterface;
}
private static IBaseInterface? CreateSerialDevice(Type type, SerialPortConfigVM? vm)
{
vm ??= new SerialPortConfigVM();
var cfg = new SerialPortConfig
{
PortName = vm.PortName,
BaudRate = vm.BaudRate,
DataBits = vm.DataBits,
StopBits = Enum.TryParse<StopBits>(vm.StopBits, true, out var sb) ? sb : StopBits.One,
Parity = Enum.TryParse<Parity>(vm.Parity, true, out var pa) ? pa : Parity.None,
ReadTimeout = vm.ReadTimeout,
WriteTimeout = vm.WriteTimeout
};
return Activator.CreateInstance(type, cfg) as IBaseInterface;
}
public void Dispose()
{
// 停止健康监控
StopHealthMonitor();
// 停止 CAN 信号监测服务
_CANMonitoringService?.Stop();
if (string.IsNullOrEmpty(_scopeName)) return;
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用
var fingerprintsToRemove = new List<string>();
foreach (var kvp in _globalInfo.DeviceAndScopeDic)
{
string fingerprint = kvp.Key;
var scopeList = kvp.Value.IsValueCreated ? kvp.Value.Value : null;
if (scopeList == null) continue;
lock (scopeList)
{
scopeList.Remove(_scopeName);
// 引用归零 → 标记为待清理
if (scopeList.Count == 0)
fingerprintsToRemove.Add(fingerprint);
}
}
// 对引用归零的指纹:销毁设备实例 + 从全局池中移除
foreach (var fingerprint in fingerprintsToRemove)
{
// 尝试从 HardwarePool 取出并销毁
if (_globalInfo.HardwarePool.TryRemove(fingerprint, out var lazy))
{
if (lazy.IsValueCreated && lazy.Value is IBaseInterface device)
{
try { device.Close(); }
catch { /* 销毁时忽略异常 */ }
LoggerHelper.Info($"指纹 [{fingerprint}] 无作用域引用,已销毁设备实例。");
}
}
// 同步清除 DeviceAndScopeDic 中的空条目
_globalInfo.DeviceAndScopeDic.TryRemove(fingerprint, out _);
}
DeviceMap.Clear();
}
#endregion
}
}