CAN驱动的重构V1
CAN报文的展示和处理
This commit is contained in:
@@ -132,6 +132,8 @@ namespace CapMachine.Wpf
|
||||
containerRegistry.RegisterSingleton<LogicRuleService>();
|
||||
containerRegistry.RegisterSingleton<ConfigService>();
|
||||
containerRegistry.RegisterSingleton<AlarmService>();
|
||||
containerRegistry.RegisterSingleton<HightDriveMsgService>();
|
||||
|
||||
////注册设备服务
|
||||
//containerRegistry.RegisterSingleton<MachineDataService>();
|
||||
containerRegistry.RegisterSingleton<CanDriveService>();
|
||||
|
||||
@@ -33,6 +33,10 @@ namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
ContainerProvider = containerProvider;
|
||||
HighSpeedDataService = ContainerProvider.Resolve<HighSpeedDataService>();
|
||||
|
||||
//Stopwatch.Frequency表示高精度计时器每秒的计数次数(ticks/秒)每毫秒的ticks数 = 每秒的ticks数 ÷ 1000
|
||||
TicksPerMs = Stopwatch.Frequency / 1000.0;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,6 +58,11 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// </summary>
|
||||
public HighSpeedDataService HighSpeedDataService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 驱动消息数据处理
|
||||
/// </summary>
|
||||
public HightDriveMsgService HightDriveMsgService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始Dbc文件写入
|
||||
/// </summary>
|
||||
@@ -288,7 +297,6 @@ namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
Console.WriteLine("Get CAN Speed Success!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -456,6 +464,24 @@ namespace CapMachine.Wpf.CanDrive
|
||||
set { _IsCycleSend = value; RaisePropertyChanged(); }
|
||||
}
|
||||
|
||||
private bool _IsSendOk;
|
||||
/// <summary>
|
||||
/// 发送报文是否OK
|
||||
/// </summary>
|
||||
public bool IsSendOk
|
||||
{
|
||||
get { return _IsSendOk; }
|
||||
set
|
||||
{
|
||||
if (_IsSendOk != value)
|
||||
{
|
||||
RaisePropertyChanged();
|
||||
_IsSendOk = value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 循环发送数据
|
||||
@@ -486,6 +512,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
/// <summary>
|
||||
/// 循环发送数据
|
||||
/// 这个是通过延迟处理的,发送的周期时间可能不精准
|
||||
/// </summary>
|
||||
public void StartCycleSendMsg()
|
||||
{
|
||||
@@ -543,6 +570,192 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
|
||||
|
||||
#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("定时发送延迟过大,重新校准时间");
|
||||
}
|
||||
|
||||
// 使用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);
|
||||
USB2CAN.CAN_MSG[] CanMsg = new USB2CAN.CAN_MSG[GroupMsg.Count()];
|
||||
for (int i = 0; i < GroupMsg.Count(); i++)
|
||||
{
|
||||
CanMsg[i] = new USB2CAN.CAN_MSG();
|
||||
CanMsg[i].Data = new Byte[64];
|
||||
}
|
||||
|
||||
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
|
||||
int Index = 0;
|
||||
//循环给MSG赋值数据
|
||||
foreach (var itemMsg in GroupMsg)
|
||||
{
|
||||
foreach (var itemSignal in itemMsg)
|
||||
{
|
||||
CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
|
||||
}
|
||||
CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
|
||||
CanMsg[Index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG));
|
||||
Index++;
|
||||
}
|
||||
|
||||
//通过DBC写入数据后生成CanMsg
|
||||
//将信号值填入CAN消息里面
|
||||
|
||||
//释放申请的临时缓冲区
|
||||
Marshal.FreeHGlobal(msgPtSend);
|
||||
|
||||
//发送CAN数据
|
||||
int SendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, CanMsg, (uint)CanMsg.Length);
|
||||
if (SendedNum >= 0)
|
||||
{
|
||||
//Console.WriteLine("Success send frames:{0}", SendedNum);
|
||||
IsSendOk=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine("Send CAN data failed! {0}", SendedNum);
|
||||
IsSendOk = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// 任务被取消,正常退出
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"CAN周期发送异常: {ex.Message}");
|
||||
// 短暂暂停避免异常情况下CPU占用过高
|
||||
await Task.Delay(10, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 确保在任何情况下(正常退出、异常、取消)都会停止计时器
|
||||
Stopwatcher.Stop();
|
||||
|
||||
// 清理其他可能的资源
|
||||
Console.WriteLine("CAN周期发送任务已结束,资源已清理");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保在任何情况下(正常退出、异常、取消)都会停止计时器
|
||||
Stopwatcher.Stop();
|
||||
}
|
||||
|
||||
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 修改停止发送的方法
|
||||
/// </summary>
|
||||
public void StopCycleSendMsg()
|
||||
{
|
||||
IsCycleSend = false;
|
||||
CycleSendCts?.Cancel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 循环获取CAN消息
|
||||
/// </summary>
|
||||
@@ -585,6 +798,15 @@ namespace CapMachine.Wpf.CanDrive
|
||||
MsgData = BitConverter.ToString(CanMsgBuffer[i].Data),
|
||||
Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
|
||||
});
|
||||
|
||||
//报文给高速报文处理服务
|
||||
await HightDriveMsgService.AddMessageAsync(new Models.HighSpeed.CommMsg()
|
||||
{
|
||||
Category = "CAN",
|
||||
MsgInfo = "0x" + CanMsgBuffer[i].ID.ToString("X8"),
|
||||
MsgData = BitConverter.ToString(CanMsgBuffer[i].Data),
|
||||
Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (CanNum == 0)
|
||||
|
||||
@@ -717,6 +717,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="LocalDll\" />
|
||||
<Folder Include="Models\HightDriveMsg\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\Images\favicon.ico">
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace CapMachine.Wpf.Models
|
||||
PauseTotalSecTime = 0;
|
||||
|
||||
//结束时间信息
|
||||
EndTimeInfo = ProStartDt.AddSeconds(PauseTotalSecTime).ToString("yyyy-MM-dd HH:mm:ss");
|
||||
EndTimeInfo = ProStartDt.AddSeconds(TotalProSec).ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +120,7 @@ namespace CapMachine.Wpf.Models
|
||||
//统计的暂停的时间
|
||||
var PauseTime = (PauseEndDt - PauseStartDt).TotalSeconds;
|
||||
PauseTotalSecTime = PauseTotalSecTime + (int)Math.Round(PauseTime);
|
||||
EndTimeInfo = ProStartDt.AddSeconds(PauseTotalSecTime).ToString("yyyy-MM-dd HH:mm:ss");
|
||||
EndTimeInfo = ProStartDt.AddSeconds(TotalProSec+ PauseTotalSecTime).ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
CycleTimer.Start();
|
||||
|
||||
|
||||
@@ -231,7 +231,8 @@ namespace CapMachine.Wpf.Services
|
||||
{
|
||||
ToomossCanDrive.IsCycleSend = true;
|
||||
ToomossCanDrive.CmdData = CmdData;
|
||||
ToomossCanDrive.StartCycleSendMsg();
|
||||
//ToomossCanDrive.StartCycleSendMsg();
|
||||
ToomossCanDrive.StartPrecisionCycleSendMsg();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace CapMachine.Wpf.Services
|
||||
/// </summary>
|
||||
public class HighSpeedDataService : BindableBase
|
||||
{
|
||||
public HighSpeedDataService(ConfigService configService, ILogService logService, IFreeSql FreeSql,IContainerProvider containerProvider)
|
||||
public HighSpeedDataService(ConfigService configService, ILogService logService, IFreeSql FreeSql, IContainerProvider containerProvider)
|
||||
{
|
||||
ConfigService = configService;
|
||||
LogService = logService;
|
||||
@@ -112,13 +112,13 @@ namespace CapMachine.Wpf.Services
|
||||
// new CommMsg() { Category = "CAN5",MsgInfo = "0x000003E3",Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),MsgData = "33 00 00 00 37 00 04 00"},
|
||||
//};
|
||||
|
||||
/// <summary>
|
||||
/// 周期定时器
|
||||
/// </summary>
|
||||
private System.Timers.Timer CycleTimer { get; set; }
|
||||
/// <summary>
|
||||
/// 周期定时器
|
||||
/// </summary>
|
||||
private System.Timers.Timer CycleTimer { get; set; }
|
||||
|
||||
|
||||
private bool _IsEnable=true;
|
||||
private bool _IsEnable = true;
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
@@ -208,7 +208,7 @@ namespace CapMachine.Wpf.Services
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CSV Helper 数据保存CSV
|
||||
|
||||
|
||||
424
CapMachine.Wpf/Services/HightDriveMsgService.cs
Normal file
424
CapMachine.Wpf/Services/HightDriveMsgService.cs
Normal file
@@ -0,0 +1,424 @@
|
||||
using CapMachine.Wpf.CanDrive;
|
||||
using CapMachine.Wpf.Models.HighSpeed;
|
||||
using Prism.Ioc;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace CapMachine.Wpf.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 高速驱动消息服务
|
||||
/// 负载高速的驱动数据的消息和转发
|
||||
/// </summary>
|
||||
public class HightDriveMsgService : BindableBase
|
||||
{
|
||||
|
||||
#region 字段和属性
|
||||
|
||||
private readonly ILogService LogService;
|
||||
|
||||
/// <summary>
|
||||
/// 接收数据Channel
|
||||
/// </summary>
|
||||
private readonly Channel<CommMsg> ReceiveChannel;
|
||||
|
||||
/// <summary>
|
||||
/// 转发数据Channel
|
||||
/// </summary>
|
||||
private readonly Channel<CommMsg> ForwardChannel;
|
||||
|
||||
/// <summary>
|
||||
/// 取消令牌
|
||||
/// </summary>
|
||||
private CancellationTokenSource TaskCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// 数据接受处理任务
|
||||
/// </summary>
|
||||
private Task ProcessTask;
|
||||
/// <summary>
|
||||
/// 数据转发处理任务
|
||||
/// </summary>
|
||||
private Task ForwardTask;
|
||||
|
||||
/// <summary>
|
||||
/// 显示的消息集合
|
||||
/// </summary>
|
||||
public ObservableCollection<CommMsg> DisplayMessages { get; set; } = new ObservableCollection<CommMsg>();
|
||||
|
||||
/// <summary>
|
||||
/// 最大显示消息数量
|
||||
/// </summary>
|
||||
public int MaxDisplayCount { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用CAN报文显示
|
||||
/// </summary>
|
||||
private bool _IsDisplayEnabled=true;
|
||||
public bool IsDisplayEnabled
|
||||
{
|
||||
get { return _IsDisplayEnabled; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref _IsDisplayEnabled, value);
|
||||
if (!value)
|
||||
{
|
||||
// 不显示时清空数据节省内存
|
||||
App.Current.Dispatcher.Invoke(() => DisplayMessages.Clear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用CAN数据转发
|
||||
/// </summary>
|
||||
private bool _IsForwardEnabled;
|
||||
public bool IsForwardEnabled
|
||||
{
|
||||
get { return _IsForwardEnabled; }
|
||||
set { SetProperty(ref _IsForwardEnabled, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造和初始化
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="containerProvider">容器服务提供者</param>
|
||||
/// <param name="logService">日志服务</param>
|
||||
public HightDriveMsgService(ILogService logService)
|
||||
{
|
||||
LogService = logService;
|
||||
|
||||
// 创建接收Channel,设置容量和溢出策略
|
||||
ReceiveChannel = Channel.CreateBounded<CommMsg>(new BoundedChannelOptions(100000)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest, // 满时删除旧数据
|
||||
SingleReader = true, // 单一读取者
|
||||
SingleWriter = false // 允许多个写入者
|
||||
});
|
||||
|
||||
// 创建转发Channel
|
||||
ForwardChannel = Channel.CreateBounded<CommMsg>(new BoundedChannelOptions(100000)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false
|
||||
});
|
||||
|
||||
// 初始化服务
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化服务
|
||||
/// </summary>
|
||||
private void Initialize()
|
||||
{
|
||||
TaskCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
// 启动数据处理任务
|
||||
ProcessTask = Task.Run(ProcessMessagesAsync);
|
||||
|
||||
// 启动数据转发任务
|
||||
ForwardTask = Task.Run(ForwardMessagesAsync);
|
||||
|
||||
//LogService.Info("HightDriveMsgService初始化完成");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 添加CAN消息进行处理
|
||||
/// </summary>
|
||||
/// <param name="message">CAN消息</param>
|
||||
/// <returns>是否添加成功</returns>
|
||||
public async Task<bool> AddMessageAsync(CommMsg message)
|
||||
{
|
||||
if (message == null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (IsDisplayEnabled)
|
||||
{
|
||||
// 添加到接收Channel
|
||||
await ReceiveChannel.Writer.WriteAsync(message, TaskCancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
|
||||
// 如果启用转发,也添加到转发Channel 需要判断目标的CAN是否已经打开,如果打开的话,则转发数据
|
||||
if (IsForwardEnabled)
|
||||
{
|
||||
await ForwardChannel.Writer.WriteAsync(message, TaskCancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error($"添加CAN消息失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加CAN消息
|
||||
/// </summary>
|
||||
/// <param name="messages">消息集合</param>
|
||||
/// <returns>成功添加的消息数量</returns>
|
||||
public async Task<int> AddMessagesAsync(IEnumerable<CommMsg> messages)
|
||||
{
|
||||
if (messages == null) return 0;
|
||||
|
||||
int successCount = 0;
|
||||
foreach (var message in messages)
|
||||
{
|
||||
if (await AddMessageAsync(message))
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return successCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置转发目标CAN设备
|
||||
/// </summary>
|
||||
/// <param name="targetDevice">目标设备</param>
|
||||
public void SetForwardTarget(ToomossCan targetDevice)
|
||||
{
|
||||
//_targetCanDevice = targetDevice;
|
||||
LogService.Info($"设置转发目标设备: {(targetDevice != null ? "成功" : "未设置")}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空显示消息
|
||||
/// </summary>
|
||||
public void ClearDisplayMessages()
|
||||
{
|
||||
App.Current.Dispatcher.Invoke(() => DisplayMessages.Clear());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 私有方法
|
||||
|
||||
/// <summary>
|
||||
/// 批量处理消息提高效率
|
||||
/// </summary>
|
||||
private List<CommMsg> MessageBatch { get; set; } = new List<CommMsg>();
|
||||
|
||||
/// <summary>
|
||||
/// 接受的批次数量
|
||||
/// </summary>
|
||||
private int ReciveBatchCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转发的批次数量
|
||||
/// </summary>
|
||||
private int ForwardBatchCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异步处理接收到的消息
|
||||
/// </summary>
|
||||
private async Task ProcessMessagesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (await ReceiveChannel.Reader.WaitToReadAsync(TaskCancellationTokenSource.Token))
|
||||
{
|
||||
// 清空列表但保留容量
|
||||
MessageBatch.Clear();
|
||||
|
||||
// 读取最多100条消息或直到没有更多消息
|
||||
ReciveBatchCount = 0;
|
||||
while (ReciveBatchCount < 30 && ReceiveChannel.Reader.TryRead(out CommMsg message))
|
||||
{
|
||||
MessageBatch.Add(message);
|
||||
ReciveBatchCount++;
|
||||
}
|
||||
|
||||
if (MessageBatch.Count > 0 && IsDisplayEnabled)
|
||||
{
|
||||
// 在UI线程更新显示
|
||||
await App.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
foreach (var msg in MessageBatch)
|
||||
{
|
||||
DisplayMessages.Add(msg);
|
||||
|
||||
// 控制显示消息数量
|
||||
while (DisplayMessages.Count > MaxDisplayCount)
|
||||
{
|
||||
DisplayMessages.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
// 避免CPU过度使用
|
||||
if (ReciveBatchCount == 0)
|
||||
{
|
||||
await Task.Delay(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
// 服务被取消,正常退出
|
||||
LogService.Error($" 服务被取消,正常退出: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error($"消息处理任务异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步转发消息
|
||||
/// </summary>
|
||||
private async Task ForwardMessagesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (await ForwardChannel.Reader.WaitToReadAsync(TaskCancellationTokenSource.Token))
|
||||
{
|
||||
//// 只有在启用转发且目标设备可用时才处理 && _targetCanDevice != null && _targetCanDevice.OpenState
|
||||
//if (IsForwardEnabled)
|
||||
//{
|
||||
// // 批量收集转发消息
|
||||
// List<USB2CAN.CAN_MSG> canMessages = new List<USB2CAN.CAN_MSG>();
|
||||
// ForwardBatchCount = 0;
|
||||
|
||||
// // 一次最多处理50条消息
|
||||
// while (ForwardBatchCount < 50 && ForwardChannel.Reader.TryRead(out CommMsg message))
|
||||
// {
|
||||
// if (message.Category == "CAN" && !string.IsNullOrEmpty(message.MsgInfo))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // 转换为CAN_MSG格式
|
||||
// USB2CAN.CAN_MSG canMsg = new USB2CAN.CAN_MSG();
|
||||
// canMsg.Data = new byte[64];
|
||||
|
||||
// // 解析CAN ID
|
||||
// string msgIdHex = message.MsgInfo.Replace("0x", "");
|
||||
// canMsg.ID = Convert.ToUInt32(msgIdHex, 16);
|
||||
|
||||
// // 解析数据
|
||||
// string[] dataBytes = message.MsgData.Split('-');
|
||||
// canMsg.DataLen = (byte)dataBytes.Length;
|
||||
|
||||
// for (int i = 0; i < dataBytes.Length && i < 64; i++)
|
||||
// {
|
||||
// canMsg.Data[i] = Convert.ToByte(dataBytes[i], 16);
|
||||
// }
|
||||
|
||||
// canMessages.Add(canMsg);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// LogService.Error($"转换CAN消息格式失败: {ex.Message}");
|
||||
// }
|
||||
// }
|
||||
|
||||
// ForwardBatchCount++;
|
||||
// }
|
||||
|
||||
// // 批量发送转发消息
|
||||
// if (canMessages.Count > 0)
|
||||
// {
|
||||
// //// 在这里调用设备API发送消息
|
||||
// //int sendResult = USB2CAN.CAN_SendMsg(
|
||||
// // _targetCanDevice.DevHandle,
|
||||
// // _targetCanDevice.WriteCANIndex,
|
||||
// // canMessages.ToArray(),
|
||||
// // (uint)canMessages.Count);
|
||||
|
||||
// //if (sendResult < 0)
|
||||
// //{
|
||||
// // LogService.Error($"CAN消息转发失败: 错误码={sendResult}");
|
||||
// //}
|
||||
// //else
|
||||
// //{
|
||||
// // LogService.Debug($"成功转发{sendResult}条CAN消息");
|
||||
// //}
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// // 不转发时,清空队列减少内存占用
|
||||
// while (ForwardChannel.Reader.TryRead(out _)) { }
|
||||
// await Task.Delay(10);
|
||||
//}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 服务被取消,正常退出
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error($"消息转发任务异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable 实现
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 发送取消信号
|
||||
TaskCancellationTokenSource?.Cancel();
|
||||
|
||||
// 等待任务完成
|
||||
Task.WaitAll(new[] { ProcessTask, ForwardTask }, 1000);
|
||||
}
|
||||
catch { /* 忽略取消异常 */ }
|
||||
finally
|
||||
{
|
||||
// 释放资源
|
||||
TaskCancellationTokenSource?.Dispose();
|
||||
TaskCancellationTokenSource = null;
|
||||
|
||||
// 完成写入器,表示不再接受新数据
|
||||
ReceiveChannel.Writer.Complete();
|
||||
ForwardChannel.Writer.Complete();
|
||||
|
||||
// 清空显示数据
|
||||
App.Current.Dispatcher.Invoke(() => DisplayMessages.Clear());
|
||||
|
||||
|
||||
|
||||
LogService.Info("HightDriveMsgService资源已释放");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace CapMachine.Wpf.ViewModels
|
||||
public CANConfigViewModel(IDialogService dialogService, IFreeSql freeSql,
|
||||
IEventAggregator eventAggregator, IRegionManager regionManager, SysRunService sysRunService,
|
||||
ComActionService actionService, LogicRuleService logicRuleService,
|
||||
ConfigService configService, CanDriveService canDriveService,
|
||||
ConfigService configService, CanDriveService canDriveService,HightDriveMsgService hightDriveMsgService,
|
||||
IMapper mapper, MachineRtDataService machineRtDataService)
|
||||
{
|
||||
//LogService = logService;
|
||||
@@ -50,6 +50,7 @@ namespace CapMachine.Wpf.ViewModels
|
||||
LogicRuleService = logicRuleService;
|
||||
ConfigService = configService;
|
||||
CanDriveService = canDriveService;
|
||||
HightDriveMsgService = hightDriveMsgService;
|
||||
Mapper = mapper;
|
||||
this.MachineRtDataService = machineRtDataService;
|
||||
|
||||
@@ -96,6 +97,7 @@ namespace CapMachine.Wpf.ViewModels
|
||||
public LogicRuleService LogicRuleService { get; }
|
||||
public ConfigService ConfigService { get; }
|
||||
public CanDriveService CanDriveService { get; }
|
||||
public HightDriveMsgService HightDriveMsgService { get; }
|
||||
public IMapper Mapper { get; }
|
||||
private MachineRtDataService MachineRtDataService { get; }
|
||||
|
||||
|
||||
@@ -910,114 +910,112 @@
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="2" Margin="3">
|
||||
<!--<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>-->
|
||||
<TabControl>
|
||||
<TabItem Header="DBC数据表">
|
||||
<DataGrid
|
||||
x:Name="DbcDatagrid"
|
||||
Margin="0,2,0,0"
|
||||
AutoGenerateColumns="False"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1"
|
||||
CanUserAddRows="False"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding ListCanDbcModel}"
|
||||
SelectionMode="Extended"
|
||||
SelectionUnit="FullRow">
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="ContextMenu">
|
||||
<Setter.Value>
|
||||
<ContextMenu>
|
||||
<MenuItem
|
||||
Command="{Binding Source={StaticResource Proxy}, Path=Data.DataGridMenuCmd}"
|
||||
CommandParameter="Write"
|
||||
Header="添加到写入操作" />
|
||||
<MenuItem
|
||||
Command="{Binding Source={StaticResource Proxy}, Path=Data.DataGridMenuCmd}"
|
||||
CommandParameter="Read"
|
||||
Header="添加到读取操作" />
|
||||
</ContextMenu>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSeletedInfo}" Value="1">
|
||||
<Setter Property="Background" Value="LightGreen" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsSeletedInfo}" Value="2">
|
||||
<Setter Property="Background" Value="SkyBlue" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
|
||||
<!--<materialDesign:Card
|
||||
Margin="0,1,0,3"
|
||||
Background="{DynamicResource MaterialDesignLightBackground}"
|
||||
Foreground="{DynamicResource PrimaryHueLightForegroundBrush}"
|
||||
UniformCornerRadius="5">
|
||||
<StackPanel Margin="5" Orientation="Horizontal">
|
||||
<Button
|
||||
Margin="5,0"
|
||||
Content="连接CAN"
|
||||
Foreground="White" />
|
||||
<Button
|
||||
Margin="5,0"
|
||||
Content="关闭CAN"
|
||||
Foreground="White" />
|
||||
<Button
|
||||
Margin="5,0"
|
||||
Content="保存配置"
|
||||
Foreground="White" />
|
||||
</StackPanel>
|
||||
</materialDesign:Card>-->
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding MsgId}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="消息ID" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
|
||||
<DataGrid
|
||||
x:Name="DbcDatagrid"
|
||||
AutoGenerateColumns="False"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1"
|
||||
CanUserAddRows="False"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding ListCanDbcModel}"
|
||||
SelectionMode="Extended"
|
||||
SelectionUnit="FullRow">
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="ContextMenu">
|
||||
<Setter.Value>
|
||||
<ContextMenu>
|
||||
<MenuItem
|
||||
Command="{Binding Source={StaticResource Proxy}, Path=Data.DataGridMenuCmd}"
|
||||
CommandParameter="Write"
|
||||
Header="添加到写入操作" />
|
||||
<MenuItem
|
||||
Command="{Binding Source={StaticResource Proxy}, Path=Data.DataGridMenuCmd}"
|
||||
CommandParameter="Read"
|
||||
Header="添加到读取操作" />
|
||||
</ContextMenu>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSeletedInfo}" Value="1">
|
||||
<Setter Property="Background" Value="LightGreen" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsSeletedInfo}" Value="2">
|
||||
<Setter Property="Background" Value="SkyBlue" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGridTextColumn Binding="{Binding MsgName}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="消息名称" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding MsgId}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="消息ID" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding SignalName}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="信号名称" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
|
||||
<DataGridTextColumn Binding="{Binding MsgName}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="消息名称" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<!--<DataGridTextColumn Binding="{Binding SignalDesc}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="信号描述" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>-->
|
||||
|
||||
<DataGridTextColumn Binding="{Binding SignalName}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="信号名称" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<!--<DataGridTextColumn Binding="{Binding SignalUnit}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="信号单位" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>-->
|
||||
|
||||
<!--<DataGridTextColumn Binding="{Binding SignalDesc}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="信号描述" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>-->
|
||||
<DataGridTextColumn Binding="{Binding SignalRtValue}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="实时值" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
|
||||
<!--<DataGridTextColumn Binding="{Binding SignalUnit}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="信号单位" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>-->
|
||||
</DataGrid.Columns>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="SelectionChanged">
|
||||
<prism:InvokeCommandAction Command="{Binding DbcGridSelectionChangedCmd}" CommandParameter="{Binding ElementName=DbcDatagrid, Path=SelectedItem}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<DataGridTextColumn Binding="{Binding SignalRtValue}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock FontWeight="Bold" Text="实时值" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
<TabItem Header="CAN报文">
|
||||
<ListView
|
||||
ItemsSource="{Binding HightDriveMsgService.DisplayMessages}"
|
||||
ScrollViewer.IsDeferredScrollingEnabled="True"
|
||||
VirtualizingPanel.CacheLength="1,1"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
VirtualizingPanel.VirtualizationMode="Recycling">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn DisplayMemberBinding="{Binding MsgInfo}" Header="报文ID" />
|
||||
<GridViewColumn DisplayMemberBinding="{Binding Category}" Header="报文名称" />
|
||||
<GridViewColumn DisplayMemberBinding="{Binding MsgData}" Header="报文数据" />
|
||||
<GridViewColumn DisplayMemberBinding="{Binding Time}" Header="报文时间" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
</DataGrid.Columns>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="SelectionChanged">
|
||||
<prism:InvokeCommandAction Command="{Binding DbcGridSelectionChangedCmd}" CommandParameter="{Binding ElementName=DbcDatagrid, Path=SelectedItem}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
</DataGrid>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user