using CapMachine.Model.CANLIN;
using CapMachine.Wpf.LinDrive;
using CapMachine.Wpf.Dtos;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace CapMachine.Wpf.Services
{
///
/// ZLG LIN 驱动服务(共享设备句柄)。
/// 说明:
/// - 该服务不直接持有/管理底层原生句柄,而是复用 中的 Driver(设备级句柄与接收线程均由 Driver 统一管理)。
/// - LIN 的“调度表/循环发送”在当前实现中使用软件精确定时( + )完成;
/// 与 ZLG CAN/CANFD 的硬件 auto_send 不同,LIN 侧没有调用硬件调度表能力。
/// - LDF 解析:此处实现了一个尽量容错的轻量解析器,用于建立“帧/信号 -> 位定义”的运行时索引,供 LIN 收发编解码使用。
/// 线程与绑定:
/// - LIN 接收回调通常来自 Driver 的接收线程;更新 WPF 绑定对象时会切换到 UI 线程(见 )。
/// - CmdData / LDF 索引 / 调度表快照均通过独立锁对象保护,避免 UI 线程与后台线程并发修改引发的竞态。
///
public sealed class ZlgLinDriveService : BindableBase
{
///
/// LIN 信号定义(运行时索引条目)。
/// 说明:目前仅使用 StartBit/BitLength 做 bit 级读写;Factor/Offset/IsSigned 预留用于后续扩展(物理量换算)。
///
private sealed class LinSignalDef
{
public string SignalName { get; set; } = string.Empty;
public int StartBit { get; set; }
public int BitLength { get; set; }
public bool IsSigned { get; set; }
public double Factor { get; set; } = 1;
public double Offset { get; set; }
}
///
/// LIN 帧定义(运行时索引条目)。
/// 说明:
/// - 同时缓存裸 FrameId(0-63)与受保护 PID(含奇偶校验位)。
/// - Signals 字典仅包含在 LDF 的 Frames 里声明且在 Signals 区能找到 bit 定义的信号。
///
private sealed class LinFrameDef
{
public string FrameName { get; set; } = string.Empty;
public byte FrameId { get; set; }
public byte Pid { get; set; }
public int DataLen { get; set; } = 8;
public string? Publisher { get; set; }
public bool IsMasterFrame { get; set; }
public Dictionary Signals { get; } = new Dictionary(StringComparer.Ordinal);
}
///
/// 软件调度项(以 ticks 为最小单位进行精确定时)。
/// 说明:
/// - NextDueTicks/LastWarnTicks 会在调度线程中更新,因此不对外暴露,仅在调度线程内使用。
/// - PeriodTicks 是基于 换算而来,与系统时间无关,抗时间跳变。
///
private sealed class SoftwareScheduleItem
{
public SoftwareScheduleItem(string msgName, long periodTicks)
{
MsgName = msgName;
PeriodTicks = periodTicks;
}
public string MsgName { get; }
public long PeriodTicks { get; }
public long NextDueTicks;
public long LastWarnTicks;
}
private readonly ILogService _log;
private readonly ZlgCanDriveService _zlgCanDriveService;
///
/// LDF 索引锁:保护帧/信号定义索引与 UI 绑定模型映射(_framesByName/_framesByPid/_modelIndex 等)。
///
private readonly object _ldfLock = new object();
///
/// CmdData 锁:保护 的替换与事件订阅关系,避免并发枚举/修改。
///
private readonly object _cmdLock = new object();
///
/// 调度锁:保护调度配置快照与调度线程的启动/停止(_scheduleCts/_scheduleTask/_scheduleRunning)。
///
private readonly object _scheduleLock = new object();
///
/// 帧名 -> 帧定义索引(用于发送:按 MsgName 找到 PID/信号位定义)。
///
private Dictionary _framesByName = new Dictionary(StringComparer.Ordinal);
///
/// PID/ID -> 帧定义索引(用于接收:按 pid/6bit id 回查帧定义并解码信号)。
///
private Dictionary _framesByPid = new Dictionary();
///
/// “帧名+信号名” -> UI 模型索引,用于接收线程快速定位绑定对象并更新实时值。
///
private Dictionary _modelIndex = new Dictionary(StringComparer.Ordinal);
///
/// 未知 PID 的日志节流表:避免接收线程在 LDF 缺失时刷屏。
///
private readonly Dictionary _unknownPidLastLogTicks = new Dictionary();
///
/// 调度取消源(置空表示未运行)。
///
private CancellationTokenSource? _scheduleCts;
///
/// 调度后台任务(LongRunning)。
///
private Task? _scheduleTask;
///
/// 调度运行标记(与 _scheduleCts 配合;主要用于可读性与状态表达)。
///
private bool _scheduleRunning;
///
/// LIN 调度表 DTO 缓存(由 ViewModel/上层写入;启动调度时会取快照)。
///
private List _linScheduleConfigs = new List();
///
/// 当前选中的配置程序(沿用原有 FreeSql 模型)。
///
public CanLinConfigPro? SelectedCanLinConfigPro { get; set; }
private bool _openState;
///
/// LIN 打开状态。
///
public bool OpenState
{
get { return _openState; }
private set { _openState = value; RaisePropertyChanged(); }
}
///
/// 解析 LDF 文本,提取 Frames/Signals/Nodes(Master)信息。
/// 说明:这是一个轻量解析器:
/// - 目标是构建“帧-信号位定义”的索引,并支持 UI 展示;
/// - 不追求覆盖所有 LDF 语法,仅尽量兼容常见格式。
///
/// LDF 原始文本(建议已剔除注释)。
/// Frames 列表、Signals 位定义字典、Master 节点名(可能为空)。
private static (List<(string FrameName, int FrameId, int DataLen, string? Publisher, List Signals)>, Dictionary, string? MasterNodeName) ParseLdf(string ldfText)
{
var master = TryExtractMasterNodeName(ldfText);
var framesBlock = TryExtractNamedBlock(ldfText, "Frames");
var signalsBlock = TryExtractNamedBlock(ldfText, "Signals");
var signals = ParseSignalsBlock(signalsBlock);
var frames = ParseFramesBlock(framesBlock);
return (frames, signals, master);
}
private static string? TryExtractMasterNodeName(string ldfText)
{
var nodesBlock = TryExtractNamedBlock(ldfText, "Nodes");
if (string.IsNullOrWhiteSpace(nodesBlock)) return null;
// 常见格式:Master: MasterName;
var m = Regex.Match(nodesBlock, @"(?im)^\s*Master\s*:\s*(?[A-Za-z_][A-Za-z0-9_]*)\s*;", RegexOptions.Compiled);
if (m.Success)
{
return m.Groups["name"].Value;
}
return null;
}
private static Dictionary ParseSignalsBlock(string? signalsBlock)
{
var dict = new Dictionary(StringComparer.Ordinal);
if (string.IsNullOrWhiteSpace(signalsBlock))
{
return dict;
}
// 兼容常见格式:SigName: 0, 8;
var sigRegex = new Regex(@"(?im)^\s*(?[A-Za-z_][A-Za-z0-9_]*)\s*:\s*(?\d+)\s*,\s*(?\d+)", RegexOptions.Compiled);
foreach (Match m in sigRegex.Matches(signalsBlock))
{
var name = m.Groups["name"].Value;
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
if (!int.TryParse(m.Groups["start"].Value, out var startBit))
{
continue;
}
if (!int.TryParse(m.Groups["len"].Value, out var bitLen))
{
bitLen = 1;
}
if (bitLen <= 0)
{
bitLen = 1;
}
dict[name] = (startBit, bitLen);
}
return dict;
}
private static List<(string FrameName, int FrameId, int DataLen, string? Publisher, List Signals)> ParseFramesBlock(string? framesBlock)
{
var list = new List<(string FrameName, int FrameId, int DataLen, string? Publisher, List Signals)>();
if (string.IsNullOrWhiteSpace(framesBlock))
{
return list;
}
// 常见格式:FrameName : 0x10, Publisher, 8 { SigA, SigB };
var frameRegex = new Regex(@"(?s)(?[A-Za-z_][A-Za-z0-9_]*)\s*:\s*(?0x[0-9A-Fa-f]+|\d+)\s*,\s*(?[A-Za-z_][A-Za-z0-9_]*)\s*,\s*(?\d+)\s*\{(?.*?)\}\s*;?", RegexOptions.Compiled);
var sigRegex = new Regex(@"(?m)^[\s\t]*(?[A-Za-z_][A-Za-z0-9_]*)\s*[,;]", RegexOptions.Compiled);
foreach (Match fm in frameRegex.Matches(framesBlock))
{
var frameName = fm.Groups["name"].Value;
var idText = fm.Groups["id"].Value;
var pub = fm.Groups["pub"].Value;
var lenText = fm.Groups["len"].Value;
var body = fm.Groups["body"].Value;
if (string.IsNullOrWhiteSpace(frameName) || string.IsNullOrWhiteSpace(idText) || string.IsNullOrWhiteSpace(lenText))
{
continue;
}
if (!TryParseIntAuto(idText, out var frameId))
{
continue;
}
if (!int.TryParse(lenText, out var dataLen))
{
dataLen = 8;
}
dataLen = Math.Max(1, Math.Min(8, dataLen));
var sigs = new List();
foreach (Match sm in sigRegex.Matches(body))
{
var sigName = sm.Groups["sig"].Value;
if (string.IsNullOrWhiteSpace(sigName))
{
continue;
}
if (IsReservedKeyword(sigName))
{
continue;
}
sigs.Add(sigName);
}
list.Add((frameName, frameId, dataLen, string.IsNullOrWhiteSpace(pub) ? null : pub, sigs));
}
// 兜底:若正则未匹配到帧(格式差异),退回到只提取 frameName 与 signals
if (list.Count == 0)
{
var fallbackFrameRegex = new Regex(@"(?s)(?[A-Za-z_][A-Za-z0-9_]*)\s*:\s*.*?\{(?.*?)\}\s*;?", RegexOptions.Compiled);
foreach (Match fm in fallbackFrameRegex.Matches(framesBlock))
{
var frameName = fm.Groups["name"].Value;
var body = fm.Groups["body"].Value;
if (string.IsNullOrWhiteSpace(frameName) || string.IsNullOrWhiteSpace(body))
{
continue;
}
var sigs = new List();
foreach (Match sm in sigRegex.Matches(body))
{
var sigName = sm.Groups["sig"].Value;
if (string.IsNullOrWhiteSpace(sigName))
{
continue;
}
if (IsReservedKeyword(sigName))
{
continue;
}
sigs.Add(sigName);
}
list.Add((frameName, 0, 8, null, sigs));
}
}
return list;
}
private static bool TryParseIntAuto(string s, out int value)
{
s = s.Trim();
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
return int.TryParse(s.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out value);
}
return int.TryParse(s, out value);
}
///
/// 基于解析结果构建运行时索引:
/// - _framesByName/_framesByPid:用于发送与接收定位帧定义;
/// - ListLinLdfModel/_modelIndex:用于 UI 展示与实时值更新。
///
/// 帧定义集合。
/// 信号位定义字典。
/// Master 节点名(可能为空)。
private void BuildRuntimeIndex(
List<(string FrameName, int FrameId, int DataLen, string? Publisher, List Signals)> frames,
Dictionary signals,
string? masterNodeName)
{
lock (_ldfLock)
{
_framesByName = new Dictionary(StringComparer.Ordinal);
_framesByPid = new Dictionary();
_unknownPidLastLogTicks.Clear();
foreach (var f in frames)
{
var pid = BuildProtectedId((byte)f.FrameId);
var isMasterFrame = !string.IsNullOrWhiteSpace(masterNodeName) && string.Equals(f.Publisher, masterNodeName, StringComparison.Ordinal);
var frame = new LinFrameDef
{
FrameName = f.FrameName,
FrameId = (byte)f.FrameId,
Pid = pid,
DataLen = f.DataLen,
Publisher = f.Publisher,
IsMasterFrame = isMasterFrame,
};
foreach (var sigName in f.Signals)
{
if (!signals.TryGetValue(sigName, out var def))
{
continue;
}
frame.Signals[sigName] = new LinSignalDef
{
SignalName = sigName,
StartBit = def.StartBit,
BitLength = def.BitLen,
IsSigned = false,
Factor = 1,
Offset = 0,
};
}
_framesByName[frame.FrameName] = frame;
// 兼容:
// - 有些设备/固件回传 pid 为“受保护 PID(含奇偶校验位)”
// - 有些回传 pid 为“裸 ID(0-63,仅 6bit)”
// 因此两种 key 都建立索引,确保接收解码稳定。
_framesByPid[frame.Pid] = frame;
_framesByPid[(byte)(frame.FrameId & 0x3F)] = frame;
}
ListLinLdfModel.Clear();
_modelIndex = new Dictionary(StringComparer.Ordinal);
foreach (var frame in _framesByName.Values.OrderBy(a => a.FrameName, StringComparer.Ordinal))
{
foreach (var sig in frame.Signals.Values.OrderBy(a => a.SignalName, StringComparer.Ordinal))
{
var model = new LinLdfModel
{
MsgName = frame.FrameName,
SignalName = sig.SignalName,
Publisher = frame.Publisher,
IsMasterFrame = frame.IsMasterFrame ? "是" : "否",
SignalDesc = null,
SignalUnit = null,
Name = null,
IsSeletedInfo = 0,
};
ListLinLdfModel.Add(model);
_modelIndex[BuildMsgSigKey(frame.FrameName, sig.SignalName)] = model;
}
}
}
}
///
/// 处理一帧 LIN 接收数据:按 pid 匹配帧定义,按信号位定义解码并更新 UI 实时值。
/// 说明:
/// - 该方法可能在 Driver 的接收线程中被调用;
/// - 不做阻塞操作(如 IO/长时间锁持有),避免拖慢接收线程。
///
/// 原始 LIN 接收帧。
private void HandleLinFrame(CanDrive.ZlgCan.ZlgLinRxFrame frame)
{
if (!LdfParserState)
{
return;
}
LinFrameDef? def;
lock (_ldfLock)
{
if (!_framesByPid.TryGetValue(frame.Pid, out def))
{
// pid 可能是 6bit ID 或 8bit PID,做回退匹配
var id6 = (byte)(frame.Pid & 0x3F);
if (!_framesByPid.TryGetValue(id6, out def))
{
var pidProtected = BuildProtectedId(id6);
_framesByPid.TryGetValue(pidProtected, out def);
}
if (def == null)
{
// 节流日志:每个 pid 至少间隔 5s 输出一次,避免刷屏
var now = Environment.TickCount64;
if (!_unknownPidLastLogTicks.TryGetValue(frame.Pid, out var last) || now - last >= 5000)
{
_unknownPidLastLogTicks[frame.Pid] = now;
_log.Debug($"ZLG LIN 收到未知 PID=0x{frame.Pid:X2}(id6=0x{id6:X2}),LDF 中未找到对应帧,已忽略。DataLen={frame.Data?.Length ?? 0}。");
}
return;
}
}
}
if (def == null)
{
return;
}
var bytes = frame.Data;
foreach (var sig in def.Signals.Values)
{
// LIN 信号位序按 Intel/Little-Endian 方式读取(与现有 CmdData 写入一致)。
var raw = ReadBitsIntel(bytes, sig.StartBit, sig.BitLength, sig.IsSigned);
UpdateSignalRtValue(def.FrameName, sig.SignalName, raw.ToString());
}
}
private void UpdateSignalRtValue(string msgName, string signalName, string value)
{
var key = BuildMsgSigKey(msgName, signalName);
LinLdfModel? model;
lock (_ldfLock)
{
_modelIndex.TryGetValue(key, out model);
}
if (model == null)
{
return;
}
// WPF:从接收线程更新 UI 绑定对象时,尽量切到 UI 线程。
var app = Application.Current;
if (app != null && !app.Dispatcher.CheckAccess())
{
app.Dispatcher.BeginInvoke(new Action(() => model.SignalRtValue = value));
return;
}
model.SignalRtValue = value;
}
///
/// 发送一帧(按当前 CmdData 聚合编码)。
///
/// 帧名。
public void SendOneFrameByMsgName(string msgName)
{
if (!OpenState)
{
throw new InvalidOperationException("设备未连接,无法发送。");
}
LinFrameDef? def;
lock (_ldfLock)
{
if (!_framesByName.TryGetValue(msgName, out def))
{
throw new InvalidOperationException($"未找到帧定义:{msgName}。请先解析 LDF。");
}
}
if (def == null)
{
return;
}
var data = new byte[8];
List cmds;
lock (_cmdLock)
{
cmds = CmdData.Where(a => string.Equals(a.MsgName, msgName, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(a.SignalName)).ToList();
}
foreach (var cmd in cmds)
{
if (cmd.SignalName == null)
{
continue;
}
if (!def.Signals.TryGetValue(cmd.SignalName, out var sigDef))
{
continue;
}
var raw = ConvertPhysicalToRaw(cmd.SignalCmdValue, sigDef);
WriteBitsIntel(data, sigDef.StartBit, sigDef.BitLength, raw);
}
// LIN 发送:复用 CAN Service 中的 Driver 发送接口。
// 说明:
// - channelIndex 当前固定为 0(项目约定)。
// - dataLen 取 LDF 帧定义中的长度,避免发送多余填充字节。
_zlgCanDriveService.Driver.TransmitLin(0, def.Pid, data.AsSpan(0, def.DataLen), 0);
}
private static ulong ConvertPhysicalToRaw(double physical, LinSignalDef sigDef)
{
var v = (physical - sigDef.Offset) / (Math.Abs(sigDef.Factor) < double.Epsilon ? 1 : sigDef.Factor);
if (sigDef.IsSigned)
{
var max = (1L << (sigDef.BitLength - 1)) - 1;
var min = -(1L << (sigDef.BitLength - 1));
var val = (long)Math.Round(v);
if (val > max) val = max;
if (val < min) val = min;
unchecked
{
return (ulong)val;
}
}
var umax = sigDef.BitLength >= 64 ? ulong.MaxValue : ((1UL << sigDef.BitLength) - 1UL);
var uval = (ulong)Math.Max(0, Math.Round(v));
if (uval > umax) uval = umax;
return uval;
}
private static void WriteBitsIntel(byte[] data, int startBit, int bitLen, ulong value)
{
if (data == null || data.Length == 0) return;
if (bitLen <= 0) return;
for (int i = 0; i < bitLen; i++)
{
var bitPos = startBit + i;
var byteIndex = bitPos / 8;
var bitIndex = bitPos % 8;
if (byteIndex < 0 || byteIndex >= data.Length) break;
var mask = (byte)(1 << bitIndex);
if (((value >> i) & 0x1) == 1)
{
data[byteIndex] |= mask;
}
else
{
data[byteIndex] &= (byte)~mask;
}
}
}
private static long ReadBitsIntel(byte[] data, int startBit, int bitLen, bool isSigned)
{
if (data == null || data.Length == 0) return 0;
if (bitLen <= 0) return 0;
ulong raw = 0;
for (int i = 0; i < bitLen; i++)
{
var bitPos = startBit + i;
var byteIndex = bitPos / 8;
var bitIndex = bitPos % 8;
if (byteIndex < 0 || byteIndex >= data.Length) break;
var bit = (data[byteIndex] >> bitIndex) & 0x1;
raw |= ((ulong)bit << i);
}
if (!isSigned)
{
return (long)raw;
}
if (bitLen >= 64)
{
unchecked
{
return (long)raw;
}
}
var signBit = 1UL << (bitLen - 1);
if ((raw & signBit) == 0)
{
return (long)raw;
}
var mask = (1UL << bitLen) - 1;
var twos = (~raw + 1) & mask;
return -(long)twos;
}
private static byte BuildProtectedId(byte frameId)
{
var id = (byte)(frameId & 0x3F);
var id0 = (id >> 0) & 1;
var id1 = (id >> 1) & 1;
var id2 = (id >> 2) & 1;
var id3 = (id >> 3) & 1;
var id4 = (id >> 4) & 1;
var id5 = (id >> 5) & 1;
var p0 = (id0 ^ id1 ^ id2 ^ id4) & 1;
var p1 = (~(id1 ^ id3 ^ id4 ^ id5)) & 1;
return (byte)(id | (p0 << 6) | (p1 << 7));
}
private static string BuildMsgSigKey(string msgName, string signalName)
{
return $"{msgName}\0{signalName}";
}
///
/// 启动软件精确定时循环发送(按统一周期发送当前 CmdData 中出现的帧)。
///
/// 周期(ms)。
public void StartPrecisionCycleSend(int cycleMs)
{
if (!OpenState)
{
throw new InvalidOperationException("设备未连接,无法启动循环发送。");
}
var ms = Math.Max(1, cycleMs);
List msgNames;
lock (_cmdLock)
{
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 sw = Stopwatch.StartNew();
var freq = Stopwatch.Frequency;
var periodTicks = (long)(ms * (double)freq / 1000);
if (periodTicks <= 0) periodTicks = 1;
var items = msgNames.Select(n => new SoftwareScheduleItem(n, periodTicks)).ToArray();
var now = sw.ElapsedTicks;
for (int i = 0; i < items.Length; i++)
{
items[i].NextDueTicks = now + items[i].PeriodTicks;
}
lock (_scheduleLock)
{
StopSchedule();
_scheduleRunning = true;
_scheduleCts = new CancellationTokenSource();
var token = _scheduleCts.Token;
_scheduleTask = Task.Factory.StartNew(
() => RunSoftwareScheduler(sw, items, token, freq),
token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
}
///
/// 停止循环发送。
///
public void StopSchedule()
{
lock (_scheduleLock)
{
_scheduleRunning = false;
try
{
_scheduleCts?.Cancel();
}
catch
{
// ignore
}
_scheduleCts = null;
_scheduleTask = null;
}
}
private void RunSoftwareScheduler(Stopwatch sw, SoftwareScheduleItem[] items, CancellationToken token, long frequency)
{
// 说明:
// - 采用“下一次到期 ticks”驱动调度,避免 Thread.Sleep 累积误差。
// - waitMs>5 时先 WaitOne 较长时间以降低 CPU;临近触发时用 1ms 等待+SpinWait 降低抖动。
// - 对单帧发送失败做节流告警,避免循环中刷屏。
var warnIntervalTicks = frequency * 10;
var spin = new SpinWait();
while (!token.IsCancellationRequested)
{
if (!OpenState)
{
if (token.WaitHandle.WaitOne(50))
{
break;
}
continue;
}
var nowTicks = sw.ElapsedTicks;
long minDue = long.MaxValue;
for (var i = 0; i < items.Length; i++)
{
var d = items[i].NextDueTicks;
if (d < minDue)
{
minDue = d;
}
}
var waitTicks = minDue - nowTicks;
if (waitTicks > 0)
{
var waitMs = (int)(waitTicks * 1000 / frequency);
if (waitMs > 5)
{
var ms = Math.Min(waitMs - 1, 200);
if (token.WaitHandle.WaitOne(ms))
{
break;
}
continue;
}
if (waitMs > 1)
{
if (token.WaitHandle.WaitOne(1))
{
break;
}
continue;
}
spin.SpinOnce();
continue;
}
nowTicks = sw.ElapsedTicks;
for (var i = 0; i < items.Length; i++)
{
var it = items[i];
if (it.NextDueTicks > nowTicks)
{
continue;
}
try
{
SendOneFrameByMsgName(it.MsgName);
}
catch (Exception ex)
{
var t = sw.ElapsedTicks;
if (t - it.LastWarnTicks >= warnIntervalTicks)
{
it.LastWarnTicks = t;
_log.Warn($"LIN 软件循环发送失败:{it.MsgName},{ex.Message}");
}
}
finally
{
// 以“上一次计划时间 + 周期”推进 next,保证周期稳定;
// 若调度发生严重滞后(next 已过期),则直接以当前时间重新对齐。
var next = it.NextDueTicks + it.PeriodTicks;
if (next <= nowTicks)
{
next = nowTicks + it.PeriodTicks;
}
it.NextDueTicks = next;
}
}
}
}
private bool _ldfParserState;
///
/// LDF 解析状态。
/// 说明:
/// - 当 成功解析并建立运行时索引后为 true;
/// - 若未解析/解析失败/关闭设备则为 false。
/// - LIN 收发编解码依赖该状态(未解析时接收帧会被忽略,发送会在找不到帧定义时抛出异常)。
///
public bool LdfParserState
{
get { return _ldfParserState; }
private set { _ldfParserState = value; RaisePropertyChanged(); }
}
///
/// LDF 消息集合(UI 绑定)。
/// 说明:
/// - 该集合由 解析并构建,用于 UI 展示“帧-信号”全集与实时值;
/// - 未解析 LDF 时保持为空。
///
public ObservableCollection ListLinLdfModel { get; private set; } = new ObservableCollection();
///
/// 要发送的 LIN 指令集合(来源于配置程序+读写设置)。
///
public List CmdData { get; } = new List();
///
/// 是否启用调度发送(与 UI 的调度表使能语义对齐)。
///
public bool SchEnable
{
get { return _zlgCanDriveService.Driver.SchEnable; }
set { _zlgCanDriveService.Driver.SchEnable = value; RaisePropertyChanged(); }
}
///
/// 是否启用事件驱动发送。
///
public bool IsCycleSend
{
get { return _zlgCanDriveService.Driver.IsCycleSend; }
set { _zlgCanDriveService.Driver.IsCycleSend = value; RaisePropertyChanged(); }
}
///
/// 构造。
///
/// 共享 CAN 服务。
/// 日志服务。
public ZlgLinDriveService(ZlgCanDriveService zlgCanDriveService, ILogService logService)
{
_zlgCanDriveService = zlgCanDriveService;
_log = logService;
// 透传 CAN Service/Driver 的状态指示灯属性(用于 UI 绑定)。
_zlgCanDriveService.PropertyChanged += (_, __) =>
{
RaisePropertyChanged(nameof(IsCycleRevice));
RaisePropertyChanged(nameof(IsSendOk));
RaisePropertyChanged(nameof(IsReviceOk));
};
// 订阅 LIN 帧接收事件:回调线程由 Driver 的接收线程决定(通常为后台线程)。
_zlgCanDriveService.Driver.LinFrameReceived += frame =>
{
try
{
HandleLinFrame(frame);
}
catch (Exception ex)
{
_log.Warn($"ZLG LIN 接收处理异常:{ex.Message}");
}
};
}
///
/// 是否正在循环接收(透传底层 Driver 接收线程状态)。
///
public bool IsCycleRevice
{
get { return _zlgCanDriveService.IsCycleRevice; }
}
///
/// 发送状态指示(短时间保持 true 后回落,透传 Driver)。
///
public bool IsSendOk
{
get { return _zlgCanDriveService.IsSendOk; }
}
///
/// 接收状态指示(短时间保持 true 后回落,透传 Driver)。
///
public bool IsReviceOk
{
get { return _zlgCanDriveService.IsReviceOk; }
}
///
/// 开关接收线程(共享设备接收线程)。
///
/// 是否启用。
public void SetReceiveEnabled(bool enable)
{
_zlgCanDriveService.SetReceiveEnabled(enable);
RaisePropertyChanged(nameof(IsCycleRevice));
}
///
/// 下发调度表配置(系统层调度表,支持每帧不同周期)。
///
/// 调度表配置集合。
public void SetScheduleConfigs(List configs)
{
lock (_scheduleLock)
{
// 仅缓存配置快照:真正启动调度时会再取一次快照,避免 UI 边编辑边启动导致的并发问题。
_linScheduleConfigs = configs ?? new List();
}
}
///
/// 启动软件调度(按调度表中每帧的 Cycle 分别调度发送)。
/// 说明:ZLG LIN 当前未接入硬件调度表/auto_send,因此用软件精确定时实现。
///
public void StartSchedule()
{
if (!OpenState)
{
throw new InvalidOperationException("设备未连接,无法启动调度发送。");
}
List snapshot;
lock (_scheduleLock)
{
snapshot = _linScheduleConfigs?.ToList() ?? new List();
}
// 仅使用“被激活的调度表”中的“被选中消息”
var msgItems = snapshot
.Where(a => a != null)
.Where(a => a.IsActive)
.Where(a => a.IsMsgActived)
.Where(a => !string.IsNullOrWhiteSpace(a.MsgName))
.GroupBy(a => a.MsgName!, StringComparer.Ordinal)
.Select(g =>
{
// 同一 MsgName 若出现多个周期,以最小周期为准(更安全)
var cycle = g.Select(x => x.Cycle).Where(x => x > 0).DefaultIfEmpty(100).Min();
return (MsgName: g.Key, Cycle: cycle);
})
.ToList();
if (msgItems.Count == 0)
{
throw new InvalidOperationException("调度表为空或未选择任何消息帧,无法启动调度。");
}
var sw = Stopwatch.StartNew();
var freq = Stopwatch.Frequency;
var items = msgItems
.Select(x =>
{
var ms = Math.Max(1, x.Cycle);
var ticks = (long)(ms * (double)freq / 1000);
if (ticks <= 0) ticks = 1;
return new SoftwareScheduleItem(x.MsgName, ticks);
})
.ToArray();
var now = sw.ElapsedTicks;
for (int i = 0; i < items.Length; i++)
{
items[i].NextDueTicks = now + items[i].PeriodTicks;
}
lock (_scheduleLock)
{
StopSchedule();
_scheduleRunning = true;
_scheduleCts = new CancellationTokenSource();
var token = _scheduleCts.Token;
_scheduleTask = Task.Factory.StartNew(
() => RunSoftwareScheduler(sw, items, token, freq),
token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
}
///
/// 设置并订阅要发送的指令集合(事件驱动)。
///
/// 指令集合。
public void LoadCmdDataToDrive(IEnumerable cmdData)
{
lock (_cmdLock)
{
if (CmdData.Count > 0)
{
foreach (var cmd in CmdData)
{
// 先退订旧对象事件,避免 UI/服务重复绑定导致“同一变化触发多次发送”。
cmd.LinCmdDataChangedHandler -= CmdData_LinCmdDataChangedHandler;
}
}
CmdData.Clear();
if (cmdData != null)
{
CmdData.AddRange(cmdData);
}
foreach (var cmd in CmdData)
{
// 订阅变化事件:用于“值变化增量发送”(由 + 共同控制)。
cmd.LinCmdDataChangedHandler += CmdData_LinCmdDataChangedHandler;
}
}
}
private void CmdData_LinCmdDataChangedHandler(object? sender, string msgName)
{
if (!OpenState)
{
return;
}
if (!IsCycleSend || !SchEnable)
{
// 语义:
// - IsCycleSend:是否允许“事件驱动发送”;
// - SchEnable:是否启用发送(与 UI 的调度表使能开关对齐)。
return;
}
if (string.IsNullOrWhiteSpace(msgName))
{
return;
}
try
{
// 事件触发只发送当前 MsgName 对应帧,避免每次改动都全量发送。
SendOneFrameByMsgName(msgName.Trim());
}
catch (Exception ex)
{
_log.Warn($"ZLG LIN 事件驱动发送失败:{msgName},{ex.Message}");
}
}
///
/// 初始化 LIN 配置信息(目前仅缓存)。
///
/// 选中的配置。
public void InitLinConfig(CanLinConfigPro selectedLinConfigPro)
{
SelectedCanLinConfigPro = selectedLinConfigPro;
}
///
/// 打开 LIN(共享设备句柄)。
///
/// 设备索引。
/// 波特率。
/// 是否主节点。
public void StartLinDrive(uint deviceIndex, uint baudRate, bool isMaster)
{
if (OpenState)
{
return;
}
try
{
// 先确保设备打开(不影响 CAN 后续 Init)
_zlgCanDriveService.Driver.OpenDevice(deviceIndex);
// 初始化 LIN 通道
_zlgCanDriveService.Driver.OpenAndInitLin(0, new CanDrive.ZlgCan.ZlgLinChannelOptions
{
BaudRate = baudRate,
IsMaster = isMaster,
MaxLength = 8,
ChecksumMode = 3
});
// 统一由 CAN 服务侧启动接收线程(设备级 merge 接收可以同时收 CAN/LIN)
if (!_zlgCanDriveService.Driver.IsReceiving)
{
// mergeReceive=true:走合并接收,减少线程与句柄数量(与 CAN 侧默认策略一致)。
_zlgCanDriveService.Driver.StartReceiveLoop(mergeReceive: true, bufferFrames: 200);
}
OpenState = true;
LdfParserState = false;
}
catch (Exception ex)
{
_log.Error($"ZLG LIN 打开失败:{ex.Message}");
OpenState = false;
throw;
}
}
///
/// 关闭 LIN(共享设备句柄下,当前实现以 CloseDevice 为准:关闭将同时关闭 CAN/LIN)。
///
public void CloseDevice()
{
// ZLG 的通道句柄都在 Driver 内部;当前 Close 会关闭所有通道,保持与旧系统“同一时刻只有一种驱动工作”的原则一致。
_zlgCanDriveService.CloseDevice();
OpenState = false;
LdfParserState = false;
}
///
/// 加载并解析 LDF 文件,建立“帧/信号”的运行时索引,并生成 UI 可绑定的 集合。
/// 说明:
/// - 这里使用轻量解析器解析常见 LDF 结构(Frames/Signals/Nodes),目的是满足当前项目的 LIN 编解码与 UI 展示需求。
/// - 若 LDF 格式差异较大,解析可能失败并抛出异常;调用方(通常为 ViewModel)应捕获并提示。
///
/// LDF 路径。
public ObservableCollection StartLdf(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("LDF 路径为空", nameof(path));
}
if (!File.Exists(path))
{
throw new FileNotFoundException($"LDF 文件不存在:{path}", path);
}
try
{
// 读取文本:项目约定 UTF-8;若现场文件是 ANSI/GBK,可能需要外部统一转码。
var text = File.ReadAllText(path, Encoding.UTF8);
// 去除单行注释,简化解析
text = Regex.Replace(text, @"//.*?$", string.Empty, RegexOptions.Multiline);
// 解析并构建索引(在 _ldfLock 下更新全量索引,避免并发读到半更新状态)。
var (frames, signals, masterNodeName) = ParseLdf(text);
BuildRuntimeIndex(frames, signals, masterNodeName);
LdfParserState = true;
return ListLinLdfModel;
}
catch (Exception ex)
{
_log.Error($"ZLG LIN 解析 LDF 失败:{ex.Message}");
LdfParserState = false;
throw;
}
}
private static List ParseLdfFramesAndSignals(string ldfText)
{
// 说明:此解析器只用于生成“帧-信号全集池”,不做位宽/缩放等语义解析。
// 目标:尽可能容错地从 Frames 区域提取 FrameName 与其包含的 SignalName 列表。
var framesBlock = TryExtractNamedBlock(ldfText, "Frames");
if (string.IsNullOrWhiteSpace(framesBlock))
{
return new List();
}
var result = new List();
var exists = new HashSet(StringComparer.Ordinal);
// Frame 定义一般形式:FrameName : ... { ... }
// 这里以非贪婪匹配提取每个 Frame 的 body
var frameRegex = new Regex(@"(?s)(?[A-Za-z_][A-Za-z0-9_]*)\s*:\s*.*?\{(?.*?)\}\s*;?", RegexOptions.Compiled);
var sigRegex = new Regex(@"(?m)^\s*(?[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;
}
}
}
}