周立功的CAN /FD实现

This commit is contained in:
2026-02-06 12:34:34 +08:00
parent 2e8ad1cffa
commit 74338fdb3a
13 changed files with 4260 additions and 310 deletions

View File

@@ -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>

View File

@@ -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;
}
}
}
}