CAN修复
This commit is contained in:
@@ -7,6 +7,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TSMasterCAN;
|
||||
using TSMaster;
|
||||
using Logger;
|
||||
namespace UIShare.GlobalVariable
|
||||
{
|
||||
public class CANMonitoringService
|
||||
@@ -23,79 +24,106 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
public int Init(CancellationToken ct = default)
|
||||
{
|
||||
int re = 0;
|
||||
if (IsInitialized == false)
|
||||
// 多工位共用同一个 TSMaster 实例:首次工位已完成 initialize_lib_tsmaster,
|
||||
// 后续工位的 CAN.Init 会直接复用首次结果(返回 0),因此这里不能再用
|
||||
// “初始化未成功就跳过”的判断,否则第二个工位永远注册不到报文回调。
|
||||
var initRe = CAN.Init(_systemConfig.TSMasterName);
|
||||
if (initRe != 0)
|
||||
{
|
||||
re = CAN.Init(_systemConfig.TSMasterName);
|
||||
}
|
||||
if (IsInitialized == false)
|
||||
{
|
||||
re = CAN.RegisterListener(MonitorEvent);
|
||||
if (re == 0) IsInitialized = true;
|
||||
LoggerHelper.Warn($"CAN 驱动初始化返回 {initRe}({CAN.GetErrorDescription(initRe)}),继续注册报文回调。");
|
||||
}
|
||||
|
||||
// RegisterListener 幂等:重复调用只补充订阅者;驱动被 Release 后再 Init 也能重新挂回原生回调
|
||||
var re = CAN.RegisterListener(MonitorEvent);
|
||||
if (re == 0) IsInitialized = true;
|
||||
return re;
|
||||
}
|
||||
public ConcurrentDictionary<string, TLIBCANFD> RealTimeMessages { get; set; } = new();
|
||||
public ConcurrentDictionary<string, double> RealTimeSignals { get; set; } = new();
|
||||
/// <summary>
|
||||
/// TSMaster 原生库收报文回调(原生线程直接调用)。
|
||||
/// 回调内抛出的异常一旦跨出原生边界会直接导致进程闪退,
|
||||
/// 因此整个方法体必须包裹异常隔离,且不做任何可能阻塞的调用。
|
||||
/// </summary>
|
||||
public void MonitorEvent(ref int AObj, ref TLIBCANFD AData)
|
||||
{
|
||||
if (IsStopped) return;
|
||||
byte[] oldbytes = null;
|
||||
|
||||
//记录至实时报文数据库
|
||||
var cpAData = AData;
|
||||
cpAData.FData = ArrayPool<byte>.Shared.Rent(64);
|
||||
Array.Copy(AData.FData, 0, cpAData.FData, 0, cpAData.FData.Length);
|
||||
var MessageName = $"{AData.FIdxChn}/{AData.FIdentifier}";
|
||||
if (RealTimeMessages.TryGetValue(MessageName, out var v))
|
||||
try
|
||||
{
|
||||
oldbytes = v.FData;
|
||||
}
|
||||
RealTimeMessages[MessageName] = cpAData;
|
||||
if (IsStopped) return;
|
||||
|
||||
//记录至DBC数据库
|
||||
var dbcfind = DBCParse.MsgDatabase[AData.FIdxChn].FirstOrDefault(s => s.msg_id == cpAData.FIdentifier);
|
||||
if (dbcfind != null)
|
||||
{
|
||||
Array.Copy(cpAData.FData, 0, dbcfind.ACANFD.FData, 0, cpAData.FData.Length);
|
||||
dbcfind.ACANFD.FTimeUS = cpAData.FTimeUS;
|
||||
dbcfind.ACANFD.FIsFD = cpAData.FIsFD;
|
||||
dbcfind.ACANFD.FIsBRS = cpAData.FIsBRS;
|
||||
dbcfind.ACANFD.FIsESI = cpAData.FIsESI;
|
||||
dbcfind.ACANFD.FIsError = cpAData.FIsError;
|
||||
dbcfind.ACANFD.FIsRemote = cpAData.FIsRemote;
|
||||
dbcfind.ACANFD.FIsExt = cpAData.FIsExt;
|
||||
}
|
||||
// 通道越界防护:多通道/异常帧的 FIdxChn 可能超出 DBC 数据库(12 通道)范围
|
||||
if (AData.FIdxChn >= DBCParse.MsgDatabase.Count) return;
|
||||
if (AData.FData == null) return;
|
||||
|
||||
if (oldbytes is not null) ArrayPool<byte>.Shared.Return(oldbytes);
|
||||
byte[] oldbytes = null;
|
||||
|
||||
var id = AData.FIdentifier;
|
||||
var ch = AData.FIdxChn;
|
||||
|
||||
var canSignalList = _systemConfig.ConfigurationList.Where(s => s.MessageID == id && s.Channel == ch).ToArray();
|
||||
|
||||
if (canSignalList.Length > 0)
|
||||
{
|
||||
var find = DBCParse.MsgDatabase[ch].First(s => s.msg_id == id);
|
||||
foreach (var item in canSignalList)
|
||||
//记录至实时报文数据库
|
||||
var cpAData = AData;
|
||||
cpAData.FData = ArrayPool<byte>.Shared.Rent(64);
|
||||
Array.Copy(AData.FData, 0, cpAData.FData, 0, Math.Min(AData.FData.Length, cpAData.FData.Length));
|
||||
var MessageName = $"{AData.FIdxChn}/{AData.FIdentifier}";
|
||||
if (RealTimeMessages.TryGetValue(MessageName, out var v))
|
||||
{
|
||||
double Signalre = double.NaN;
|
||||
var re = CAN.GetSignalValue(ref cpAData, item.MessageName, item.SignalName, ref Signalre);
|
||||
//Debug.Assert(re == 0);
|
||||
var SinalName = $"{AData.FIdxChn}/{item.MessageName}/{item.SignalName}";
|
||||
RealTimeSignals[SinalName] = Signalre;
|
||||
oldbytes = v.FData;
|
||||
}
|
||||
RealTimeMessages[MessageName] = cpAData;
|
||||
|
||||
//记录至DBC数据库
|
||||
var dbcfind = DBCParse.MsgDatabase[AData.FIdxChn].FirstOrDefault(s => s.msg_id == cpAData.FIdentifier);
|
||||
if (dbcfind != null)
|
||||
{
|
||||
Array.Copy(cpAData.FData, 0, dbcfind.ACANFD.FData, 0, Math.Min(cpAData.FData.Length, dbcfind.ACANFD.FData.Length));
|
||||
dbcfind.ACANFD.FTimeUS = cpAData.FTimeUS;
|
||||
dbcfind.ACANFD.FIsFD = cpAData.FIsFD;
|
||||
dbcfind.ACANFD.FIsBRS = cpAData.FIsBRS;
|
||||
dbcfind.ACANFD.FIsESI = cpAData.FIsESI;
|
||||
dbcfind.ACANFD.FIsError = cpAData.FIsError;
|
||||
dbcfind.ACANFD.FIsRemote = cpAData.FIsRemote;
|
||||
dbcfind.ACANFD.FIsExt = cpAData.FIsExt;
|
||||
}
|
||||
|
||||
if (oldbytes is not null) ArrayPool<byte>.Shared.Return(oldbytes);
|
||||
|
||||
var id = AData.FIdentifier;
|
||||
var ch = AData.FIdxChn;
|
||||
|
||||
var configList = _systemConfig.ConfigurationList;
|
||||
if (configList == null) return;
|
||||
|
||||
var canSignalList = configList.Where(s => s.MessageID == id && s.Channel == ch).ToArray();
|
||||
|
||||
if (canSignalList.Length > 0)
|
||||
{
|
||||
// DBC 中查不到该报文时静默跳过(配置与 DBC 不一致不应导致进程崩溃)
|
||||
var find = DBCParse.MsgDatabase[ch].FirstOrDefault(s => s.msg_id == id);
|
||||
if (find == null) return;
|
||||
foreach (var item in canSignalList)
|
||||
{
|
||||
double Signalre = double.NaN;
|
||||
var re = CAN.GetSignalValue(ref cpAData, item.MessageName, item.SignalName, ref Signalre);
|
||||
//Debug.Assert(re == 0);
|
||||
var SinalName = $"{AData.FIdxChn}/{item.MessageName}/{item.SignalName}";
|
||||
RealTimeSignals[SinalName] = Signalre;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 原生回调内绝不能向外抛异常:跨原生边界即进程闪退。
|
||||
LoggerHelper.Error($"CANMonitoringService.MonitorEvent 回调异常(已隔离):{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止监测:从广播器注销并清理实时数据。
|
||||
/// 停止监测:注销原生报文回调、从广播器注销并清理实时数据。
|
||||
/// <para>必须先反注册回调再释放实例,否则原生线程仍会回调到已停止的工位。</para>
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
IsStopped = true;
|
||||
if (IsRegistered)
|
||||
{
|
||||
CAN.UnRegisterListener(MonitorEvent);
|
||||
CANSignalBroadcaster.UnregisterService(this);
|
||||
IsRegistered = false;
|
||||
}
|
||||
|
||||
@@ -150,20 +150,28 @@ namespace UIShare.GlobalVariable
|
||||
string methodName = BuildMethodName((uint)cfg.MessageID, signalName);
|
||||
|
||||
// 广播信号值到对应作用域
|
||||
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
|
||||
// 逐信号隔离:单个信号广播/报警失败不应中断整个广播循环
|
||||
try
|
||||
{
|
||||
Scope = scopeName,
|
||||
HardwareFingerprint = fingerprint,
|
||||
MethodName = methodName,
|
||||
Value = kvp.Value,
|
||||
Time = DateTime.Now
|
||||
});
|
||||
_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 != "未报警")
|
||||
// 报警检查(使用该作用域自己的配置)
|
||||
string alarmStatus = ValueLimitAlarmHelper.CheckAlarm(fingerprint, methodName, kvp.Value, scopeConfig);
|
||||
if (!string.IsNullOrEmpty(alarmStatus) && alarmStatus != "未报警")
|
||||
{
|
||||
_eventAggregator.GetEvent<AlarmEvent>().Publish((scopeName, canFingerprint, alarmStatus));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_eventAggregator.GetEvent<AlarmEvent>().Publish((scopeName, canFingerprint, alarmStatus));
|
||||
Logger.LoggerHelper.Error($"CANSignalBroadcaster 广播信号异常(作用域 {scopeName} {methodName},已隔离):{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,39 @@ namespace UIShare.GlobalVariable
|
||||
_CANMonitoringService=new CANMonitoringService(_systemConfig);
|
||||
// 用 SystemConfig.Title 作为作用域唯一标识,无需反查 ConfigDic
|
||||
_scopeName = _systemConfig.Title;
|
||||
AcquireCanScope();
|
||||
InitDevices();
|
||||
InitCAN();
|
||||
}
|
||||
|
||||
/// <summary>本工位(作用域)是否已登记 CAN 卡引用,保证 Acquire/Release 成对且幂等</summary>
|
||||
private bool _canScopeAcquired;
|
||||
|
||||
/// <summary>
|
||||
/// 登记本工位对 CAN 卡的引用。
|
||||
/// <para>CAN 卡是一拖多共享硬件,与 HardwarePool 中的设备一样需要按作用域引用计数。</para>
|
||||
/// </summary>
|
||||
private void AcquireCanScope()
|
||||
{
|
||||
if (_canScopeAcquired) return;
|
||||
CAN.AcquireScope(_scopeName);
|
||||
_canScopeAcquired = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销本工位对 CAN 卡的引用。
|
||||
/// <para>
|
||||
/// 只有最后一个工位释放时才真正停止循环报文、断开总线并释放 TSMaster 驱动;
|
||||
/// 仍有其他工位在用时只做引用减一,避免误伤其他工位。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private void ReleaseCanScope()
|
||||
{
|
||||
if (!_canScopeAcquired) return;
|
||||
_canScopeAcquired = false;
|
||||
CAN.ReleaseScope(_scopeName);
|
||||
}
|
||||
|
||||
public void InitCAN()
|
||||
{
|
||||
var re1 = _CANMonitoringService.Init();
|
||||
@@ -112,6 +141,13 @@ namespace UIShare.GlobalVariable
|
||||
{
|
||||
if (config == null || !config.IsEnabled) continue;
|
||||
|
||||
// CAN 卡由 ConnectCanAsync 独立管理(静态 TSMaster 驱动 + 作用域引用计数),
|
||||
// 不参与 HardwarePool 的设备实例池,这里直接跳过避免误报"未知设备类型"
|
||||
if (string.Equals(config.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.DeviceType) ||
|
||||
!_deviceTypeMap.TryGetValue(config.DeviceType, out var deviceType))
|
||||
{
|
||||
@@ -314,11 +350,18 @@ namespace UIShare.GlobalVariable
|
||||
// 先停止健康监控,避免重连定时器与关闭操作冲突
|
||||
StopHealthMonitor();
|
||||
|
||||
// CAN 卡按作用域引用计数释放:仅最后一个工位关闭时才停循环报文、断总线并释放驱动
|
||||
ReleaseCanScope();
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
if (DeviceMap.Count == 0 ) return;
|
||||
if (DeviceMap.Count == 0)
|
||||
{
|
||||
LoggerHelper.Info("所有设备已执行关闭操作。");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var kvp in DeviceMap)
|
||||
{
|
||||
@@ -331,7 +374,6 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
}
|
||||
|
||||
CAN.DisConnect();
|
||||
await Task.WhenAll(tasks);
|
||||
LoggerHelper.Info("所有设备已执行关闭操作。");
|
||||
}
|
||||
@@ -453,9 +495,16 @@ namespace UIShare.GlobalVariable
|
||||
|
||||
try
|
||||
{
|
||||
// 重新连接时恢复本工位对 CAN 卡的引用(之前可能被主动关闭过)
|
||||
AcquireCanScope();
|
||||
// 驱动若曾被最后一个工位释放,这里重新初始化并把报文回调挂回原生库
|
||||
InitCAN();
|
||||
|
||||
bool ok = await Task.Run(() =>
|
||||
{
|
||||
// 自动加载 DBC 文件
|
||||
// 一拖多共享 CAN 卡:同一通道 + 同一文件只加载一次,
|
||||
// 其他工位已经加载过时直接跳过,避免重复下发并重复追加 DBCParse 报文库
|
||||
if (_systemConfig?.DBCAutoLoadList != null)
|
||||
{
|
||||
foreach (var item in _systemConfig.DBCAutoLoadList)
|
||||
@@ -474,11 +523,22 @@ namespace UIShare.GlobalVariable
|
||||
LoggerHelper.Warn($"CAN 通道 {item.DBCChannel} 自动加载 DBC 失败:文件不存在 [{item.DBCFilePath}]");
|
||||
continue;
|
||||
}
|
||||
bool loadOk = CAN.LoadDBC(item.DBCFilePath,new int[] { item.DBCChannel },out _ )==0;
|
||||
|
||||
var dbcChannels = new int[] { item.DBCChannel };
|
||||
bool alreadyLoaded = CAN.IsDbcLoaded(item.DBCFilePath, dbcChannels);
|
||||
bool loadOk = CAN.LoadDBCOnce(item.DBCFilePath, dbcChannels, out _) == 0;
|
||||
if (loadOk)
|
||||
{
|
||||
LoggerHelper.Info($"CAN 通道 {item.DBCChannel} 已自动加载 DBC:{item.DBCFilePath}");
|
||||
// 发布 DBC 加载完成事件,通知监控系统刷新信号列表
|
||||
if (alreadyLoaded)
|
||||
{
|
||||
LoggerHelper.Info($"CAN 通道 {item.DBCChannel} DBC 已由其他工位加载,跳过重复加载:{item.DBCFilePath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerHelper.Info($"CAN 通道 {item.DBCChannel} 已自动加载 DBC:{item.DBCFilePath}");
|
||||
}
|
||||
|
||||
// 即使跳过加载,也要通知本工位监控系统刷新信号列表
|
||||
_eventAggregator.GetEvent<DBCLoadedEvent>().Publish(new DBCLoadedArgs
|
||||
{
|
||||
Channel = (uint)item.DBCChannel,
|
||||
@@ -519,11 +579,22 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭 CAN 卡
|
||||
/// 关闭 CAN 卡:按作用域引用计数释放。
|
||||
/// <para>
|
||||
/// CAN 卡为一拖多共享硬件,这里只注销本工位的引用;
|
||||
/// 仅当最后一个工位释放时,<see cref="CAN.ReleaseScope"/> 才停止循环报文、断开总线并释放驱动。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private void CloseCan()
|
||||
{
|
||||
CAN.DisConnect();
|
||||
try
|
||||
{
|
||||
ReleaseCanScope();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"关闭 CAN 卡异常(已隔离):{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, Type> BuildDeviceTypeMap()
|
||||
@@ -612,6 +683,9 @@ namespace UIShare.GlobalVariable
|
||||
// 停止 CAN 信号监测服务
|
||||
_CANMonitoringService?.Stop();
|
||||
|
||||
// CAN 卡按作用域引用计数释放:最后一个工位销毁时才停循环报文、断总线并释放驱动
|
||||
ReleaseCanScope();
|
||||
|
||||
if (string.IsNullOrEmpty(_scopeName)) return;
|
||||
|
||||
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用
|
||||
|
||||
Reference in New Issue
Block a user