244 lines
10 KiB
C#
244 lines
10 KiB
C#
using Model.Models;
|
||
using Prism.Events;
|
||
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using UIShare.PubEvent;
|
||
using UIShare.UIViewModel;
|
||
|
||
namespace UIShare.GlobalVariable
|
||
{
|
||
/// <summary>
|
||
/// CAN 信号广播器(全局单例,支持多作用域):
|
||
/// 从所有已注册的 <see cref="CANMonitoringService"/> 的 RealTimeSignals 字典中轮询读取信号值,
|
||
/// 按每个已注册作用域的 ConfigurationList 分别映射,向对应作用域广播 HardwareDataReportedEvent。
|
||
/// <para>
|
||
/// 一拖三场景下 CAN 硬件共享,但各工位的 DBC/ConfigurationList 不同,
|
||
/// 因此广播器必须遍历所有已注册作用域的配置分别广播。
|
||
/// </para>
|
||
/// </summary>
|
||
public class CANSignalBroadcaster
|
||
{
|
||
private readonly IEventAggregator _eventAggregator;
|
||
|
||
/// <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();
|
||
|
||
private CancellationTokenSource? _cts;
|
||
private Task? _broadcastTask;
|
||
|
||
/// <summary>广播轮询间隔(毫秒)</summary>
|
||
public int PollingIntervalMs { get; set; } = 100;
|
||
|
||
public CANSignalBroadcaster(SystemConfig systemConfig, IEventAggregator eventAggregator)
|
||
{
|
||
_eventAggregator = eventAggregator;
|
||
// 构造时自动注册第一个作用域(向后兼容)
|
||
RegisterScope(systemConfig.Title, systemConfig);
|
||
}
|
||
|
||
#region 服务注册(供 CANMonitoringService 调用)
|
||
|
||
/// <summary>注册一个 CANMonitoringService,使其信号纳入广播</summary>
|
||
public static void RegisterService(CANMonitoringService service)
|
||
{
|
||
_registeredServices.TryAdd(service, 0);
|
||
}
|
||
|
||
/// <summary>注销一个 CANMonitoringService</summary>
|
||
public static void UnregisterService(CANMonitoringService service)
|
||
{
|
||
_registeredServices.TryRemove(service, out _);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 启动 / 停止
|
||
|
||
/// <summary>启动广播(幂等:多次调用只启动一次)</summary>
|
||
public void Start()
|
||
{
|
||
if (_broadcastTask != null && !_broadcastTask.IsCompleted) return;
|
||
|
||
_cts = new CancellationTokenSource();
|
||
_broadcastTask = Task.Run(() => BroadcastLoop(_cts.Token));
|
||
}
|
||
|
||
/// <summary>停止广播</summary>
|
||
public void Stop()
|
||
{
|
||
_cts?.Cancel();
|
||
_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() { }
|
||
|
||
#endregion
|
||
|
||
#region 广播核心逻辑
|
||
|
||
/// <summary>
|
||
/// 轮询所有已注册的 CANMonitoringService 的 RealTimeSignals,
|
||
/// 遍历所有已注册作用域的配置分别映射并广播 HardwareDataReportedEvent。
|
||
/// </summary>
|
||
private async Task BroadcastLoop(CancellationToken ct)
|
||
{
|
||
while (!ct.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
// 快照当前作用域注册表,避免枚举期间被修改
|
||
var scopeSnapshot = _scopeRegistry.ToArray();
|
||
|
||
foreach (var scopeEntry in scopeSnapshot)
|
||
{
|
||
if (ct.IsCancellationRequested) return;
|
||
|
||
string scopeName = scopeEntry.Key;
|
||
var scopeConfig = scopeEntry.Value.Config;
|
||
var configMap = scopeEntry.Value.ConfigMap;
|
||
|
||
foreach (var service in _registeredServices.Keys)
|
||
{
|
||
if (service.IsStopped) continue;
|
||
|
||
foreach (var kvp in service.RealTimeSignals)
|
||
{
|
||
if (ct.IsCancellationRequested) return;
|
||
|
||
// 信号 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));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
await Task.Delay(PollingIntervalMs, ct);
|
||
}
|
||
catch (OperationCanceledException) { break; }
|
||
catch (Exception ex)
|
||
{
|
||
Logger.LoggerHelper.Error($"CANSignalBroadcaster 广播异常: {ex.Message}");
|
||
await Task.Delay(1000, ct);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 SystemConfig.ConfigurationList 构建 "channel/MessageName/SignalName" → CANSignalConfig 的映射
|
||
/// </summary>
|
||
private static Dictionary<string, CANSignalConfig> BuildConfigMap(SystemConfig systemConfig)
|
||
{
|
||
var map = new Dictionary<string, CANSignalConfig>(StringComparer.OrdinalIgnoreCase);
|
||
if (systemConfig?.ConfigurationList == null) return map;
|
||
|
||
foreach (var cfg in systemConfig.ConfigurationList)
|
||
{
|
||
if (string.IsNullOrEmpty(cfg.SignalName) || string.IsNullOrEmpty(cfg.MessageName)) continue;
|
||
string key = $"{cfg.Channel}/{cfg.MessageName}/{cfg.SignalName}";
|
||
map.TryAdd(key, cfg); // 第一个匹配的优先
|
||
}
|
||
return map;
|
||
}
|
||
|
||
/// <summary>解析信号 Key: "{channel}/{MessageName}/{SignalName}"</summary>
|
||
private static bool TryParseSignalKey(string key, out int channel, out string? messageName, out string? signalName)
|
||
{
|
||
channel = 0;
|
||
messageName = null;
|
||
signalName = null;
|
||
|
||
var parts = key.Split('/');
|
||
if (parts.Length < 3) return false;
|
||
|
||
if (!int.TryParse(parts[0], out channel)) return false;
|
||
messageName = parts[1];
|
||
signalName = parts[2];
|
||
return !string.IsNullOrEmpty(messageName) && !string.IsNullOrEmpty(signalName);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 静态工具方法(保持向后兼容)
|
||
|
||
/// <summary>生成 CAN 信号的 DisplayName 格式:"{MessageName}.{SignalName}"</summary>
|
||
public static string BuildDisplayName(string messageName, string signalName)
|
||
{
|
||
return $"{messageName}.{signalName}";
|
||
}
|
||
|
||
/// <summary>生成 CAN 信号的 MethodName 格式:"{MessageId:X}.{SignalName}"</summary>
|
||
public static string BuildMethodName(uint messageId, string signalName)
|
||
{
|
||
return $"{messageId:X}.{signalName}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成 CAN 信号的 Fingerprint 格式:"{canDeviceFingerprint}:{channel}"
|
||
/// canDeviceFingerprint 来自 DeviceManager.ExtractHardwareFingerprint,如 "CAN:0"
|
||
/// </summary>
|
||
public static string BuildFingerprint(string canDeviceFingerprint, uint channel)
|
||
{
|
||
return $"{canDeviceFingerprint}:{channel}";
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|