CAN 调度表同步 25002
This commit is contained in:
@@ -1,9 +1,4 @@
|
||||
using CapMachine.Wpf.Dtos;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
@@ -12,6 +7,12 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// </summary>
|
||||
public class CanCmdData
|
||||
{
|
||||
/// <summary>
|
||||
/// 指令数据改变Handler
|
||||
/// 改变发送消息名称
|
||||
/// </summary>
|
||||
public event EventHandler<string>? CanCmdDataChangedHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 配置项名称-比如转速、功率限制等
|
||||
/// </summary>
|
||||
@@ -27,11 +28,38 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// </summary>
|
||||
public string? SignalName { get; set; }
|
||||
|
||||
private double _SignalCmdValue;
|
||||
/// <summary>
|
||||
/// 指令值
|
||||
/// 没有的话,则给默认值
|
||||
/// </summary>
|
||||
public double SignalCmdValue { get; set; }
|
||||
public double SignalCmdValue
|
||||
{
|
||||
get { return _SignalCmdValue; }
|
||||
set
|
||||
{
|
||||
if (_SignalCmdValue != value)
|
||||
{
|
||||
_SignalCmdValue = value;
|
||||
if (CanCmdDataChangedHandler != null)
|
||||
{
|
||||
CanCmdDataChangedHandler!.Invoke(this, MsgName!);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 指令值
|
||||
///// 没有的话,则给默认值
|
||||
///// </summary>
|
||||
//public double SignalCmdValue { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 逻辑规则Id
|
||||
///// </summary>
|
||||
//public long LogicRuleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CanLinConfig的逻辑转换规则
|
||||
|
||||
@@ -47,8 +47,18 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private string? _SignalRtValue = "--";
|
||||
/// <summary>
|
||||
/// 信号实时值
|
||||
/// 信号实时值(供 UI 绑定显示)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 仅当文本发生变化时才触发通知,避免无谓 UI 刷新。
|
||||
/// - 若期望进一步降低分配开销,建议在调用方(接收循环)先做“数值层去重”,
|
||||
/// 即:仅在数值变化(可设置容差)时才调用 ToString() 格式化并赋值到本属性。
|
||||
/// 这样可以显著减少字符串分配与 GC 压力。
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// // 示例:数值层去重后再格式化(接收循环中使用)
|
||||
/// // if (Math.Abs(newVal - lastVal) > 1e-3) { model.SignalRtValue = newVal.ToString("F3"); lastVal = newVal; }
|
||||
/// </example>
|
||||
public string? SignalRtValue
|
||||
{
|
||||
get { return _SignalRtValue; }
|
||||
@@ -62,21 +72,56 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
}
|
||||
|
||||
private StringBuilder _SignalRtValueSb = new StringBuilder(10);
|
||||
private StringBuilder _SignalRtValueSb = new StringBuilder(16);
|
||||
/// <summary>
|
||||
/// 信号实时值 StringBuilder
|
||||
/// 信号实时值的可变文本缓冲接口。
|
||||
/// 设计目的:
|
||||
/// - 避免外部传入的 StringBuilder 被直接保存(引用别名问题),统一复制文本到内部缓冲 _SignalRtValueSb;
|
||||
/// - 仅当文本内容变化时才更新 SignalRtValue 并 RaisePropertyChanged,减少 UI 抖动与无谓刷新;
|
||||
/// - 对 value == null 做防御:清空内部缓冲并置空显示文本(如需显示 “--” 可按需替换)。
|
||||
/// 使用方式:
|
||||
/// - 接收环路可重复复用同一个外部 StringBuilder 作为临时缓存,调用本属性进行更新不会产生实例共享风险。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 注意事项:
|
||||
/// 1) 本属性会复制文本,不会保存外部 StringBuilder 引用;这可避免多个模型共享同一实例导致的数据串扰和并发问题。
|
||||
/// 2) 若你的 UI 需要统一的“空值占位符”,可将 setter 中的空字符串替换为 "--",与字段初始值保持一致。
|
||||
/// 3) 线程模型:建议仍在 UI 线程更新以避免跨线程通知问题;如需后台线程更新,请确保有合适的调度(Dispatcher/同步上下文)。
|
||||
/// 4) 性能建议:结合 SignalRtValue 的备注,在进入本 setter 前尽量做数值层去重,进一步减少字符串分配。
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// // 示例:接收循环中复用临时缓存
|
||||
/// // var tmp = new StringBuilder(32);
|
||||
/// // ... 填充 tmp ...
|
||||
/// // model.SignalRtValueSb = tmp; // 本属性会复制 tmp 的内容,安全且不会产生实例共享
|
||||
/// </example>
|
||||
public StringBuilder SignalRtValueSb
|
||||
{
|
||||
get { return _SignalRtValueSb; }
|
||||
set
|
||||
{
|
||||
//if (_SignalRtValueSb != value)
|
||||
//{
|
||||
SignalRtValue = value.ToString();
|
||||
_SignalRtValueSb = value;
|
||||
//}
|
||||
// 防御:若外部传入 null,清空内部状态并复位显示文本
|
||||
// 注意:此处将显示文本设为空字符串 "",如果希望与初始占位符 "--" 一致,可改为 SignalRtValue = "--"。
|
||||
if (value == null)
|
||||
{
|
||||
if (_SignalRtValue != string.Empty)
|
||||
{
|
||||
_SignalRtValueSb.Clear();
|
||||
SignalRtValue = string.Empty;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 复制策略:不保存外部 StringBuilder 的引用,改为复制其当前文本内容
|
||||
// 这样可避免多个模型共享同一 StringBuilder 实例导致的数据串扰与线程安全问题
|
||||
var str = value.ToString();
|
||||
// 仅当文本内容确实发生变化时,才更新内部缓冲与绑定属性,减少无谓的 UI 刷新与字符串分配
|
||||
if (!string.Equals(_SignalRtValue, str, StringComparison.Ordinal))
|
||||
{
|
||||
_SignalRtValueSb.Clear();
|
||||
_SignalRtValueSb.Append(str);
|
||||
SignalRtValue = str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +133,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
public int IsSeletedInfo
|
||||
{
|
||||
get { return _IsSeletedInfo; }
|
||||
set { _IsSeletedInfo = value;RaisePropertyChanged(); }
|
||||
set { _IsSeletedInfo = value; RaisePropertyChanged(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
using CapMachine.Core;
|
||||
using CapMachine.Model.CANLIN;
|
||||
using CapMachine.Wpf.Dtos;
|
||||
using CapMachine.Wpf.Models.Tag;
|
||||
using CapMachine.Wpf.Dtos;
|
||||
using CapMachine.Wpf.Models;
|
||||
using CapMachine.Wpf.Services;
|
||||
using HslCommunication;
|
||||
using ImTools;
|
||||
using NLog;
|
||||
using NPOI.OpenXmlFormats.Wordprocessing;
|
||||
using Prism.Ioc;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using static CapMachine.Wpf.CanDrive.USB2CAN;
|
||||
|
||||
namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
@@ -34,11 +22,13 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// <summary>
|
||||
/// 实例化函数
|
||||
/// </summary>
|
||||
public ToomossCan(IContainerProvider containerProvider)
|
||||
public ToomossCan(IContainerProvider containerProvider, ILogService logService)
|
||||
{
|
||||
ContainerProvider = containerProvider;
|
||||
HighSpeedDataService = ContainerProvider.Resolve<HighSpeedDataService>();
|
||||
LoggerService = ContainerProvider.Resolve<ILogService>();
|
||||
LoggerService = logService;
|
||||
|
||||
//monitorValueLog = new MonitorValueLog(logService, "SetSignalValue", "SyncValueToCanMsg", "ret", "CanNum", "SyncCANMsgToValue");
|
||||
|
||||
//Stopwatch.Frequency表示高精度计时器每秒的计数次数(ticks/秒)每毫秒的ticks数 = 每秒的ticks数 ÷ 1000
|
||||
TicksPerMs = Stopwatch.Frequency / 1000.0;
|
||||
@@ -56,6 +46,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
GetCANConfig();
|
||||
InitCAN();
|
||||
|
||||
//LoggerService.Info($"Start CAN Drive");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -500,6 +491,32 @@ namespace CapMachine.Wpf.CanDrive
|
||||
StringBuilder ValueSb = new StringBuilder(16);
|
||||
double[] ValueDouble = new double[5];
|
||||
|
||||
// 接收缓冲池(重用,避免每轮分配)
|
||||
private IntPtr RecvMsgBufferPtr = IntPtr.Zero;
|
||||
private int RecvMsgBufferCapacity = 1024;
|
||||
private readonly int CanMsgSize = Marshal.SizeOf(typeof(USB2CAN.CAN_MSG));
|
||||
|
||||
// 名称 StringBuilder 缓存(DBC 调用复用,避免频繁分配)
|
||||
private readonly Dictionary<string, StringBuilder> MsgNameSBCache = new Dictionary<string, StringBuilder>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, StringBuilder> SigNameSBCache = new Dictionary<string, StringBuilder>(StringComparer.Ordinal);
|
||||
|
||||
// 控制台调试输出开关(默认关闭,防止日志风暴)
|
||||
public bool EnableConsoleDebugLog { get; set; } = false;
|
||||
|
||||
// 保护接收缓冲的并发锁(接收读与关闭释放之间的互斥)
|
||||
private readonly object RecvBufferSync = new object();
|
||||
|
||||
private StringBuilder GetCachedSB(Dictionary<string, StringBuilder> cache, string key)
|
||||
{
|
||||
key ??= string.Empty;
|
||||
if (cache.TryGetValue(key, out var sb)) return sb;
|
||||
var nsb = new StringBuilder(key);
|
||||
cache[key] = nsb;
|
||||
return nsb;
|
||||
}
|
||||
private StringBuilder GetMsgSB(string key) => GetCachedSB(MsgNameSBCache, key);
|
||||
private StringBuilder GetSigSB(string key) => GetCachedSB(SigNameSBCache, key);
|
||||
|
||||
private bool _IsSendOk;
|
||||
/// <summary>
|
||||
/// 发送报文是否OK
|
||||
@@ -598,6 +615,234 @@ namespace CapMachine.Wpf.CanDrive
|
||||
});
|
||||
}
|
||||
|
||||
#region 定时器发送报文数据
|
||||
|
||||
// 定时器发送:采用单次触发+校准的方式避免周期漂移,减少长时间运行误差
|
||||
// 说明:不修改 StartPrecisionCycleSendMsg 的任何内容;此处仅实现定时器版本
|
||||
|
||||
// 定时器与资源
|
||||
private System.Threading.Timer _sendTimer;
|
||||
private readonly object _sendTimerSync = new object();
|
||||
private volatile int _sendTimerRunningFlag = 0; // 防止回调重入
|
||||
private int _sendTimerPeriodMs = 100; // 默认100ms
|
||||
private readonly Stopwatch _sendTimerWatch = new Stopwatch();
|
||||
private long _sendTimerNextTicks;
|
||||
|
||||
// 发送缓冲重用,减少分配
|
||||
private USB2CAN.CAN_MSG[] _timerCanMsgBuffer;
|
||||
private IntPtr _timerMsgPtr = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// 启动基于定时器的周期发送(毫秒)。支持典型的50/100/200ms等周期。
|
||||
/// 使用单次触发+绝对时间校准的算法,尽量减少漂移;并使用重用缓冲降低内存分配。
|
||||
/// </summary>
|
||||
/// <param name="periodMs">发送周期(毫秒)</param>
|
||||
public void StartTimerCycleSendMsg(int periodMs)
|
||||
{
|
||||
if (periodMs <= 0) periodMs = 1;
|
||||
|
||||
lock (_sendTimerSync)
|
||||
{
|
||||
// 先停止旧的
|
||||
StopTimerCycleSendMsg_NoLock();
|
||||
|
||||
_sendTimerPeriodMs = periodMs;
|
||||
IsCycleSend = true;
|
||||
|
||||
if (_timerMsgPtr == IntPtr.Zero)
|
||||
{
|
||||
_timerMsgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
|
||||
}
|
||||
|
||||
_sendTimerWatch.Restart();
|
||||
_sendTimerNextTicks = _sendTimerWatch.ElapsedTicks + (long)(_sendTimerPeriodMs * TicksPerMs);
|
||||
|
||||
// 使用单次触发模式,每次回调后手动调度下一次,便于按绝对时间校准
|
||||
_sendTimer = new System.Threading.Timer(SendTimerCallback, null, 0, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动基于定时器的周期发送(使用 SendCycle 属性作为周期参数)
|
||||
/// </summary>
|
||||
public void StartTimerCycleSendMsg()
|
||||
{
|
||||
StartTimerCycleSendMsg((int)SendCycle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 便捷方法:启动50ms周期发送
|
||||
/// </summary>
|
||||
public void StartTimerCycleSendMsg50ms() => StartTimerCycleSendMsg(50);
|
||||
|
||||
/// <summary>
|
||||
/// 便捷方法:启动100ms周期发送
|
||||
/// </summary>
|
||||
public void StartTimerCycleSendMsg100ms() => StartTimerCycleSendMsg(100);
|
||||
|
||||
/// <summary>
|
||||
/// 便捷方法:启动200ms周期发送
|
||||
/// </summary>
|
||||
public void StartTimerCycleSendMsg200ms() => StartTimerCycleSendMsg(200);
|
||||
|
||||
/// <summary>
|
||||
/// 停止定时器周期发送并释放资源
|
||||
/// </summary>
|
||||
public void StopTimerCycleSendMsg()
|
||||
{
|
||||
lock (_sendTimerSync)
|
||||
{
|
||||
IsCycleSend = false;
|
||||
StopTimerCycleSendMsg_NoLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void StopTimerCycleSendMsg_NoLock()
|
||||
{
|
||||
var t = _sendTimer;
|
||||
_sendTimer = null;
|
||||
if (t != null)
|
||||
{
|
||||
try { t.Dispose(); } catch { }
|
||||
}
|
||||
|
||||
// 等待在途回调结束,避免释放资源时发生竞争
|
||||
System.Threading.SpinWait.SpinUntil(() => System.Threading.Interlocked.CompareExchange(ref _sendTimerRunningFlag, 0, 0) == 0, 200);
|
||||
|
||||
_sendTimerWatch.Stop();
|
||||
|
||||
if (_timerMsgPtr != IntPtr.Zero)
|
||||
{
|
||||
try { Marshal.FreeHGlobal(_timerMsgPtr); } catch { } finally { _timerMsgPtr = IntPtr.Zero; }
|
||||
}
|
||||
_timerCanMsgBuffer = null; // 让GC回收
|
||||
IsSendOk = false;
|
||||
}
|
||||
|
||||
// 定时器回调(单次触发);回调结束时会按绝对时间校准下一次触发
|
||||
private void SendTimerCallback(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsCycleSend) return;
|
||||
|
||||
// 防止回调重入(当某次发送耗时超过周期时避免并发)
|
||||
if (System.Threading.Interlocked.Exchange(ref _sendTimerRunningFlag, 1) == 1)
|
||||
{
|
||||
ScheduleNextTick();
|
||||
return;
|
||||
}
|
||||
|
||||
var localCmd = CmdData; // 快照引用,避免枚举时被替换
|
||||
if (localCmd == null || localCmd.Count == 0)
|
||||
{
|
||||
IsSendOk = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 依据消息名进行分组(与 StartPrecisionCycleSendMsg 的发送逻辑一致)
|
||||
IEnumerable<IGrouping<string, CanCmdData>> groupMsg;
|
||||
try
|
||||
{
|
||||
groupMsg = localCmd.GroupBy(x => x.MsgName).ToList(); // ToList 避免重复枚举
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 罕见并发异常,跳过本次
|
||||
IsSendOk = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int msgCount = groupMsg.Count();
|
||||
if (msgCount <= 0)
|
||||
{
|
||||
IsSendOk = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保发送缓冲容量,尽量复用,减少频繁分配
|
||||
if (_timerCanMsgBuffer == null || _timerCanMsgBuffer.Length != msgCount)
|
||||
{
|
||||
_timerCanMsgBuffer = new USB2CAN.CAN_MSG[msgCount];
|
||||
for (int i = 0; i < msgCount; i++)
|
||||
{
|
||||
_timerCanMsgBuffer[i] = new USB2CAN.CAN_MSG();
|
||||
_timerCanMsgBuffer[i].Data = new byte[64];
|
||||
}
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
foreach (var itemMsg in groupMsg)
|
||||
{
|
||||
foreach (var itemSignal in itemMsg)
|
||||
{
|
||||
// 与 StartPrecisionCycleSendMsg 保持一致的调用形态,避免与其他线程共享缓存的并发问题
|
||||
CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
|
||||
}
|
||||
|
||||
// 将信号同步到 CAN 帧
|
||||
CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), _timerMsgPtr);
|
||||
_timerCanMsgBuffer[index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(_timerMsgPtr, typeof(USB2CAN.CAN_MSG));
|
||||
index++;
|
||||
}
|
||||
|
||||
// 发送 CAN 数据
|
||||
int sendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, _timerCanMsgBuffer, (uint)_timerCanMsgBuffer.Length);
|
||||
IsSendOk = sendedNum >= 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsSendOk = false;
|
||||
LoggerService?.Info($"定时器周期发送CAN数据异常: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 标记回调完成并调度下一次
|
||||
System.Threading.Interlocked.Exchange(ref _sendTimerRunningFlag, 0);
|
||||
ScheduleNextTick();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算并调度下一次触发时间(基于绝对时间进行校准,减少漂移)
|
||||
private void ScheduleNextTick()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsCycleSend) return;
|
||||
var timer = _sendTimer;
|
||||
if (timer == null) return;
|
||||
|
||||
long now = _sendTimerWatch.ElapsedTicks;
|
||||
long periodTicks = (long)(_sendTimerPeriodMs * TicksPerMs);
|
||||
|
||||
if (_sendTimerNextTicks == 0)
|
||||
{
|
||||
_sendTimerNextTicks = now + periodTicks;
|
||||
}
|
||||
else if (now >= _sendTimerNextTicks)
|
||||
{
|
||||
// 若已错过计划时间,跳至最近的未来时刻,避免累计漂移
|
||||
long missed = (now - _sendTimerNextTicks) / periodTicks + 1;
|
||||
_sendTimerNextTicks += missed * periodTicks;
|
||||
}
|
||||
|
||||
long dueTicks = _sendTimerNextTicks - now;
|
||||
int dueMs = dueTicks <= 0 ? 0 : (int)(dueTicks / TicksPerMs);
|
||||
|
||||
timer.Change(dueMs, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 计时器已被释放,忽略
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerService?.Info($"定时器调度异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 精确发送报文数据
|
||||
|
||||
@@ -846,7 +1091,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// <summary>
|
||||
/// 定时更新时间
|
||||
/// </summary>
|
||||
private int UpdateCycle = 100;
|
||||
private int UpdateCycle { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// CNA 调度表的配置信息
|
||||
@@ -855,6 +1100,12 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
/// <summary>
|
||||
/// 监控数据
|
||||
/// 查找问题用,平时不用
|
||||
/// </summary>
|
||||
//public MonitorValueLog monitorValueLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新数据 测试用废弃了
|
||||
/// </summary>
|
||||
@@ -1121,7 +1372,6 @@ namespace CapMachine.Wpf.CanDrive
|
||||
// Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex:{(byte)itemMsgSchConfig.SchTabIndex} -- MsgIndex:{(byte)(itemMsgSchConfig.MsgIndex)}");
|
||||
// //return;
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
|
||||
@@ -1138,6 +1388,126 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 加载要发送的数据
|
||||
/// 一般是激活后才注册事件
|
||||
/// </summary>
|
||||
/// <param name="cmdData"></param>
|
||||
public void LoadCmdDataToDrive(List<CanCmdData> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指令数据发生变化执行方法
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void CmdData_CanCmdDataChangedHandler(object? sender, string e)
|
||||
{
|
||||
UpdateSchDataByCmdDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指令数据发生变化执行更新调度表锁
|
||||
/// </summary>
|
||||
private readonly object SchUpdateLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 指令数据发生变化执行方法
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
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(USB2CAN.CAN_MSG)));
|
||||
int CycleUpdateIndex = 0;
|
||||
//循环给MSG赋值数据,顺序是固定的,跟初始时设置是一样的
|
||||
foreach (var itemMsg in GroupMsg)
|
||||
{
|
||||
foreach (var itemSignal in itemMsg)
|
||||
{
|
||||
//itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用
|
||||
var SetSignalValue = CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
|
||||
//monitorValueLog.UpdateValue1(SetSignalValue);
|
||||
}
|
||||
var SyncValueToCanMsg = CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
|
||||
//monitorValueLog.UpdateValue2(SyncValueToCanMsg);
|
||||
SchCanMsg[CycleUpdateIndex] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_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 = USB2CAN.CAN_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
|
||||
if (ret == USB2CAN.CAN_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
|
||||
|
||||
/// <summary>
|
||||
@@ -1145,101 +1515,121 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// </summary>
|
||||
public void StartCycleReviceCanMsg()
|
||||
{
|
||||
// 防止重复启动,若已有任务在运行则直接返回
|
||||
if (CycleReviceTask != null && !CycleReviceTask.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//monitorValueLog
|
||||
CycleReviceTask = Task.Run(async () =>
|
||||
{
|
||||
while (IsCycleRevice)
|
||||
try
|
||||
{
|
||||
await Task.Delay(ReviceCycle);
|
||||
try
|
||||
while (IsCycleRevice)
|
||||
{
|
||||
//另外一个CAN通道读取数据
|
||||
USB2CAN.CAN_MSG[] CanMsgBuffer = new USB2CAN.CAN_MSG[1024];
|
||||
//申请数据缓冲区
|
||||
IntPtr msgPtRead = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)) * CanMsgBuffer.Length);
|
||||
int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPtRead, CanMsgBuffer.Length);
|
||||
//int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, 1, msgPtRead, CanMsgBuffer.Length);//测试用,CAN卡 CAN1和CAN2 短接时测试用
|
||||
if (CanNum > 0)
|
||||
await Task.Delay(ReviceCycle);
|
||||
try
|
||||
{
|
||||
IsReviceOk = true;
|
||||
Console.WriteLine("Read CanMsgNum = {0}", CanNum);
|
||||
for (int i = 0; i < CanNum; i++)
|
||||
// 另一个CAN通道读取数据(与 CloseDevice 释放互斥,保护指针安全)
|
||||
IntPtr msgPtRead;
|
||||
int CanNum;
|
||||
lock (RecvBufferSync)
|
||||
{
|
||||
//CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)msgPtRead + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG)); //有溢出报错
|
||||
CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)(msgPtRead + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG));
|
||||
|
||||
Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
|
||||
Console.WriteLine("CanMsg[{0}].TimeStamp = {1}", i, CanMsgBuffer[i].TimeStamp);
|
||||
Console.Write("CanMsg[{0}].Data = ", i);
|
||||
for (int j = 0; j < CanMsgBuffer[i].DataLen; j++)
|
||||
if (RecvMsgBufferPtr == IntPtr.Zero)
|
||||
{
|
||||
Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
|
||||
RecvMsgBufferPtr = Marshal.AllocHGlobal(CanMsgSize * RecvMsgBufferCapacity);
|
||||
LoggerService.Info("申请 RecvMsgBufferPtr");
|
||||
}
|
||||
Console.WriteLine("");
|
||||
|
||||
//报文给高速记录的服务
|
||||
HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg()
|
||||
msgPtRead = RecvMsgBufferPtr;
|
||||
CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPtRead, RecvMsgBufferCapacity);
|
||||
//int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, 1, msgPtRead, RecvMsgBufferCapacity);//测试用,CAN卡 CAN1和CAN2 短接时测试用
|
||||
//monitorValueLog.UpdateValue4(CanNum);
|
||||
if (CanNum > 0)
|
||||
{
|
||||
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")
|
||||
});
|
||||
IsReviceOk = true;
|
||||
if (EnableConsoleDebugLog) Console.WriteLine("Read CanMsgNum = {0}", CanNum);
|
||||
for (int i = 0; i < CanNum; i++)
|
||||
{
|
||||
var msgPtr = (IntPtr)(msgPtRead + i * CanMsgSize);
|
||||
var msg = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtr, typeof(USB2CAN.CAN_MSG));
|
||||
|
||||
if (EnableConsoleDebugLog)
|
||||
{
|
||||
Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, msg.ID.ToString("X8"));
|
||||
Console.WriteLine("CanMsg[{0}].TimeStamp = {1}", i, msg.TimeStamp);
|
||||
Console.Write("CanMsg[{0}].Data = ", i);
|
||||
for (int j = 0; j < msg.DataLen; j++)
|
||||
{
|
||||
Console.Write("{0} ", msg.Data[j].ToString("X2"));
|
||||
}
|
||||
Console.WriteLine("");
|
||||
}
|
||||
|
||||
// 报文给高速记录的服务
|
||||
HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg()
|
||||
{
|
||||
Category = "CAN",
|
||||
MsgInfo = "0x" + msg.ID.ToString("X8"),
|
||||
MsgData = BitConverter.ToString(msg.Data),
|
||||
Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (CanNum == 0)
|
||||
{
|
||||
IsReviceOk = false;
|
||||
if (EnableConsoleDebugLog) Console.WriteLine("No CAN data!");
|
||||
}
|
||||
else
|
||||
{
|
||||
IsReviceOk = false;
|
||||
if (EnableConsoleDebugLog) Console.WriteLine("Get CAN data error!");
|
||||
}
|
||||
// 将CAN消息数据填充到信号里面,用DBC解析数据(仍在锁内,避免指针被并发释放)
|
||||
var SyncCANMsgToValue = CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum);
|
||||
//monitorValueLog.UpdateValue5(SyncCANMsgToValue);
|
||||
}
|
||||
|
||||
//循环获取消息的数据
|
||||
foreach (var item in ListCanDbcModel)
|
||||
{
|
||||
// 复用 StringBuilder 缓存,避免频繁分配
|
||||
var msgNameSB = GetMsgSB(item.MsgName);
|
||||
var sigNameSB = GetSigSB(item.SignalName);
|
||||
CAN_DBCParser.DBC_GetSignalValue(DBCHandle, msgNameSB, sigNameSB, ValueDouble);
|
||||
item.SignalRtValue = ValueDouble[0].ToString();
|
||||
//Console.Write(ValueSb.ToString());
|
||||
}
|
||||
// 缓冲区在 CloseDevice 或任务退出的 finally 中统一释放,避免频繁申请/释放
|
||||
|
||||
|
||||
}
|
||||
else if (CanNum == 0)
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsReviceOk = false;
|
||||
Console.WriteLine("No CAN data!");
|
||||
LoggerService.Info($"CAN指令接收出现异常:{ex.Message}");
|
||||
}
|
||||
//finally
|
||||
//{
|
||||
// IsReviceOk = false;
|
||||
//}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsReviceOk = false;
|
||||
LoggerService.Info("CAN指令接收 finally结束");
|
||||
// 接收任务退出时释放接收缓冲,避免仅停止接收时的缓冲常驻
|
||||
lock (RecvBufferSync)
|
||||
{
|
||||
if (RecvMsgBufferPtr != IntPtr.Zero)
|
||||
{
|
||||
try { Marshal.FreeHGlobal(RecvMsgBufferPtr); }
|
||||
catch { }
|
||||
finally { RecvMsgBufferPtr = IntPtr.Zero; }
|
||||
}
|
||||
else
|
||||
{
|
||||
IsReviceOk = false;
|
||||
Console.WriteLine("Get CAN data error!");
|
||||
}
|
||||
//Console.WriteLine("");
|
||||
|
||||
//将CAN消息数据填充到信号里面,用DBC解析数据
|
||||
CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum);
|
||||
|
||||
//循环获取消息的数据
|
||||
foreach (var item in ListCanDbcModel)
|
||||
{
|
||||
//有配置的名称的,认为是有用的,则需要读取数据
|
||||
//if (!string.IsNullOrEmpty(item.Name))
|
||||
//{
|
||||
//CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueSb);
|
||||
//double[] ValueDouble;
|
||||
CAN_DBCParser.DBC_GetSignalValue(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueDouble);
|
||||
//item.SignalRtValueSb = ValueSb;
|
||||
item.SignalRtValue = ValueDouble[0].ToString();
|
||||
//Console.Write(ValueSb.ToString());
|
||||
//}
|
||||
}
|
||||
|
||||
//释放数据缓冲区,必须释放,否则程序运行一段时间后会报内存不足
|
||||
Marshal.FreeHGlobal(msgPtRead);
|
||||
Thread.Sleep(10);
|
||||
|
||||
////获取信号值并打印出来
|
||||
//StringBuilder ValueStr = new StringBuilder(32);
|
||||
//CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
|
||||
//Console.WriteLine("moto_speed = {0}", ValueStr);
|
||||
//CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
|
||||
//Console.WriteLine("oil_pressure = {0}", ValueStr);
|
||||
//CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
|
||||
//Console.WriteLine("speed_can = {0}", ValueStr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsReviceOk = false;
|
||||
LoggerService.Info("接收出现异常");
|
||||
}
|
||||
//finally
|
||||
//{
|
||||
// IsReviceOk = false;
|
||||
//}
|
||||
}
|
||||
IsReviceOk = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1251,43 +1641,55 @@ namespace CapMachine.Wpf.CanDrive
|
||||
//另外一个CAN通道读取数据
|
||||
USB2CAN.CAN_MSG[] CanMsgBuffer = new USB2CAN.CAN_MSG[10];
|
||||
msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)) * CanMsgBuffer.Length);
|
||||
int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPt, CanMsgBuffer.Length);
|
||||
if (CanNum > 0)
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Read CanMsgNum = {0}", CanNum);
|
||||
for (int i = 0; i < CanNum; i++)
|
||||
int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPt, CanMsgBuffer.Length);
|
||||
if (CanNum > 0)
|
||||
{
|
||||
CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)msgPt + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG));
|
||||
Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
|
||||
//Console.WriteLine("CanMsg[{0}].TimeStamp = {1}",i,CanMsgBuffer[i].TimeStamp);
|
||||
Console.Write("CanMsg[{0}].Data = ", i);
|
||||
for (int j = 0; j < CanMsgBuffer[i].DataLen; j++)
|
||||
Console.WriteLine("Read CanMsgNum = {0}", CanNum);
|
||||
for (int i = 0; i < CanNum; i++)
|
||||
{
|
||||
Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
|
||||
CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)msgPt + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG));
|
||||
Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
|
||||
//Console.WriteLine("CanMsg[{0}].TimeStamp = {1}",i,CanMsgBuffer[i].TimeStamp);
|
||||
Console.Write("CanMsg[{0}].Data = ", i);
|
||||
for (int j = 0; j < CanMsgBuffer[i].DataLen; j++)
|
||||
{
|
||||
Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
|
||||
}
|
||||
Console.WriteLine("");
|
||||
}
|
||||
Console.WriteLine("");
|
||||
}
|
||||
else if (CanNum == 0)
|
||||
{
|
||||
Console.WriteLine("No CAN data!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Get CAN data error!");
|
||||
}
|
||||
Console.WriteLine("");
|
||||
|
||||
//将CAN消息数据填充到信号里面
|
||||
CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPt, CanNum);
|
||||
//获取信号值并打印出来
|
||||
StringBuilder ValueStr = new StringBuilder(32);
|
||||
CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
|
||||
Console.WriteLine("moto_speed = {0}", ValueStr);
|
||||
CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
|
||||
Console.WriteLine("oil_pressure = {0}", ValueStr);
|
||||
CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
|
||||
Console.WriteLine("speed_can = {0}", ValueStr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (msgPt != IntPtr.Zero)
|
||||
{
|
||||
try { Marshal.FreeHGlobal(msgPt); }
|
||||
catch { }
|
||||
finally { msgPt = IntPtr.Zero; }
|
||||
}
|
||||
}
|
||||
else if (CanNum == 0)
|
||||
{
|
||||
Console.WriteLine("No CAN data!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Get CAN data error!");
|
||||
}
|
||||
Console.WriteLine("");
|
||||
|
||||
//将CAN消息数据填充到信号里面
|
||||
CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPt, CanNum);
|
||||
//获取信号值并打印出来
|
||||
StringBuilder ValueStr = new StringBuilder(32);
|
||||
CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
|
||||
Console.WriteLine("moto_speed = {0}", ValueStr);
|
||||
CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
|
||||
Console.WriteLine("oil_pressure = {0}", ValueStr);
|
||||
CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
|
||||
Console.WriteLine("speed_can = {0}", ValueStr);
|
||||
}
|
||||
|
||||
|
||||
@@ -1306,6 +1708,30 @@ namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
StopSchedule();
|
||||
}
|
||||
|
||||
// 确保定时器发送被停止并释放资源
|
||||
try { StopTimerCycleSendMsg(); } catch { }
|
||||
|
||||
// 等待接收任务结束后释放非托管缓冲区,避免并发释放
|
||||
try
|
||||
{
|
||||
var task = CycleReviceTask;
|
||||
if (task != null && !task.IsCompleted)
|
||||
{
|
||||
task.Wait(TimeSpan.FromMilliseconds(ReviceCycle + 500));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
// 在锁内安全释放,避免与接收线程并发访问同一指针
|
||||
lock (RecvBufferSync)
|
||||
{
|
||||
if (RecvMsgBufferPtr != IntPtr.Zero)
|
||||
{
|
||||
try { Marshal.FreeHGlobal(RecvMsgBufferPtr); }
|
||||
catch { }
|
||||
finally { RecvMsgBufferPtr = IntPtr.Zero; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user