diff --git a/CapMachine.Wpf/CanDrive/ToomossCan.cs b/CapMachine.Wpf/CanDrive/ToomossCan.cs index 95dd74e..9de3eee 100644 --- a/CapMachine.Wpf/CanDrive/ToomossCan.cs +++ b/CapMachine.Wpf/CanDrive/ToomossCan.cs @@ -23,6 +23,7 @@ using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Interop; using static CapMachine.Wpf.CanDrive.USB2CAN; +using CapMachine.Wpf.Models; namespace CapMachine.Wpf.CanDrive { @@ -36,11 +37,13 @@ namespace CapMachine.Wpf.CanDrive /// /// 实例化函数 /// - public ToomossCan(IContainerProvider containerProvider) + public ToomossCan(IContainerProvider containerProvider, ILogService logService) { ContainerProvider = containerProvider; HighSpeedDataService = ContainerProvider.Resolve(); - LoggerService = ContainerProvider.Resolve(); + LoggerService = logService; + + monitorValueLog = new MonitorValueLog(logService, "SetSignalValue", "SyncValueToCanMsg", "ret", "CanNum", "SyncCANMsgToValue"); //Stopwatch.Frequency表示高精度计时器每秒的计数次数(ticks/秒)每毫秒的ticks数 = 每秒的ticks数 ÷ 1000 TicksPerMs = Stopwatch.Frequency / 1000.0; @@ -58,6 +61,7 @@ namespace CapMachine.Wpf.CanDrive GetCANConfig(); InitCAN(); + //LoggerService.Info($"Start CAN Drive"); } /// @@ -568,120 +572,6 @@ namespace CapMachine.Wpf.CanDrive /// public List CmdData { get; set; } = new List(); - /// - /// 加载要发送的数据 - /// 一般是激活后才注册事件 - /// - /// - 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(USB2CAN.CAN_MSG))); - int CycleUpdateIndex = 0; - //循环给MSG赋值数据,顺序是固定的,跟初始时设置是一样的 - foreach (var itemMsg in GroupMsg) - { - foreach (var itemSignal in itemMsg) - { - //itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用 - 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); - 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)} "); - } - else - { - IsSendOk = false; - Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)}"); - //return; - } - } - - } - catch (Exception ex) - { - IsSendOk = false; - LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}"); - } - - } - /// /// 循环发送数据 /// @@ -997,6 +887,11 @@ namespace CapMachine.Wpf.CanDrive Random random = new Random(); + /// + /// 监控数据 + /// + public MonitorValueLog monitorValueLog { get; set; } + /// /// 更新数据 测试用废弃了 /// @@ -1279,6 +1174,126 @@ namespace CapMachine.Wpf.CanDrive } + /// + /// 加载要发送的数据 + /// 一般是激活后才注册事件 + /// + /// + 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(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)} "); + + } + 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 /// @@ -1291,7 +1306,7 @@ namespace CapMachine.Wpf.CanDrive { return; } - + //monitorValueLog CycleReviceTask = Task.Run(async () => { try @@ -1301,91 +1316,95 @@ namespace CapMachine.Wpf.CanDrive await Task.Delay(ReviceCycle); try { - // 另一个CAN通道读取数据(与 CloseDevice 释放互斥,保护指针安全) - IntPtr msgPtRead; - int CanNum; - lock (RecvBufferSync) - { - if (RecvMsgBufferPtr == IntPtr.Zero) + // 另一个CAN通道读取数据(与 CloseDevice 释放互斥,保护指针安全) + IntPtr msgPtRead; + int CanNum; + lock (RecvBufferSync) { - RecvMsgBufferPtr = Marshal.AllocHGlobal(CanMsgSize * RecvMsgBufferCapacity); - } - msgPtRead = RecvMsgBufferPtr; - CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPtRead, RecvMsgBufferCapacity); - //int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, 1, msgPtRead, RecvMsgBufferCapacity);//测试用,CAN卡 CAN1和CAN2 短接时测试用 - if (CanNum > 0) - { - IsReviceOk = true; - if (EnableConsoleDebugLog) Console.WriteLine("Read CanMsgNum = {0}", CanNum); - for (int i = 0; i < CanNum; i++) + if (RecvMsgBufferPtr == IntPtr.Zero) { - 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") - }); + RecvMsgBufferPtr = Marshal.AllocHGlobal(CanMsgSize * RecvMsgBufferCapacity); + LoggerService.Info("申请 RecvMsgBufferPtr"); } - } - 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解析数据(仍在锁内,避免指针被并发释放) - CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum); - } + 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) + { + 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)); - //循环获取消息的数据 - 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 中统一释放,避免频繁申请/释放 + 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 中统一释放,避免频繁申请/释放 } catch (Exception ex) { IsReviceOk = false; - LoggerService.Info("接收出现异常"); + LoggerService.Info($"CAN指令接收出现异常:{ex.Message}"); } - //finally - //{ - // IsReviceOk = false; - //} + //finally + //{ + // IsReviceOk = false; + //} } } finally { IsReviceOk = false; + LoggerService.Info("CAN指令接收 finally结束"); // 接收任务退出时释放接收缓冲,避免仅停止接收时的缓冲常驻 lock (RecvBufferSync) { diff --git a/CapMachine.Wpf/Models/MonitorValueLog.cs b/CapMachine.Wpf/Models/MonitorValueLog.cs new file mode 100644 index 0000000..05705de --- /dev/null +++ b/CapMachine.Wpf/Models/MonitorValueLog.cs @@ -0,0 +1,143 @@ +using CapMachine.Wpf.Services; +using System; +using System.Threading; + +namespace CapMachine.Wpf.Models +{ + /// + /// 监控值日志 + /// 负责监控5个整型值的变化。通过5个独立的更新方法实时赋值(建议调用周期≈500ms)。 + /// 当值发生变化时,记录“旧值 -> 新值”到日志,仅在变化时记录以保证效率。 + /// 线程安全:采用 Interlocked/Volatile 保证无锁原子更新,避免不必要的锁竞争。 + /// + public class MonitorValueLog + { + /// + /// 日志服务 + /// + public ILogService LogService { get; } + + private readonly string _name1; + private readonly string _name2; + private readonly string _name3; + private readonly string _name4; + private readonly string _name5; + + private volatile int _value1; + private volatile int _value2; + private volatile int _value3; + private volatile int _value4; + private volatile int _value5; + + // 0=未初始化,1=已初始化;使用int以便Interlocked操作 + private int _v1Initialized; + private int _v2Initialized; + private int _v3Initialized; + private int _v4Initialized; + private int _v5Initialized; + + private readonly string _context; + + /// + /// 构造函数 + /// + /// 日志服务 + public MonitorValueLog(ILogService logService) : this(logService, "Value1", "Value2", "Value3", "Value4", "Value5", null) + { } + + /// + /// 构造函数,支持为5个值自定义名称和上下文标识 + /// + /// 日志服务 + /// 值1名称 + /// 值2名称 + /// 值3名称 + /// 值4名称 + /// 值5名称 + /// 上下文/标签(可选),用于区分不同监控源 + public MonitorValueLog(ILogService logService, string name1, string name2, string name3, string name4, string name5, string context = null) + { + LogService = logService ?? throw new ArgumentNullException(nameof(logService)); + _name1 = string.IsNullOrWhiteSpace(name1) ? "Value1" : name1; + _name2 = string.IsNullOrWhiteSpace(name2) ? "Value2" : name2; + _name3 = string.IsNullOrWhiteSpace(name3) ? "Value3" : name3; + _name4 = string.IsNullOrWhiteSpace(name4) ? "Value4" : name4; + _name5 = string.IsNullOrWhiteSpace(name5) ? "Value5" : name5; + _context = string.IsNullOrWhiteSpace(context) ? string.Empty : $"[{context}] "; + } + + /// + /// 当前值的只读访问器(可选) + /// + public int Value1 => Volatile.Read(ref _value1); + public int Value2 => Volatile.Read(ref _value2); + public int Value3 => Volatile.Read(ref _value3); + public int Value4 => Volatile.Read(ref _value4); + public int Value5 => Volatile.Read(ref _value5); + + /// + /// 更新第1个值(仅在变化时记录:旧值 -> 新值) + /// + /// 新值 + public void UpdateValue1(int newValue) => Update(ref _value1, ref _v1Initialized, _name1, newValue); + + /// + /// 更新第2个值(仅在变化时记录:旧值 -> 新值) + /// + /// 新值 + public void UpdateValue2(int newValue) => Update(ref _value2, ref _v2Initialized, _name2, newValue); + + /// + /// 更新第3个值(仅在变化时记录:旧值 -> 新值) + /// + /// 新值 + public void UpdateValue3(int newValue) => Update(ref _value3, ref _v3Initialized, _name3, newValue); + + /// + /// 更新第4个值(仅在变化时记录:旧值 -> 新值) + /// + /// 新值 + public void UpdateValue4(int newValue) => Update(ref _value4, ref _v4Initialized, _name4, newValue); + + /// + /// 更新第5个值(仅在变化时记录:旧值 -> 新值) + /// + /// 新值 + public void UpdateValue5(int newValue) => Update(ref _value5, ref _v5Initialized, _name5, newValue); + + /// + /// 内部更新逻辑:原子更新与变化日志 + /// + private void Update(ref int target, ref int initializedFlag, string name, int newValue) + { + // 首次赋值:仅建立基线,不记录日志 + if (Interlocked.CompareExchange(ref initializedFlag, 1, 0) == 0) + { + Volatile.Write(ref target, newValue); + return; + } + + // 原子交换拿到旧值 + int oldValue = Interlocked.Exchange(ref target, newValue); + if (oldValue != newValue) + { + LogChange(name, oldValue, newValue); + } + } + + /// + /// 记录变化日志(防御性:日志失败不影响主流程) + /// + private void LogChange(string name, int oldValue, int newValue) + { + try + { + LogService.Info($"{_context}[MonitorValueLog] {name} 变化: {oldValue} -> {newValue}"); + } + catch + { + // 忽略日志异常,保证主流程稳定 + } + } + } +} diff --git a/CapMachine.Wpf/Services/CanDriveService.cs b/CapMachine.Wpf/Services/CanDriveService.cs index a7e5266..b6f429f 100644 --- a/CapMachine.Wpf/Services/CanDriveService.cs +++ b/CapMachine.Wpf/Services/CanDriveService.cs @@ -21,18 +21,21 @@ namespace CapMachine.Wpf.Services { public HighSpeedDataService HighSpeedDataService { get; } + public ILogService LogService { get; } public LogicRuleService LogicRuleService { get; } /// /// 实例化函数 /// - public CanDriveService(HighSpeedDataService highSpeedDataService, IContainerProvider containerProvider, LogicRuleService logicRuleService) + public CanDriveService(HighSpeedDataService highSpeedDataService, IContainerProvider containerProvider,ILogService logService, LogicRuleService logicRuleService) { - ToomossCanDrive = new ToomossCan(containerProvider); + ToomossCanDrive = new ToomossCan(containerProvider, logService); //高速数据服务 HighSpeedDataService = highSpeedDataService; + LogService = logService; LogicRuleService = logicRuleService; + //ToomossCanDrive.StartCanDrive(); } diff --git a/CapMachine.Wpf/Services/PPCService.cs b/CapMachine.Wpf/Services/PPCService.cs index 38b1974..9a76ea2 100644 --- a/CapMachine.Wpf/Services/PPCService.cs +++ b/CapMachine.Wpf/Services/PPCService.cs @@ -137,7 +137,7 @@ namespace CapMachine.Wpf.Services SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath"); SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen"); - RtScanDeviceStart(); + //RtScanDeviceStart(); }