From 4d16b474c6fc3bad89a9464caf817d4339e5062f Mon Sep 17 00:00:00 2001 From: Tyrone CT Date: Wed, 14 Jan 2026 17:55:45 +0800 Subject: [PATCH] =?UTF-8?q?CAN=20FD=20=E8=B0=83=E5=BA=A6=E8=A1=A8=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CapMachine.Model/CANLIN/CANFdConfigExd.cs | 6 + .../CANLIN/CANFdScheduleConfig.cs | 60 ++ CapMachine.Model/CANLIN/CanLinConfigPro.cs | 6 + CapMachine.Model/HistoryWorkCondFile.cs | 21 +- CapMachine.Wpf/App.xaml.cs | 3 +- .../CanDrive/CanFD/DBCParserByFD.cs | 18 + CapMachine.Wpf/CanDrive/CanFD/ToomossCanFD.cs | 726 +++++++++++++++++- CapMachine.Wpf/CanDrive/CanFD/USB2CANFD.cs | 37 + CapMachine.Wpf/Dtos/CANFdConfigExdDto.cs | 11 + CapMachine.Wpf/Dtos/CANFdScheduleConfigDto.cs | 83 ++ .../CANFdScheduleConfigProfile.cs | 14 + CapMachine.Wpf/Services/CanFdDriveService.cs | 55 +- .../ViewModels/CANFDConfigViewModel.cs | 139 +++- .../DialogCANFdSchConfigViewModel.cs | 377 +++++++++ .../ViewModels/HistoryDataViewModel.cs | 345 +++++---- CapMachine.Wpf/Views/CANFDConfigView.xaml | 99 +++ .../Views/DialogCANFdSchConfigView.xaml | 206 +++++ .../Views/DialogCANFdSchConfigView.xaml.cs | 28 + CapMachine.Wpf/Views/HistoryDataView.xaml | 82 +- 19 files changed, 2130 insertions(+), 186 deletions(-) create mode 100644 CapMachine.Model/CANLIN/CANFdScheduleConfig.cs create mode 100644 CapMachine.Wpf/Dtos/CANFdScheduleConfigDto.cs create mode 100644 CapMachine.Wpf/MapperProfile/CANFdScheduleConfigProfile.cs create mode 100644 CapMachine.Wpf/ViewModels/DialogCANFdSchConfigViewModel.cs create mode 100644 CapMachine.Wpf/Views/DialogCANFdSchConfigView.xaml create mode 100644 CapMachine.Wpf/Views/DialogCANFdSchConfigView.xaml.cs diff --git a/CapMachine.Model/CANLIN/CANFdConfigExd.cs b/CapMachine.Model/CANLIN/CANFdConfigExd.cs index 4425e27..bbb670d 100644 --- a/CapMachine.Model/CANLIN/CANFdConfigExd.cs +++ b/CapMachine.Model/CANLIN/CANFdConfigExd.cs @@ -49,5 +49,11 @@ namespace CapMachine.Model.CANLIN /// [Column(Name = "DbcPath", IsNullable = false, StringLength = 500)] public string? DbcPath { get; set; } + + /// + /// 调度表是否启用 + /// + [Column(Name = "SchEnable")] + public bool SchEnable { get; set; } } } diff --git a/CapMachine.Model/CANLIN/CANFdScheduleConfig.cs b/CapMachine.Model/CANLIN/CANFdScheduleConfig.cs new file mode 100644 index 0000000..5789e15 --- /dev/null +++ b/CapMachine.Model/CANLIN/CANFdScheduleConfig.cs @@ -0,0 +1,60 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CapMachine.Model.CANLIN +{ + /// + /// 调度表的配置 + /// 其实这些调度表是在DBC中有的,但是图莫斯的驱动没有读取到这些信息 + /// 那么我们在系统层面进行操作和保存这些信息 + /// + [Table(Name = "CANFdScheduleConfig")] + public class CANFdScheduleConfig + { + /// + /// 主键 + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 消息名称 + /// + [Column(Name = "MsgName")] + public string? MsgName { get; set; } + + /// + /// 消息的周期 + /// + [Column(Name = "Cycle")] + public int Cycle { get; set; } + + /// + /// 发送方式 + /// + [Column(Name = "OrderSend")] + public int OrderSend { get; set; } + + /// + /// 调度表的Index + /// //约定每帧对应一个调度表,预设5个调度表,每个调度表对应一个帧 + /// 0-4这个范围的设置Index + /// + [Column(Name = "SchTabIndex")] + public int SchTabIndex { get; set; } + + + + + /// + /// ///////////////////////////////////////////导航属性/////////////////////////////////////////////////////// + /// + + public long CanLinConfigProId { get; set; } + public CanLinConfigPro? CanLinConfigPro { get; set; } + } +} diff --git a/CapMachine.Model/CANLIN/CanLinConfigPro.cs b/CapMachine.Model/CANLIN/CanLinConfigPro.cs index 48f1f48..1756c75 100644 --- a/CapMachine.Model/CANLIN/CanLinConfigPro.cs +++ b/CapMachine.Model/CANLIN/CanLinConfigPro.cs @@ -50,6 +50,12 @@ namespace CapMachine.Model.CANLIN ///CAN 的调度表配置模式 public List? CanScheduleConfigs { get; set; } + /// + /// ///////////////////////////////////////////导航属性/////////////////////////////////////////////////////// + /// + ///CAN 的调度表配置模式 + public List? CanFdScheduleConfigs { get; set; } + /// /// ///////////////////////////////////////////导航属性/////////////////////////////////////////////////////// /// diff --git a/CapMachine.Model/HistoryWorkCondFile.cs b/CapMachine.Model/HistoryWorkCondFile.cs index bc8eb49..21439f5 100644 --- a/CapMachine.Model/HistoryWorkCondFile.cs +++ b/CapMachine.Model/HistoryWorkCondFile.cs @@ -1,4 +1,5 @@ using FreeSql.DataAnnotations; +using System.ComponentModel; namespace CapMachine.Model { @@ -6,7 +7,7 @@ namespace CapMachine.Model /// 历史工况对应的文件信息 /// [Table(Name = "HistoryWorkCondFile")] - public class HistoryWorkCondFile + public class HistoryWorkCondFile : INotifyPropertyChanged { /// /// 主键 @@ -49,5 +50,23 @@ namespace CapMachine.Model /// public long HistoryExpId { get; set; } public HistoryExp? HistoryExp { get; set; } + + [Column(IsIgnore = true)] + public bool IsSelected + { + get { return _isSelected; } + set + { + if (_isSelected != value) + { + _isSelected = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected))); + } + } + } + + private bool _isSelected; + + public event PropertyChangedEventHandler? PropertyChanged; } } diff --git a/CapMachine.Wpf/App.xaml.cs b/CapMachine.Wpf/App.xaml.cs index 9f37b58..8bc5f8f 100644 --- a/CapMachine.Wpf/App.xaml.cs +++ b/CapMachine.Wpf/App.xaml.cs @@ -199,7 +199,8 @@ namespace CapMachine.Wpf containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); - + containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/CapMachine.Wpf/CanDrive/CanFD/DBCParserByFD.cs b/CapMachine.Wpf/CanDrive/CanFD/DBCParserByFD.cs index 0967da2..2c0b7c7 100644 --- a/CapMachine.Wpf/CanDrive/CanFD/DBCParserByFD.cs +++ b/CapMachine.Wpf/CanDrive/CanFD/DBCParserByFD.cs @@ -28,6 +28,23 @@ namespace CapMachine.Wpf.CanDrive.CanFD public static extern Int32 DBC_GetMsgQuantity(UInt64 DBCHandle); [DllImport("USB2XXX.dll")] public static extern Int32 DBC_GetMsgName(UInt64 DBCHandle, int index, StringBuilder pMsgName); + /// + /// + /// + /// + /// + /// + /// + [DllImport("USB2XXX.dll")] + public static extern Int32 DBC_GetMsgNameByID(UInt64 DBCHandle, int ID, StringBuilder pMsgName); + /// + /// 获取ID根据名称 + /// + /// + /// + /// + [DllImport("USB2XXX.dll")] + public static extern Int32 DBC_GetMsgIDByName(UInt64 DBCHandle, StringBuilder pMsgName); [DllImport("USB2XXX.dll")] public static extern Int32 DBC_GetMsgSignalQuantity(UInt64 DBCHandle, StringBuilder pMsgName); [DllImport("USB2XXX.dll")] @@ -53,5 +70,6 @@ namespace CapMachine.Wpf.CanDrive.CanFD public static extern Int32 DBC_SyncValueToCANMsg(UInt64 DBCHandle, StringBuilder pMsgName, IntPtr pCANMsg); [DllImport("USB2XXX.dll")] public static extern Int32 DBC_SyncValueToCANFDMsg(UInt64 DBCHandle, StringBuilder pMsgName, IntPtr pCANFDMsg); + } } diff --git a/CapMachine.Wpf/CanDrive/CanFD/ToomossCanFD.cs b/CapMachine.Wpf/CanDrive/CanFD/ToomossCanFD.cs index ca2fbad..e1956eb 100644 --- a/CapMachine.Wpf/CanDrive/CanFD/ToomossCanFD.cs +++ b/CapMachine.Wpf/CanDrive/CanFD/ToomossCanFD.cs @@ -1,10 +1,13 @@ using CapMachine.Wpf.CanDrive.CanFD; +using CapMachine.Wpf.Dtos; using CapMachine.Wpf.Models.Tag; using CapMachine.Wpf.Services; +using ImTools; using NPOI.OpenXmlFormats.Wordprocessing; using Prism.Ioc; using Prism.Mvvm; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -28,11 +31,12 @@ namespace CapMachine.Wpf.CanDrive /// /// 实例化函数 /// - public ToomossCanFD(IContainerProvider containerProvider) + public ToomossCanFD(IContainerProvider containerProvider, ILogService logService) { ContainerProvider = containerProvider; + LoggerService = logService; HighSpeedDataService = ContainerProvider.Resolve(); - LogService = ContainerProvider.Resolve(); + //LogService = ContainerProvider.Resolve(); //Stopwatch.Frequency表示高精度计时器每秒的计数次数(ticks/秒)每毫秒的ticks数 = 每秒的ticks数 ÷ 1000 TicksPerMs = Stopwatch.Frequency / 1000.0; } @@ -42,7 +46,7 @@ namespace CapMachine.Wpf.CanDrive /// public HighSpeedDataService HighSpeedDataService { get; set; } - public ILogService LogService { get; set; } + public ILogService LoggerService { get; set; } /// /// 开始CAN的驱动 @@ -60,7 +64,7 @@ namespace CapMachine.Wpf.CanDrive } catch (Exception ex) { - LogService.Error(ex.Message); + LoggerService.Error(ex.Message); System.Windows.MessageBox.Show($"{ex.Message}", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); } } @@ -482,6 +486,11 @@ namespace CapMachine.Wpf.CanDrive private static readonly Random _random = new Random(); + /// + /// 定时扫描更新数据 扫描Task + /// + private static Task CycleUpdateCmdTask { get; set; } + /// /// 精确周期发送CAN数据 /// @@ -549,6 +558,7 @@ namespace CapMachine.Wpf.CanDrive //此时重置NextExecutionTime为当前时间,避免连续的延迟累积 // 严重延迟,重新校准 NextExecutionTime = Stopwatcher.ElapsedTicks; + LoggerService.Info("定时发送延迟过大,重新校准时间"); Console.WriteLine("定时发送延迟过大,重新校准时间"); } @@ -611,29 +621,35 @@ namespace CapMachine.Wpf.CanDrive catch (TaskCanceledException) { // 任务被取消,正常退出 + IsSendOk = false; break; } catch (Exception ex) { - LogService.Error(ex.Message); + LoggerService.Error(ex.Message); Console.WriteLine($"CAN周期发送异常: {ex.Message}"); // 短暂暂停避免异常情况下CPU占用过高 + IsSendOk = false; await Task.Delay(10, token); } } + + IsSendOk = false; } catch (Exception ex) { // 确保在任何情况下(正常退出、异常、取消)都会停止计时器 Stopwatcher.Stop(); - LogService.Error(ex.Message); + LoggerService.Error(ex.Message); // 清理其他可能的资源 Console.WriteLine("CAN周期发送任务已结束,资源已清理"); + IsSendOk = false; } finally { // 确保在任何情况下(正常退出、异常、取消)都会停止计时器 Stopwatcher.Stop(); + IsSendOk = false; } }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default); @@ -677,6 +693,651 @@ namespace CapMachine.Wpf.CanDrive #endregion + #region 并行多线程发送报文 + + /// + /// 并行精确周期发送CAN数据 + /// + public void StartParallelPrecisionCycleSendMsg() + { + // 创建取消标记源 + var cancellationTokenSource = new CancellationTokenSource(); + var token = cancellationTokenSource.Token; + + // 保存取消标记,以便在停止时使用 + CycleSendCts = cancellationTokenSource; + + // 初始化计时基准 + TicksPerMs = Stopwatch.Frequency / 1000.0; + + // 分组消息,按照消息名称进行分组 + var groupedMessages = CmdData.GroupBy(x => x.MsgName).ToList(); + + // 创建发送任务列表 + var sendTasks = new List(); + + // 创建线程同步对象,确保DBC操作和发送操作的线程安全 + object dbcLock = new object(); + + // 为每组消息创建单独的发送任务 + foreach (var messageGroup in groupedMessages) + { + var msgName = messageGroup.Key; + var signals = messageGroup.ToList(); + + // 为每个消息组创建一个发送任务 + var sendTask = Task.Factory.StartNew(async () => + { + try + { + // 设置线程优先级 + Thread.CurrentThread.Priority = ThreadPriority.AboveNormal; + + // 每个消息使用独立的计时器 + var messageStopwatch = new Stopwatch(); + messageStopwatch.Start(); + + // 计算该消息的发送周期(可以为不同消息设置不同周期) + // 这里可以从配置或参数中获取特定消息的周期 + long cycleInTicks = (long)(SendCycle * TicksPerMs); + long nextExecutionTime = 0; + + while (IsCycleSend && !token.IsCancellationRequested) + { + try + { + // 计算下一次执行时间 + nextExecutionTime += cycleInTicks; + + // 获取当前时间 + long currentTime = messageStopwatch.ElapsedTicks; + long delayTicks = nextExecutionTime - currentTime; + + // 精确等待逻辑 + if (delayTicks > 0) + { + int delayMs = (int)(delayTicks / TicksPerMs); + + if (delayMs <= 20) + { + SpinWait.SpinUntil(() => messageStopwatch.ElapsedTicks >= nextExecutionTime); + } + else + { + await Task.Delay(delayMs - 20, token); + SpinWait.SpinUntil(() => messageStopwatch.ElapsedTicks >= nextExecutionTime); + } + } + + // 校准时间,处理严重延迟情况 + if (messageStopwatch.ElapsedTicks >= nextExecutionTime + cycleInTicks) + { + nextExecutionTime = messageStopwatch.ElapsedTicks; + LoggerService.Info($"消息{msgName}定时发送延迟过大,重新校准时间"); + } + + // 使用锁确保DBC操作和发送的线程安全 + lock (dbcLock) + { + // 构建CAN消息 + IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))); + + try + { + // 为信号赋值 + foreach (var signal in signals) + { + DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(msgName), + new StringBuilder(signal.SignalName), signal.SignalCmdValue); + } + + // 同步值到CAN消息 + DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(msgName), msgPtSend); + var canMsg = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG)); + + // 直接发送此消息,无需通过队列 + USB2CAN.CAN_MSG[] canMsgArray = new USB2CAN.CAN_MSG[] { canMsg }; + int sendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, canMsgArray, 1); + + // 更新发送状态 + if (sendedNum >= 0) + { + // 发送成功 + IsSendOk = true; + } + else + { + IsSendOk = false; + LoggerService.Info($"消息{msgName}发送失败: {sendedNum}"); + } + } + finally + { + // 确保释放非托管资源 + Marshal.FreeHGlobal(msgPtSend); + } + } + } + catch (TaskCanceledException) + { + LoggerService.Info($"消息{msgName}发送任务被取消"); + break; + } + catch (Exception ex) + { + LoggerService.Info($"消息{msgName}发送异常: {ex.Message}"); + await Task.Delay(10, token); + } + } + } + catch (Exception ex) + { + LoggerService.Info($"消息{msgName}发送线程异常: {ex.Message}"); + } + finally + { + LoggerService.Info($"消息{msgName}发送线程已退出"); + } + }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + sendTasks.Add(sendTask); + } + + // 创建监控任务,监控所有发送任务的状态 + CycleSendTask = Task.Factory.StartNew(async () => + { + try + { + // 等待所有任务完成或取消 + await Task.WhenAll(sendTasks.ToArray()); + } + catch (Exception ex) + { + LoggerService.Info($"并行发送监控任务异常: {ex.Message}"); + } + finally + { + LoggerService.Info("并行发送任务已全部结束"); + IsSendOk = false; + } + }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + + #endregion + + + + #region 调度表发送报文 + + private bool _SchEnable; + /// + /// 调度表使能 + /// + public bool SchEnable + { + get { return _SchEnable; } + set + { + _SchEnable = value; + RaisePropertyChanged(); + } + } + + + /// + /// 调度表的发送的报文数据 + /// + private USB2CANFD.CANFD_MSG[] SchCanMsg { get; set; } + + /// + /// 依据消息的分组 + /// + private IEnumerable> GroupMsg { get; set; } + + /// + /// 调度表集合数据 + /// 总共3个调度表,第一个表里面包含3帧数据,第二个调度表包含6帧数据,第三个调度表包含11帧数据 + /// Byte[] MsgTabNum = new Byte[3] { 3, 6, 11 }; + /// + private Byte[] MsgTabNum { get; set; } + + /// + /// 调度表发送的次数集合 + /// 第一个调度表循环发送数据,第二个调度表循环发送数据,第三个调度表只发送3次 + /// UInt16[] SendTimes = new UInt16[3] { 0xFFFF, 0xFFFF, 3 }; + /// + private UInt16[] SendTimes { get; set; } + + /// + /// 预设的调度表的个数 常值 + /// + private const int MsgTabCount = 5; + + /// + /// 定时更新时间 + /// + private int UpdateCycle { get; set; } = 100; + + /// + /// CNA 调度表的配置信息 + /// + public List ListCANScheduleConfig { get; set; } + + Random random = new Random(); + + /// + /// 监控数据 + /// 查找问题用,平时不用 + /// + //public MonitorValueLog monitorValueLog { get; set; } + + /// + /// 更新数据 测试用废弃了 + /// + public void UpdateValue() + { + //通过DBC进行对消息赋值 + IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG))); + int Index = 0; + //循环给MSG赋值数据 + foreach (var itemMsg in GroupMsg) + { + foreach (var itemSignal in itemMsg) + { + itemSignal.SignalCmdValue = random.Next(0, 100); + DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + } + DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); + SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG)); + Index++; + } + + //通过DBC写入数据后生成CanMsg + //将信号值填入CAN消息里面 + + //释放申请的临时缓冲区 + Marshal.FreeHGlobal(msgPtSend); + + + ////总共3个调度表,第一个表里面包含3帧数据,第二个调度表包含6帧数据,第三个调度表包含11帧数据 + //MsgTabNum = new Byte[1] { 1 }; + ////第一个调度表循环发送数据,第二个调度表循环发送数据,第三个调度表只发送3次 + //SendTimes = new UInt16[1] { 0xFFFF }; + //var ret = USB2CANFD.CAN_SetSchedule(DevHandle, WriteCANIndex, SchCanMsg, MsgTabNum, SendTimes, 1);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可 + var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, 0, 0, SchCanMsg, 1);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可 + if (ret == USB2CANFD.CANFD_SUCCESS) + { + Console.WriteLine("Update CAN Schedule Success"); + } + else + { + Console.WriteLine("Update CAN Schedule Error ret = {0}", ret); + return; + } + } + + /// + /// 开始调度表执行 + /// + public void StartSchedule() + { + if (CmdData.Count() == 0) return; + + //依据报文进行分组 + GroupMsg = CmdData.GroupBy(x => x.MsgName)!; + //初始化调度表要发送的消息结构 + SchCanMsg = new USB2CANFD.CANFD_MSG[GroupMsg.Count()]; + for (int i = 0; i < GroupMsg.Count(); i++) + { + SchCanMsg[i] = new USB2CANFD.CANFD_MSG(); + SchCanMsg[i].Data = new Byte[64]; + } + + //通过DBC进行对消息赋值 + IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG))); + int Index = 0; + //循环给MSG赋值数据 + foreach (var itemMsg in GroupMsg) + { + foreach (var itemSignal in itemMsg) + { + DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + } + DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); + //每个分组就是一个帧指令/消息数据 + SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG)); + //分配当前消息帧在集合中的位置信息到ListCANScheduleConfig中,方便实时更新时定位帧的位置 + if (ListCANScheduleConfig.Any(a => a.MsgName == itemMsg.Key)) + { + //把帧的位置给ListCANScheduleConfig,方便更新时定位数据 + ListCANScheduleConfig.FindFirst(a => a.MsgName == itemMsg.Key).MsgIndex = Index; + + //设置当前这个报文的在调度表中的发送周期 + SchCanMsg[Index].TimeStamp = (uint)ListCANScheduleConfig.FindFirst(a => a.MsgName == itemMsg.Key).Cycle; + } + Index++; + } + //通过DBC写入数据后生成CanMsg + //将信号值填入CAN消息里面 + //释放申请的临时缓冲区 + Marshal.FreeHGlobal(msgPtSend); + + //********就是可以设置多个调度表放那里,但是运行时同一个时刻只能运行调度表其中的一个 ******** + //****** 控制报文SchCanMsg和调度器中第一个调度器中的报文集合和要更新的报文集合都是同一个变量SchCanMsg, ******** + // *** SchCanMsg的Index序号和ListCANScheduleConfig的MsgIndex是一样的 *** + + //图莫斯的Sample:总共3个调度表,第一个表里面包含3帧数据,第二个调度表包含6帧数据,第三个调度表包含11帧数据 + //预设5个调度表,但是我们只用其中第一个调度表,第一个调度表中包括多少消息帧,由系统的控制指令的帧的分布决定,SchCanMsg.Count()是所需要的控制发送的帧,都放到第一个调度表中 + + MsgTabNum = new Byte[MsgTabCount] { (byte)SchCanMsg.Count(), 1, 1, 1, 1 }; + //0xFFFF:调度表循环发送数据,X:调度表循环发送的次数 + //设置每个调度表的发送方式,约定全部为循环发送 + SendTimes = new UInt16[MsgTabCount] { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF }; + + //SchCanMsg:需要发送的消息集合;MsgTabNum:调度表集合数据;SendTimes:发送次数集合数据;调度表个数:MsgTabCount + var ret = USB2CANFD.CANFD_SetSchedule(DevHandle, WriteCANIndex, SchCanMsg, MsgTabNum, SendTimes, MsgTabCount);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可 + if (ret == USB2CANFD.CANFD_SUCCESS) + { + Console.WriteLine("Set CAN Schedule Success"); + } + else + { + Console.WriteLine("Set CAN Schedule Error ret = {0}", ret); + LoggerService.Info($"Set CAN Schedule Error; 返回错误代码:{ret}"); + return; + } + + //约定使用Index=0 1号调度器,因为同一个时刻只能有一个调度器工作,把所有的报文都要放到这个调度器中 + //CAN_MSG的TimeStamp就是这个报文发送的周期,由调度器协调 + ret = USB2CANFD.CANFD_StartSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)100, (byte)ListCANScheduleConfig.FirstOrDefault()!.OrderSend); + if (ret == USB2CANFD.CANFD_SUCCESS) + { + Console.WriteLine($"Start CAN Schedule 1 Success,SchTabIndex:{(byte)0} - Cycle:{(byte)100} - OrderSend:{(byte)1}"); + } + else + { + Console.WriteLine("Start CAN Schedule 1 Error ret = {0}", ret); + LoggerService.Info($"Start CAN Schedule 1 Error;"); + return; + } + + + //foreach (var itemGroupMsg in GroupMsg) + //{ + // if (itemGroupMsg == null) continue; + // if (ListCANScheduleConfig.Any(a => a.MsgName!.Contains(itemGroupMsg.Key))) + // { + // var CANScheduleConfig = ListCANScheduleConfig.FindFirst(a => a.MsgName!.Contains(itemGroupMsg.Key)); + // //配置表里面包括这个报文消息内容 + // ret = USB2CANFD.CAN_StartSchedule(DevHandle, WriteCANIndex, (byte)CANScheduleConfig.SchTabIndex, (byte)CANScheduleConfig.Cycle, (byte)CANScheduleConfig.OrderSend); + // if (ret == USB2CANFD.CAN_SUCCESS) + // { + // Console.WriteLine($"Start CAN Schedule 1 Success,SchTabIndex:{(byte)CANScheduleConfig.SchTabIndex} - Cycle:{(byte)CANScheduleConfig.Cycle} - OrderSend:{(byte)CANScheduleConfig.OrderSend}"); + // } + // else + // { + // Console.WriteLine("Start CAN Schedule 1 Error ret = {0}", ret); + // LoggerService.Info($"Start CAN Schedule 1 Error;消息名称:{CANScheduleConfig.MsgName}"); + // return; + // } + // } + // else + // { + // LoggerService.Info($"调度表配置未发现对应的消息报文信息;报文信息{itemGroupMsg.Key}"); + // } + //} + + //走到这里说明调度表执行的是OK的 + //IsSendOk = true; + + } + + + /// + /// 停止调度表 + /// + public void StopSchedule() + { + ret = USB2CANFD.CANFD_StopSchedule(DevHandle, WriteCANIndex);//启动第一个调度表,表里面的CAN帧并行发送 + if (ret == USB2CANFD.CANFD_SUCCESS) + { + IsSendOk = false; + Console.WriteLine("Stop CAN Schedule"); + LoggerService.Info($"Stop CAN Schedule"); + } + else + { + Console.WriteLine("Start CAN Schedule Error ret = {0}", ret); + LoggerService.Info($"Stop CAN Schedule"); + return; + } + + + } + + /// + /// 循环使用的 + /// + private int CycleUpdateIndex = 0; + + /// + /// 循环更新调度表的指令数据 + /// 定时更新数据到调度表中 + /// + public void StartCycleUpdateCmd() + { + CycleUpdateCmdTask = Task.Run(async () => + { + while (IsCycleSend) + { + await Task.Delay(UpdateCycle); + try + { + + //通过DBC进行对消息赋值 + IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG))); + CycleUpdateIndex = 0; + //循环给MSG赋值数据,顺序是固定的,跟初始时设置是一样的 + foreach (var itemMsg in GroupMsg) + { + foreach (var itemSignal in itemMsg) + { + //itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用 + DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + } + DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); + SchCanMsg[CycleUpdateIndex] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG)); + CycleUpdateIndex++; + } + + //通过DBC写入数据后生成CanMsg + //将信号值填入CAN消息里面 + + //释放申请的临时缓冲区 + Marshal.FreeHGlobal(msgPtSend); + + //CAN_UpdateSchedule 官网解释 + // ---MsgTabIndex CAN调度表索引号 + // ---MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面 + // ---pCanMsg 需要更新的CAN帧指针 + // ---MsgNum pCanMsgTab里面包含的有效帧数 + + //CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号 + //因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg + //默认1号调度表,一个更新所有的帧数据 + var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可 + if (ret == USB2CANFD.CANFD_SUCCESS) + { + IsSendOk = true; + Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)} "); + } + else + { + IsSendOk = false; + Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)}"); + //return; + } + + //一个报文帧一个报文帧进行更新数据 + ////配置信息 默认启用1号调度器,MsgTabIndex=0; + //foreach (var itemMsgSchConfig in ListCANScheduleConfig) + //{ + // //USB2CANFD.CAN_MSG[] SchCanMsg1=new CAN_MSG[1]; + // //SchCanMsg1[0] = SchCanMsg[itemMsgSchConfig.MsgIndex]; + + // // MsgTabIndex CAN调度表索引号 ;MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面, ; + // // pCanMsg 需要更新的CAN帧指针,消息数据 ; MsgNum pCanMsgTab里面包含的有效帧数,一个调度表对应一个帧/消息,即为:1 (byte)(itemMsgSchConfig.MsgIndex+0) + // var ret = USB2CANFD.CAN_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(itemMsgSchConfig.MsgIndex), SchCanMsg, 1);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可 + // if (ret == USB2CANFD.CAN_SUCCESS) + // { + // Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex:{(byte)itemMsgSchConfig.SchTabIndex} -- MsgIndex:{(byte)(itemMsgSchConfig.MsgIndex)} "); + // } + // else + // { + // Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex:{(byte)itemMsgSchConfig.SchTabIndex} -- MsgIndex:{(byte)(itemMsgSchConfig.MsgIndex)}"); + // //return; + // } + //} + + + } + catch (Exception ex) + { + IsSendOk = false; + LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}"); + } + } + + IsSendOk = false; + }); + } + + + /// + /// 加载要发送的数据 + /// 一般是激活后才注册事件 + /// + /// + public void LoadCmdDataToDrive(List cmdData) + { + // Unsubscribe from events on the old CmdData items + if (CmdData != null && CmdData.Count > 0) + { + foreach (var cmd in CmdData) + { + cmd.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler; + } + } + + // Set the new data and subscribe to events + CmdData = cmdData; + foreach (var cmd in cmdData) + { + cmd.CanCmdDataChangedHandler += CmdData_CanCmdDataChangedHandler; + } + } + + /// + /// 指令数据发生变化执行方法 + /// + /// + /// + private void CmdData_CanCmdDataChangedHandler(object? sender, string e) + { + UpdateSchDataByCmdDataChanged(); + } + + /// + /// 指令数据发生变化执行更新调度表锁 + /// + private readonly object SchUpdateLock = new object(); + + /// + /// 指令数据发生变化执行方法 + /// + /// + /// + private void UpdateSchDataByCmdDataChanged() + { + try + { + if (!IsCycleSend) return; + if (!SchEnable) return; + + // 基础防御:确保 DBC/ 调度表 / 分组已经初始化 + if (DBCHandle == 0 || SchCanMsg == null || GroupMsg == null) + { + return; + } + + lock (SchUpdateLock) + { + //通过DBC进行对消息赋值 + IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG))); + int CycleUpdateIndex = 0; + //循环给MSG赋值数据,顺序是固定的,跟初始时设置是一样的 + foreach (var itemMsg in GroupMsg) + { + foreach (var itemSignal in itemMsg) + { + //itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用 + var SetSignalValue = DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + //monitorValueLog.UpdateValue1(SetSignalValue); + } + var SyncValueToCanMsg = DBCParserByFD.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); + //monitorValueLog.UpdateValue2(SyncValueToCanMsg); + SchCanMsg[CycleUpdateIndex] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG)); + CycleUpdateIndex++; + } + + //通过DBC写入数据后生成CanMsg + //将信号值填入CAN消息里面 + + //释放申请的临时缓冲区 + Marshal.FreeHGlobal(msgPtSend); + + //CAN_UpdateSchedule 官网解释 + // ---MsgTabIndex CAN调度表索引号 + // ---MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面 + // ---pCanMsg 需要更新的CAN帧指针 + // ---MsgNum pCanMsgTab里面包含的有效帧数 + + //CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号 + //因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg + //默认1号调度表,一个更新所有的帧数据 + var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可 + if (ret == USB2CANFD.CANFD_SUCCESS) + { + IsSendOk = true; + //Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)} "); + //monitorValueLog.UpdateValue3(ret); + } + else + { + IsSendOk = false; + //Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)}"); + //monitorValueLog.UpdateValue3(ret); + LoggerService.Info($"更新调度表失败,错误码:{ret}"); + //return; + } + } + + } + catch (Exception ex) + { + IsSendOk = false; + LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}"); + } + + } + + + + #endregion + + + + private bool _IsCycleRevice; /// /// 是否循环接收数据 @@ -739,6 +1400,25 @@ namespace CapMachine.Wpf.CanDrive } } + + private bool _IsReviceOk; + /// + /// 接收报文是否OK + /// + public bool IsReviceOk + { + get { return _IsReviceOk; } + set + { + if (_IsReviceOk != value) + { + RaisePropertyChanged(); + _IsReviceOk = value; + } + } + } + + /// /// 要发送的数据 /// @@ -751,7 +1431,7 @@ namespace CapMachine.Wpf.CanDrive { CycleReviceTask = Task.Run(async () => { - //var ret = USB2CANFD.CANFD_StartGetMsg(DevHandle, ReadCANIndex); + //var ret = USB2CANFDFD.CANFD_StartGetMsg(DevHandle, ReadCANIndex); while (IsCycleRevice) { await Task.Delay(ReviceCycle); @@ -763,6 +1443,7 @@ namespace CapMachine.Wpf.CanDrive int CanNum = USB2CANFD.CANFD_GetMsg(DevHandle, ReadCANIndex, msgPtRead, CanMsgBuffer.Length); if (CanNum > 0) { + IsReviceOk = true; Console.WriteLine("Read CanMsgNum = {0}", CanNum); for (int i = 0; i < CanNum; i++) { @@ -787,10 +1468,12 @@ namespace CapMachine.Wpf.CanDrive } else if (CanNum == 0) { + IsReviceOk = false; Console.WriteLine("No CAN data!"); } else { + IsReviceOk = false; Console.WriteLine("Get CAN data error!"); } Console.WriteLine(""); @@ -804,7 +1487,7 @@ namespace CapMachine.Wpf.CanDrive //有配置的名称的,认为是有用的,则需要读取数据 //if (!string.IsNullOrEmpty(item.Name)) //{ - //CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueSb); + //DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueSb); //double[] ValueDouble; DBCParserByFD.DBC_GetSignalValue(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueDouble); //item.SignalRtValueSb = ValueSb; @@ -826,10 +1509,14 @@ namespace CapMachine.Wpf.CanDrive } catch (Exception ex) { - LogService.Error(ex.Message); - //LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}"); + IsReviceOk = false; + LoggerService.Error(ex.Message); + //LoggerService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}"); } } + + + IsReviceOk = false; }); } @@ -893,6 +1580,25 @@ namespace CapMachine.Wpf.CanDrive DbcParserState = false; IsCycleRevice = false; IsCycleSend = false; + if (SchEnable) + { + StopSchedule(); + } + + // 确保定时器发送被停止并释放资源 + //try { StopTimerCycleSendMsg(); } catch { } + + // 等待接收任务结束后释放非托管缓冲区,避免并发释放 + try + { + var task = CycleReviceTask; + if (task != null && !task.IsCompleted) + { + task.Wait(TimeSpan.FromMilliseconds(ReviceCycle + 500)); + } + } + catch { } + } } diff --git a/CapMachine.Wpf/CanDrive/CanFD/USB2CANFD.cs b/CapMachine.Wpf/CanDrive/CanFD/USB2CANFD.cs index 67a278e..e69adbe 100644 --- a/CapMachine.Wpf/CanDrive/CanFD/USB2CANFD.cs +++ b/CapMachine.Wpf/CanDrive/CanFD/USB2CANFD.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using static CapMachine.Wpf.CanDrive.USB2CAN; namespace CapMachine.Wpf.CanDrive { @@ -119,16 +120,24 @@ namespace CapMachine.Wpf.CanDrive [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_Init(Int32 DevHandle, Byte CANIndex, ref CANFD_INIT_CONFIG pCanConfig); [DllImport("USB2XXX.dll")] + public static extern Int32 CANFD_Init2(Int32 DevHandle, Byte CANIndex, Int32 BaudRateNBTBps, Int32 BaudRateDBTBps, Byte EnResistor, Byte ISOCRCEnable); + [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_StartGetMsg(Int32 DevHandle, Byte CANIndex); [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_StopGetMsg(Int32 DevHandle, Byte CANIndex); [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_SendMsg(Int32 DevHandle, Byte CANIndex, CANFD_MSG[] pCanSendMsg, Int32 SendMsgNum); [DllImport("USB2XXX.dll")] + public static extern Int32 CANFD_SendMsgWithTime(Int32 DevHandle, Byte CANIndex, CANFD_MSG[] pCanSendMsg, Int32 SendMsgNum); + [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_GetMsg(Int32 DevHandle, Byte CANIndex, IntPtr pCanGetMsg, Int32 BufferSize); [DllImport("USB2XXX.dll")] + public static extern Int32 CANFD_ClearMsg(Int32 DevHandle, Byte CANIndex); + [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_SetFilter(Int32 DevHandle, Byte CANIndex, ref CANFD_FILTER_CONFIG pCanFilter, Byte Len); [DllImport("USB2XXX.dll")] + public static extern Int32 CANFD_FilterList_Init(Int32 DevHandle, Byte CANIndex, UInt32[] pIDList, Byte IDListLen); + [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_GetDiagnostic(Int32 DevHandle, Byte CANIndex, ref CANFD_DIAGNOSTIC pCanDiagnostic); [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_GetBusError(Int32 DevHandle, Byte CANIndex, ref CANFD_BUS_ERROR pCanBusError); @@ -139,9 +148,37 @@ namespace CapMachine.Wpf.CanDrive [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_StartSchedule(Int32 DevHandle, Byte CANIndex, Byte MsgTabIndex, Byte TimePrecMs, Byte OrderSend); [DllImport("USB2XXX.dll")] + public static extern Int32 CANFD_UpdateSchedule(Int32 DevHandle, Byte CANIndex, Byte MsgTabIndex, Byte MsgIndex, CANFD_MSG[] pCanMsg, Byte MsgNum); + [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_StopSchedule(Int32 DevHandle, Byte CANIndex); + [DllImport("USB2XXX.dll")] + public static extern Int64 CANFD_GetStartTime(Int32 DevHandle, Byte CANIndex); + [DllImport("USB2XXX.dll")] + public static extern Int32 CANFD_ResetStartTime(Int32 DevHandle, Byte CANIndex); [DllImport("USB2XXX.dll")] public static extern Int32 CANFD_SetRelay(Int32 DevHandle, Byte RelayState); + + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_Stop(Int32 DevHandle, Byte CANIndex); + [DllImport("USB2XXX.dll")] + public static extern Int64 CAN_GetStartTime(Int32 DevHandle, Byte CANIndex); + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_ResetStartTime(Int32 DevHandle, Byte CANIndex); + + //CAN Bootloader相关函数 + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_BL_Init(Int32 DevHandle, Int32 CANIndex, ref CAN_INIT_CONFIG pInitConfig, ref CBL_CMD_LIST pCmdList); + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_BL_NodeCheck(Int32 DevHandle, Int32 CANIndex, UInt16 NodeAddr, UInt32[] pVersion, UInt32[] pType, UInt32 TimeOut); + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_BL_Erase(Int32 DevHandle, Int32 CANIndex, UInt16 NodeAddr, UInt32 FlashSize, UInt32 TimeOut); + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_BL_Write(Int32 DevHandle, Int32 CANIndex, UInt16 NodeAddr, UInt32 AddrOffset, Byte[] pData, UInt32 DataNum, UInt32 TimeOut); + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_BL_Excute(Int32 DevHandle, Int32 CANIndex, UInt16 NodeAddr, UInt32 Type); + [DllImport("USB2XXX.dll")] + public static extern Int32 CAN_BL_SetNewBaudRate(Int32 DevHandle, Int32 CANIndex, UInt16 NodeAddr, ref CAN_INIT_CONFIG pInitConfig, UInt32 NewBaudRate, UInt32 TimeOut); + } } diff --git a/CapMachine.Wpf/Dtos/CANFdConfigExdDto.cs b/CapMachine.Wpf/Dtos/CANFdConfigExdDto.cs index 8d4d873..078912b 100644 --- a/CapMachine.Wpf/Dtos/CANFdConfigExdDto.cs +++ b/CapMachine.Wpf/Dtos/CANFdConfigExdDto.cs @@ -73,5 +73,16 @@ namespace CapMachine.Wpf.Dtos get { return _DbcPath; } set { _DbcPath = value; RaisePropertyChanged(); } } + + + private bool _SchEnable; + /// + /// 调度表是否启用 + /// + public bool SchEnable + { + get { return _SchEnable; } + set { _SchEnable = value; RaisePropertyChanged(); } + } } } diff --git a/CapMachine.Wpf/Dtos/CANFdScheduleConfigDto.cs b/CapMachine.Wpf/Dtos/CANFdScheduleConfigDto.cs new file mode 100644 index 0000000..2ff33ad --- /dev/null +++ b/CapMachine.Wpf/Dtos/CANFdScheduleConfigDto.cs @@ -0,0 +1,83 @@ +using CapMachine.Model.CANLIN; +using Prism.Mvvm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CapMachine.Wpf.Dtos +{ + /// + /// CANFdScheduleConfigDto + /// + public class CANFdScheduleConfigDto : BindableBase + { + /// + /// 主键 + /// + public long Id { get; set; } + + private string? _MsgName; + /// + /// 消息名称/帧名称 + /// + public string? MsgName + { + get { return _MsgName; } + set { _MsgName = value; RaisePropertyChanged(); } + } + + private int _Cycle; + /// + /// 周期 + /// + public int Cycle + { + get { return _Cycle; } + set { _Cycle = value; RaisePropertyChanged(); } + } + + private int _OrderSend; + /// + /// 发送方式 + /// + public int OrderSend + { + get { return _OrderSend; } + set { _OrderSend = value; RaisePropertyChanged(); } + } + + private int _SchTabIndex; + /// + /// 调度表的Index序列 + /// + public int SchTabIndex + { + get { return _SchTabIndex; } + set { _SchTabIndex = value; RaisePropertyChanged(); } + } + + /// + /// 在更新调度表数据时,我们有一个整体的帧数据指令集合,但是这些帧数据集合,分属于不同的调度表, + /// 这个在开始时生成整体的帧数据指令集合才会把这个MsgIndex分配上,这个不需要保存到数据库中,对接使用 + /// + public int MsgIndex { get; set; } + + /// + /// 程序的ID + /// + public long CanLinConfigProId { get; set; } + + + private CanLinConfigPro _CanLinConfigPro; + /// + /// 所属的程序 + /// + public CanLinConfigPro CanLinConfigPro + { + get { return _CanLinConfigPro; } + set { _CanLinConfigPro = value; RaisePropertyChanged(); } + } + } +} diff --git a/CapMachine.Wpf/MapperProfile/CANFdScheduleConfigProfile.cs b/CapMachine.Wpf/MapperProfile/CANFdScheduleConfigProfile.cs new file mode 100644 index 0000000..98877cd --- /dev/null +++ b/CapMachine.Wpf/MapperProfile/CANFdScheduleConfigProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using CapMachine.Model.CANLIN; +using CapMachine.Wpf.Dtos; + +namespace CapMachine.Wpf.MapperProfile +{ + public class CANFdScheduleConfigProfile : Profile + { + public CANFdScheduleConfigProfile() + { + CreateMap().ReverseMap(); + } + } +} diff --git a/CapMachine.Wpf/Services/CanFdDriveService.cs b/CapMachine.Wpf/Services/CanFdDriveService.cs index bb01213..c28b0ec 100644 --- a/CapMachine.Wpf/Services/CanFdDriveService.cs +++ b/CapMachine.Wpf/Services/CanFdDriveService.cs @@ -1,5 +1,6 @@ using CapMachine.Model.CANLIN; using CapMachine.Wpf.CanDrive; +using CapMachine.Wpf.Dtos; using CapMachine.Wpf.Models; using CapMachine.Wpf.Models.ProModelPars; using ImTools; @@ -25,9 +26,9 @@ namespace CapMachine.Wpf.Services /// /// 实例化函数 /// - public CanFdDriveService(HighSpeedDataService highSpeedDataService, IContainerProvider containerProvider, LogicRuleService logicRuleService) + public CanFdDriveService(HighSpeedDataService highSpeedDataService, IContainerProvider containerProvider, LogicRuleService logicRuleService, ILogService logService) { - ToomossCanFDDrive = new ToomossCanFD(containerProvider); + ToomossCanFDDrive = new ToomossCanFD(containerProvider, logService); //高速数据服务 HighSpeedDataService = highSpeedDataService; LogicRuleService = logicRuleService; @@ -126,6 +127,11 @@ namespace CapMachine.Wpf.Services /// public List CmdData { get; set; } = new List(); + /// + /// CNA 调度表的配置信息 + /// + public List ListCANFdScheduleConfig { get; set; } + /// /// 增加发送的指令数据 /// @@ -322,10 +328,39 @@ namespace CapMachine.Wpf.Services { if (CmdData.Count > 0) { - ToomossCanFDDrive.IsCycleSend = true; + //把指令数据赋值给CAN 驱动 ToomossCanFDDrive.CmdData = CmdData; - //ToomossCanFDDrive.StartCycleSendMsg(); - ToomossCanFDDrive.StartPrecisionCycleSendMsg(); + + if (ToomossCanFDDrive.SchEnable) + { + + //使用调度表的话,需要在调度表中配置信息后才可以进行操作 + var GroupMsg = ToomossCanFDDrive.CmdData.GroupBy(a => a.MsgName).ToList(); + foreach (var itemMsg in GroupMsg) + { + if (!ListCANFdScheduleConfig.ToList().Any(a => a.MsgName == itemMsg.Key)) + { + System.Windows.MessageBox.Show($"你使能了调度表,但是调度表中没有设置【{itemMsg.Key}】信息,请设置后再操作", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); + return; + } + } + + if (ListCANFdScheduleConfig == null && ListCANFdScheduleConfig!.Count() == 0) + { + System.Windows.MessageBox.Show("调度表配置为空数据,请检查!将无法发送数据", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); + return; + } + ToomossCanFDDrive.ListCANScheduleConfig = ListCANFdScheduleConfig!; + ToomossCanFDDrive.StartSchedule(); + //ToomossCanFDDrive.StartCycleUpdateCmd(); + } + else + { + ToomossCanFDDrive.StartPrecisionCycleSendMsg(); + //ToomossCanFDDrive.StartCycleSendMsg(); + } + + ToomossCanFDDrive.IsCycleSend = true; } else { @@ -334,7 +369,15 @@ namespace CapMachine.Wpf.Services } else { - ToomossCanFDDrive.IsCycleSend = false; + if (ToomossCanFDDrive.SchEnable) + { + ToomossCanFDDrive.IsCycleSend = false; + ToomossCanFDDrive.StopSchedule(); + } + else + { + ToomossCanFDDrive.IsCycleSend = false; + } } } diff --git a/CapMachine.Wpf/ViewModels/CANFDConfigViewModel.cs b/CapMachine.Wpf/ViewModels/CANFDConfigViewModel.cs index 86d2cf8..c59a5cd 100644 --- a/CapMachine.Wpf/ViewModels/CANFDConfigViewModel.cs +++ b/CapMachine.Wpf/ViewModels/CANFDConfigViewModel.cs @@ -200,7 +200,7 @@ namespace CapMachine.Wpf.ViewModels canLinConfigPros = FreeSql.Select().Where(a => a.CANLINInfo == CANLIN.CANFD) .Include(a => a.CANFdConfigExd) .IncludeMany(a => a.CanLinConfigContents, then => then.Include(b => b.LogicRule))//,then=> then.Include(b=>b.LogicRule) - //.IncludeMany(a => a.CanLinConfigContents) + .IncludeMany(a => a.CanFdScheduleConfigs)// .ToList(); ListCanLinConfigPro = new ObservableCollection(canLinConfigPros); @@ -245,11 +245,25 @@ namespace CapMachine.Wpf.ViewModels //}); } } + else + { + ListWriteCanLinRWConfigDto = new ObservableCollection(); + } var ReadData = SelectCanLinConfigPro.CanLinConfigContents!.Where(a => a.RWInfo == RW.Read).ToList(); if (ReadData != null && ReadData.Count > 0) { ListReadCanLinRWConfigDto = new ObservableCollection(Mapper.Map>(ReadData)); } + else + { + ListReadCanLinRWConfigDto = new ObservableCollection(); + } + + //调度表配置信息 + if (SelectCanLinConfigPro.CanFdScheduleConfigs != null && SelectCanLinConfigPro.CanFdScheduleConfigs.Count() > 0) + { + ListCANFdScheduleConfigDto = new ObservableCollection(Mapper.Map>(SelectCanLinConfigPro.CanFdScheduleConfigs)); + } //匹配选中的SelectCanLinConfigPro.CanLinConfigContents和ListCanDbcModel MatchSeletedAndCanDbcModel(); @@ -499,6 +513,8 @@ namespace CapMachine.Wpf.ViewModels CanFdDriveService.InitCanConfig(SelectCanLinConfigPro); InitLoadCanConfigPro(); + + CanFdDriveService.ToomossCanFDDrive.LoadCmdDataToDrive(CanFdDriveService.CmdData); } else { @@ -616,6 +632,18 @@ namespace CapMachine.Wpf.ViewModels SelectCanLinConfigProConfigName = SelectCanLinConfigPro.ConfigName; + + //调度表配置信息 + if (SelectCanLinConfigPro.CanFdScheduleConfigs != null && SelectCanLinConfigPro.CanFdScheduleConfigs.Count() > 0) + { + ListCANFdScheduleConfigDto = new ObservableCollection(Mapper.Map>(SelectCanLinConfigPro.CanFdScheduleConfigs)); + } + else + { + ListCANFdScheduleConfigDto = new ObservableCollection(); + } + + return; } //先判断是否是正确的集合数据,防止DataGrid的数据源刷新导致的触发事件 @@ -918,6 +946,49 @@ namespace CapMachine.Wpf.ViewModels set { _ArbBaudRateCbxItems = value; RaisePropertyChanged(); } } + + private DelegateCommand _SchEnableCmd; + /// + /// 调度表的使能 操作的指令 + /// + public DelegateCommand SchEnableCmd + { + set + { + _SchEnableCmd = value; + } + get + { + if (_SchEnableCmd == null) + { + _SchEnableCmd = new DelegateCommand((Par) => SchEnableCmdCall(Par)); + } + return _SchEnableCmd; + } + } + /// + /// 调度表的使能信息 + /// + /// + /// + private void SchEnableCmdCall(object par) + { + var dd = SelectedCANConfigExdDto.SchEnable; + var Result = (bool)par; + if (Result) + { + CanFdDriveService.ToomossCanFDDrive.SchEnable = true; + + } + else + { + CanFdDriveService.ToomossCanFDDrive.SchEnable = false; + + } + } + + + private DelegateCommand _CanOpCmd; /// /// CAN操作的指令 @@ -1009,6 +1080,7 @@ namespace CapMachine.Wpf.ViewModels .Set(a => a.ArbBaudRate, SelectedCANConfigExdDto.ArbBaudRate) .Set(a => a.ISOEnable, SelectedCANConfigExdDto.ISOEnable) .Set(a => a.ResEnable, SelectedCANConfigExdDto.ResEnable) + .Set(a => a.SchEnable, SelectedCANConfigExdDto.SchEnable) .Where(a => a.Id == SelectedCANConfigExdDto.Id) .ExecuteUpdated(); } @@ -1070,6 +1142,12 @@ namespace CapMachine.Wpf.ViewModels break; case "CycleSend"://循环发送你 + + //有可能加载完毕后不手动改变状态,导致值没有传输,所以这里赋值 + CanFdDriveService.ToomossCanFDDrive.SchEnable = SelectedCANConfigExdDto.SchEnable; + //无论有没有调度表,都要把这个配置给CanDriveService + CanFdDriveService.ListCANFdScheduleConfig = ListCANFdScheduleConfigDto.ToList(); + //循环发送数据 CanFdDriveService.CycleSendMsg(); @@ -1133,6 +1211,26 @@ namespace CapMachine.Wpf.ViewModels set { _WriteRuleCbxItems = value; RaisePropertyChanged(); } } + private ObservableCollection _ReadRuleCbxItems; + /// + /// 读取的规格集合 + /// + public ObservableCollection ReadRuleCbxItems + { + get { return _ReadRuleCbxItems; } + set { _ReadRuleCbxItems = value; RaisePropertyChanged(); } + } + + private ObservableCollection _ListCANFdScheduleConfigDto; + /// + ///调度表集合 + /// + public ObservableCollection ListCANFdScheduleConfigDto + { + get { return _ListCANFdScheduleConfigDto; } + set { _ListCANFdScheduleConfigDto = value; RaisePropertyChanged(); } + } + //private string _SelectedWriteName; ///// ///// 选中的写入的Name @@ -1278,6 +1376,45 @@ namespace CapMachine.Wpf.ViewModels System.Windows.MessageBox.Show("请选中后再进行【删除】操作?", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); } + break; + + case "WriteSch"://调度表 + + var data = CanFdDriveService.CmdData.GroupBy(a => a.MsgName).Select(a => a.Key).ToList(); + + if (data != null && data.Count > 0) + { + //弹窗 + DialogService.ShowDialog("DialogCANFdSchConfigView", new DialogParameters() { + {"ListMsg", CanFdDriveService.CmdData.GroupBy(a=>a.MsgName).Select(a=>a.Key).ToList() }, + { "ListCANFdScheduleConfigDto",ListCANFdScheduleConfigDto}, + { "SelectCanLinConfigProId",SelectCanLinConfigPro.Id}, + + }, (par) => + { + if (par.Result == ButtonResult.OK) + { + ////程序名称 + ListCANFdScheduleConfigDto = par.Parameters.GetValue>("ReturnValue"); + //把更新后的最新值给当前的主集合中 + SelectCanLinConfigPro.CanFdScheduleConfigs = Mapper.Map>(ListCANFdScheduleConfigDto.ToList()); + + } + else if (par.Result == ButtonResult.Cancel) + { + //取消 + + } + + }); + } + else + { + System.Windows.MessageBox.Show("未发现写入的执行的命令数据,无法配置?", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand); + } + + + break; default: break; diff --git a/CapMachine.Wpf/ViewModels/DialogCANFdSchConfigViewModel.cs b/CapMachine.Wpf/ViewModels/DialogCANFdSchConfigViewModel.cs new file mode 100644 index 0000000..7b44af7 --- /dev/null +++ b/CapMachine.Wpf/ViewModels/DialogCANFdSchConfigViewModel.cs @@ -0,0 +1,377 @@ +using AutoMapper; +using CapMachine.Core; +using CapMachine.Model.CANLIN; +using CapMachine.Wpf.Dtos; +using Prism.Commands; +using Prism.Services.Dialogs; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace CapMachine.Wpf.ViewModels +{ + /// + /// CAN Fd 调度表配置 + /// + public class DialogCANFdSchConfigViewModel : DialogViewModel + { + /// + /// 构造函数 + /// + public DialogCANFdSchConfigViewModel(IFreeSql freeSql, IMapper mapper) + { + Title = "调度表 CAN 配置"; + FreeSql = freeSql; + Mapper = mapper; + + + SendOrderCbxItems = new ObservableCollection() + { + new CbxItems(){ + Key="0", + Text="并行", + }, + new CbxItems(){ + Key="1", + Text="顺序", + }, + }; + + + //默认只能用1号调度器 + SchTabIndexCbxItems = new ObservableCollection() + { + new CbxItems(){ + Key="0", + Text="0", + }, + //new CbxItems(){ + // Key="1", + // Text="1", + //}, + //new CbxItems(){ + // Key="2", + // Text="2", + //}, + //new CbxItems(){ + // Key="3", + // Text="3", + //}, + //new CbxItems(){ + // Key="4", + // Text="4", + //}, + }; + + } + + public IFreeSql FreeSql { get; } + public IMapper Mapper { get; } + + private string name; + /// + /// 名称 + /// + public string Name + { + get { return name; } + set { name = value; RaisePropertyChanged(); } + } + + private ObservableCollection _ListCANFdScheduleConfigDto = new ObservableCollection(); + /// + /// CAN 调度表数据集合 + /// + public ObservableCollection ListCANFdScheduleConfigDto + { + get { return _ListCANFdScheduleConfigDto; } + set { _ListCANFdScheduleConfigDto = value; RaisePropertyChanged(); } + } + + /// + /// 消息/帧报文信息集合 + /// + public List ListMsg { get; set; } + + private ObservableCollection _MsgCbxItems; + /// + /// 消息名称 集合信息 + /// + public ObservableCollection MsgCbxItems + { + get { return _MsgCbxItems; } + set { _MsgCbxItems = value; RaisePropertyChanged(); } + } + + /// + /// 选中的程序的Id + /// + public long SelectCanLinConfigProId { get; set; } + + private ObservableCollection _SendOrderCbxItems; + /// + /// 发送方式 集合信息 + /// + public ObservableCollection SendOrderCbxItems + { + get { return _SendOrderCbxItems; } + set { _SendOrderCbxItems = value; RaisePropertyChanged(); } + } + + + private string? _CurSendOrder; + /// + /// 当前发送方式 + /// + public string? CurSendOrder + { + get { return _CurSendOrder; } + set { _CurSendOrder = value; RaisePropertyChanged(); } + } + + + private ObservableCollection _SchTabIndexCbxItems; + /// + /// 调度器序号 集合信息 + /// + public ObservableCollection SchTabIndexCbxItems + { + get { return _SchTabIndexCbxItems; } + set { _SchTabIndexCbxItems = value; RaisePropertyChanged(); } + } + + private CANFdScheduleConfigDto _CurSelectedItem; + /// + /// 选中的数据 + /// + public CANFdScheduleConfigDto CurSelectedItem + { + get { return _CurSelectedItem; } + set { _CurSelectedItem = value; RaisePropertyChanged(); } + } + + + private DelegateCommand _GridSelectionChangedCmd; + /// + /// 选中行数据命令 + /// + public DelegateCommand GridSelectionChangedCmd + { + set + { + _GridSelectionChangedCmd = value; + } + get + { + if (_GridSelectionChangedCmd == null) + { + _GridSelectionChangedCmd = new DelegateCommand((par) => GridSelectionChangedCmdMethod(par)); + } + return _GridSelectionChangedCmd; + } + } + private void GridSelectionChangedCmdMethod(object par) + { + //先判断是否是正确的集合数据,防止DataGrid的数据源刷新导致的触发事件 + var Selecteddata = par as CANFdScheduleConfigDto; + + if (Selecteddata != null) + { + CurSelectedItem = Selecteddata; + } + } + + //OpCmd + private DelegateCommand _OpCmd; + /// + /// 增加方法命令 + /// + public DelegateCommand OpCmd + { + set + { + _OpCmd = value; + } + get + { + if (_OpCmd == null) + { + _OpCmd = new DelegateCommand((Par) => OpCmdCall(Par)); + } + return _OpCmd; + } + } + + private void OpCmdCall(string Par) + { + switch (Par) + { + case "Add": + ListCANFdScheduleConfigDto.Add(new CANFdScheduleConfigDto + { + CanLinConfigProId = SelectCanLinConfigProId, + Cycle = 100, + SchTabIndex = 0, + OrderSend = 1, + }); + break; + case "Delete": + if (CurSelectedItem != null) + { + //直接删除掉,如果没有ID的话,这就不需要删除了 + FreeSql.Delete(CurSelectedItem.Id).ExecuteAffrows(); + ListCANFdScheduleConfigDto.Remove(CurSelectedItem); + + CurSelectedItem = null; + } + else + { + MessageBox.Show("请选中后再进行【删除】操作?", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); + } + break; + default: + break; + } + } + + private DelegateCommand saveCmd; + /// + /// 保存命令 + /// + public DelegateCommand SaveCmd + { + set + { + saveCmd = value; + } + get + { + if (saveCmd == null) + { + saveCmd = new DelegateCommand(() => SaveCmdMethod()); + } + return saveCmd; + } + } + + /// + /// 保存命令方法 + /// + /// + private void SaveCmdMethod() + { + //检查空的数据 + foreach (var item in ListCANFdScheduleConfigDto) + { + //整个的发送方式赋值给每个子项 + item.OrderSend = CurSendOrder == "0" ? 0 : 1; + + if (string.IsNullOrEmpty(item.MsgName)) + { + MessageBox.Show("请确认消息名称是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); + return; + } + if (item.Cycle == 0) + { + MessageBox.Show("请确认周期是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); + return; + } + if (item.OrderSend >= 2) + { + MessageBox.Show("请确认发送方式是否正确", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); + return; + } + } + + + //发送的控制帧都放到同一个调度表中,不需要检查了 + ////检查重复设置问题 + //bool isRepeat = ListCANScheduleConfigDto.GroupBy(i => i.MsgName).Any(g => g.Count() > 1); + //if (isRepeat) + //{ + // MessageBox.Show("请确认是否重复设置", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); + // return; + //} + + //检查数据是否正常 + foreach (var item in ListCANFdScheduleConfigDto) + { + FreeSql.InsertOrUpdate() + .SetSource(Mapper.Map(item)). + ExecuteAffrows(); + } + + ListCANFdScheduleConfigDto = new ObservableCollection(Mapper.Map>(FreeSql.Select().Where(a => a.CanLinConfigProId == SelectCanLinConfigProId).ToList())); + + DialogParameters pars = new DialogParameters + { + { "ReturnValue", ListCANFdScheduleConfigDto } + }; + + RaiseRequestClose(new DialogResult(ButtonResult.OK, pars)); + } + + private DelegateCommand cancelCmd; + /// + /// 保存命令 + /// + public DelegateCommand CancelCmd + { + set + { + cancelCmd = value; + } + get + { + if (cancelCmd == null) + { + cancelCmd = new DelegateCommand(() => CancelCmdMethod()); + } + return cancelCmd; + } + } + + + /// + /// 取消命令方法 + /// + /// + private void CancelCmdMethod() + { + RaiseRequestClose(new DialogResult(ButtonResult.Cancel)); + } + + /// + /// 窗口打开时的传递的参数 + /// + /// + public override void OnDialogOpened(IDialogParameters parameters) + { + ListMsg = parameters.GetValue>("ListMsg"); + // 转换为CbxItems集合,都是文本内容 + MsgCbxItems = new ObservableCollection( + ListMsg.Select(value => new CbxItems + { + Key = value, + Text = value + })); + + ListCANFdScheduleConfigDto = parameters.GetValue>("ListCANFdScheduleConfigDto"); + //防止返回的数据为空,就无法增加了 + if (ListCANFdScheduleConfigDto == null) ListCANFdScheduleConfigDto = new ObservableCollection(); + //Name = parameters.GetValue("Name"); + if (ListCANFdScheduleConfigDto.Count > 0) + { + CurSendOrder = ListCANFdScheduleConfigDto.FirstOrDefault()!.OrderSend.ToString(); + } + + SelectCanLinConfigProId = parameters.GetValue("SelectCanLinConfigProId"); + } + + + } +} diff --git a/CapMachine.Wpf/ViewModels/HistoryDataViewModel.cs b/CapMachine.Wpf/ViewModels/HistoryDataViewModel.cs index bd17564..1b5026b 100644 --- a/CapMachine.Wpf/ViewModels/HistoryDataViewModel.cs +++ b/CapMachine.Wpf/ViewModels/HistoryDataViewModel.cs @@ -15,8 +15,8 @@ using Prism.Events; using Prism.Regions; using Prism.Services.Dialogs; using System; +using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; @@ -51,7 +51,7 @@ namespace CapMachine.Wpf.ViewModels /// /// /// - public HistoryDataViewModel(IDialogService dialogService, IFreeSql freeSql, IEventAggregator eventAggregator, IMapper mapper,ConfigService configService) + public HistoryDataViewModel(IDialogService dialogService, IFreeSql freeSql, IEventAggregator eventAggregator, IMapper mapper, ConfigService configService) { DialogService = dialogService; FreeSql = freeSql; @@ -132,6 +132,20 @@ namespace CapMachine.Wpf.ViewModels set { _IsLeftDrawerOpen = value; RaisePropertyChanged(); } } + private bool _IsBusy; + public bool IsBusy + { + get { return _IsBusy; } + set { _IsBusy = value; RaisePropertyChanged(); } + } + + private string _BusyMessage = "正在加载,请稍候..."; + public string BusyMessage + { + get { return _BusyMessage; } + set { _BusyMessage = value; RaisePropertyChanged(); } + } + /// @@ -224,15 +238,7 @@ namespace CapMachine.Wpf.ViewModels set { _SelectedHistoryExp = value; RaisePropertyChanged(); } } - private HistoryWorkCondFile _SelectedHistoryWorkCondFile; - /// - /// 选中的历史数据文件 - /// - public HistoryWorkCondFile SelectedHistoryWorkCondFile - { - get { return _SelectedHistoryWorkCondFile; } - set { _SelectedHistoryWorkCondFile = value; RaisePropertyChanged(); } - } + /// /// 当前搜索的工况条件 @@ -268,7 +274,13 @@ namespace CapMachine.Wpf.ViewModels if (par is HistoryExp) { SelectedHistoryExp = par as HistoryExp; - ListHistoryWorkCondFile = SelectedHistoryExp!.HistoryWorkCondFiles!; + var files = SelectedHistoryExp!.HistoryWorkCondFiles!; + foreach (var f in files) + { + if (f != null) f.IsSelected = false; + } + ListHistoryWorkCondFile = files.ToList(); + //ListHistoryWorkCondFile = ListHistoryWorkCondFile.ToList(); return; } if ((par as SelectionChangedEventArgs)!.AddedItems.Count == 0) @@ -290,63 +302,18 @@ namespace CapMachine.Wpf.ViewModels if (Selecteddata != null) { SelectedHistoryExp = Selecteddata; - ListHistoryWorkCondFile = SelectedHistoryExp.HistoryWorkCondFiles!; - } - } - - - private DelegateCommand _FileGridSelectionChangedCmd; - /// - /// 试验信息选中行数据命令 - /// - public DelegateCommand FileGridSelectionChangedCmd - { - set - { - _FileGridSelectionChangedCmd = value; - } - get - { - if (_FileGridSelectionChangedCmd == null) + var files = SelectedHistoryExp.HistoryWorkCondFiles!; + foreach (var f in files) { - _FileGridSelectionChangedCmd = new DelegateCommand((par) => FileGridSelectionChangedCmdMethod(par)); + if (f != null) f.IsSelected = false; } - return _FileGridSelectionChangedCmd; + ListHistoryWorkCondFile = files; + ListHistoryWorkCondFile = ListHistoryWorkCondFile.ToList(); } } - private void FileGridSelectionChangedCmdMethod(object par) - { - if (par == null) - { - return; - } - if (par is HistoryWorkCondFile) - { - SelectedHistoryWorkCondFile = par as HistoryWorkCondFile; - return; - } - if ((par as SelectionChangedEventArgs)!.AddedItems.Count == 0) - { - return; - } - //先判断是否是正确的集合数据,防止DataGrid的数据源刷新导致的触发事件 - var Selecteddata = (par as SelectionChangedEventArgs)!.AddedItems[0] as HistoryWorkCondFile; - ////选中的行数据 - //var Selecteddata = (par as DataGrid)!.SelectedItem as HistoryExp; - ////选中的列数据 - //var selectedColumn = (par as DataGrid)!.CurrentColumn; - //SelectedIndex= ProParsHelper.GetIndexByName(selectedColumn); - - //SelectedProStepDto = par as ProStepDto; - if (Selecteddata != null) - { - SelectedHistoryWorkCondFile = Selecteddata; - - } - } private DelegateCommand _FilterHistoryDataFileCmd; /// @@ -397,6 +364,48 @@ namespace CapMachine.Wpf.ViewModels HasHeaderRecord = false, }; + private DelegateCommand _SelectAllFilesCmd; + public DelegateCommand SelectAllFilesCmd + { + set + { + _SelectAllFilesCmd = value; + } + get + { + if (_SelectAllFilesCmd == null) + { + _SelectAllFilesCmd = new DelegateCommand((par) => SelectAllFilesCmdMethod(par)); + } + return _SelectAllFilesCmd; + } + } + private void SelectAllFilesCmdMethod(object par) + { + bool isChecked = false; + if (par is bool b) { isChecked = b; } + else + { + var nb = par as bool?; + if (nb.HasValue) isChecked = nb.Value; + else + { + var sb = par as string; + if (!string.IsNullOrWhiteSpace(sb)) + { + bool.TryParse(sb, out isChecked); + } + } + } + if (ListHistoryWorkCondFile == null) return; + foreach (var f in ListHistoryWorkCondFile) + { + if (f != null) f.IsSelected = isChecked; + } + // 触发刷新 + ListHistoryWorkCondFile = ListHistoryWorkCondFile.ToList(); + } + Stopwatch stopwatch = new Stopwatch(); private DelegateCommand _HistoryDataFileCmd; @@ -422,118 +431,134 @@ namespace CapMachine.Wpf.ViewModels /// /// 获取试验数据的命令的执行方法 /// - private void HistoryDataFileCmdMethod() + private async void HistoryDataFileCmdMethod() { - //SelectedHistoryWorkCondFile - if (SelectedHistoryExp != null && SelectedHistoryWorkCondFile != null) - { - CsvRecordModels = new List(); - - ////第一次计时 - stopwatch.Start(); //启动Stopwatch - - if (!string.IsNullOrEmpty(SelectedHistoryWorkCondFile.FilePath)) - { - if (File.Exists(SelectedHistoryWorkCondFile.FilePath))//是否存在文件 - { - lock (ConfigService.CsvFileLock) - { - using (var reader = new StreamReader(SelectedHistoryWorkCondFile.FilePath)) - using (var csv = new CsvReader(reader, CultureInfo.CurrentCulture)) - { - csv.Context.RegisterClassMap(); - //GetRecords().ToList(); - - CsvRecordModels.AddRange(csv.GetRecords().ToList()); - } - } - } - else - { - System.Windows.MessageBox.Show("没有发现地址对应的文件"); - } - } - else - { - System.Windows.MessageBox.Show("发现空的文件地址"); - } - - //数据加载完毕后进入到Chart曲线中 - if (CsvRecordModels.Count > 0) - { - EventAggregator.GetEvent().Publish(new HistoryDataToChartMsg() { Machine = "History", Data = CsvRecordModels }); - } - - stopwatch.Stop(); //停止Stopwatch - Console.WriteLine("加载CSV数据耗时::{0}", stopwatch.Elapsed.TotalSeconds.ToString()); - stopwatch.Reset(); - - IsLeftDrawerOpen = false; - } - else + if (SelectedHistoryExp == null) { System.Windows.MessageBox.Show("请选中后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); + return; } + var files = new List(); + if (ListHistoryWorkCondFile != null) + { + foreach (var f in ListHistoryWorkCondFile) + { + if (f != null && f.IsSelected) + { + files.Add(f); + } + } + } - #region 整个工况 - //整个试验工况一起加载数据 - //if (SelectedHistoryExp != null && SelectedHistoryExp.HistoryWorkCondFiles != null) - //{ - // CsvRecordModels = new List(); + files = files + .Where(f => f != null) + .GroupBy(f => string.IsNullOrWhiteSpace(f.FilePath) ? $"ID:{f.Id}" : f.FilePath) + .Select(g => g.First()) + .ToList(); - // ////第一次计时 - // stopwatch.Start(); //启动Stopwatch + if (files.Count == 0) + { + System.Windows.MessageBox.Show("请先在右侧勾选至少一个CSV文件", "提示", MessageBoxButton.OK, MessageBoxImage.Information); + return; + } - // foreach (var item in SelectedHistoryExp.HistoryWorkCondFiles) - // { - // if (!string.IsNullOrEmpty(item.FilePath)) - // { - // if (File.Exists(item.FilePath))//是否存在文件 - // { - // lock (ConfigService.CsvFileLock) - // { - // using (var reader = new StreamReader(item.FilePath)) - // using (var csv = new CsvReader(reader, CultureInfo.CurrentCulture)) - // { - // csv.Context.RegisterClassMap(); - // //GetRecords().ToList(); + BusyMessage = "正在加载CSV数据,请稍候..."; + IsBusy = true; - // CsvRecordModels.AddRange(csv.GetRecords().ToList()); - // } - // } + var loaded = new List(); + string errorText = string.Empty; - // } - // else - // { - // System.Windows.MessageBox.Show("没有发现地址对应的文件"); - // } - // } - // else - // { - // System.Windows.MessageBox.Show("发现空的文件地址"); - // } - // } + stopwatch.Start(); - // //数据加载完毕后进入到Chart曲线中 - // if (CsvRecordModels.Count > 0) - // { - // EventAggregator.GetEvent().Publish(new HistoryDataToChartMsg() { Machine = "History", Data = CsvRecordModels }); - // } + try + { + await Task.Run(() => + { + var errors = new StringBuilder(); + for (int i = 0; i < files.Count; i++) + { + var f = files[i]; + try + { + Application.Current.Dispatcher.Invoke(() => + { + BusyMessage = $"正在加载({i + 1}/{files.Count}):{System.IO.Path.GetFileName(f?.FilePath)}"; + }); - // stopwatch.Stop(); //停止Stopwatch - // Console.WriteLine("加载CSV数据耗时::{0}", stopwatch.Elapsed.TotalSeconds.ToString()); - // stopwatch.Reset(); + if (f == null) continue; + if (string.IsNullOrWhiteSpace(f.FilePath)) + { + errors.AppendLine("发现空的文件地址"); + continue; + } + if (!File.Exists(f.FilePath)) + { + errors.AppendLine($"没有发现地址对应的文件: {f.FilePath}"); + continue; + } - // IsLeftDrawerOpen = false; - //} - //else - //{ - // System.Windows.MessageBox.Show("请选中后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); - //} + var rec = ReadCsvFile(f.FilePath); + if (rec != null && rec.Count > 0) + { + loaded.AddRange(rec); + } + } + catch (Exception ex) + { + errors.AppendLine($"读取CSV失败: {ex.Message}"); + } + } + errorText = errors.ToString(); + }); - #endregion + if (!string.IsNullOrWhiteSpace(errorText)) + { + System.Windows.MessageBox.Show(errorText, "读取提示", MessageBoxButton.OK, MessageBoxImage.Warning); + } + CsvRecordModels = new List(); + if (loaded != null && loaded.Count > 0) + { + CsvRecordModels = loaded + .Where(r => r != null) + .OrderBy(r => r.CreateTime) + .ToList(); + Application.Current.Dispatcher.Invoke(() => + { + EventAggregator.GetEvent().Publish(new HistoryDataToChartMsg() { Machine = "History", Data = CsvRecordModels }); + }); + } + } + finally + { + stopwatch.Stop(); + Console.WriteLine("加载CSV数据耗时::{0}", stopwatch.Elapsed.TotalSeconds.ToString()); + stopwatch.Reset(); + IsBusy = false; + IsLeftDrawerOpen = false; + } + + } + + private List ReadCsvFile(string filePath) + { + var result = new List(); + lock (ConfigService.CsvFileLock) + { + using (var reader = new StreamReader(filePath, Encoding.UTF8)) + using (var csv = new CsvReader(reader, CultureInfo.CurrentCulture)) + { + csv.Context.RegisterClassMap(); + var list = csv.GetRecords().ToList(); + if (list != null && list.Count > 0) + { + // 过滤无效时间,避免排序异常 + result = list.Where(r => r != null && r.CreateTime != default && r.CreateTime != DateTime.MinValue).ToList(); + } + } + } + return result; } @@ -1075,7 +1100,7 @@ namespace CapMachine.Wpf.ViewModels SeletedGroup6ListViewTabIndex = 0; SeletedGroup7ListViewTabIndex = 0; SeletedGroup8ListViewTabIndex = 0; - + } else { diff --git a/CapMachine.Wpf/Views/CANFDConfigView.xaml b/CapMachine.Wpf/Views/CANFDConfigView.xaml index 28032a4..df0b4bd 100644 --- a/CapMachine.Wpf/Views/CANFDConfigView.xaml +++ b/CapMachine.Wpf/Views/CANFDConfigView.xaml @@ -636,6 +636,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -694,6 +775,24 @@ Margin="0,0,15,0" HorizontalAlignment="Right" Orientation="Horizontal"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +