Files
ACP/UIShare/GlobalVariable/CANSignalBroadcaster.cs
T
2026-08-07 09:06:43 +08:00

206 lines
8.0 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 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
}
}