处理掉多余文件,修改CAN描述
This commit is contained in:
@@ -12,14 +12,23 @@ using UIShare.UIViewModel;
|
||||
namespace UIShare.GlobalVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// CAN 信号广播器(全局单例):
|
||||
/// 不再订阅 CAN 实体实例的事件,改为从所有已注册的 <see cref="CANMonitoringService"/>
|
||||
/// 的 RealTimeSignals 字典中轮询读取信号值,并通过 <see cref="HardwareDataReportedEvent"/> 广播。
|
||||
/// CAN 信号广播器(全局单例,支持多作用域):
|
||||
/// 从所有已注册的 <see cref="CANMonitoringService"/> 的 RealTimeSignals 字典中轮询读取信号值,
|
||||
/// 按每个已注册作用域的 ConfigurationList 分别映射,向对应作用域广播 HardwareDataReportedEvent。
|
||||
/// <para>
|
||||
/// 一拖三场景下 CAN 硬件共享,但各工位的 DBC/ConfigurationList 不同,
|
||||
/// 因此广播器必须遍历所有已注册作用域的配置分别广播。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class CANSignalBroadcaster
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly SystemConfig _systemConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 作用域注册表:scopeName → (SystemConfig, 预构建的 ConfigMap)。
|
||||
/// 每个工位的 MonitorViewModel 在初始化时调用 <see cref="RegisterScope"/> 注册自己的配置。
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, (SystemConfig Config, Dictionary<string, CANSignalConfig> ConfigMap)> _scopeRegistry = new();
|
||||
|
||||
/// <summary>全局已注册的 CANMonitoringService 实例列表(静态,跨作用域共享)</summary>
|
||||
private static readonly ConcurrentDictionary<CANMonitoringService, byte> _registeredServices = new();
|
||||
@@ -32,8 +41,9 @@ namespace UIShare.GlobalVariable
|
||||
|
||||
public CANSignalBroadcaster(SystemConfig systemConfig, IEventAggregator eventAggregator)
|
||||
{
|
||||
_systemConfig = systemConfig;
|
||||
_eventAggregator = eventAggregator;
|
||||
// 构造时自动注册第一个作用域(向后兼容)
|
||||
RegisterScope(systemConfig.Title, systemConfig);
|
||||
}
|
||||
|
||||
#region 服务注册(供 CANMonitoringService 调用)
|
||||
@@ -70,6 +80,27 @@ namespace UIShare.GlobalVariable
|
||||
_broadcastTask = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个作用域的 CAN 配置。
|
||||
/// 每个工位在 MonitorViewModel 初始化时调用此方法,将自己的 SystemConfig 注册进来,
|
||||
/// 广播器会在每轮广播中为该作用域独立映射信号并广播。
|
||||
/// </summary>
|
||||
/// <param name="scopeName">作用域名称(SystemConfig.Title)</param>
|
||||
/// <param name="config">该作用域的 SystemConfig</param>
|
||||
public void RegisterScope(string scopeName, SystemConfig config)
|
||||
{
|
||||
if (string.IsNullOrEmpty(scopeName) || config == null) return;
|
||||
var configMap = BuildConfigMap(config);
|
||||
_scopeRegistry.AddOrUpdate(scopeName, (config, configMap), (_, _) => (config, configMap));
|
||||
}
|
||||
|
||||
/// <summary>注销一个作用域(工位销毁时调用)</summary>
|
||||
public void UnregisterScope(string scopeName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(scopeName))
|
||||
_scopeRegistry.TryRemove(scopeName, out _);
|
||||
}
|
||||
|
||||
/// <summary>兼容旧接口:Discover 已无需执行任何操作</summary>
|
||||
public void Discover() { }
|
||||
|
||||
@@ -79,7 +110,7 @@ namespace UIShare.GlobalVariable
|
||||
|
||||
/// <summary>
|
||||
/// 轮询所有已注册的 CANMonitoringService 的 RealTimeSignals,
|
||||
/// 结合 ConfigurationList 映射出 MessageID/Channel,广播 HardwareDataReportedEvent。
|
||||
/// 遍历所有已注册作用域的配置分别映射并广播 HardwareDataReportedEvent。
|
||||
/// </summary>
|
||||
private async Task BroadcastLoop(CancellationToken ct)
|
||||
{
|
||||
@@ -87,46 +118,53 @@ namespace UIShare.GlobalVariable
|
||||
{
|
||||
try
|
||||
{
|
||||
// 预构建信号名 → 配置 的映射(避免内层循环重复查找)
|
||||
var configMap = BuildConfigMap();
|
||||
// 快照当前作用域注册表,避免枚举期间被修改
|
||||
var scopeSnapshot = _scopeRegistry.ToArray();
|
||||
|
||||
string scope = _systemConfig.Title;
|
||||
|
||||
foreach (var service in _registeredServices.Keys)
|
||||
foreach (var scopeEntry in scopeSnapshot)
|
||||
{
|
||||
if (service.IsStopped) continue;
|
||||
if (ct.IsCancellationRequested) return;
|
||||
|
||||
foreach (var kvp in service.RealTimeSignals)
|
||||
string scopeName = scopeEntry.Key;
|
||||
var scopeConfig = scopeEntry.Value.Config;
|
||||
var configMap = scopeEntry.Value.ConfigMap;
|
||||
|
||||
foreach (var service in _registeredServices.Keys)
|
||||
{
|
||||
if (ct.IsCancellationRequested) return;
|
||||
if (service.IsStopped) continue;
|
||||
|
||||
// 信号 Key 格式: "{channel}/{MessageName}/{SignalName}"
|
||||
if (!TryParseSignalKey(kvp.Key, out int channel, out string? messageName, out string? signalName))
|
||||
continue;
|
||||
|
||||
// 从配置映射中查找对应的 MessageID
|
||||
if (!configMap.TryGetValue($"{channel}/{messageName}/{signalName}", out var cfg))
|
||||
continue;
|
||||
|
||||
string canFingerprint = $"CAN:{channel}";
|
||||
string fingerprint = BuildFingerprint(canFingerprint, (uint)channel);
|
||||
string methodName = BuildMethodName((uint)cfg.MessageID, signalName);
|
||||
|
||||
// 广播信号值
|
||||
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
|
||||
foreach (var kvp in service.RealTimeSignals)
|
||||
{
|
||||
Scope = scope,
|
||||
HardwareFingerprint = fingerprint,
|
||||
MethodName = methodName,
|
||||
Value = kvp.Value,
|
||||
Time = DateTime.Now
|
||||
});
|
||||
if (ct.IsCancellationRequested) return;
|
||||
|
||||
// 报警检查
|
||||
string alarmStatus = ValueLimitAlarmHelper.CheckAlarm(fingerprint, methodName, kvp.Value, _systemConfig);
|
||||
if (!string.IsNullOrEmpty(alarmStatus) && alarmStatus != "未报警")
|
||||
{
|
||||
_eventAggregator.GetEvent<AlarmEvent>().Publish((scope, canFingerprint, alarmStatus));
|
||||
// 信号 Key 格式: "{channel}/{MessageName}/{SignalName}"
|
||||
if (!TryParseSignalKey(kvp.Key, out int channel, out string? messageName, out string? signalName))
|
||||
continue;
|
||||
|
||||
// 从该作用域的配置映射中查找对应的 MessageID
|
||||
if (!configMap.TryGetValue($"{channel}/{messageName}/{signalName}", out var cfg))
|
||||
continue;
|
||||
|
||||
string canFingerprint = $"CAN:{channel}";
|
||||
string fingerprint = BuildFingerprint(canFingerprint, (uint)channel);
|
||||
string methodName = BuildMethodName((uint)cfg.MessageID, signalName);
|
||||
|
||||
// 广播信号值到对应作用域
|
||||
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
|
||||
{
|
||||
Scope = scopeName,
|
||||
HardwareFingerprint = fingerprint,
|
||||
MethodName = methodName,
|
||||
Value = kvp.Value,
|
||||
Time = DateTime.Now
|
||||
});
|
||||
|
||||
// 报警检查(使用该作用域自己的配置)
|
||||
string alarmStatus = ValueLimitAlarmHelper.CheckAlarm(fingerprint, methodName, kvp.Value, scopeConfig);
|
||||
if (!string.IsNullOrEmpty(alarmStatus) && alarmStatus != "未报警")
|
||||
{
|
||||
_eventAggregator.GetEvent<AlarmEvent>().Publish((scopeName, canFingerprint, alarmStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +175,7 @@ namespace UIShare.GlobalVariable
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LoggerHelper.Error($"CANSignalBroadcaster 广播异常: {ex.Message}");
|
||||
await Task.Delay(1000, ct); // 异常后等待一段时间再重试
|
||||
await Task.Delay(1000, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,12 +183,12 @@ namespace UIShare.GlobalVariable
|
||||
/// <summary>
|
||||
/// 从 SystemConfig.ConfigurationList 构建 "channel/MessageName/SignalName" → CANSignalConfig 的映射
|
||||
/// </summary>
|
||||
private Dictionary<string, CANSignalConfig> BuildConfigMap()
|
||||
private static Dictionary<string, CANSignalConfig> BuildConfigMap(SystemConfig systemConfig)
|
||||
{
|
||||
var map = new Dictionary<string, CANSignalConfig>(StringComparer.OrdinalIgnoreCase);
|
||||
if (_systemConfig?.ConfigurationList == null) return map;
|
||||
if (systemConfig?.ConfigurationList == null) return map;
|
||||
|
||||
foreach (var cfg in _systemConfig.ConfigurationList)
|
||||
foreach (var cfg in systemConfig.ConfigurationList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cfg.SignalName) || string.IsNullOrEmpty(cfg.MessageName)) continue;
|
||||
string key = $"{cfg.Channel}/{cfg.MessageName}/{cfg.SignalName}";
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace UIShare.GlobalVariable
|
||||
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);
|
||||
@@ -209,6 +212,30 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -277,6 +304,9 @@ namespace UIShare.GlobalVariable
|
||||
/// </summary>
|
||||
public async Task CloseAllDevicesAsync()
|
||||
{
|
||||
// 先停止健康监控,避免重连定时器与关闭操作冲突
|
||||
StopHealthMonitor();
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
lock (_lockObj)
|
||||
@@ -523,6 +553,9 @@ namespace UIShare.GlobalVariable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// 停止健康监控
|
||||
StopHealthMonitor();
|
||||
|
||||
// 停止 CAN 信号监测服务
|
||||
_CANMonitoringService?.Stop();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user