Files
CapMachine/CapMachine.Wpf/LinDrive/ToomossLin.cs

1169 lines
46 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.CanDrive;
using CapMachine.Wpf.Dtos;
using CapMachine.Wpf.Services;
using ImTools;
using Microsoft.VisualBasic;
using Prism.Ioc;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
namespace CapMachine.Wpf.LinDrive
{
/// <summary>
/// Toomoss 的LIN驱动
/// </summary>
public class ToomossLin : BindableBase
{
/// <summary>
/// 设备Handles集合
/// </summary>
public Int32[] DevHandles { get; set; } = new Int32[20];
/// <summary>
/// 设备Handles
/// 设备句柄本质为设备序号的低4字节可以通过调用USB_ScanDevice函数获得
/// </summary>
public Int32 DevHandle { get; set; } = 0;
/// <summary>
/// Lin的Index
/// LIN通道索引号填0或者1若只有一个通道LIN则填0.
/// </summary>
public Byte LINIndex { get; set; } = 0;
/// <summary>
/// Lin的连接State
/// </summary>
public bool state { get; set; }
/// <summary>
/// Lin的连接设备数量
/// </summary>
public Int32 DevNum { get; set; }
/// <summary>
/// Lin的Dll文件的路径
/// </summary>
public string dllFilePath { get; set; } = "USB2XXX.dll";
private readonly IContainerProvider ContainerProvider;
public ToomossLin(IContainerProvider containerProvider)
{
ContainerProvider = containerProvider;
HighSpeedDataService = ContainerProvider.Resolve<HighSpeedDataService>();
LoggerService = ContainerProvider.Resolve<ILogService>();
//Stopwatch.Frequency表示高精度计时器每秒的计数次数ticks/秒每毫秒的ticks数 = 每秒的ticks数 ÷ 1000
TicksPerMs = Stopwatch.Frequency / 1000.0;
}
/// <summary>
/// Logger 实例
/// </summary>
public ILogService LoggerService { get; set; }
/// <summary>
/// HighSpeedDataService 实例
/// </summary>
public HighSpeedDataService HighSpeedDataService { get; set; }
/// <summary>
/// 接收优化上下文:缓存帧与信号的 StringBuilder避免循环中重复分配
/// </summary>
private class SignalReadCtx
{
public string SignalName { get; set; }
public StringBuilder SignalNameSB { get; set; }
public LinLdfModel ModelRef { get; set; }
}
private class FrameReadCtx
{
public string MsgName { get; set; }
public StringBuilder MsgNameSB { get; set; }
public bool IsMasterFrame { get; set; }
public List<SignalReadCtx> Signals { get; set; } = new List<SignalReadCtx>();
}
private List<FrameReadCtx> RecvFrameCtxs = new List<FrameReadCtx>();
/// <summary>
/// 开始LDF文件写入
/// </summary>
public ObservableCollection<LinLdfModel> StartLdf(string LdfPath)
{
LDF_Parser(LdfPath);
BuildRecvFrameCtxs();
return ListLinLdfModel;
}
/// <summary>
/// 开始Lin的驱动
/// </summary>
public void StartLinDrive()
{
IsExistsDllFile();
ScanDevice();
OpenDevice();
}
private bool _IsCycleRevice;
/// <summary>
/// 是否循环接收数据
/// </summary>
public bool IsCycleRevice
{
get { return _IsCycleRevice; }
set { _IsCycleRevice = value; RaisePropertyChanged(); }
}
private bool _IsCycleSend;
/// <summary>
/// 是否循环发送数据
/// </summary>
public bool IsCycleSend
{
get { return _IsCycleSend; }
set { _IsCycleSend = value; RaisePropertyChanged(); }
}
/// <summary>
/// 循环发送数据
/// </summary>
public ushort SendCycle { get; set; } = 100;
/// <summary>
/// 循环接受数据
/// </summary>
public ushort ReviceCycle { get; set; } = 500;
/// <summary>
/// CycleRevice 扫描Task
/// </summary>
private static Task CycleReviceTask { get; set; }
/// <summary>
/// CycleSend 扫描Task
/// </summary>
private static Task CycleSendTask { get; set; }
private bool _OpenState;
/// <summary>
/// 打开设备的状态
/// </summary>
public bool OpenState
{
get { return _OpenState; }
set { _OpenState = value; RaisePropertyChanged(); }
}
private bool _LdfParserState;
/// <summary>
/// LDF解析的状态
/// </summary>
public bool LdfParserState
{
get { return _LdfParserState; }
set { _LdfParserState = value; RaisePropertyChanged(); }
}
/// <summary>
/// LDFHandle
/// </summary>
public UInt64 LDFHandle { get; set; }
/// <summary>
/// LIN波特率从LDF读取
/// </summary>
public int LINBaudRate { get; private set; }
/// <summary>
/// 当前正在适配器中运行的调度表名称(用于增量刷新)
/// 同一个时刻只能运行一个调度表
/// </summary>
public string ActiveSchName { get; private set; }
/// <summary>
/// LDF消息集合
/// 包括读取的实时值和数据
/// </summary>
public ObservableCollection<LinLdfModel> ListLinLdfModel { get; set; } = new ObservableCollection<LinLdfModel>();
/// <summary>
/// 要发送的LIN数据
/// </summary>
public List<LinCmdData> CmdData { get; set; } = new List<LinCmdData>();
/// <summary>
/// 加载要发送的数据(订阅数据变化事件)
/// 一般是激活后才注册事件
/// </summary>
/// <param name="cmdData"></param>
public void LoadCmdDataToDrive(List<LinCmdData> cmdData)
{
// 取消订阅旧数据源事件
if (CmdData != null && CmdData.Count > 0)
{
foreach (var cmd in CmdData)
{
cmd.LinCmdDataChangedHandler -= CmdData_LinCmdDataChangedHandler;
}
}
// 设置新数据源并订阅事件
CmdData = cmdData ?? new List<LinCmdData>();
foreach (var cmd in CmdData)
{
cmd.LinCmdDataChangedHandler += CmdData_LinCmdDataChangedHandler;
}
}
/// <summary>
/// 当前激活调度表下,启用的帧/报文名称集合(来自 ListLINScheduleConfig 的 IsMsgActived
/// 为空(null) 表示不过滤,使用所有 CmdData 中的帧;非空则仅对包含在集合内的帧做信号更新与下发。
/// </summary>
private HashSet<string>? ActiveMsgNames;
/// <summary>
/// ******************【1】*********************
/// 是否存在Dll文件
/// </summary>
/// <returns></returns>
public bool IsExistsDllFile()
{
if (!File.Exists(dllFilePath))
{
Console.WriteLine("请先将USB2XXX.dll和libusb-1.0.dll文件复制到exe程序文件输出目录下!");
Console.WriteLine("dll文件在usb2can_lin_pwm_example/sdk/libs/windows目录下");
Console.WriteLine("程序是32位的就复制x86目录下文件程序是64位的就复制x86_64目录下文件");
return false;
}
return true;
}
/// <summary>
/// ******************【2】*********************
/// 扫描查找设备,并将每个设备的唯一设备号存放到数组中,后面的函数需要用到
/// </summary>
/// <returns></returns>
public bool ScanDevice()
{
DevNum = USB_DEVICE.USB_ScanDevice(DevHandles);
if (DevNum <= 0)
{
Console.WriteLine("No device connected!");
return false;
}
else
{
Console.WriteLine("Have {0} device connected!", DevNum);
DevHandle = DevHandles[0];//获取第一个设备的设备号
return true;
}
}
/// <summary>
/// ******************【3】*********************
/// 打开设备
/// </summary>
/// <returns></returns>
public bool OpenDevice()
{
//打开设备
OpenState = USB_DEVICE.USB_OpenDevice(DevHandle);
if (!OpenState)
{
Console.WriteLine("Open device error!");
return false;
}
else
{
Console.WriteLine("Open device success!");
return true;
}
}
/// <summary>
/// ******************【4】*********************
/// 解析LDF信息
/// </summary>
/// <returns></returns>
public void LDF_Parser(string Path)
{
LDFHandle = LDFParser.LDF_ParserFile(DevHandle, LINIndex, 1, new StringBuilder(Path));
if (LDFHandle == 0)
{
Console.WriteLine("解析LDF文件失败!");
LdfParserState = false;
return;
}
//读取LDF文件信息
Console.WriteLine("ProtocolVersion = {0}", LDFParser.LDF_GetProtocolVersion(LDFHandle));
LINBaudRate = LDFParser.LDF_GetLINSpeed(LDFHandle);
Console.WriteLine("LINSpeed = {0}", LINBaudRate);
// 使用LDF中的波特率初始化LIN通道主机模式
var initRet = USB2LIN_EX.LIN_EX_Init(DevHandle, LINIndex, LINBaudRate, USB2LIN_EX.LIN_EX_MASTER);
if (initRet < USB2LIN_EX.LIN_EX_SUCCESS)
{
Console.WriteLine("LIN通道初始化失败!");
LdfParserState = false;
return;
}
else
{
Console.WriteLine("LIN通道初始化成功");
// 可选:开启适配器供电以驱动从节点(若硬件支持)
try { USB2LIN_EX.LIN_EX_CtrlPowerOut(DevHandle, LINIndex, 1); } catch { }
}
//读取主机名称
StringBuilder MasterName = new StringBuilder(64);
LDFParser.LDF_GetMasterName(LDFHandle, MasterName);
Console.WriteLine("Master Name = {0}", MasterName);
ListLinLdfModel.Clear();
//读取信息
int FrameLen = LDFParser.LDF_GetFrameQuantity(LDFHandle);
for (int i = 0; i < FrameLen; i++)
{
//读取帧名称和发布者
StringBuilder FrameName = new StringBuilder(64);
string IsMasterFrame = "";
if (LDFParser.LDF_PARSER_OK == LDFParser.LDF_GetFrameName(LDFHandle, i, FrameName))
{
StringBuilder PublisherName = new StringBuilder(64);
LDFParser.LDF_GetFramePublisher(LDFHandle, FrameName, PublisherName);
if (MasterName.Equals(PublisherName))
{
IsMasterFrame = "是";
//当前帧为主机发送数据帧
Console.WriteLine("[MW]Frame[{0}].Name={1},Publisher={2}", i, FrameName, PublisherName);
}
else
{
IsMasterFrame = "否";
//当前帧为主机读数据帧
Console.WriteLine("[MR]Frame[{0}].Name={1},Publisher={2}", i, FrameName, PublisherName);
}
//读取信号信息
int SignalNum = LDFParser.LDF_GetFrameSignalQuantity(LDFHandle, FrameName);
for (int j = 0; j < SignalNum; j++)
{
StringBuilder SignalName = new StringBuilder(64);
if (LDFParser.LDF_PARSER_OK == LDFParser.LDF_GetFrameSignalName(LDFHandle, FrameName, j, SignalName))
{
Console.WriteLine("\tSignal[{0}].Name={1}", j, SignalName);
ListLinLdfModel.Add(new LinLdfModel()
{
MsgName = FrameName.ToString(),
Publisher = PublisherName.ToString(),
IsMasterFrame = IsMasterFrame,
SignalName = SignalName.ToString()
});
}
}
}
}
// 构建接收优化上下文,避免每次循环 GroupBy 与临时对象分配
BuildRecvFrameCtxs();
//解析成功
LdfParserState = true;
}
/// <summary>
/// 构建接收优化上下文
/// </summary>
private void BuildRecvFrameCtxs()
{
if (ListLinLdfModel == null || ListLinLdfModel.Count == 0)
{
RecvFrameCtxs = new List<FrameReadCtx>();
return;
}
var grouped = ListLinLdfModel.GroupBy(x => x.MsgName);
var list = new List<FrameReadCtx>();
foreach (var g in grouped)
{
var first = g.First();
var frame = new FrameReadCtx
{
MsgName = g.Key,
MsgNameSB = new StringBuilder(g.Key),
IsMasterFrame = (first.IsMasterFrame != null && first.IsMasterFrame.Contains("是"))
};
foreach (var model in g)
{
frame.Signals.Add(new SignalReadCtx
{
SignalName = model.SignalName,
SignalNameSB = new StringBuilder(model.SignalName),
ModelRef = model
});
}
list.Add(frame);
}
RecvFrameCtxs = list;
}
/// <summary>
/// 发送LIN数据
/// 发送一次
/// </summary>
public void SendLinMsg(List<LinCmdData> CmdData)
{
try
{
//防止有多个不同的消息名称/帧,每个帧单独处理发送
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
//主机写操作,发送数据给从机
LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
LDFParser.LDF_ExeFrameToBus(LDFHandle, new StringBuilder(itemMsg.Key), 1);
//LDFParser.LDF_ExeSchToBus(LDFHandle, new StringBuilder(itemMsg.Key), 1);
}
}
catch (Exception ex)
{
}
}
/// <summary>
/// 读取信号值
/// </summary>
private StringBuilder ReadValueStr = new StringBuilder(64);
/// <summary>
/// 循环
/// 主机读操作,读取从机返回的数据值
/// </summary>
public void StartCycleReviceMsg()
{
CycleReviceTask = Task.Run(async () =>
{
while (IsCycleRevice)
{
await Task.Delay(ReviceCycle);
try
{
var frames = RecvFrameCtxs;
if (frames == null || frames.Count == 0)
{
IsReviceOk = true;
continue;
}
foreach (var frame in frames)
{
// 仅对从机发布的帧执行读取
if (frame.IsMasterFrame)
continue;
// 主机读操作,读取从机返回的数据值
LDFParser.LDF_ExeFrameToBus(LDFHandle, frame.MsgNameSB, 1);
foreach (var sig in frame.Signals)
{
LDFParser.LDF_GetSignalValueStr(LDFHandle, frame.MsgNameSB, sig.SignalNameSB, ReadValueStr);
sig.ModelRef.SignalRtValueSb = ReadValueStr;
}
}
IsReviceOk = true;
}
catch (Exception ex)
{
IsReviceOk = false;
LoggerService.Info($"{ex.Message}");
}
}
IsReviceOk = false;
});
}
/// <summary>
/// 循环发送数据
/// </summary>
public void StartCycleSendMsg()
{
CycleSendTask = Task.Run(async () =>
{
while (IsCycleSend)
{
await Task.Delay(SendCycle);
try
{
//防止有多个不同的消息名称/帧,每个帧单独处理发送
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
//主机写操作,发送数据给从机
LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
//【0】参数注意
LDFParser.LDF_ExeFrameToBus(LDFHandle, new StringBuilder(itemMsg.Key), 0);
//读取当前的指令帧数据LDF_ExeFrameToBus执行后就可以读取本身的数据
foreach (var item in ListLinLdfModel)
{
if (CmdData.Any(a => a.MsgName == item.MsgName))
{
LDFParser.LDF_GetSignalValueStr(LDFHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ReadValueStr);
item.SignalRtValueSb = ReadValueStr;
}
}
}
////主机写操作,发送数据给从机
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("Reg_Set_Voltage"), 13.5);
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("Ramp_Time"), 3);
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("Cut_Off_Speed"), 4);
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("Exc_Limitation"), 15.6);
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("Derat_Shift"), 2);
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("MM_Request"), 2);
//LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder("LIN_CONTROL"), new StringBuilder("Reg_Blind"), 1);
}
catch (Exception ex)
{
//LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}");
}
}
});
}
private bool _IsSendOk;
/// <summary>
/// 发送报文是否OK
/// </summary>
public bool IsSendOk
{
get { return _IsSendOk; }
set
{
if (_IsSendOk != value)
{
RaisePropertyChanged();
_IsSendOk = value;
}
}
}
private bool _IsReviceOk;
/// <summary>
/// 接收报文是否OK
/// </summary>
public bool IsReviceOk
{
get { return _IsReviceOk; }
set
{
if (_IsReviceOk != value)
{
RaisePropertyChanged();
_IsReviceOk = value;
}
}
}
#region
// 添加取消标记源字段用于停止任务
private CancellationTokenSource CycleSendCts;
/// <summary>
/// 计算每毫秒对应的ticks数只需计算一次
/// </summary>
private double TicksPerMs;
// 类成员变量定义 精确记时用
private readonly Stopwatch Stopwatcher = new Stopwatch();
private long NextExecutionTime;
// 计算需要等待的时间
private long CurrentTime;
private long DelayTicks;
private int DelayMs;
private static readonly Random _random = new Random();
/// <summary>
/// 精确周期发送CAN数据
/// </summary>
public void StartPrecisionCycleSendMsg()
{
// 创建取消标记源 用于控制任务的取消 允许在需要时通过取消令牌来优雅停止任务
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
// 保存取消标记,以便在停止时使用
CycleSendCts = cancellationTokenSource;//将取消标记源保存到类的成员变量CycleSendCts这样在外部调用停止方法时可以访问它
NextExecutionTime = 0;//初始化NextExecutionTime为0这个变量用于记录下一次执行的目标时间点
CycleSendTask = Task.Factory.StartNew(async () =>
{
try
{
// 设置当前线程为高优先级
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
// 初始化完成后开始计时
Stopwatcher.Restart();
// 预先计算固定值
long CycleInTicks = (long)(SendCycle * TicksPerMs);
//临时测试用
//long lastTicks = Stopwatcher.ElapsedTicks;
//IsCycleSend
while (IsCycleSend && !token.IsCancellationRequested)
{
try
{
// 计算下一次执行时间点 将当前设置的发送周期SendCycle(毫秒)转换为Stopwatch的计时单位(tick)累加到NextExecutionTime上
NextExecutionTime += CycleInTicks; // 转换为Stopwatch计时单位
// 获取当前时间点以Stopwatch的tick为单位
CurrentTime = Stopwatcher.ElapsedTicks;
//计算需要等待的时间,即目标时间点(NextExecutionTime)与当前时间点(CurrentTime)的差值
DelayTicks = NextExecutionTime - CurrentTime;
// 如果还有等待时间,则等待,只有在目标时间点还未到达时才执行等待
if (DelayTicks > 0)
{
////此时是需要等待的,那么需要等待多久呢, 将需等待的tick数转换回毫秒
DelayMs = (int)(DelayTicks / TicksPerMs);
//20这个数据是预估和测试的可能跟Windows抖动误差就是20ms左右当然可以不用这个IF()判断直接SpinWait.SpinUntil(() => Stopwatcher.ElapsedTicks >= NextExecutionTime);但是会导致当前独占一个CPU核心线程
//所以设置一个20的阈值20ms以下的延迟使用SpinWait.SpinUntil进行自旋等待20ms以上的延迟使用Task.Delay进行异步等待让CPU不至于一直的独占
if (DelayMs <= 20)
{
SpinWait.SpinUntil(() => Stopwatcher.ElapsedTicks >= NextExecutionTime);
}
else
{
////使用Task.Delay进行异步等待大部分等待时间通过这种方式完成避免线程阻塞
await Task.Delay(DelayMs - 20, token);
//// 使用SpinWait.SpinUntil进行精确的微调等待。自旋等待会占用CPU资源但能提供更高的定时精度确保在精确的时间点执行
////上面的Task.Delay可能会因为系统调度等原因导致实际执行时间稍晚于预期因此在这里使用SpinWait.SpinUntil来确保在精确的时间点执行
SpinWait.SpinUntil(() => Stopwatcher.ElapsedTicks >= NextExecutionTime);
}
}
// 如果已经超过了计划时间,立即执行并重新校准
if (Stopwatcher.ElapsedTicks >= NextExecutionTime + CycleInTicks)
{
//检测是否发生了严重延迟(超过一个周期)。如果当前时间已经超过了下一次计划时间,则说明系统负载过高或其他原因导致无法按时执行,
//此时重置NextExecutionTime为当前时间避免连续的延迟累积
// 严重延迟,重新校准
NextExecutionTime = Stopwatcher.ElapsedTicks;
Console.WriteLine("定时发送延迟过大,重新校准时间");
LoggerService.Info("定时发送延迟过大,重新校准时间");
}
// 使用Stopwatch记录实际的执行间隔而不是DateTime
//Console.WriteLine($"--实际间隔(ms): {(Stopwatcher.ElapsedTicks - lastTicks) / TicksPerMs:F3}, 目标: {SendCycle}");
//lastTicks = Stopwatcher.ElapsedTicks;
//Console.WriteLine($"--当前时间(毫秒): {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
// 执行发送CAN逻辑
{
//防止有多个不同的消息名称/帧,每个帧单独处理发送
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
//主机写操作,发送数据给从机
LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
//【0】参数注意
LDFParser.LDF_ExeFrameToBus(LDFHandle, new StringBuilder(itemMsg.Key), 0);
//读取当前的指令帧数据LDF_ExeFrameToBus执行后就可以读取本身的数据
foreach (var item in ListLinLdfModel)
{
if (CmdData.Any(a => a.MsgName == item.MsgName))
{
LDFParser.LDF_GetSignalValueStr(LDFHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ReadValueStr);
item.SignalRtValueSb = ReadValueStr;
}
}
}
IsSendOk = true;
}
}
catch (TaskCanceledException)
{
IsSendOk = false;
// 任务被取消,正常退出
break;
}
catch (Exception ex)
{
IsSendOk = false;
Console.WriteLine($"LIN周期发送异常: {ex.Message}");
// 短暂暂停避免异常情况下CPU占用过高
await Task.Delay(10, token);
}
}
IsSendOk = false;
}
catch (Exception ex)
{
IsSendOk = false;
// 确保在任何情况下(正常退出、异常、取消)都会停止计时器
Stopwatcher.Stop();
LoggerService.Info("接收出现异常");
// 清理其他可能的资源
Console.WriteLine("LIN周期发送任务已结束资源已清理");
}
finally
{
IsSendOk = false;
// 确保在任何情况下(正常退出、异常、取消)都会停止计时器
Stopwatcher.Stop();
}
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
/// <summary>
/// 修改停止发送的方法
/// </summary>
public void StopCycleSendMsg()
{
IsCycleSend = false;
CycleSendCts?.Cancel();
}
#endregion
#region
/// <summary>
/// 指令数据变化时,更新调度表的线程锁
/// </summary>
private readonly object SchUpdateLock = new object();
private bool _SchEnable;
/// <summary>
/// 调度表使能
/// </summary>
public bool SchEnable
{
get { return _SchEnable; }
set
{
_SchEnable = value;
RaisePropertyChanged();
}
}
/// <summary>
/// 定时扫描更新数据 扫描Task
/// </summary>
private static Task CycleUpdateCmdTask { get; set; }
/// <summary>
/// 定时更新时间
/// 定时更新调度表的周期
/// </summary>
private int UpdateCycle { get; set; } = 5000;
/// <summary>
/// 调度表数量
/// </summary>
public int SchCount { get; set; }
/// <summary>
/// LIN 调度表的配置信息
/// </summary>
public List<LINScheduleConfigDto> ListLINScheduleConfig { get; set; }
/// <summary>
/// 获取LIN的LINScheduleConfig
/// </summary>
/// <returns></returns>
public List<LINScheduleConfig> GetLINScheduleConfigs()
{
List<LINScheduleConfig> LINScheduleConfigs = new List<LINScheduleConfig>();
SchCount = GetSchCount();
for (int i = 0; i < SchCount; i++)
{
var SchName = GetSchName(i);
var FrameCount = GetSchFrameCount(SchName);
for (int j = 0; j < FrameCount; j++)
{
LINScheduleConfigs.Add(new LINScheduleConfig()
{
SchTabName = GetSchName(i),
SchTabIndex = i,
MsgName = GetSchFrameName(SchName, j),
MsgNameIndex = j,
Cycle = 100,
IsActive = false,
});
}
}
return LINScheduleConfigs;
}
/// <summary>
/// 获取调度的帧的信号个数
/// 消息/报文的个数
/// 一个调度表包含多个帧/消息/报文
/// </summary>
/// <returns></returns>
public int GetSchFrameCount(string SchName)
{
return LDFParser.LDF_GetSchFrameQuantity(LDFHandle, new StringBuilder(SchName));
}
/// <summary>
/// 获取调度的帧的名称
/// </summary>
/// <returns></returns>
public string GetSchFrameName(string SchName, int FrameIndex)
{
StringBuilder FrameName = new StringBuilder();
LDFParser.LDF_GetSchFrameName(LDFHandle, new StringBuilder(SchName), FrameIndex, FrameName);
return FrameName.ToString();
}
/// <summary>
/// 获取调度表数量
/// </summary>
/// <returns></returns>
public int GetSchCount()
{
SchCount = LDFParser.LDF_GetSchQuantity(LDFHandle);
return SchCount;
}
/// <summary>
/// 获取调度表数量
/// </summary>
/// <returns></returns>
public string GetSchName(int index)
{
StringBuilder pSchName = new StringBuilder();
var SchResult = LDFParser.LDF_GetSchName(LDFHandle, index, pSchName);
return pSchName.ToString();
}
Random Random = new Random();
/// <summary>
/// 开始调度表执行
/// 同一个时刻只能运行一个调度表,如果有多个调度表,只能选择一个运行,
/// ********** 如果控制指令分布在不同的调度表中需要更改LDF文件的配置把控制的报文放在同一个调度表中 **********
/// </summary>
public void StartSchedule()
{
if (LDFHandle == 0)
return;
// 1) 将要执行的调度表烧入适配器并自动运行(若不指定,则启动全部调度表)
// 优先使用 ListLINScheduleConfig 指定的调度表名;否则默认取第一个
string selectedSch = string.Empty;
if (ListLINScheduleConfig != null && ListLINScheduleConfig.Count > 0)
{
selectedSch = ListLINScheduleConfig.FirstOrDefault(a => a.IsActive && !string.IsNullOrEmpty(a.SchTabName))?.SchTabName
?? ListLINScheduleConfig.FirstOrDefault(a => !string.IsNullOrEmpty(a.SchTabName))?.SchTabName;
}
if (string.IsNullOrEmpty(selectedSch))
{
// 回退为索引0的调度表
if (GetSchCount() > 0)
{
selectedSch = GetSchName(0);
LoggerService?.Info($"未指定调度表,默认启动调度表[{selectedSch}],注意当前调度表可能需要的调度表");
}
}
//当前激活的调度表
ActiveSchName = selectedSch;
// 计算当前激活调度表下的“被选中帧”集合IsMsgActived=true
if (!string.IsNullOrEmpty(ActiveSchName) && ListLINScheduleConfig != null)
{
var actives = ListLINScheduleConfig
.Where(d => d.IsActive && d.IsMsgActived && string.Equals(d.SchTabName ?? string.Empty, ActiveSchName ?? string.Empty, StringComparison.Ordinal))
.Select(d => d.MsgName)
.Where(n => !string.IsNullOrEmpty(n))
.Distinct()
.ToList();
ActiveMsgNames = actives.Count > 0 ? new HashSet<string>(actives) : new HashSet<string>();
}
else
{
ActiveMsgNames = null; // 不过滤
}
// 2) 先将当前指令值写入对应帧(可选,用于初始化,仅对被选中的帧)
if (CmdData != null && CmdData.Count > 0)
{
var groupMsg = CmdData.GroupBy(x => x.MsgName);
foreach (var itemMsg in groupMsg)
{
// 若设置了激活的帧集合,则进行过滤
if (ActiveMsgNames != null && !ActiveMsgNames.Contains(itemMsg.Key))
continue;
foreach (var itemSignal in itemMsg)
{
LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
}
}
// 3) 下发选中调度表到表格并自动执行
var ret = LDFParser.LDF_SetSchToTable(LDFHandle, new StringBuilder(ActiveSchName), 0);
if (ret < 0)
{
LoggerService?.Info($"启动调度表[{ActiveSchName}]失败, 返回:{ret}");
}
else
{
Console.WriteLine($"调度表[{ActiveSchName}]已启动(自动执行)");
LoggerService?.Info($"调度表[{ActiveSchName}]已启动(自动执行)");
}
// 4) 置位调度更新开关(事件驱动刷新,不再自动启动周期刷新任务)
//SchEnable = true;
// 不再自动启动周期更新任务,改为数据变化事件驱动刷新
// if (CycleUpdateCmdTask == null || CycleUpdateCmdTask.IsCompleted) { StartCycleUpdateCmd(); }
}
/// <summary>
/// 循环更新调度表的指令数据
/// 定时更新数据到调度表中
/// 被数值更新事件驱动的方法取代了
/// </summary>
public void StartCycleUpdateCmd()
{
CycleUpdateCmdTask = Task.Run(async () =>
{
while (IsCycleSend)
{
await Task.Delay(UpdateCycle);
try
{
if (CmdData.Count() == 0) return;
//防止有多个不同的消息名称/帧,每个帧单独处理发送
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
foreach (var itemMsg in GroupMsg)
{
// 若设置了激活的帧集合,则进行过滤
if (ActiveMsgNames != null && !ActiveMsgNames.Contains(itemMsg.Key))
continue;
foreach (var itemSignal in itemMsg)
{
if (itemSignal.ConfigName.Contains("速"))
{
itemSignal.SignalCmdValue = 1250+ Random.NextDouble()*1000;
}
//主机写操作,发送数据给从机
LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
////【0】参数注意
//LDFParser.LDF_ExeFrameToBus(LDFHandle, new StringBuilder(itemMsg.Key), 0);
//读取当前的指令帧数据LDF_ExeFrameToBus执行后就可以读取本身的数据
foreach (var item in ListLinLdfModel)
{
if (CmdData.Any(a => a.MsgName == item.MsgName)
&& (ActiveMsgNames == null || ActiveMsgNames.Contains(item.MsgName)))
{
LDFParser.LDF_GetSignalValueStr(LDFHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ReadValueStr);
item.SignalRtValueSb = ReadValueStr;
}
}
// 调度表已在适配器中自动执行,这里仅更新信号值即可
}
// 将更新后的信号值下发到适配器的自动调度表(刷新离线表的数据)
if (!string.IsNullOrEmpty(ActiveSchName))
{
var retPush = LDFParser.LDF_SetSchToTable(LDFHandle, new StringBuilder(ActiveSchName), 0);
if (retPush < 0)
{
LoggerService?.Info($"刷新调度表[{ActiveSchName}]失败, 返回:{retPush}");
}
}
IsSendOk = true;
}
catch (Exception ex)
{
IsSendOk = false;
LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}");
}
}
IsSendOk = false;
});
}
/// <summary>
/// 指令数据发生变化事件回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e">消息/帧名称</param>
private void CmdData_LinCmdDataChangedHandler(object? sender, string e)
{
UpdateSchDataByCmdDataChanged(e);
}
/// <summary>
/// 指令数据变化时,按消息名增量刷新调度表数据
/// </summary>
/// <param name="changedMsgName">发生变化的帧名称</param>
private void UpdateSchDataByCmdDataChanged(string changedMsgName)
{
try
{
// 与CAN保持一致仅在循环发送开启且调度表使能时才更新
if (!IsCycleSend) return;
if (!SchEnable) return;
// 基础防御
if (LDFHandle == 0) return;
if (string.IsNullOrEmpty(ActiveSchName)) return;
if (string.IsNullOrEmpty(changedMsgName)) return;
// 若配置了激活帧过滤,则不在集合内的帧不更新
if (ActiveMsgNames != null && ActiveMsgNames.Count > 0 && !ActiveMsgNames.Contains(changedMsgName))
return;
lock (SchUpdateLock)
{
// 仅更新对应消息/帧的信号值
var relatedCmds = CmdData.Where(x => string.Equals(x.MsgName, changedMsgName, StringComparison.Ordinal));
foreach (var cmd in relatedCmds)
{
LDFParser.LDF_SetSignalValue(LDFHandle, new StringBuilder(changedMsgName), new StringBuilder(cmd.SignalName), cmd.SignalCmdValue);
}
//读取当前的指令帧数据,执行后就可以读取本身的数据
foreach (var item in ListLinLdfModel)
{
if (CmdData.Any(a => a.MsgName == item.MsgName))
{
LDFParser.LDF_GetSignalValueStr(LDFHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ReadValueStr);
item.SignalRtValueSb = ReadValueStr;
}
}
// 将更新后的信号值推送到适配器当前运行的调度表(离线表刷新)
var retPush = LDFParser.LDF_SetSchToTable(LDFHandle, new StringBuilder(ActiveSchName), 0);
if (retPush < 0)
{
IsSendOk = false;
LoggerService?.Info($"刷新调度表[{ActiveSchName}]失败, 返回:{retPush}");
}
else
{
IsSendOk = true;
//Console.WriteLine($"Update LIN Schedule Success -- Sch:[{ActiveSchName}] Msg:[{changedMsgName}]");
}
}
}
catch (Exception ex)
{
IsSendOk = false;
LoggerService?.Info($"时间:{DateTime.Now.ToString()}-【LIN_SCH】-{ex.Message}");
}
}
/// <summary>
/// 停止调度表自动执行
/// </summary>
public void StopSchedule()
{
try
{
IsCycleSend = false;
SchEnable = false;
ActiveMsgNames = null; // 清空激活帧过滤集合
if (LDFHandle != 0)
{
LDFParser.LDF_StopSchTable(LDFHandle);
}
}
catch (Exception ex)
{
LoggerService?.Info($"停止调度表异常: {ex.Message}");
}
}
#endregion
/// <summary>
/// 关闭设备
/// </summary>
public void CloseDevice()
{
//关闭设备
try
{
// 停止调度并释放LDF资源
if (LDFHandle != 0)
{
try { LDFParser.LDF_StopSchTable(LDFHandle); } catch { }
try { LDFParser.LDF_Release(LDFHandle); } catch { }
LDFHandle = 0;
}
}
catch { }
USB_DEVICE.USB_CloseDevice(DevHandle);
OpenState = false;
LdfParserState = false;
IsCycleRevice = false;
IsCycleSend = false;
}
}
}