206 lines
8.0 KiB
C#
206 lines
8.0 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 信号广播器(全局单例):
|
||
/// 不再订阅 CAN 实体实例的事件,改为从所有已注册的 <see cref="CANMonitoringService"/>
|
||
/// 的 RealTimeSignals 字典中轮询读取信号值,并通过 <see cref="HardwareDataReportedEvent"/> 广播。
|
||
/// </summary>
|
||
public class CANSignalBroadcaster
|
||
{
|
||
private readonly IEventAggregator _eventAggregator;
|
||
private readonly SystemConfig _systemConfig;
|
||
|
||
/// <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)
|
||
{
|
||
_systemConfig = systemConfig;
|
||
_eventAggregator = eventAggregator;
|
||
}
|
||
|
||
#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>兼容旧接口:Discover 已无需执行任何操作</summary>
|
||
public void Discover() { }
|
||
|
||
#endregion
|
||
|
||
#region 广播核心逻辑
|
||
|
||
/// <summary>
|
||
/// 轮询所有已注册的 CANMonitoringService 的 RealTimeSignals,
|
||
/// 结合 ConfigurationList 映射出 MessageID/Channel,广播 HardwareDataReportedEvent。
|
||
/// </summary>
|
||
private async Task BroadcastLoop(CancellationToken ct)
|
||
{
|
||
while (!ct.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
// 预构建信号名 → 配置 的映射(避免内层循环重复查找)
|
||
var configMap = BuildConfigMap();
|
||
|
||
string scope = _systemConfig.Title;
|
||
|
||
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 = scope,
|
||
HardwareFingerprint = fingerprint,
|
||
MethodName = methodName,
|
||
Value = kvp.Value,
|
||
Time = DateTime.Now
|
||
});
|
||
|
||
// 报警检查
|
||
string alarmStatus = ValueLimitAlarmHelper.CheckAlarm(fingerprint, methodName, kvp.Value, _systemConfig);
|
||
if (!string.IsNullOrEmpty(alarmStatus) && alarmStatus != "未报警")
|
||
{
|
||
_eventAggregator.GetEvent<AlarmEvent>().Publish((scope, 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 Dictionary<string, CANSignalConfig> BuildConfigMap()
|
||
{
|
||
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
|
||
}
|
||
}
|