318 lines
11 KiB
C#
318 lines
11 KiB
C#
using CapMachine.Model.CANLIN;
|
||
using CapMachine.Wpf.LinDrive;
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// ZLG LIN 驱动服务(共享设备句柄)。
|
||
/// </summary>
|
||
public sealed class ZlgLinDriveService : BindableBase
|
||
{
|
||
private readonly ILogService _log;
|
||
private readonly ZlgCanDriveService _zlgCanDriveService;
|
||
|
||
/// <summary>
|
||
/// 当前选中的配置程序(沿用原有 FreeSql 模型)。
|
||
/// </summary>
|
||
public CanLinConfigPro? SelectedCanLinConfigPro { get; set; }
|
||
|
||
private bool _openState;
|
||
/// <summary>
|
||
/// LIN 打开状态。
|
||
/// </summary>
|
||
public bool OpenState
|
||
{
|
||
get { return _openState; }
|
||
private set { _openState = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
private bool _ldfParserState;
|
||
/// <summary>
|
||
/// LDF 解析状态(ZLG LIN 暂未接入 LDF,固定为 false)。
|
||
/// </summary>
|
||
public bool LdfParserState
|
||
{
|
||
get { return _ldfParserState; }
|
||
private set { _ldfParserState = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// LDF 消息集合(UI 绑定),ZLG LIN 暂未接入 LDF,默认空。
|
||
/// </summary>
|
||
public ObservableCollection<LinLdfModel> ListLinLdfModel { get; private set; } = new ObservableCollection<LinLdfModel>();
|
||
|
||
/// <summary>
|
||
/// 是否启用调度发送(与 UI 的调度表使能语义对齐)。
|
||
/// </summary>
|
||
public bool SchEnable
|
||
{
|
||
get { return _zlgCanDriveService.Driver.SchEnable; }
|
||
set { _zlgCanDriveService.Driver.SchEnable = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否启用事件驱动发送。
|
||
/// </summary>
|
||
public bool IsCycleSend
|
||
{
|
||
get { return _zlgCanDriveService.Driver.IsCycleSend; }
|
||
set { _zlgCanDriveService.Driver.IsCycleSend = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构造。
|
||
/// </summary>
|
||
/// <param name="zlgCanDriveService">共享 CAN 服务。</param>
|
||
/// <param name="logService">日志服务。</param>
|
||
public ZlgLinDriveService(ZlgCanDriveService zlgCanDriveService, ILogService logService)
|
||
{
|
||
_zlgCanDriveService = zlgCanDriveService;
|
||
_log = logService;
|
||
|
||
_zlgCanDriveService.Driver.LinFrameReceived += frame =>
|
||
{
|
||
// 未来接入 LDF 后在这里做 Sync/Decode;当前仅保留事件链路,不做假解析。
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化 LIN 配置信息(目前仅缓存)。
|
||
/// </summary>
|
||
/// <param name="selectedLinConfigPro">选中的配置。</param>
|
||
public void InitLinConfig(CanLinConfigPro selectedLinConfigPro)
|
||
{
|
||
SelectedCanLinConfigPro = selectedLinConfigPro;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开 LIN(共享设备句柄)。
|
||
/// </summary>
|
||
/// <param name="deviceIndex">设备索引。</param>
|
||
/// <param name="baudRate">波特率。</param>
|
||
/// <param name="isMaster">是否主节点。</param>
|
||
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)
|
||
{
|
||
_zlgCanDriveService.Driver.StartReceiveLoop(mergeReceive: true, bufferFrames: 200);
|
||
}
|
||
|
||
OpenState = true;
|
||
LdfParserState = false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_log.Error($"ZLG LIN 打开失败:{ex.Message}");
|
||
OpenState = false;
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭 LIN(共享设备句柄下,当前实现以 CloseDevice 为准:关闭将同时关闭 CAN/LIN)。
|
||
/// </summary>
|
||
public void CloseDevice()
|
||
{
|
||
// ZLG 的通道句柄都在 Driver 内部;当前 Close 会关闭所有通道,保持与旧系统“同一时刻只有一种驱动工作”的原则一致。
|
||
_zlgCanDriveService.CloseDevice();
|
||
OpenState = false;
|
||
LdfParserState = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载 LDF(ZLG LIN 暂未接入 LDF 解析 DLL,因此此处明确失败,不返回模拟数据)。
|
||
/// </summary>
|
||
/// <param name="path">LDF 路径。</param>
|
||
public ObservableCollection<LinLdfModel> StartLdf(string path)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|