周立功的CAN /FD实现
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
using CapMachine.Model.CANLIN;
|
||||
using CapMachine.Wpf.CanDrive;
|
||||
using CapMachine.Wpf.CanDrive.ZlgCan;
|
||||
using CapMachine.Wpf.Dtos;
|
||||
using ImTools;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CapMachine.Wpf.Services
|
||||
{
|
||||
@@ -109,6 +112,12 @@ namespace CapMachine.Wpf.Services
|
||||
/// </summary>
|
||||
public List<CanCmdData> CmdData { get; } = new List<CanCmdData>();
|
||||
|
||||
private readonly object _scheduleLock = new object();
|
||||
private List<(string MsgName, int CycleMs, int OrderSend, int SchTabIndex)> _scheduleItems = new List<(string, int, int, int)>();
|
||||
private CancellationTokenSource? _scheduleCts;
|
||||
private Task? _scheduleTask;
|
||||
private bool _scheduleUseConfigItems;
|
||||
|
||||
private CanCmdData? SpeedCanCmdData { get; set; }
|
||||
|
||||
private uint _deviceIndex = 0;
|
||||
@@ -224,6 +233,12 @@ namespace CapMachine.Wpf.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
// Close 语义:关闭时必须停止循环发送与循环接收。
|
||||
// - 循环发送:停止软件调度,并关闭事件驱动发送标志。
|
||||
// - 循环接收:Driver.Close 内部会 StopReceiveLoop。
|
||||
StopSchedule();
|
||||
IsCycleSend = false;
|
||||
|
||||
Driver.Close();
|
||||
}
|
||||
finally
|
||||
@@ -233,6 +248,178 @@ namespace CapMachine.Wpf.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void SetScheduleConfigs(IEnumerable<CANScheduleConfigDto> configs)
|
||||
{
|
||||
var list = configs?.Where(a => !string.IsNullOrWhiteSpace(a.MsgName)).ToList() ?? new List<CANScheduleConfigDto>();
|
||||
lock (_scheduleLock)
|
||||
{
|
||||
_scheduleItems = list
|
||||
.Select(a => (a.MsgName!.Trim(), Math.Max(1, a.Cycle), a.OrderSend, a.SchTabIndex))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetScheduleConfigs(IEnumerable<CANFdScheduleConfigDto> configs)
|
||||
{
|
||||
var list = configs?.Where(a => !string.IsNullOrWhiteSpace(a.MsgName)).ToList() ?? new List<CANFdScheduleConfigDto>();
|
||||
lock (_scheduleLock)
|
||||
{
|
||||
_scheduleItems = list
|
||||
.Select(a => (a.MsgName!.Trim(), Math.Max(1, a.Cycle), a.OrderSend, a.SchTabIndex))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartSchedule()
|
||||
{
|
||||
if (!OpenState)
|
||||
{
|
||||
throw new InvalidOperationException("设备未连接,无法启动调度表。");
|
||||
}
|
||||
|
||||
List<(string MsgName, int CycleMs, int OrderSend, int SchTabIndex)> items;
|
||||
lock (_scheduleLock)
|
||||
{
|
||||
items = _scheduleItems.ToList();
|
||||
}
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("调度表为空,无法启动调度表。");
|
||||
}
|
||||
|
||||
_scheduleUseConfigItems = true;
|
||||
StartSoftwareScheduler(items);
|
||||
}
|
||||
|
||||
public void StartPrecisionCycleSend(int cycleMs)
|
||||
{
|
||||
if (!OpenState)
|
||||
{
|
||||
throw new InvalidOperationException("设备未连接,无法启动循环发送。");
|
||||
}
|
||||
|
||||
var ms = Math.Max(1, cycleMs);
|
||||
var msgNames = CmdData.Where(a => !string.IsNullOrWhiteSpace(a.MsgName)).Select(a => a.MsgName!).Distinct(StringComparer.Ordinal).ToList();
|
||||
if (msgNames.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("CmdData 为空,无法启动循环发送。");
|
||||
}
|
||||
|
||||
var items = msgNames.Select(n => (n, ms, 1, 0)).ToList();
|
||||
_scheduleUseConfigItems = false;
|
||||
StartSoftwareScheduler(items);
|
||||
}
|
||||
|
||||
public void StopSchedule()
|
||||
{
|
||||
CancellationTokenSource? cts;
|
||||
Task? task;
|
||||
lock (_scheduleLock)
|
||||
{
|
||||
cts = _scheduleCts;
|
||||
task = _scheduleTask;
|
||||
_scheduleCts = null;
|
||||
_scheduleTask = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cts?.Cancel();
|
||||
if (task != null)
|
||||
{
|
||||
task.Wait(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
cts?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartSoftwareScheduler(List<(string MsgName, int CycleMs, int OrderSend, int SchTabIndex)> items)
|
||||
{
|
||||
StopSchedule();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
lock (_scheduleLock)
|
||||
{
|
||||
_scheduleCts = cts;
|
||||
}
|
||||
|
||||
// 统一:软件调度开启后,等同“循环发送开启”
|
||||
IsCycleSend = true;
|
||||
|
||||
_scheduleTask = Task.Run(async () =>
|
||||
{
|
||||
var token = cts.Token;
|
||||
|
||||
// next due time for each msg
|
||||
var now = DateTime.UtcNow;
|
||||
var due = new Dictionary<string, DateTime>(StringComparer.Ordinal);
|
||||
var cycle = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
var order = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
foreach (var it in items)
|
||||
{
|
||||
if (!due.ContainsKey(it.MsgName))
|
||||
{
|
||||
due[it.MsgName] = now;
|
||||
cycle[it.MsgName] = Math.Max(1, it.CycleMs);
|
||||
order[it.MsgName] = it.OrderSend;
|
||||
}
|
||||
}
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
if (!OpenState)
|
||||
{
|
||||
await Task.Delay(50, token).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
var minDue = due.Values.Min();
|
||||
var delay = minDue - utcNow;
|
||||
if (delay > TimeSpan.Zero)
|
||||
{
|
||||
var ms = (int)Math.Min(delay.TotalMilliseconds, 200);
|
||||
await Task.Delay(ms, token).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// due messages
|
||||
var ready = due.Where(kv => kv.Value <= utcNow).Select(kv => kv.Key).ToList();
|
||||
if (ready.Count == 0)
|
||||
{
|
||||
await Task.Delay(1, token).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 顺序/并行:这里只决定同一 tick 内的发送顺序(并行模式仍按字典序依次发)
|
||||
ready.Sort(StringComparer.Ordinal);
|
||||
foreach (var msg in ready)
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
try
|
||||
{
|
||||
Driver.SendOneMsgByCmdData(msg, 0, Mode == ZlgCanMode.Can ? (byte)ZDBC.FT_CAN : (byte)ZDBC.FT_CANFD);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn($"调度表发送失败:{msg},{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
due[msg] = DateTime.UtcNow.AddMilliseconds(cycle[msg]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, cts.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载 DBC。
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,10 @@ using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CapMachine.Wpf.Services
|
||||
{
|
||||
@@ -150,8 +153,165 @@ namespace CapMachine.Wpf.Services
|
||||
/// <param name="path">LDF 路径。</param>
|
||||
public ObservableCollection<LinLdfModel> StartLdf(string path)
|
||||
{
|
||||
_log.Warn("ZLG LIN 当前版本未接入 LDF 解析(项目内仅存在 USB2XXX.dll 的 LDFParser)。");
|
||||
throw new NotSupportedException("ZLG LIN 暂未支持 LDF 解析,请后续提供/确认 ZLG 的 LDF DLL 接口(如 zldf.dll)后再接入。");
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new ArgumentException("LDF 路径为空", nameof(path));
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException($"LDF 文件不存在:{path}", path);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(path, Encoding.UTF8);
|
||||
|
||||
// 去除单行注释,简化解析
|
||||
text = Regex.Replace(text, @"//.*?$", string.Empty, RegexOptions.Multiline);
|
||||
|
||||
var models = ParseLdfFramesAndSignals(text);
|
||||
|
||||
ListLinLdfModel.Clear();
|
||||
foreach (var item in models)
|
||||
{
|
||||
ListLinLdfModel.Add(item);
|
||||
}
|
||||
|
||||
LdfParserState = true;
|
||||
return ListLinLdfModel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error($"ZLG LIN 解析 LDF 失败:{ex.Message}");
|
||||
LdfParserState = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<LinLdfModel> ParseLdfFramesAndSignals(string ldfText)
|
||||
{
|
||||
// 说明:此解析器只用于生成“帧-信号全集池”,不做位宽/缩放等语义解析。
|
||||
// 目标:尽可能容错地从 Frames 区域提取 FrameName 与其包含的 SignalName 列表。
|
||||
|
||||
var framesBlock = TryExtractNamedBlock(ldfText, "Frames");
|
||||
if (string.IsNullOrWhiteSpace(framesBlock))
|
||||
{
|
||||
return new List<LinLdfModel>();
|
||||
}
|
||||
|
||||
var result = new List<LinLdfModel>();
|
||||
var exists = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
// Frame 定义一般形式:FrameName : ... { ... }
|
||||
// 这里以非贪婪匹配提取每个 Frame 的 body
|
||||
var frameRegex = new Regex(@"(?s)(?<name>[A-Za-z_][A-Za-z0-9_]*)\s*:\s*.*?\{(?<body>.*?)\}\s*;?", RegexOptions.Compiled);
|
||||
var sigRegex = new Regex(@"(?m)^\s*(?<sig>[A-Za-z_][A-Za-z0-9_]*)\s*[,;]", RegexOptions.Compiled);
|
||||
|
||||
foreach (Match fm in frameRegex.Matches(framesBlock))
|
||||
{
|
||||
var frameName = fm.Groups["name"].Value;
|
||||
var body = fm.Groups["body"].Value;
|
||||
if (string.IsNullOrWhiteSpace(frameName) || string.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Match sm in sigRegex.Matches(body))
|
||||
{
|
||||
var sigName = sm.Groups["sig"].Value;
|
||||
if (string.IsNullOrWhiteSpace(sigName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 排除明显的关键字(避免误采集)
|
||||
if (IsReservedKeyword(sigName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = $"{frameName}:{sigName}";
|
||||
if (!exists.Add(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(new LinLdfModel
|
||||
{
|
||||
MsgName = frameName,
|
||||
SignalName = sigName,
|
||||
Name = null,
|
||||
SignalDesc = null,
|
||||
SignalUnit = null,
|
||||
IsSeletedInfo = 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string? TryExtractNamedBlock(string text, string blockName)
|
||||
{
|
||||
// 提取形如:blockName { ... } 的块内容(不包含外层大括号)。
|
||||
// 基于括号深度扫描,避免正则在嵌套结构上失效。
|
||||
var idx = CultureInvariantIndexOf(text, blockName);
|
||||
if (idx < 0) return null;
|
||||
|
||||
var braceIdx = text.IndexOf('{', idx);
|
||||
if (braceIdx < 0) return null;
|
||||
|
||||
int depth = 0;
|
||||
for (int i = braceIdx; i < text.Length; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (ch == '{') depth++;
|
||||
else if (ch == '}')
|
||||
{
|
||||
depth--;
|
||||
if (depth == 0)
|
||||
{
|
||||
return text.Substring(braceIdx + 1, i - braceIdx - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int CultureInvariantIndexOf(string text, string value)
|
||||
{
|
||||
return text.IndexOf(value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsReservedKeyword(string token)
|
||||
{
|
||||
// LDF 常见关键字/区块名(用于降低误匹配概率)
|
||||
switch (token)
|
||||
{
|
||||
case "Frames":
|
||||
case "Signals":
|
||||
case "Signal":
|
||||
case "Nodes":
|
||||
case "Master":
|
||||
case "Slaves":
|
||||
case "Diagnostic":
|
||||
case "Diagnostics":
|
||||
case "Checksum":
|
||||
case "Event_triggered_frames":
|
||||
case "Sporadic_frames":
|
||||
case "Schedule_tables":
|
||||
case "Node_attributes":
|
||||
case "Node_composition":
|
||||
case "LIN_protocol":
|
||||
case "LIN_protocol_version":
|
||||
case "LIN_speed":
|
||||
case "Protocol_version":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user