678 lines
28 KiB
C#
678 lines
28 KiB
C#
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;
|
||
using TSMasterCAN;
|
||
|
||
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; }
|
||
private readonly GlobalInfo _globalInfo;
|
||
private readonly string _scopeName;
|
||
private readonly IEventAggregator _eventAggregator;
|
||
|
||
/// <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 CAN CANFD { get; set; }
|
||
public IOBoardGroup IOGroup { get; set; }
|
||
|
||
public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo, IEventAggregator eventAggregator)
|
||
{
|
||
_systemConfig = systemConfig;
|
||
_globalInfo = globalInfo;
|
||
_eventAggregator = eventAggregator;
|
||
// 用 SystemConfig.Title 作为作用域唯一标识,无需反查 ConfigDic
|
||
_scopeName = _systemConfig.Title;
|
||
InitDevices();
|
||
}
|
||
|
||
/// <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;
|
||
|
||
// CAN 设备:ZLGCANFD 不实现 IBaseInterface,通过 SystemConfig.CANFD 单独管理,
|
||
// 按指纹从全局 CanPool 创建/复用实例,并注册作用域引用计数。
|
||
if (string.Equals(config.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var fingerprint = ExtractHardwareFingerprint(config);
|
||
if (string.IsNullOrEmpty(fingerprint))
|
||
{
|
||
LoggerHelper.Warn($"设备 [{config.DeviceName}] 无法提取硬件指纹(连接方式={config.ConnectionType}),已跳过。");
|
||
continue;
|
||
}
|
||
|
||
if (config.CANConfig == null)
|
||
{
|
||
LoggerHelper.Warn($"设备 [{config.DeviceName}] 缺少 CAN 连接参数,已跳过。");
|
||
continue;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 按指纹全局唯一创建同星 CAN 实例(maxChannels 默认 4,对应 USBCANFD-400U)
|
||
// 波特率与终端电阻从 CANConfigVM 传入,初始化并启动通道 时直接使用
|
||
var canLazy = _globalInfo.CanPool.GetOrAdd(fingerprint, key => new Lazy<CAN>(() =>
|
||
new CAN(config.CANConfig.DeviceType, config.CANConfig.DeviceIndex, 4,
|
||
config.CANConfig.ABitBaud, config.CANConfig.DBitBaud, config.CANConfig.EnableTerminalResistance)));
|
||
|
||
_systemConfig.CANFD = canLazy.Value;
|
||
CANFD = canLazy.Value;
|
||
|
||
// 注册作用域引用计数
|
||
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($"已加载 CAN 设备 [{config.DeviceName}] 指纹={fingerprint}(通过 SystemConfig.CANFD 管理)");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LoggerHelper.ErrorWithNotify(_scopeName, $"CAN 设备 [{config.DeviceName}] 实例化失败:{ex.Message}");
|
||
}
|
||
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(2).ToList();
|
||
if (ioBoards.Count == 2)
|
||
{
|
||
IOGroup = new IOBoardGroup(ioBoards[0], ioBoards[1]);
|
||
LoggerHelper.Info($"IOBoardGroup 已初始化,Board1={ioBoards[0].GetType().Name}, Board2={ioBoards[1].GetType().Name}。");
|
||
}
|
||
else
|
||
{
|
||
LoggerHelper.Warn($"IOBoardGroup 初始化跳过:需要 2 个 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);
|
||
}
|
||
|
||
|
||
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))
|
||
{
|
||
await CloseCanAsync(info);
|
||
return;
|
||
}
|
||
|
||
IBaseInterface? device;
|
||
|
||
lock (_lockObj)
|
||
{
|
||
if (!DeviceMap.TryGetValue(deviceName, out device)) return;
|
||
}
|
||
|
||
await CloseInternalAsync(info, device);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步关闭所有设备
|
||
/// </summary>
|
||
public async Task CloseAllDevicesAsync()
|
||
{
|
||
List<Task> tasks = new List<Task>();
|
||
|
||
lock (_lockObj)
|
||
{
|
||
if (DeviceMap.Count == 0 && (CANFD == null)) 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 设备:直接关闭 CAN 卡
|
||
if (CANFD != null)
|
||
{
|
||
var canInfo = _systemConfig?.DeviceList?
|
||
.FirstOrDefault(d => d != null && string.Equals(d.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase));
|
||
if (canInfo != null)
|
||
{
|
||
tasks.Add(CloseCanAsync(canInfo));
|
||
}
|
||
}
|
||
|
||
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)
|
||
{
|
||
string name = info.DeviceName ?? "CAN";
|
||
|
||
try
|
||
{
|
||
if (CANFD == null)
|
||
{
|
||
LoggerHelper.Warn($"CAN 设备 [{name}] 尚未实例化,无法连接。");
|
||
info.IsConnected = false;
|
||
return false;
|
||
}
|
||
|
||
if (info.IsConnected)
|
||
{
|
||
LoggerHelper.Info($"CAN 设备 [{name}] 已连接,跳过。");
|
||
return true;
|
||
}
|
||
|
||
bool ok = await Task.Run(() =>
|
||
{
|
||
if (!CANFD.打开设备()) return false;
|
||
// 自动加载 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;
|
||
}
|
||
CANFD.初始化并启动通道((uint)item.DBCChannel);
|
||
bool loadOk = CANFD.加载通道DBC文件((uint)item.DBCChannel, item.DBCFilePath);
|
||
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
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
info.IsConnected = ok;
|
||
|
||
if (ok)
|
||
LoggerHelper.Info($"CAN 设备 [{name}] 连接成功,已初始化 {CANFD.DBCParser.MaxChannels} 个通道。");
|
||
else
|
||
LoggerHelper.Warn($"CAN 设备 [{name}] 连接失败。");
|
||
|
||
return ok;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
info.IsConnected = false;
|
||
var inner = ex.InnerException?.Message ?? ex.Message;
|
||
LoggerHelper.ErrorWithNotify(_scopeName, $"CAN 设备 [{name}] 连接异常:{inner}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭 CAN 卡
|
||
/// </summary>
|
||
private async Task CloseCanAsync(DeviceInfoVM info)
|
||
{
|
||
string name = info.DeviceName ?? "CAN";
|
||
|
||
try
|
||
{
|
||
if (CANFD == null)
|
||
{
|
||
info.IsConnected = false;
|
||
return;
|
||
}
|
||
|
||
if (!info.IsConnected)
|
||
{
|
||
LoggerHelper.Info($"CAN 设备 [{name}] 本就处于断开状态。");
|
||
return;
|
||
}
|
||
|
||
await Task.Run(() => CANFD.关闭CAN卡设备());
|
||
LoggerHelper.Info($"CAN 设备 [{name}] 已成功关闭连接。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var inner = ex.InnerException?.Message ?? ex.Message;
|
||
LoggerHelper.Error($"CAN 设备 [{name}] 关闭连接时出现异常: {inner}");
|
||
}
|
||
finally
|
||
{
|
||
info.IsConnected = false;
|
||
}
|
||
}
|
||
|
||
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()
|
||
{
|
||
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}] 无作用域引用,已销毁设备实例。");
|
||
}
|
||
}
|
||
|
||
// 尝试从 CanPool 取出并销毁
|
||
if (_globalInfo.CanPool.TryRemove(fingerprint, out var canLazy))
|
||
{
|
||
if (canLazy.IsValueCreated)
|
||
{
|
||
try { canLazy.Value.Dispose(); }
|
||
catch { /* 销毁时忽略异常 */ }
|
||
LoggerHelper.Info($"指纹 [{fingerprint}] 无作用域引用,已销毁 CAN 设备实例。");
|
||
}
|
||
}
|
||
|
||
// 同步清除 DeviceAndScopeDic 中的空条目
|
||
_globalInfo.DeviceAndScopeDic.TryRemove(fingerprint, out _);
|
||
}
|
||
|
||
DeviceMap.Clear();
|
||
}
|
||
#endregion
|
||
}
|
||
}
|