From 3bf33bb561a3551c39cf5d273165485115bae1d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Chsc=E2=80=9D?= <“huangsucan@kaiyili-lab.com”>
Date: Wed, 29 Jul 2026 14:20:32 +0800
Subject: [PATCH] =?UTF-8?q?=E5=91=A8=E7=AB=8B=E5=8A=9F=E6=8D=A2=E5=90=8C?=
=?UTF-8?q?=E6=98=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ACP.sln | 12 +-
ACP/ACP.csproj | 2 +-
CANModule/CANModule.cs | 2 +-
CANModule/CANModule.csproj | 25 +-
CANModule/ViewModels/CANViewModel.cs | 581 +++++++++
.../CollectCANSingalSettingViewModel.cs | 182 ++-
.../ViewModels/SelectCanMessageViewModel.cs | 129 +-
CANModule/ViewModels/ZLGCANFDViewModel.cs | 649 ----------
.../Views/{ZLGCANFDView.xaml => CANView.xaml} | 164 +--
.../{ZLGCANFDView.xaml.cs => CANView.xaml.cs} | 9 +-
....xaml => CollectCANSingalSettingView.xaml} | 37 +-
...cs => CollectCANSingalSettingView.xaml.cs} | 15 +-
CANModule/Views/SelectCanMessageView.xaml | 21 +-
.../ViewModels/Dialogs/ValueLimitViewModel.cs | 10 +-
MonitorModule/ViewModels/MonitorViewModel.cs | 11 +-
TSMasterCAN/CAN.cs | 679 ++++++++++
TSMasterCAN/DBCParse.cs | 305 +++++
TSMasterCAN/Interop.TSMasterAPI.dll | Bin 0 -> 109568 bytes
TSMasterCAN/TSMaster.dll | Bin 0 -> 1565184 bytes
.../TSMasterCAN.csproj | 12 +-
.../GlobalVariable/CANSignalBroadcaster.cs | 9 +-
UIShare/GlobalVariable/DeviceManager.cs | 11 +-
UIShare/GlobalVariable/GlobalInfo.cs | 6 +-
UIShare/GlobalVariable/SystemConfig.cs | 4 +-
UIShare/UIShare.csproj | 2 +-
UIShare/UIViewModel/CanMessageShowVM.cs | 33 +-
ZLGUSBCANFD/USBCANFD.cs | 983 --------------
ZLGUSBCANFD/ZLGAPI.cs | 1150 -----------------
ZLGUSBCANFD/ZLGDBCParser.cs | 125 --
29 files changed, 1913 insertions(+), 3255 deletions(-)
create mode 100644 CANModule/ViewModels/CANViewModel.cs
delete mode 100644 CANModule/ViewModels/ZLGCANFDViewModel.cs
rename CANModule/Views/{ZLGCANFDView.xaml => CANView.xaml} (75%)
rename CANModule/Views/{ZLGCANFDView.xaml.cs => CANView.xaml.cs} (85%)
rename CANModule/Views/{CollectCANSignalSettingView.xaml => CollectCANSingalSettingView.xaml} (83%)
rename CANModule/Views/{CollectCANSignalSettingView.xaml.cs => CollectCANSingalSettingView.xaml.cs} (58%)
create mode 100644 TSMasterCAN/CAN.cs
create mode 100644 TSMasterCAN/DBCParse.cs
create mode 100644 TSMasterCAN/Interop.TSMasterAPI.dll
create mode 100644 TSMasterCAN/TSMaster.dll
rename ZLGUSBCANFD/ZLGUSBCANFD.csproj => TSMasterCAN/TSMasterCAN.csproj (52%)
delete mode 100644 ZLGUSBCANFD/USBCANFD.cs
delete mode 100644 ZLGUSBCANFD/ZLGAPI.cs
delete mode 100644 ZLGUSBCANFD/ZLGDBCParser.cs
diff --git a/ACP.sln b/ACP.sln
index 160b3d3..1808d71 100644
--- a/ACP.sln
+++ b/ACP.sln
@@ -35,8 +35,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorMod
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceEditModule\DeviceEditModule.csproj", "{170AD4C1-189D-4FBE-B10D-2A4304527834}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZLGUSBCANFD", "ZLGUSBCANFD\ZLGUSBCANFD.csproj", "{542FE380-2344-4343-9AC6-F0C587C13B88}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CANModule", "CANModule\CANModule.csproj", "{AF2533DD-599D-49D7-942B-B0C26534D15F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "加密狗", "加密狗\加密狗.csproj", "{A35CA316-7B5B-4183-8636-4ECA532B1489}"
@@ -47,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportModule", "ExportModul
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ACP", "ACP\ACP.csproj", "{CC810066-06A3-4635-96C7-1D30D78C2130}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSMasterCAN", "TSMasterCAN\TSMasterCAN.csproj", "{B45DC94C-2CBB-4119-8787-B52A9E7EC58B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -113,10 +113,6 @@ Global
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.Build.0 = Debug|Any CPU
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.ActiveCfg = Release|Any CPU
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.Build.0 = Release|Any CPU
- {542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.Build.0 = Release|Any CPU
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -137,6 +133,10 @@ Global
{CC810066-06A3-4635-96C7-1D30D78C2130}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC810066-06A3-4635-96C7-1D30D78C2130}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC810066-06A3-4635-96C7-1D30D78C2130}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ACP/ACP.csproj b/ACP/ACP.csproj
index 77a4fd2..37b3348 100644
--- a/ACP/ACP.csproj
+++ b/ACP/ACP.csproj
@@ -31,9 +31,9 @@
+
-
diff --git a/CANModule/CANModule.cs b/CANModule/CANModule.cs
index 9944f14..0aeed6e 100644
--- a/CANModule/CANModule.cs
+++ b/CANModule/CANModule.cs
@@ -12,7 +12,7 @@ namespace CANModule
{
containerRegistry.RegisterDialog("SelectCanMessageView");
containerRegistry.RegisterDialog("CollectCANSignalSettingView");
- containerRegistry.RegisterForNavigation("ZLGCANFDView");
+ containerRegistry.RegisterForNavigation("CANView");
}
}
}
diff --git a/CANModule/CANModule.csproj b/CANModule/CANModule.csproj
index 3df7504..fea4280 100644
--- a/CANModule/CANModule.csproj
+++ b/CANModule/CANModule.csproj
@@ -1,14 +1,21 @@
-
- net8.0-windows
- enable
- true
- enable
-
+
+ net8.0-windows
+ enable
+ true
+ enable
+
-
-
-
+
+
+
+
+
+
+
+ ..\TSMasterCAN\Interop.TSMasterAPI.dll
+
+
diff --git a/CANModule/ViewModels/CANViewModel.cs b/CANModule/ViewModels/CANViewModel.cs
new file mode 100644
index 0000000..0f486aa
--- /dev/null
+++ b/CANModule/ViewModels/CANViewModel.cs
@@ -0,0 +1,581 @@
+
+using Microsoft.Win32;
+using Model;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Threading;
+using TSMasterCAN;
+using UIShare.GlobalVariable;
+using UIShare.UIViewModel;
+
+namespace CANModule.ViewModels
+{
+ public class CANViewModel : BindableBase, IDialogAware
+ {
+
+ #region 属性
+ private string _Title = "同星CAN";
+
+ public string Title
+ {
+ get { return _Title; }
+ set { SetProperty(ref _Title, value); }
+ }
+ private ObservableCollection _CanMessageList = new();
+
+ public ObservableCollection CanMessageList
+ {
+ get { return _CanMessageList; }
+ set { SetProperty(ref _CanMessageList, value); }
+ }
+ private string _DBCChannel = "0";
+
+ public string DBCChannel
+ {
+ get { return _DBCChannel; }
+ set { SetProperty(ref _DBCChannel, value); }
+ }
+ private bool _IsDirectlySend = false;
+
+ public bool IsDirectlySend
+ {
+ get { return _IsDirectlySend; }
+ set { SetProperty(ref _IsDirectlySend, value); }
+ }
+ private ObservableCollection _Message;
+ public ObservableCollection Message
+ {
+ get { return _Message; }
+ set { SetProperty(ref _Message, value); }
+ }
+ private ObservableCollection _Signal;
+ public ObservableCollection Signal
+ {
+ get { return _Signal; }
+ set { SetProperty(ref _Signal, value); }
+ }
+ byte channel;
+ private string _selectedMessage;
+ public string SelectedMessage
+ {
+ get { return _selectedMessage; }
+ set
+ {
+ if (_selectedMessage != value)
+ {
+ Signal = new(DBCParse.MsgDatabase[channel].Where(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value).First().signal_Name);
+ _selectedMessage = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ private string _selectedSignal;
+ public string SelectedSignal
+ {
+ get { return _selectedSignal; }
+ set { SetProperty(ref _selectedSignal, value); }
+ }
+
+ private string _signalValue;
+ public string SignalValue
+ {
+ get { return _signalValue; }
+ set { SetProperty(ref _signalValue, value); }
+ }
+
+ private string _cycleSendInterval;
+ public string CycleSendInterval
+ {
+ get { return _cycleSendInterval; }
+ set { SetProperty(ref _cycleSendInterval, value); }
+ }
+
+ private string _messageSendChannel = "0";
+ public string MessageSendChannel
+ {
+ get { return _messageSendChannel; }
+ set { SetProperty(ref _messageSendChannel, value); }
+ }
+ public string BLFPath
+ {
+ get => _systemConfig?.DefaultBLFFilePath ?? "";
+ set
+ {
+ if (_systemConfig != null && _systemConfig.DefaultBLFFilePath != value)
+ {
+ _systemConfig.DefaultBLFFilePath = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public string DBCPath
+ {
+ get => _systemConfig?.DefaultDBCFilePath ?? "";
+ set
+ {
+ if (_systemConfig != null && _systemConfig.DefaultDBCFilePath != value)
+ {
+ _systemConfig.DefaultDBCFilePath = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+ private bool _IsSaved;
+ public bool IsSaved
+ {
+ get { return _IsSaved; }
+ set { SetProperty(ref _IsSaved, value); }
+ }
+ public ObservableCollection DBCAutoLoadList
+ {
+ get => _systemConfig?.DBCAutoLoadList ?? new ObservableCollection();
+ }
+
+ private AutoDBCLoadItem _SelectedDcAutoLoad;
+ public AutoDBCLoadItem SelectedDcAutoLoad
+ {
+ get { return _SelectedDcAutoLoad; }
+ set { SetProperty(ref _SelectedDcAutoLoad, value); }
+ }
+ private TSMaster.APP_CHANNEL[] _appChannelNames = new[] { TSMaster.APP_CHANNEL.CHN1, TSMaster.APP_CHANNEL.CHN2, TSMaster.APP_CHANNEL.CHN3, TSMaster.APP_CHANNEL.CHN4 };
+ public TSMaster.APP_CHANNEL[] APP_CHANNEL
+ {
+ get { return _appChannelNames; }
+ set { SetProperty(ref _appChannelNames, value); }
+ }
+
+ private TSMaster.APP_CHANNEL _aIdxChn;
+ public TSMaster.APP_CHANNEL AIdxChn
+ {
+ get { return _aIdxChn; }
+ set { SetProperty(ref _aIdxChn, value); }
+ }
+
+ private int _aID;
+ public int AID
+ {
+ get { return _aID; }
+ set { SetProperty(ref _aID, value); }
+ }
+
+ private bool _aIsTx;
+ public bool AIsTx
+ {
+ get { return _aIsTx; }
+ set { SetProperty(ref _aIsTx, value); }
+ }
+
+ private bool _aIsExt;
+ public bool AIsExt
+ {
+ get { return _aIsExt; }
+ set { SetProperty(ref _aIsExt, value); }
+ }
+
+ private bool _aIsRemote;
+ public bool AIsRemote
+ {
+ get { return _aIsRemote; }
+ set { SetProperty(ref _aIsRemote, value); }
+ }
+
+ private byte _aDlc;
+ public byte ADLC
+ {
+ get { return _aDlc; }
+ set { SetProperty(ref _aDlc, value); }
+ }
+
+ private byte[] _aDataArray;
+ public byte[] ADataArray
+ {
+ get { return _aDataArray; }
+ set { SetProperty(ref _aDataArray, value); }
+ }
+
+ private bool _aIsFD = true;
+ public bool AIsFD
+ {
+ get { return _aIsFD; }
+ set { SetProperty(ref _aIsFD, value); }
+ }
+
+ private bool _aIsBRS = false;
+ public bool AIsBRS
+ {
+ get { return _aIsBRS; }
+ set { SetProperty(ref _aIsBRS, value); }
+ }
+
+ private bool _AutoSetting = true;
+ public bool AutoSetting
+ {
+ get { return _AutoSetting; }
+ set { SetProperty(ref _AutoSetting, value); }
+ }
+
+ private string _CustomMessage = "[00,11,22,33,AA,BB,CC,DD]";
+ public string CustomMessage
+ {
+ get { return _CustomMessage; }
+ set { SetProperty(ref _CustomMessage, value); }
+ }
+
+ private float _CustomSendCycleInterval = 0.0f;
+ public float CustomSendCycleInterval
+ {
+ get { return _CustomSendCycleInterval; }
+ set { SetProperty(ref _CustomSendCycleInterval, value); }
+ }
+ private bool _UpdateToDB;
+ public bool UpdateToDB
+ {
+ get { return _UpdateToDB; }
+ set { SetProperty(ref _UpdateToDB, value); }
+ }
+ #endregion
+ #region 命令
+ public ICommand SelectBLFPathCommand { get; set; }
+ public ICommand SaveBLFPathCommand { get; set; }
+ public ICommand StartTranscirbeCommand { get; set; }
+ public ICommand EndTranscirbeCommand { get; set; }
+ public ICommand CloseCommand { get; set; }
+ public ICommand ConnectCanCommand { get; set; }
+ public ICommand CloseCanCommand { get; set; }
+ public ICommand ClearCycleCommand { get; set; }
+ public ICommand UpLoadDBCCommand { get; set; }
+ public ICommand ChangeDBCCommand { get; set; }
+ public ICommand LoadDBCCommand { get; set; }
+ public ICommand CheckDBCCommand { get; set; }
+ public ICommand InitTSMasterCommand { get; set; }
+ public ICommand ReleaseTSMasterCommand { get; set; }
+ public ICommand LoadMessageCommand { get; set; }
+ public ICommand SetAndSendMessageCommand { get; set; }
+ public ICommand DeleteDcAutoLoadCommand { get; set; }
+ public ICommand SendCustomMessageCommand { get; set; }
+
+ #endregion
+ private IEventAggregator _eventAggregator { get; set; }
+ private CAN? _canfd;
+ private SystemConfig? _systemConfig;
+ private readonly DeviceManager _deviceManager;
+ private IDialogService _dialogService { get; set; }
+ CancellationTokenSource cts = new CancellationTokenSource();
+ public CANViewModel(IContainerProvider containerProvider)
+ {
+ _eventAggregator = containerProvider.Resolve();
+ _systemConfig = containerProvider.Resolve();
+ _deviceManager = containerProvider.Resolve();
+ _canfd = _deviceManager.CANFD;
+ _dialogService = containerProvider.Resolve();
+ SelectBLFPathCommand = new DelegateCommand(SelectBLFPath);
+ SaveBLFPathCommand = new DelegateCommand(SaveBLFPath);
+ StartTranscirbeCommand = new DelegateCommand(StartTranscirbe);
+ EndTranscirbeCommand = new DelegateCommand(EndTranscirbe);
+ CloseCommand = new DelegateCommand(Close);
+ ConnectCanCommand = new DelegateCommand(ConnectCan);
+ CloseCanCommand = new DelegateCommand(CloseCan);
+ ClearCycleCommand = new DelegateCommand(ClearCycle);
+ UpLoadDBCCommand = new DelegateCommand(UpLoadDBC);
+ ChangeDBCCommand = new DelegateCommand(ChangeDBC);
+ LoadDBCCommand = new DelegateCommand(LoadDBC);
+ CheckDBCCommand = new DelegateCommand(CheckDBC);
+ InitTSMasterCommand = new DelegateCommand(InitTSMaster);
+ ReleaseTSMasterCommand = new DelegateCommand(ReleaseTSMaster);
+ SetAndSendMessageCommand = new DelegateCommand(SetAndSendMessage);
+ LoadMessageCommand = new DelegateCommand(LoadMessage);
+ DeleteDcAutoLoadCommand = new DelegateCommand(DeleteDcAutoLoad);
+ SendCustomMessageCommand = new DelegateCommand(SendCustomMessage);
+ }
+
+ #region 命令
+ private void SendCustomMessage()
+ {
+ try
+ {
+ ADataArray = 报文转换(CustomMessage);
+ if (ADataArray.Length > byte.MaxValue) throw new Exception("报文数量超过上限!");
+ if (AutoSetting)
+ {
+ ADLC = (byte)ADataArray.Length;
+ }
+ }
+ catch (Exception ex)
+ {
+ _dialogService.ShowDialog("MessageDialog", new DialogParameters
+ {
+ { "message", $"报文转换失败:{ex.Message}" }
+ });
+ return;
+ }
+ var re = CAN.SendMsg(AIdxChn, AID, AIsTx, AIsExt,
+ AIsRemote, ADLC, ADataArray, AIsFD, AIsBRS, UpdateToDB, CustomSendCycleInterval);
+ }
+ private void DeleteDcAutoLoad()
+ {
+ DBCAutoLoadList.Remove(SelectedDcAutoLoad);
+ ConfigService.Save(_systemConfig);
+ }
+ private void LoadMessage()
+ {
+ if (int.TryParse(MessageSendChannel, out int channel))
+ {
+ Message = new(DBCParse.MsgDatabase[channel].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
+ }
+ else
+ {
+
+ }
+ }
+
+ private void SetAndSendMessage()
+ {
+ if (int.TryParse(CycleSendInterval, out int sendPeriod) && double.TryParse(SignalValue, out double SValue) && byte.TryParse(MessageSendChannel, out byte c))
+ {
+ channel = c;
+ var message = SelectedMessage.Split(']')[1];
+ var re = CAN.SetSignalValue(channel, message, SelectedSignal, SValue, IsDirectlySend, sendPeriod);
+ }
+ else
+ {
+
+ }
+ }
+
+ private void ReleaseTSMaster()
+ {
+ CAN.Release();
+ }
+
+ private void InitTSMaster()
+ {
+ var re = CAN.Init("ACP");
+ }
+ private void UpLoadDBC()
+ {
+ var re = CAN.UnLoadDBC();
+ }
+
+ private void ChangeDBC()
+ {
+ var dialog = new OpenFileDialog
+ {
+ Title = "更换DBC文件",
+ Filter = "DBC 文件 (*.dbc)|*.dbc|所有文件 (*.*)|*.*",
+ DefaultExt = ".dbc",
+ };
+ if (dialog.ShowDialog() == true)
+ {
+ DBCPath = Path.GetFullPath(dialog.FileName);
+ ConfigService.Save(_systemConfig);
+ }
+ }
+
+ private void LoadDBC()
+ {
+ if (int.TryParse(DBCChannel, out var channel))
+ {
+ var re = CAN.LoadDBC(DBCPath, [channel], out _);
+ if (re == 0 && IsSaved)
+ {
+ var item = DBCAutoLoadList.Where(s => s.DBCChannel == channel).FirstOrDefault();
+ if (item == null)
+ {
+ DBCAutoLoadList.Add(new AutoDBCLoadItem
+ {
+ DBCFilePath = DBCPath,
+ DBCChannel = channel
+ });
+ }
+ else
+ {
+ DBCAutoLoadList.Remove(item);
+ DBCAutoLoadList.Add(new AutoDBCLoadItem
+ {
+ DBCFilePath = DBCPath,
+ DBCChannel = channel
+ });
+ }
+ ConfigService.Save(_systemConfig);
+ }
+ }
+ else
+ {
+
+ }
+ }
+
+ private void CheckDBC()
+ {
+ if (int.TryParse(DBCChannel, out var channel))
+ {
+ _dialogService.ShowDialog("DBCInfo", new DialogParameters
+ {
+ { "can_Network", DBCParse.can_Network },
+ { "MsgDatabase", DBCParse.MsgDatabase[channel] }
+ });
+ }
+
+ }
+ private void ClearCycle()
+ {
+ var re = CAN.DeleteCyclicMsgs();
+ }
+
+ private void CloseCan()
+ {
+ var re = CAN.DisConnect();
+ }
+
+ private void ConnectCan()
+ {
+ var re = CAN.Connect();
+ }
+ private void EndTranscirbe()
+ {
+ var re = CAN.StopLogging();
+ }
+
+ private void StartTranscirbe()
+ {
+ var re = CAN.StartLogging(BLFPath);
+ }
+
+ private void SaveBLFPath()
+ {
+ _systemConfig.DefaultBLFFilePath = BLFPath;
+ ConfigService.Save(_systemConfig);
+ }
+
+ private void SelectBLFPath()
+ {
+ var dialog = new SaveFileDialog
+ {
+ Title = "保存 CAN报文回放 BLF 文件",
+ Filter = "BLF 文件 (*.blf)|*.blf|所有文件 (*.*)|*.*",
+ DefaultExt = ".blf",
+ FileName = "log.blf",
+ OverwritePrompt = true
+ };
+ if (dialog.ShowDialog() == true)
+ {
+ BLFPath = Path.GetFullPath(dialog.FileName);
+ }
+ }
+ private void Close()
+ {
+ RequestClose.Invoke();
+ }
+ #endregion
+ public byte[] 报文转换(string s)
+ {
+ s = s.Replace("[", string.Empty).Replace("]", string.Empty);
+ var strs = s.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ byte[] bytes = new byte[strs.Length];
+ for (int i = 0; i < strs.Length; i++)
+ {
+ bytes[i] = Convert.ToByte(strs[i], 16);
+ }
+ return bytes;
+ }
+
+ private async Task Refresh(CancellationToken ct = default)
+ {
+ while (ct.IsCancellationRequested == false)
+ {
+ var temp = CAN.RealTimeMessages.Select(s => s.Value).ToArray();
+ foreach (var item in temp)
+ {
+ var find = CanMessageList.FirstOrDefault(s => s.报文ID == item.FIdentifier);
+ if (find != null)
+ {
+ if (find.通道 != item.FIdxChn) find.通道 = item.FIdxChn;
+ if (find.报文ID != item.FIdentifier) find.报文ID = item.FIdentifier;
+ if (find.时间戳 != item.FTimeUS) find.时间戳 = item.FTimeUS;
+ if (find.FD != item.FIsFD) find.FD = item.FIsFD;
+ if (find.IsTx != item.FIsTx) find.IsTx = item.FIsTx;
+
+ var 长度改变 = find.长度 == item.FDLC;
+ var 数据改变 = find.Bytes.SequenceEqual(item.FData);
+ if (长度改变 || 数据改变)
+ {
+ find.长度 = item.FDLC;
+ Array.Copy(item.FData, 0, find.Bytes, 0, 64);
+ find.报文 = 十六进制字符串转换(item.FData.AsSpan(0, find.长度));
+ }
+ }
+ else
+ {
+ var 报文temp = new CanMessageShowVM()
+ {
+ 通道 = item.FIdxChn,
+ 报文ID = item.FIdentifier,
+ 时间戳 = item.FTimeUS,
+ FD = item.FIsFD,
+ IsTx = item.FIsTx,
+ 长度 = item.FDLC,
+ 报文 = 十六进制字符串转换(item.FData.AsSpan(0, item.FDLC)),
+ };
+ Array.Copy(item.FData, 0, 报文temp.Bytes, 0, 64);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ CanMessageList.Add(报文temp);
+ });
+ }
+
+ }
+ await Task.Delay(50, ct);
+ }
+ }
+ string 十六进制字符串转换(Span s)
+ {
+ if (s == null || s.Length == 0)
+ return "[]";
+
+ var sb = new StringBuilder();
+ sb.Append('[');
+ for (int i = 0; i < s.Length; i++)
+ {
+ sb.Append("0x");
+ sb.Append(s[i].ToString("X2"));
+ if (i < s.Length - 1)
+ sb.Append(", ");
+ }
+ sb.Append(']');
+ return sb.ToString();
+ }
+ #region dialog规范
+ public DialogCloseListener RequestClose { get; set; }
+
+ public bool CanCloseDialog()
+ {
+ return true;
+ }
+
+ public void OnDialogClosed()
+ {
+ cts.Cancel();
+ }
+
+ public void OnDialogOpened(IDialogParameters parameters)
+ {
+ Task.Run(() => Refresh(cts.Token), cts.Token);
+ }
+
+
+ #endregion
+ }
+}
diff --git a/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs b/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
index 80cf9f7..f2c15d0 100644
--- a/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
+++ b/CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
@@ -1,30 +1,41 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Configuration;
using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
using System.Windows.Input;
+using Model;
+using TSMasterCAN;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
-using UIShare.ViewModelBase;
-using ZLGUSBCANFD;
namespace CANModule.ViewModels
{
- public class CollectCANSignalSettingViewModel : DialogViewModelBase
+ public class CollectCANSignalSettingViewModel : BindableBase, IDialogAware
{
- #region 字段
- private ZLGCANFD? _canfd;
- private SystemConfig? _systemConfig;
- #endregion
-
#region 属性
public ObservableCollection ConfigurationList
{
- get => _systemConfig?.ConfigurationList ?? new ObservableCollection();
+ get => _systemConfig.ConfigurationList;
+ set
+ {
+ if(_systemConfig.ConfigurationList != value)
+ {
+ _systemConfig.ConfigurationList = value;
+ RaisePropertyChanged();
+ }
+ }
}
-
-
+ private ObservableCollection _ChannelList;
+ public ObservableCollection ChannelList
+ {
+ get => _ChannelList;
+ set => SetProperty(ref _ChannelList, value);
+ }
private ObservableCollection _messageList;
public ObservableCollection MessageList
{
@@ -39,42 +50,23 @@ namespace CANModule.ViewModels
set => SetProperty(ref _signalList, value);
}
- private string _title = "信号采集设置";
+ private string _title= "信号采集设置";
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
-
private string _selectedMessage;
public string SelectedMessage
{
get => _selectedMessage;
set
{
- if (SetProperty(ref _selectedMessage, value))
+ if (SetProperty(ref _selectedMessage,value))
{
- if (!string.IsNullOrEmpty(value) && _canfd != null)
+ if (value != null && value != "")
{
- var db = _canfd.DBCParser.MsgDatabase;
- if (MessageChannel >= 0 && MessageChannel < db.Count)
- {
- var targetMsg = db[MessageChannel]
- .FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
-
- if (targetMsg != null && targetMsg.signal_Name != null)
- {
- SignalList = new ObservableCollection(targetMsg.signal_Name);
- }
- else
- {
- SignalList = new ObservableCollection();
- }
- }
- }
- else
- {
- SignalList = new ObservableCollection();
+ SignalList = new(DBCParse.MsgDatabase[MessageChannel].Where(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value).First().signal_Name);
}
}
}
@@ -86,7 +78,6 @@ namespace CANModule.ViewModels
get => _SelectedItem;
set => SetProperty(ref _SelectedItem, value);
}
-
private string _selectedSignal;
public string SelectedSignal
{
@@ -94,7 +85,7 @@ namespace CANModule.ViewModels
set => SetProperty(ref _selectedSignal, value);
}
- private int _MessageChannel=-1;
+ private int _MessageChannel;
public int MessageChannel
{
get => _MessageChannel;
@@ -103,127 +94,91 @@ namespace CANModule.ViewModels
if (_MessageChannel != value)
{
_MessageChannel = value;
-
+ MessageList = new(DBCParse.MsgDatabase[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
+ SignalList = new();
SelectedMessage = null;
SelectedSignal = null;
- SignalList = new ObservableCollection();
-
- if (_canfd != null)
- {
- var db = _canfd.DBCParser.MsgDatabase;
- if (db != null && value >= 0 && value < db.Count)
- {
- MessageList = new ObservableCollection(
- db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
- );
- }
- else
- {
- MessageList = new ObservableCollection();
- }
- }
- else
- {
- MessageList = new ObservableCollection();
- }
-
RaisePropertyChanged();
}
}
}
- private int _collectionInterval = 1000;
+ private int _collectionInterval=1000;
public int CollectionInterval
{
get => _collectionInterval;
set => SetProperty(ref _collectionInterval, value);
}
- #endregion
-
- #region 命令
+ #endregion Properties
public ICommand AddCommand { get; set; }
- public ICommand CloseCommand { get; set; }
- public ICommand SaveCommand { get; set; }
- public ICommand DeleteCommand { get; set; }
- #endregion
-
- public CollectCANSignalSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ public ICommand CloseCommand { get; set; }
+ public ICommand LoadCommand { get; set; }
+ public ICommand SaveCommand { get; set; }
+ public ICommand DeleteCommand { get; set; }
+ public DialogCloseListener RequestClose { get; set; }
+ public IEventAggregator _eventAggregator { get; set; }
+ private SystemConfig _systemConfig;
+ public CollectCANSignalSettingViewModel(IContainerProvider containerProvider)
{
+ _eventAggregator=containerProvider.Resolve();
+ _systemConfig= containerProvider.Resolve();
CloseCommand = new DelegateCommand(Close);
AddCommand = new DelegateCommand(Add);
+ LoadCommand = new DelegateCommand(Load);
SaveCommand = new DelegateCommand(Save);
DeleteCommand = new DelegateCommand(Delete);
}
- #region Dialog 生命周期
- public override void OnDialogOpened(IDialogParameters parameters)
- {
- _systemConfig = parameters.GetValue("SystemConfig");
- _canfd = _systemConfig?.CANFD;
- string title = _systemConfig.Title;
- char lastChar = title[title.Length - 1];
- // 3. "TestCellX"转int
- MessageChannel = int.Parse(lastChar.ToString())-1;
- if (MessageChannel >= 4) MessageChannel -= 4;
- RaisePropertyChanged(nameof(ConfigurationList));
- }
- #endregion
-
- #region 命令处理
private void Delete()
{
- if (SelectedItem != null)
- {
- _systemConfig?.ConfigurationList.Remove(SelectedItem);
- }
+ ConfigurationList.Remove(SelectedItem);
}
private void Save()
{
+ _eventAggregator.GetEvent().Publish();
ConfigService.Save(_systemConfig);
}
+ private void Load()
+ {
+ ChannelList = new ObservableCollection(Enumerable.Range(0, 12));
+ }
+
private void Add()
{
- if (string.IsNullOrEmpty(SelectedSignal) || string.IsNullOrEmpty(SelectedMessage) || CollectionInterval < 100)
+ if (string.IsNullOrEmpty(SelectedSignal) || string.IsNullOrEmpty(SelectedMessage)||CollectionInterval<100)
{
return;
}
- if (_systemConfig == null) return;
-
string hexString = ExtractHex(SelectedMessage);
- if (string.IsNullOrEmpty(hexString)) return;
-
- int decimalValue = ConvertHexToDecimal(hexString);
- if (_systemConfig.ConfigurationList.Any(x => x.SignalName == SelectedSignal && x.Channel == MessageChannel))
+ if (string.IsNullOrEmpty(hexString))
{
return;
}
-
- _systemConfig.ConfigurationList.Add(new CANSignalConfig
+ int decimalValue = ConvertHexToDecimal(hexString);
+ if (ConfigurationList.Where(x => x.SignalName == SelectedSignal&&x.Channel==MessageChannel).FirstOrDefault() != null) return;
+ ConfigurationList.Add(new CANSignalConfig
{
CollectionID = Guid.NewGuid(),
- Channel = MessageChannel,
- MethodName = CANSignalBroadcaster.BuildMethodName((uint)decimalValue, SelectedSignal),
+ Channel = MessageChannel,
MessageName = ExtractMessageName(SelectedMessage),
SignalName = SelectedSignal,
MessageID = decimalValue,
CollectionInterval = CollectionInterval
});
}
-
private string ExtractHex(string input)
{
int startIndex = input.IndexOf("0x") + 2;
- int endIndex = input.IndexOf("]", startIndex);
+ int endIndex = input.IndexOf("]", startIndex);
if (startIndex >= 2 && endIndex > startIndex)
{
return input.Substring(startIndex, endIndex - startIndex);
}
return string.Empty;
}
-
private int ConvertHexToDecimal(string hex)
{
try
@@ -232,20 +187,37 @@ namespace CANModule.ViewModels
}
catch (FormatException)
{
- return -1;
+ return -1;
}
}
-
private string ExtractMessageName(string input)
{
var parts = input.Split(']');
- return parts.Length > 1 ? parts[1] : string.Empty;
+ return parts.Length > 1 ? parts[1] : string.Empty;
}
private void Close()
{
RequestClose.Invoke();
}
- #endregion
+
+ public bool CanCloseDialog()
+ {
+ return true;
+ }
+
+ public void OnDialogClosed()
+ {
+
+ }
+
+ public void OnDialogOpened(IDialogParameters parameters)
+ {
+ if (DBCParse.MsgDatabase.Count == 0 || DBCParse.MsgDatabase[0].Count==0)
+ {
+
+ }
+ MessageList = new(DBCParse.MsgDatabase[0].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
+ }
}
}
diff --git a/CANModule/ViewModels/SelectCanMessageViewModel.cs b/CANModule/ViewModels/SelectCanMessageViewModel.cs
index 3b06b12..baa3808 100644
--- a/CANModule/ViewModels/SelectCanMessageViewModel.cs
+++ b/CANModule/ViewModels/SelectCanMessageViewModel.cs
@@ -1,29 +1,26 @@
using System;
+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;
using System.Windows.Input;
-using UIShare.ViewModelBase;
-using ZLGUSBCANFD;
+using TSMasterCAN;
+using UIShare.UIViewModel;
namespace CANModule.ViewModels
{
- public class SelectCanMessageViewModel : DialogViewModelBase
+ public class SelectCanMessageViewModel : BindableBase, IDialogAware
{
- #region 字段
- private ZLGCANFD? _canfd;
- #endregion
-
#region 属性
- public DialogCloseListener RequestClose { get; set; }
-
private ObservableCollection _ChannelList;
public ObservableCollection ChannelList
{
get => _ChannelList;
set => SetProperty(ref _ChannelList, value);
}
-
private ObservableCollection _messageList;
public ObservableCollection MessageList
{
@@ -44,7 +41,6 @@ namespace CANModule.ViewModels
get => _title;
set => SetProperty(ref _title, value);
}
-
private string _selectedMessage;
public string SelectedMessage
{
@@ -53,27 +49,9 @@ namespace CANModule.ViewModels
{
if (SetProperty(ref _selectedMessage, value))
{
- if (!string.IsNullOrEmpty(value) && _canfd != null)
+ if (value != null && value != "")
{
- var db = _canfd.DBCParser.MsgDatabase;
- if (MessageChannel >= 0 && MessageChannel < db.Count)
- {
- var targetMsg = db[MessageChannel]
- .FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
-
- if (targetMsg != null && targetMsg.signal_Name != null)
- {
- SignalList = new ObservableCollection(targetMsg.signal_Name);
- }
- else
- {
- SignalList = new ObservableCollection();
- }
- }
- }
- else
- {
- SignalList = new ObservableCollection();
+ SignalList = new(DBCParse.MsgDatabase[MessageChannel].Where(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value).First().signal_Name);
}
}
}
@@ -85,8 +63,7 @@ namespace CANModule.ViewModels
get => _selectedSignal;
set => SetProperty(ref _selectedSignal, value);
}
-
- private int _MessageChannel = -1;
+ private int _MessageChannel;
public int MessageChannel
{
get => _MessageChannel;
@@ -94,94 +71,84 @@ namespace CANModule.ViewModels
{
if (_MessageChannel != value)
{
- _MessageChannel = value;
-
- // 1. 先安全清空旧的选择项和信号列表
+ _MessageChannel=value;
+ MessageList = new(DBCParse.MsgDatabase[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
+ SignalList = new();
SelectedMessage = null;
SelectedSignal = null;
- SignalList = new ObservableCollection();
-
- // 2. 刷新当前通道的报文列表
- if (_canfd != null)
- {
- var db = _canfd.DBCParser.MsgDatabase;
- if (db != null && value >= 0 && value < db.Count)
- {
- MessageList = new ObservableCollection(
- db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
- );
- }
- else
- {
- MessageList = new ObservableCollection();
- }
- }
- else
- {
- MessageList = new ObservableCollection();
- }
-
RaisePropertyChanged();
}
}
}
#endregion
-
- #region 命令
public ICommand CloseCommand { get; set; }
public ICommand GetSelectedMessageCommand { get; set; }
public ICommand GetSelectedSignalCommand { get; set; }
public ICommand LoadCommand { get; set; }
- #endregion
-
- public SelectCanMessageViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ public DialogCloseListener RequestClose { get; set; }
+ public SelectCanMessageViewModel()
{
CloseCommand = new DelegateCommand(Close);
GetSelectedMessageCommand = new DelegateCommand(GetSelectedMessage);
GetSelectedSignalCommand = new DelegateCommand(GetSelectedSignal);
LoadCommand = new DelegateCommand(Load);
- }
- #region Dialog 生命周期
- public override void OnDialogOpened(IDialogParameters parameters)
- {
- _canfd = parameters.GetValue("CANFD");
- }
- #endregion
-
- #region 命令处理
- private void Close()
- {
- RequestClose.Invoke();
}
private void Load()
{
- int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
- ChannelList = new ObservableCollection(Enumerable.Range(0, maxCh));
+ ChannelList = new ObservableCollection(Enumerable.Range(0, 12));
}
private void GetSelectedSignal()
{
- if (!string.IsNullOrEmpty(SelectedSignal))
+ if (SelectedSignal!=null)
{
Application.Current.Dispatcher.Invoke(() =>
{
Clipboard.SetDataObject(SelectedSignal);
});
}
+ else
+ {
+
+ }
+
}
private void GetSelectedMessage()
{
- if (!string.IsNullOrEmpty(SelectedMessage) && SelectedMessage.Contains(']'))
+ if (SelectedSignal != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
- Clipboard.SetDataObject(SelectedMessage);
+ Clipboard.SetDataObject(SelectedMessage.Split("]")[1]);
});
}
+ else
+ {
+
+ }
+ }
+
+ private void Close()
+ {
+ RequestClose.Invoke();
+ }
+
+ public bool CanCloseDialog()
+ {
+ return true;
+ }
+
+ public void OnDialogClosed()
+ {
+
+ }
+
+ public void OnDialogOpened(IDialogParameters parameters)
+ {
+ MessageList = new(DBCParse.MsgDatabase[0].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
}
- #endregion
}
}
diff --git a/CANModule/ViewModels/ZLGCANFDViewModel.cs b/CANModule/ViewModels/ZLGCANFDViewModel.cs
deleted file mode 100644
index 1ebc85e..0000000
--- a/CANModule/ViewModels/ZLGCANFDViewModel.cs
+++ /dev/null
@@ -1,649 +0,0 @@
-using Microsoft.Win32;
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Input;
-using UIShare.GlobalVariable;
-using UIShare.UIViewModel;
-using UIShare.ViewModelBase;
-using ZLGUSBCANFD;
-
-namespace CANModule.ViewModels
-{
- public class ZLGCANFDViewModel : NavigateViewModelBase
- {
- #region 字段
- private ZLGCANFD? _canfd;
- private SystemConfig? _systemConfig;
- private CancellationTokenSource? _refreshCts;
- private readonly DeviceManager _deviceManager;
- #endregion
-
- #region 属性
- private string _Title = "周立功CAN";
- public string Title
- {
- get => _Title;
- set => SetProperty(ref _Title, value);
- }
-
- private ObservableCollection _CanMessageList = new();
- public ObservableCollection CanMessageList
- {
- get => _CanMessageList;
- set => SetProperty(ref _CanMessageList, value);
- }
-
- private int _DBCChannel = 0;
- public int DBCChannel
- {
- get => _DBCChannel;
- set => SetProperty(ref _DBCChannel, value);
- }
-
- private bool _IsDirectlySend = false;
- public bool IsDirectlySend
- {
- get => _IsDirectlySend;
- set => SetProperty(ref _IsDirectlySend, value);
- }
-
- private ObservableCollection _Message;
- public ObservableCollection Message
- {
- get => _Message;
- set => SetProperty(ref _Message, value);
- }
-
- private ObservableCollection _Signal;
- public ObservableCollection Signal
- {
- get => _Signal;
- set => SetProperty(ref _Signal, value);
- }
-
- private byte _channel;
- private string _selectedMessage;
- public string SelectedMessage
- {
- get => _selectedMessage;
- set
- {
- if (_selectedMessage != value)
- {
- if (!string.IsNullOrEmpty(value) && _canfd != null)
- {
- var db = _canfd.DBCParser.MsgDatabase;
- if (_channel >= 0 && _channel < db.Count)
- {
- var targetMsg = db[(int)_channel]
- .FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
-
- if (targetMsg != null && targetMsg.signal_Name != null)
- {
- Signal = new ObservableCollection(targetMsg.signal_Name);
- }
- else
- {
- Signal = new ObservableCollection();
- }
- }
- }
- else
- {
- Signal = new ObservableCollection();
- }
- _selectedMessage = value;
- RaisePropertyChanged();
- }
- }
- }
-
- private string _selectedSignal;
- public string SelectedSignal
- {
- get => _selectedSignal;
- set => SetProperty(ref _selectedSignal, value);
- }
-
- private string _signalValue;
- public string SignalValue
- {
- get => _signalValue;
- set => SetProperty(ref _signalValue, value);
- }
-
- private string _cycleSendInterval;
- public string CycleSendInterval
- {
- get => _cycleSendInterval;
- set => SetProperty(ref _cycleSendInterval, value);
- }
-
- private int _messageSendChannel = 0;
- public int MessageSendChannel
- {
- get => _messageSendChannel;
- set => SetProperty(ref _messageSendChannel, value);
- }
-
- public string BLFPath
- {
- get => _systemConfig?.DefaultBLFFilePath ?? "";
- set
- {
- if (_systemConfig != null && _systemConfig.DefaultBLFFilePath != value)
- {
- _systemConfig.DefaultBLFFilePath = value;
- RaisePropertyChanged();
- }
- }
- }
-
- public string DBCPath
- {
- get => _systemConfig?.DefaultDBCFilePath ?? "";
- set
- {
- if (_systemConfig != null && _systemConfig.DefaultDBCFilePath != value)
- {
- _systemConfig.DefaultDBCFilePath = value;
- RaisePropertyChanged();
- }
- }
- }
-
- private bool _IsSaved;
- public bool IsSaved
- {
- get => _IsSaved;
- set => SetProperty(ref _IsSaved, value);
- }
-
- public ObservableCollection DBCAutoLoadList
- {
- get => _systemConfig?.DBCAutoLoadList ?? new ObservableCollection();
- }
-
- private AutoDBCLoadItem _SelectedDcAutoLoad;
- public AutoDBCLoadItem SelectedDcAutoLoad
- {
- get => _SelectedDcAutoLoad;
- set => SetProperty(ref _SelectedDcAutoLoad, value);
- }
-
- // ===== 自定义报文发送属性 =====
- private APP_CHANNEL[] _appChannelNames = Enum.GetValues();
- public APP_CHANNEL[] APP_CHANNEL
- {
- get => _appChannelNames;
- set => SetProperty(ref _appChannelNames, value);
- }
-
- private APP_CHANNEL _aIdxChn;
- public APP_CHANNEL AIdxChn
- {
- get => _aIdxChn;
- set => SetProperty(ref _aIdxChn, value);
- }
-
- private int _aID;
- public int AID
- {
- get => _aID;
- set => SetProperty(ref _aID, value);
- }
-
- private bool _aIsTx;
- public bool AIsTx
- {
- get => _aIsTx;
- set => SetProperty(ref _aIsTx, value);
- }
-
- private bool _aIsExt;
- public bool AIsExt
- {
- get => _aIsExt;
- set => SetProperty(ref _aIsExt, value);
- }
-
- private bool _aIsRemote;
- public bool AIsRemote
- {
- get => _aIsRemote;
- set => SetProperty(ref _aIsRemote, value);
- }
-
- private byte _aDlc;
- public byte ADLC
- {
- get => _aDlc;
- set => SetProperty(ref _aDlc, value);
- }
-
- private byte[] _aDataArray;
- public byte[] ADataArray
- {
- get => _aDataArray;
- set => SetProperty(ref _aDataArray, value);
- }
-
- private bool _aIsFD = true;
- public bool AIsFD
- {
- get => _aIsFD;
- set => SetProperty(ref _aIsFD, value);
- }
-
- private bool _aIsBRS = false;
- public bool AIsBRS
- {
- get => _aIsBRS;
- set => SetProperty(ref _aIsBRS, value);
- }
-
- private bool _AutoSetting = true;
- public bool AutoSetting
- {
- get => _AutoSetting;
- set => SetProperty(ref _AutoSetting, value);
- }
-
- private string _CustomMessage = "[00,11,22,33,AA,BB,CC,DD]";
- public string CustomMessage
- {
- get => _CustomMessage;
- set => SetProperty(ref _CustomMessage, value);
- }
-
- private float _CustomSendCycleInterval = 0.0f;
- public float CustomSendCycleInterval
- {
- get => _CustomSendCycleInterval;
- set => SetProperty(ref _CustomSendCycleInterval, value);
- }
-
- //private bool _UpdateToDB;
- //public bool UpdateToDB
- //{
- // get => _UpdateToDB;
- // set => SetProperty(ref _UpdateToDB, value);
- //}
-
- // ===== 连接状态 =====
- private bool _IsDeviceOpened;
- public bool IsDeviceOpened
- {
- get => _IsDeviceOpened;
- set => SetProperty(ref _IsDeviceOpened, value);
- }
-
- private ObservableCollection _ChannelList;
- public ObservableCollection ChannelList
- {
- get => _ChannelList;
- set => SetProperty(ref _ChannelList, value);
- }
-
- private int _SelectedChannel = -1;
- public int SelectedChannel
- {
- get => _SelectedChannel;
- set
- {
- if (_SelectedChannel != value)
- {
- _SelectedChannel = value;
- if (value >= 0 && _canfd != null)
- {
- _canfd.初始化并启动通道((uint)value);
- }
- RaisePropertyChanged();
- }
- }
- }
- #endregion
-
- #region 命令
- public ICommand SelectBLFPathCommand { get; set; }
- public ICommand SaveBLFPathCommand { get; set; }
- public ICommand StartTranscribeCommand { get; set; }
- public ICommand EndTranscribeCommand { get; set; }
- public ICommand ConnectCanCommand { get; set; }
- public ICommand CloseCanCommand { get; set; }
- public ICommand ClearCycleCommand { get; set; }
- public ICommand UpLoadDBCCommand { get; set; }
- public ICommand ChangeDBCCommand { get; set; }
- public ICommand LoadDBCCommand { get; set; }
- public ICommand LoadMessageCommand { get; set; }
- public ICommand SetAndSendMessageCommand { get; set; }
- public ICommand DeleteDcAutoLoadCommand { get; set; }
- public ICommand SendCustomMessageCommand { get; set; }
- public ICommand LoadCommand { get; set; }
- #endregion
-
- public ZLGCANFDViewModel(IContainerProvider containerProvider) : base(containerProvider)
- {
- _systemConfig = containerProvider.Resolve();
- _deviceManager = containerProvider.Resolve();
- _canfd = _deviceManager.CANFD;
- SelectBLFPathCommand = new DelegateCommand(SelectBLFPath);
- SaveBLFPathCommand = new DelegateCommand(SaveBLFPath);
- StartTranscribeCommand = new DelegateCommand(StartTranscribe);
- EndTranscribeCommand = new DelegateCommand(EndTranscribe);
- ConnectCanCommand = new DelegateCommand(ConnectCan);
- CloseCanCommand = new DelegateCommand(CloseCan);
- ClearCycleCommand = new DelegateCommand(ClearCycle);
- UpLoadDBCCommand = new DelegateCommand(UpLoadDBC);
- ChangeDBCCommand = new DelegateCommand(ChangeDBC);
- LoadDBCCommand = new DelegateCommand(LoadDBC);
- SetAndSendMessageCommand = new DelegateCommand(SetAndSendMessage);
- LoadMessageCommand = new DelegateCommand(LoadMessage);
- DeleteDcAutoLoadCommand = new DelegateCommand(DeleteDcAutoLoad);
- SendCustomMessageCommand = new DelegateCommand(SendCustomMessage);
- _canfd.OnDbcMessageDecoded += OnDbcMessageDecoded;
- LoadCommand = new DelegateCommand(Load);
- }
-
- #region Dialog 生命周期
- public override void OnNavigatedTo(NavigationContext navigationContext)
- {
- if (_canfd != null)
- {
-
- }
-
- RaisePropertyChanged(nameof(BLFPath));
- RaisePropertyChanged(nameof(DBCPath));
- RaisePropertyChanged(nameof(DBCAutoLoadList));
- }
-
- public override void OnNavigatedFrom(NavigationContext navigationContext)
- {
- if (_canfd != null)
- {
- _canfd.OnDbcMessageDecoded -= OnDbcMessageDecoded;
- }
- _refreshCts?.Cancel();
- }
- #endregion
-
- #region 命令处理
- private void Load()
- {
- int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
- ChannelList = new ObservableCollection(Enumerable.Range(0, maxCh));
- }
-
- private void SendCustomMessage()
- {
- if (_canfd == null) return;
-
- try
- {
- ADataArray = 报文转换(CustomMessage);
- if (ADataArray.Length > byte.MaxValue) throw new Exception("报文数量超过上限!");
- if (AutoSetting)
- {
- ADLC = (byte)ADataArray.Length;
- }
- }
- catch (Exception ex)
- {
- ShowErrorMessageBox($"报文转换失败:{ex.Message}", () => { });
- return;
- }
-
- _canfd.发送自定义报文(
- (uint)AIdxChn,
- AID.ToString(),
- ADataArray,
- ADLC,
- AIsExt,
- AIsFD,
- AIsBRS);
- }
-
- private void DeleteDcAutoLoad()
- {
- if (SelectedDcAutoLoad != null)
- {
- DBCAutoLoadList.Remove(SelectedDcAutoLoad);
- }
- }
-
- private void LoadMessage()
- {
- if (_canfd == null) return;
-
- if (MessageSendChannel >= 0)
- {
- var db = _canfd.DBCParser.MsgDatabase;
- if (MessageSendChannel < db.Count)
- {
- Message = new ObservableCollection(
- db[MessageSendChannel].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
- }
- }
- }
-
- private void SetAndSendMessage()
- {
- if (_canfd == null) return;
-
- if (int.TryParse(CycleSendInterval, out int sendPeriod) &&
- double.TryParse(SignalValue, out double SValue) &&
- MessageSendChannel >= 0)
- {
- _channel = (byte)MessageSendChannel;
- var message = SelectedMessage.Split(']')[1];
-
- // 通过 DBC 查找帧 ID
- var db = _canfd.DBCParser.MsgDatabase;
- if (MessageSendChannel < db.Count)
- {
- var targetMsg = db[MessageSendChannel]
- .FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == SelectedMessage);
-
- if (targetMsg != null)
- {
- _canfd.设置报文((uint)MessageSendChannel, targetMsg.msg_id.ToString(), SelectedSignal, SValue, sendPeriod, IsDirectlySend);
- }
- }
- }
- }
-
- private void UpLoadDBC()
- {
- if (_canfd == null) return;
-
- // 释放当前通道的 DBC
- if (DBCChannel >= 0)
- {
- _canfd.释放通道DBC((uint)DBCChannel);
- }
- }
-
- private void ChangeDBC()
- {
- var dialog = new OpenFileDialog
- {
- Title = "更换DBC文件",
- Filter = "DBC 文件 (*.dbc)|*.dbc|所有文件 (*.*)|*.*",
- DefaultExt = ".dbc",
- };
- if (dialog.ShowDialog() == true)
- {
- DBCPath = System.IO.Path.GetFullPath(dialog.FileName);
- }
- }
-
- private void LoadDBC()
- {
- if (_canfd == null || DBCChannel < 0) return;
-
- bool re = _canfd.加载通道DBC文件((uint)DBCChannel, DBCPath);
-
- if (re && IsSaved)
- {
- var item = DBCAutoLoadList.FirstOrDefault(s => s.DBCChannel == DBCChannel);
- if (item == null)
- {
- DBCAutoLoadList.Add(new AutoDBCLoadItem
- {
- DBCFilePath = DBCPath,
- DBCChannel = DBCChannel
- });
- }
- else
- {
- DBCAutoLoadList.Remove(item);
- DBCAutoLoadList.Add(new AutoDBCLoadItem
- {
- DBCFilePath = DBCPath,
- DBCChannel = DBCChannel
- });
- }
- ConfigService.Save(_systemConfig);
- }
- }
- private void ClearCycle()
- {
- _canfd?.停止所有循环发送();
- }
-
- private void CloseCan()
- {
- if (_canfd == null) return;
-
- _canfd.关闭CAN卡设备();
- IsDeviceOpened = false;
- _refreshCts?.Cancel();
- }
-
- private void ConnectCan()
- {
- if (_canfd == null) return;
-
- bool re = _canfd.打开设备();
- IsDeviceOpened = re;
-
- if (re)
- {
- // 启动实时报文刷新
- _refreshCts?.Cancel();
- _refreshCts = new CancellationTokenSource();
- _ = Refresh(_refreshCts.Token);
- }
- }
-
- private void EndTranscribe()
- {
- // TODO: BLF 录制功能在 ZLGCANFD 中未实现,暂不支持
- ShowInfoMessageBox("BLF 录制功能尚未在此硬件驱动中实现。", () => { });
- }
-
- private void StartTranscribe()
- {
- // TODO: BLF 录制功能在 ZLGCANFD 中未实现,暂不支持
- ShowInfoMessageBox("BLF 录制功能尚未在此硬件驱动中实现。", () => { });
- }
-
- private void SaveBLFPath()
- {
- // 路径已直接绑定到 SystemConfig,无需额外操作
- }
-
- private void SelectBLFPath()
- {
- var dialog = new SaveFileDialog
- {
- Title = "保存 CAN报文回放 BLF 文件",
- Filter = "BLF 文件 (*.blf)|*.blf|所有文件 (*.*)|*.*",
- DefaultExt = ".blf",
- FileName = "log.blf",
- OverwritePrompt = true
- };
- if (dialog.ShowDialog() == true)
- {
- BLFPath = System.IO.Path.GetFullPath(dialog.FileName);
- }
- }
-
- #endregion
-
- #region 辅助方法
- public byte[] 报文转换(string s)
- {
- s = s.Replace("[", string.Empty).Replace("]", string.Empty);
- var strs = s.Split(',', StringSplitOptions.RemoveEmptyEntries);
- byte[] bytes = new byte[strs.Length];
- for (int i = 0; i < strs.Length; i++)
- {
- bytes[i] = Convert.ToByte(strs[i], 16);
- }
- return bytes;
- }
-
- private async Task Refresh(CancellationToken ct = default)
- {
- while (!ct.IsCancellationRequested)
- {
- // 通过 OnDbcMessageDecoded 事件已实时更新 CanMessageList
- // 此处仅做周期性 UI 刷新触发
- await Task.Delay(50, ct);
- }
- }
-
- private void OnDbcMessageDecoded(uint channel, ZDBC.DBCMessage msg)
- {
- // DBC 解码后的报文回调
- Application.Current.Dispatcher.BeginInvoke(() =>
- {
- var msgName = System.Text.Encoding.Default.GetString(msg.strName).TrimEnd('\0');
- var find = CanMessageList.FirstOrDefault(s => s.报文ID == (int)msg.nID&& s.通道==channel);
- if (find != null)
- {
- find.通道 = (byte)channel;
- find.报文ID = (int)msg.nID;
- find.时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(); ;
- find.长度 = (byte)msg.nSize;
- }
- else
- {
- CanMessageList.Add(new CanMessageShowVM
- {
- 通道 = (byte)channel,
- 报文ID = (int)msg.nID,
- 时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(),
- 长度 = (byte)msg.nSize
- });
- }
- });
- }
-
- private string 十六进制字符串转换(System.Span s)
- {
- if (s == null || s.Length == 0)
- return "[]";
-
- var sb = new StringBuilder();
- sb.Append('[');
- for (int i = 0; i < s.Length; i++)
- {
- sb.Append("0x");
- sb.Append(s[i].ToString("X2"));
- if (i < s.Length - 1)
- sb.Append(", ");
- }
- sb.Append(']');
- return sb.ToString();
- }
- #endregion
- }
-}
diff --git a/CANModule/Views/ZLGCANFDView.xaml b/CANModule/Views/CANView.xaml
similarity index 75%
rename from CANModule/Views/ZLGCANFDView.xaml
rename to CANModule/Views/CANView.xaml
index b063f7c..61022fa 100644
--- a/CANModule/Views/ZLGCANFDView.xaml
+++ b/CANModule/Views/CANView.xaml
@@ -1,10 +1,11 @@
-
-
-
-
-
-
+ MouseLeftButtonDown="MouseLeftButtonDown">
@@ -38,59 +34,46 @@
Foreground="White"
VerticalAlignment="Center"
Margin="5,0,10,0" />
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
@@ -98,11 +81,13 @@
-
+
+ Command="{Binding ChangeDBCCommand}"
+ CommandParameter="更换DBC" />
+ Command="{Binding LoadDBCCommand}"
+ CommandParameter="加载DBC" />
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Command="{Binding SetAndSendMessageCommand}"
+ CommandParameter="设置" />
-
+ ItemsSource="{Binding APP_CHANNEL}"
+ />
@@ -250,10 +247,11 @@
-
+ IsChecked="{Binding UpdateToDB}" />
@@ -268,16 +266,20 @@
VerticalContentAlignment="Center" />
+ Command="{Binding SelectBLFPathCommand}"
+ CommandParameter="选择路径" />
+ Command="{Binding SaveBLFPathCommand}"
+ CommandParameter="保存路径" />
+ Command="{Binding StartTranscirbeCommand}"
+ CommandParameter="开始录制" />
+ Command="{Binding EndTranscirbeCommand}"
+ CommandParameter="结束录制" />
diff --git a/CANModule/Views/ZLGCANFDView.xaml.cs b/CANModule/Views/CANView.xaml.cs
similarity index 85%
rename from CANModule/Views/ZLGCANFDView.xaml.cs
rename to CANModule/Views/CANView.xaml.cs
index 2e4e7f6..4d9cf01 100644
--- a/CANModule/Views/ZLGCANFDView.xaml.cs
+++ b/CANModule/Views/CANView.xaml.cs
@@ -16,14 +16,15 @@ using System.Windows.Shapes;
namespace CANModule.Views
{
///
- /// ZLGCANFDView.xaml 的交互逻辑
+ /// CANView.xaml 的交互逻辑
///
- public partial class ZLGCANFDView : UserControl
+ public partial class CANView : UserControl
{
- public ZLGCANFDView()
+ public CANView()
{
InitializeComponent();
}
+
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
@@ -31,5 +32,7 @@ namespace CANModule.Views
Window.GetWindow(this)?.DragMove();
}
}
+
+
}
}
diff --git a/CANModule/Views/CollectCANSignalSettingView.xaml b/CANModule/Views/CollectCANSingalSettingView.xaml
similarity index 83%
rename from CANModule/Views/CollectCANSignalSettingView.xaml
rename to CANModule/Views/CollectCANSingalSettingView.xaml
index dfb3fa9..ea65d6d 100644
--- a/CANModule/Views/CollectCANSignalSettingView.xaml
+++ b/CANModule/Views/CollectCANSingalSettingView.xaml
@@ -1,23 +1,27 @@
+xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+xmlns:oxy="http://oxyplot.org/wpf"
+xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+mc:Ignorable="d"
+xmlns:prism="http://prismlibrary.com/"
+xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
+Background="White"
+prism:ViewModelLocator.AutoWireViewModel="True"
+Height="400"
+Width="1000">
+
+
+
+
+
@@ -79,11 +83,12 @@
Margin="10">
-
-
-
-
+
+
+
-
+
-
+
-
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
index 6c6b01a..29d5fcc 100644
--- a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
+++ b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
@@ -7,11 +7,11 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
+using TSMasterCAN;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
-using ZLGUSBCANFD;
namespace MonitorModule.ViewModels.Dialogs
{
@@ -197,10 +197,10 @@ namespace MonitorModule.ViewModels.Dialogs
///
private IEnumerable<(string DisplayName, string Fingerprint, string MethodName)> DiscoverCanSignals()
{
- if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) yield break;
+ if (_deviceManager?.CANFD == null) yield break;
if (_systemConfig?.ConfigurationList == null) yield break;
- var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
+ var msgDb = DBCParse.MsgDatabase;
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
foreach (var cfg in _systemConfig.ConfigurationList)
{
@@ -331,10 +331,10 @@ namespace MonitorModule.ViewModels.Dialogs
private void OnDbcLoaded(DBCLoadedArgs args)
{
if (args.Scope != _systemConfig?.Title) return;
- if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) return;
+ if (_deviceManager?.CANFD == null) return;
int channel = (int)args.Channel;
- var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
+ var msgDb = DBCParse.MsgDatabase;
if (channel < 0 || channel >= msgDb.Count) return;
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs
index 4843448..f796a02 100644
--- a/MonitorModule/ViewModels/MonitorViewModel.cs
+++ b/MonitorModule/ViewModels/MonitorViewModel.cs
@@ -18,11 +18,11 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
+using TSMasterCAN;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
-using ZLGUSBCANFD;
namespace MonitorModule.ViewModels
@@ -535,9 +535,9 @@ namespace MonitorModule.ViewModels
private void RefreshConfiguredCanSignals()
{
if (_systemConfig?.ConfigurationList == null || _systemConfig.ConfigurationList.Count == 0) return;
- if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) return;
+ if (_deviceManager?.CANFD == null) return;
- var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
+ var msgDb = DBCParse.MsgDatabase;
foreach (var channel in _systemConfig.ConfigurationList.Select(c => c.Channel).Distinct())
{
if (channel < 0 || channel >= msgDb.Count) continue;
@@ -633,10 +633,9 @@ namespace MonitorModule.ViewModels
private void OnDbcLoaded(DBCLoadedArgs args)
{
if (args.Scope != TestStatus) return;
- if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) return;
-
+ if (_deviceManager?.CANFD == null) return;
int channel = (int)args.Channel;
- var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
+ var msgDb = DBCParse.MsgDatabase;
if (channel < 0 || channel >= msgDb.Count) return;
RefreshCanSignalsForChannel(args.Channel, msgDb[channel]);
diff --git a/TSMasterCAN/CAN.cs b/TSMasterCAN/CAN.cs
new file mode 100644
index 0000000..029c0d6
--- /dev/null
+++ b/TSMasterCAN/CAN.cs
@@ -0,0 +1,679 @@
+using Common.Attributes;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.Json;
+using TSMaster;
+namespace TSMasterCAN
+{
+ [ACPCommand]
+ public class CAN : IDisposable
+ {
+ #region 静态成员
+
+ public static event Action? ConnectEvent;
+ public static event Action? DisConnectEvent;
+ public static bool ConnectFlag { get; set; } = false;
+
+ ///
+ /// 实时接收到的 CAN 报文缓存(按报文 ID 索引,保留最新帧)。
+ /// 供 CANViewModel.Refresh 等界面轮询读取。
+ ///
+ public static ConcurrentDictionary RealTimeMessages { get; } = new();
+
+ #endregion
+
+ #region 实例字段与属性
+
+ /// 设备类型号(43 = USBCANFD-400U 等,TSMaster 中作为配置参考保留)
+ public uint DeviceType { get; }
+ /// 设备索引
+ public uint DeviceIndex { get; }
+ /// 最大通道数
+ public int MaxChannels { get; }
+ /// 仲裁域波特率
+ public string ABitBaud { get; }
+ /// 数据域波特率
+ public string DBitBaud { get; }
+ /// 是否开启终端电阻
+ public bool EnableTerminalResistance { get; }
+
+ /// DBC 解析器(提供 MsgDatabase、MaxChannels 等访问)
+ public DBCParse DBCParser => DBCParse.Instance;
+
+ ///
+ /// DBC 报文解码事件:当接收到 CAN 帧并通过 DBC 解码后触发。
+ /// 参数:(通道号, 解码后的报文信息)
+ ///
+ public event Action? OnDbcMessageDecoded;
+
+ private bool _disposed;
+ private bool _isOpened;
+ private TCANFDQueueEvent_Win32? _instanceListener;
+
+ #endregion
+
+ #region 构造函数
+
+ /// 默认构造函数(兼容 SystemConfig.CANFD = new() 等场景)
+ public CAN() : this(43, 0, 4, "500000", "2000000", true) { }
+
+ ///
+ /// 带配置参数的构造函数(兼容原 ZLG 设备管理流程)。
+ ///
+ /// 设备类型号
+ /// 设备索引
+ /// 最大通道数
+ /// 仲裁域波特率
+ /// 数据域波特率
+ /// 是否开启终端电阻
+ public CAN(uint deviceType, uint deviceIndex, int maxChannels, string aBitBaud, string dBitBaud, bool enableTerminalResistance)
+ {
+ DeviceType = deviceType;
+ DeviceIndex = deviceIndex;
+ MaxChannels = maxChannels;
+ ABitBaud = aBitBaud;
+ DBitBaud = dBitBaud;
+ EnableTerminalResistance = enableTerminalResistance;
+ }
+
+ #endregion
+
+ #region 实例方法 — 设备管理(替代原 ZLG 实例方法)
+
+ ///
+ /// 打开设备:初始化 TSMaster 库 → 设置通道数 → 配置波特率 → 连接 → 启动 RBS → 注册监听。
+ ///
+ /// true 表示成功
+ public bool 打开设备()
+ {
+ if (_isOpened) return true;
+ if (ConnectFlag) { _isOpened = true; return true; }
+
+ try
+ {
+ // 1. 初始化 TSMaster 库
+ var re = Init("ACP");
+ if (re != 0)
+ {
+ Debug.WriteLine($"TSMaster 初始化失败,错误代码:{re}");
+ return false;
+ }
+
+ // 2. 设置通道数
+ TsMasterApi.tsapp_set_can_channel_count(MaxChannels);
+
+ // 3. 配置各通道波特率
+ float arbKbps = float.Parse(ABitBaud) / 1000f;
+ float dataKbps = float.Parse(DBitBaud) / 1000f;
+ for (int ch = 0; ch < MaxChannels; ch++)
+ {
+ TsMasterApi.tsapp_configure_baudrate_canfd(
+ ch, arbKbps, dataKbps,
+ TLIBCANFDControllerType.lfdtISOCAN,
+ TLIBCANFDControllerMode.lfdmNormal,
+ EnableTerminalResistance);
+ }
+
+ // 4. 连接硬件
+ re = Connect();
+ if (re != 0)
+ {
+ Debug.WriteLine($"CAN 连接失败,错误代码:{re}");
+ return false;
+ }
+
+ // 5. 注册接收监听(用于 DBC 信号解码广播)
+ _instanceListener = new TCANFDQueueEvent_Win32(OnCanFdReceived);
+ RegisterListener(_instanceListener);
+
+ _isOpened = true;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"打开设备异常:{ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// 关闭 CAN 卡设备:注销监听 → 断开连接。
+ ///
+ public void 关闭CAN卡设备()
+ {
+ if (!_isOpened && !ConnectFlag) return;
+
+ try
+ {
+ if (_instanceListener != null)
+ {
+ UnRegisterListener(_instanceListener);
+ _instanceListener = null;
+ }
+ DisConnect();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"关闭CAN卡设备异常:{ex.Message}");
+ }
+ finally
+ {
+ _isOpened = false;
+ }
+ }
+
+ ///
+ /// 初始化并启动指定通道(配置波特率,TSMaster 在 Connect 时已自动启动所有通道)。
+ ///
+ /// 通道号
+ public void 初始化并启动通道(uint channel)
+ {
+ if (channel >= MaxChannels) return;
+ try
+ {
+ float arbKbps = float.Parse(ABitBaud) / 1000f;
+ float dataKbps = float.Parse(DBitBaud) / 1000f;
+ TsMasterApi.tsapp_configure_baudrate_canfd(
+ (int)channel, arbKbps, dataKbps,
+ TLIBCANFDControllerType.lfdtISOCAN,
+ TLIBCANFDControllerMode.lfdmNormal,
+ EnableTerminalResistance);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"初始化通道 {channel} 异常:{ex.Message}");
+ }
+ }
+
+ ///
+ /// 加载通道 DBC 文件。
+ ///
+ /// 通道号
+ /// DBC 文件路径
+ /// true 表示加载成功
+ public bool 加载通道DBC文件(uint channel, string filePath)
+ {
+ try
+ {
+ var re = LoadDBC(filePath, [(int)channel], out _);
+ return re == 0;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"加载通道 {channel} DBC 文件异常:{ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// CAN FD 接收回调:接收原始帧 → 查找 DBC → 解码信号 → 触发 OnDbcMessageDecoded 事件。
+ ///
+ private void OnCanFdReceived(ref int AObj, ref TLIBCANFD AData)
+ {
+ if (_disposed) return;
+
+ // 始终缓存最新报文,供界面轮询刷新
+ int identifier = AData.FIdentifier;
+ var canfdCopy = AData;
+ RealTimeMessages[identifier] = canfdCopy;
+
+ if (OnDbcMessageDecoded == null) return;
+
+ try
+ {
+ uint channel = AData.FIdxChn;
+ if (channel >= DBCParse.MsgDatabase.Count) return;
+
+ // 在 DBC 数据库中查找匹配的报文
+ var msgList = DBCParse.MsgDatabase[(int)channel];
+ var match = msgList.FirstOrDefault(m => m.msg_id == identifier);
+ if (match == null || match.signal_Name == null || match.signal_Name.Length == 0) return;
+
+ // 解码各信号值
+ var signals = new DBCSignal[match.signal_Name.Length];
+ for (int i = 0; i < match.signal_Name.Length; i++)
+ {
+ double value = 0;
+ TsMasterApi.tsdb_get_signal_value_canfd(ref canfdCopy, match.msg_name, match.signal_Name[i], ref value);
+ signals[i] = new DBCSignal
+ {
+ strName = Encoding.Default.GetBytes(match.signal_Name[i]),
+ nRawvalue = value,
+ nFactor = 1,
+ nOffset = 0
+ };
+ }
+
+ var decoded = new DBCMessage
+ {
+ nID = (uint)identifier,
+ nSignalCount = signals.Length,
+ vSignals = signals
+ };
+
+ OnDbcMessageDecoded?.Invoke(channel, decoded);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"DBC 信号解码异常:{ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ 关闭CAN卡设备();
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+
+ ///
+ /// 初始化TSMasterCAN
+ ///
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ public static int Init(string ProjectName, string? filePath = null)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ return TsMasterApi.initialize_lib_tsmaster(ProjectName);
+ }
+ else
+ {
+ return TsMasterApi.initialize_lib_tsmaster_with_project(ProjectName, Path.GetFullPath(filePath));
+ }
+ }
+
+ ///
+ /// 释放TSMasterCAN
+ ///
+ [Browsable(false)]
+ public static void Release()
+ {
+ TsMasterApi.finalize_lib_tsmaster();
+ }
+
+ public static int obj = 0;
+ public static TCANFDQueueEvent_Win32 listener;
+ [Browsable(false)]
+ public static int RegisterListener(TCANFDQueueEvent_Win32 listenEvent)
+ {
+ listener = listenEvent;
+ var re = TsMasterApi.tsapp_register_event_canfd(ref obj, listener);
+ Debug.Assert(re == 0);
+ return re;
+ }
+
+ [Browsable(false)]
+ public static int UnRegisterListener(TCANFDQueueEvent_Win32 listenEvent = null)
+ {
+ if (listenEvent is not null)
+ {
+ listener = listenEvent;
+ }
+ var re = TsMasterApi.tsapp_unregister_event_canfd(ref obj, listener);
+ Debug.Assert(re == 0);
+ return re;
+ }
+
+ ///
+ /// 连接
+ ///
+ ///
+ public static int Connect()
+ {
+
+ var re = TsMasterApi.tsapp_connect();
+ if (re == 0)
+ {
+ re = TsMasterApi.tscom_can_rbs_start();
+ if (re == 0)
+ {
+ ConnectFlag = true;
+ Task.Run(() => ConnectEvent?.Invoke());
+ }
+ else
+ {
+ Debug.WriteLine($"CAN_RBS启动失败,错误代码:{re}");
+ }
+ }
+ else
+ {
+ Debug.WriteLine($"CAN连接失败,错误代码:{re}");
+ return re;
+ }
+ return re;
+ }
+
+ ///
+ /// 断开
+ ///
+ ///
+ public static int DisConnect()
+ {
+ var re = TsMasterApi.tsapp_disconnect();
+ //Debug.Assert(re == 0);
+ if (re == 0)
+ {
+ ConnectFlag = false;
+ Task.Run(() => DisConnectEvent?.Invoke());
+ }
+ else
+ {
+ Debug.WriteLine($"断开CAN连接失败,错误代码:{re}");
+ }
+
+
+ return re;
+ }
+
+ ///
+ /// 加载DBC
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ public static int LoadDBC(string filePath, int[] channel, out uint databaseID)
+ {
+ databaseID = 0;
+
+ var re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID);
+ foreach (var item in channel)
+ {
+ DBCParse.parse(databaseID, item);
+ DBCParse.rbs_parse(databaseID, item);
+ }
+
+ return re;
+ }
+
+ ///
+ /// 卸载DBC
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ public static int UnLoadDBC(uint? databaseID = null)
+ {
+ if (databaseID == null)
+ {
+ return TsMasterApi.tsdb_unload_can_dbs();
+ }
+ else
+ {
+ return TsMasterApi.tsdb_unload_can_db(databaseID.Value);
+ }
+ }
+
+ ///
+ /// 开始记录日志
+ ///
+ ///
+ ///
+ public static int StartLogging(string filePath)
+ {
+ return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
+ }
+
+ ///
+ /// 结束记录日志
+ ///
+ ///
+ public static int StopLogging()
+ {
+ return TsMasterApi.tsapp_stop_logging();
+ }
+
+ ///
+ /// 弹出通道映射窗口
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ public static int ShowChannelMappingWindow(bool isWait = false)
+ {
+ return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait);
+ }
+
+ ///
+ /// 获取信号值
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static double GetSignalValue(byte channel, string AMsgName, string ASgnName)
+ {
+ double value = double.NaN;
+ var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
+ var re = TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
+
+ return value;
+ }
+
+ ///
+ /// 获取信号值
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Browsable(false)]
+ public static int GetSignalValue(byte channel, string AMsgName, string ASgnName, ref double value)
+ {
+ var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
+ return TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
+ }
+
+ ///
+ /// 获取信号值
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+
+ [Browsable(false)]
+ public static int GetSignalValue(ref TLIBCANFD ACANFD, string AMsgName, string ASgnName, ref double value)
+ {
+ return TsMasterApi.tsdb_get_signal_value_canfd(ref ACANFD, AMsgName, ASgnName, ref value);
+ }
+
+ ///
+ /// 设置信号值
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0)
+ {
+ var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
+ find.ACANFD.FIdxChn = channel;
+ var re = TsMasterApi.tsdb_set_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, AValue);
+ Debug.Assert(re == 0);
+ if (re != 0) throw new Exception($"设置报文失败!返回代码{re}");
+ if (re != 0) return re;
+ if (isSend)
+ {
+ if (sendPeriod == 0)
+ {
+ return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
+ }
+ else
+ {
+ return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
+ }
+ }
+ return 0;
+ }
+
+
+ ///
+ /// 设置信号值
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0)
+ {
+ var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName);
+ find.ACANFD.FIdxChn = (byte)channel;
+ if (sendPeriod == 0)
+ {
+ return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
+ }
+ else
+ {
+ return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
+ }
+ }
+
+ ///
+ /// 发送自定义报文
+ ///
+ ///
+ [Browsable(false)]
+ public static int SetMsg(TLIBCANFD msg)
+ {
+ return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
+ }
+
+ ///
+ /// 设置报文
+ ///
+ /// 通道
+ /// DBC数据库ID
+ /// 报文数组
+ public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes)
+ {
+ var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID);
+ if (find != null)
+ {
+ Array.Copy(bytes, 0, find.ACANFD.FData, 0, Math.Min(bytes.Length, find.ACANFD.FData.Length));
+ }
+ }
+
+ ///
+ /// 发送自定义报文
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt,
+ bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false,
+ bool isUpdateDBCDatabase = false, float sendPeriod = 0)
+ {
+ var send = new TLIBCANFD
+ {
+ FIdxChn = (byte)AIdxChn,
+ FProperties = 0,
+ FIdentifier = AID,
+ FDLC = ADLC,
+ FTimeUS = 0uL,
+ FData = new byte[64],
+ FFDProperties = 0,
+ FIsTx = AIsTx,
+ FIsError = false,
+ FIsExt = AIsExt,
+ FIsRemote = AIsRemote,
+ FIsFD = AIsFD,
+ FIsBRS = AIsBRS
+ };
+ int length = Math.Min(ADataArray.Length, 64);
+ Array.Copy(ADataArray, 0, send.FData, 0, length);
+ int re;
+ if (sendPeriod == 0)
+ {
+ re = TsMasterApi.tsapp_transmit_canfd_async(ref send);
+ }
+ else
+ {
+ re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod);
+ }
+
+ if (isUpdateDBCDatabase)
+ {
+ var find = DBCParse.MsgDatabase[(byte)AIdxChn].FirstOrDefault(s => s.ACANFD.FIdentifier == send.FIdentifier);
+ if (find != null)
+ {
+ Array.Copy(send.FData, 0, find.ACANFD.FData, 0, Math.Min(ADataArray.Length, find.ACANFD.FData.Length));
+ }
+ }
+ return re;
+ }
+
+ ///
+ /// 添加循环发送报文
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod)
+ {
+ var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
+ find.ACANFD.FIdxChn = channel;
+ return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
+ }
+
+ ///
+ /// 清除循环发送的报文
+ ///
+ ///
+ public static int DeleteCyclicMsgs()
+ {
+ return TsMasterApi.tsapp_delete_cyclic_msgs();
+ }
+
+ ///
+ /// 获取错误提示
+ ///
+ /// 错误代码
+ ///
+ public static string GetErrorDescription(int errorCode)
+ {
+ IntPtr ADesc = IntPtr.Zero;
+ TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc);
+
+ if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}";
+
+ // 假设返回的是 ANSI 字符串(如 C 的 char*)
+ string? description = Marshal.PtrToStringAnsi(ADesc);
+
+ return description ?? $"未知错误代码: {errorCode}";
+ }
+
+ }
+}
diff --git a/TSMasterCAN/DBCParse.cs b/TSMasterCAN/DBCParse.cs
new file mode 100644
index 0000000..e515d6f
--- /dev/null
+++ b/TSMasterCAN/DBCParse.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using TSMaster;
+
+namespace TSMasterCAN
+{
+ public struct can_network
+ {
+ [JsonInclude]
+ public string network_name;
+ [JsonInclude]
+ public can_node[] can_nodes;
+
+ }
+ public struct can_node
+ {
+ [JsonInclude]
+ public string node_name;
+ [JsonInclude]
+ public can_message[] tx_can_Messages;
+ [JsonInclude]
+ public can_message[] rx_can_Messages;
+ }
+ public struct can_message
+ {
+ [JsonInclude]
+ public string message_name;
+ [JsonInclude]
+ public string[] signals;
+ }
+
+ public class _Msg_
+ {
+ public int msg_type { get; set; }
+ public int msg_id { get; set; }
+ public int msg_cyclic { get; set; }
+ public byte msg_dlc { get; set; }
+ public string msg_name { get; set; }
+ public string[] signal_Name { get; set; }
+ public int msg_brs { get; set; }
+ public TLIBCANFD ACANFD;
+ }
+ public enum Msg_Type
+ {
+ CAN = 0,
+ CANFD = 1,
+ }
+ ///
+ /// DBC 文件信息类型枚举,用于指定 tsdb_get_can_db_info 查询的内容类型。
+ ///
+ public enum DBCINFO
+ {
+ /// 网络名称
+ NETWORK_Name = 0,
+
+ /// CAN 报文总数
+ CAN_MSG_COUNT = 11,
+ /// CAN FD 报文总数
+ CANFD_MSG_COUNT = 12,
+ /// 节点总数
+ NODE_COUNT = 14,
+
+ /// 信号名称
+ SIGNAL_NAME = 33,
+
+ /// CAN 报文类型
+ CAN_MSG_TYPE = 40,
+ /// CAN 报文 DLC(数据长度码)
+ CAN_MSG_DLC = 41,
+ /// CAN 报文 ID
+ CAN_MSG_ID = 42,
+ /// CAN 报文周期
+ CAN_MSG_CYCLIC = 43,
+ /// CAN 报文名称
+ CAN_MSG_NAME = 44,
+ /// CAN 报文信号数量
+ CAN_MSG_SIGNAL_COUNT = 47,
+ /// CAN 报文信号名称索引
+ CAN_MSG_SIGNAL_NAME_INDEX = 48,
+
+ /// CAN FD 报文类型
+ CANFD_MSG_TYPE = 60,
+ /// CAN FD 报文 DLC(数据长度码)
+ CANFD_MSG_DLC = 61,
+ /// CAN FD 报文 ID
+ CANFD_MSG_ID = 62,
+ /// CAN FD 报文周期
+ CANFD_MSG_CYCLIC = 63,
+ /// CAN FD 报文名称
+ CANFD_MSG_NAME = 64,
+ /// CAN FD 报文信号数量
+ CANFD_MSG_SIGNAL_COUNT = 67,
+ /// CAN FD 报文信号名称索引
+ CANFD_MSG_SIGNAL_NAME_INDEX = 68,
+
+ /// 节点名称
+ NODE_Name = 101,
+ /// 节点发送 CAN 报文数量
+ NODE_TX_CAN_COUNT = 103,
+ /// 节点发送 CAN 报文索引
+ NODE_TX_CAN_MESSAGE_INDEX = 104,
+ /// 节点接收 CAN 报文数量
+ NODE_RX_CAN_COUNT = 105,
+ /// 节点接收 CAN 报文索引
+ NODE_RX_CAN_MESSAGE_INDEX = 106,
+ /// 节点发送 CAN FD 报文数量
+ NODE_TX_CANFD_COUNT = 107,
+ /// 节点发送 CAN FD 报文索引
+ NODE_TX_CANFD_MESSAGE_INDEX = 108,
+ /// 节点接收 CAN FD 报文数量
+ NODE_RX_CANFD_COUNT = 109,
+ /// 节点接收 CAN FD 报文索引
+ NODE_RX_CANFD_MESSAGE_INDEX = 110,
+ }
+
+ ///
+ /// DBC 解码后的信号信息(替代原 ZLG ZDBC.DBCSignal)。
+ ///
+ public struct DBCSignal
+ {
+ /// 信号名称(原始字节,兼容 Encoding.Default 解码)
+ public byte[] strName;
+ /// 原始值(TSMaster tsdb_get_signal_value_canfd 返回的物理值)
+ public double nRawvalue;
+ /// 因子(TSMaster 返回物理值,因子固定为 1)
+ public double nFactor;
+ /// 偏移(TSMaster 返回物理值,偏移固定为 0)
+ public double nOffset;
+ }
+
+ ///
+ /// DBC 解码后的报文信息(替代原 ZLG ZDBC.DBCMessage)。
+ /// 在 CAN 接收回调中对原始 TLIBCANFD 帧解码后构建此对象并触发 OnDbcMessageDecoded 事件。
+ ///
+ public class DBCMessage
+ {
+ /// 报文 ID
+ public uint nID;
+ /// 信号数量
+ public int nSignalCount;
+ /// 信号数组
+ public DBCSignal[] vSignals;
+ }
+
+ public class DBCParse
+ {
+ private static DBCParse? _instance;
+ /// 单例实例,供 CAN.DBCParser 属性使用(MsgDatabase 等成员为 static,实例仅用于属性访问兼容)。
+ public static DBCParse Instance => _instance ??= new DBCParse();
+
+ /// 最大通道数(等于 MsgDatabase 的维度)
+ public int MaxChannels => MsgDatabase.Count;
+
+ public static List> MsgDatabase { get; set; } =
+ [.. Enumerable.Range(0, 12).Select(_ => new List<_Msg_>())];
+ public static can_network can_Network;
+ public static string tsdb_get_can_db_info(uint ADatabaseId, int AType, int AIndex, int ASubIndex)
+ {
+ nint tmpInt = new nint();
+ int ret = TsMasterApi.tsdb_get_can_db_info(ADatabaseId, AType, AIndex, ASubIndex, ref tmpInt);
+ if (ret == 0)
+ {
+ return Marshal.PtrToStringAnsi(tmpInt) ?? "";
+ }
+ else
+ return "";
+ }
+
+ public static void parse(uint Aid, int channel)
+ {
+ int CANcount = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_COUNT, 0, 0));
+ for (int i = 0; i < CANcount; i++)
+ {
+ _Msg_ temp = new _Msg_();
+ temp.msg_type = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_TYPE, i, 0));
+ temp.msg_dlc = byte.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_DLC, i, 0));
+ temp.msg_id = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_ID, i, 0));
+ temp.msg_name = tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_NAME, i, 0);
+ temp.msg_cyclic = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_CYCLIC, i, 0));
+ temp.signal_Name = new string[int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, i, 0))];
+ for (int signalcount = 0; signalcount < temp.signal_Name.Length; signalcount++)
+ {
+ temp.signal_Name[signalcount] = tsdb_get_can_db_info(Aid, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, i, signalcount)), 0);
+ }
+ temp.msg_brs = 0;
+ temp.ACANFD = new TLIBCANFD(APP_CHANNEL.CHN1,
+ temp.msg_id,
+ true,
+ temp.msg_type == 1 || temp.msg_type == 4 ? true : false,
+ false, temp.msg_dlc,
+ temp.msg_type < 2 ? false : true,
+ false);
+ MsgDatabase[channel].Add(temp);
+
+ }
+
+ CANcount = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_COUNT, 0, 0));
+ for (int i = 0; i < CANcount; i++)
+ {
+ _Msg_ temp = new _Msg_();
+ temp.msg_type = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_TYPE, i, 0));
+ temp.msg_dlc = byte.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_DLC, i, 0));
+ temp.msg_id = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_ID, i, 0));
+ temp.msg_name = tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_NAME, i, 0);
+ temp.msg_cyclic = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_CYCLIC, i, 0));
+ temp.signal_Name = new string[int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, i, 0))];
+ for (int signalcount = 0; signalcount < temp.signal_Name.Length; signalcount++)
+ {
+ temp.signal_Name[signalcount] = tsdb_get_can_db_info(Aid, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, i, signalcount)), 0);
+ }
+ temp.msg_brs = int.Parse(tsdb_get_can_db_info(Aid, 69, i, 0));
+ temp.ACANFD = new TLIBCANFD(APP_CHANNEL.CHN1, temp.msg_id, true, temp.msg_type == 1 || temp.msg_type == 4 ? true : false, false, temp.msg_dlc, temp.msg_type < 2 ? false : true, temp.msg_brs == 1 ? true : false);
+ MsgDatabase[channel].Add(temp);
+ }
+ MsgDatabase[channel].Sort((a, b) => a.msg_id.CompareTo(b.msg_id));
+ }
+
+ public static void rbs_parse(uint AID, int channel)
+ {
+ can_Network = new can_network();
+ can_Network.network_name = tsdb_get_can_db_info(AID, (int)DBCINFO.NETWORK_Name, 0, 0);
+ int Node_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_COUNT, 0, 0));
+ can_Network.can_nodes = new can_node[Node_count];
+ for (int i = 0; i < Node_count; i++)
+ {
+ can_Network.can_nodes[i].node_name = tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_Name, i, 0);
+ int tx_can_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CAN_COUNT, i, 0));
+ int tx_canfd_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CANFD_COUNT, i, 0));
+
+ int rx_can_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CAN_COUNT, i, 0));
+ int rx_canfd_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CANFD_COUNT, i, 0));
+
+ //获取tx_can结构体
+ can_Network.can_nodes[i].tx_can_Messages = new can_message[tx_can_count + tx_canfd_count];
+
+ can_Network.can_nodes[i].rx_can_Messages = new can_message[rx_can_count + rx_canfd_count];
+ //获取报文名以及信号名
+ //1 get can_message idnex
+ //2 get can message name
+ //3 get signal count
+ //4 get signal name
+ int tx_index = -1;
+ for (int tx_can = 0; tx_can < tx_can_count; tx_can++)
+ {
+ int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CAN_MESSAGE_INDEX, i, tx_can));
+ can_Network.can_nodes[i].tx_can_Messages[tx_can].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_NAME, can_message_idnex, 0);
+
+ can_Network.can_nodes[i].tx_can_Messages[tx_can].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
+ for (int signalcount = 0; signalcount < can_Network.can_nodes[i].tx_can_Messages[tx_can].signals.Length; signalcount++)
+ {
+ can_Network.can_nodes[i].tx_can_Messages[tx_can].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
+ }
+
+ tx_index = tx_can;
+ }
+ for (int tx_can = 0; tx_can < tx_canfd_count; tx_can++, tx_index++)
+ {
+ int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CANFD_MESSAGE_INDEX, i, tx_can));
+ can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_NAME, can_message_idnex, 0);
+
+ can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
+ for (int signalcount = 0; signalcount < can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals.Length; signalcount++)
+ {
+ can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
+ }
+ }
+
+ int rx_index = -1;
+ for (int tx_can = 0; tx_can < rx_can_count; tx_can++)
+ {
+ int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CAN_MESSAGE_INDEX, i, tx_can));
+ can_Network.can_nodes[i].rx_can_Messages[tx_can].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_NAME, can_message_idnex, 0);
+
+ can_Network.can_nodes[i].rx_can_Messages[tx_can].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
+ for (int signalcount = 0; signalcount < can_Network.can_nodes[i].rx_can_Messages[tx_can].signals.Length; signalcount++)
+ {
+ can_Network.can_nodes[i].rx_can_Messages[tx_can].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
+ }
+
+ rx_index = tx_can;
+ }
+ for (int tx_can = 0; tx_can < rx_canfd_count; tx_can++, rx_index++)
+ {
+ int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CANFD_MESSAGE_INDEX, i, tx_can));
+ can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_NAME, can_message_idnex, 0);
+
+ can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
+ for (int signalcount = 0; signalcount < can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals.Length; signalcount++)
+ {
+ can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
+ }
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/TSMasterCAN/Interop.TSMasterAPI.dll b/TSMasterCAN/Interop.TSMasterAPI.dll
new file mode 100644
index 0000000000000000000000000000000000000000..1d1400bc80851b24ccb6ebe2697231a1928b5fcf
GIT binary patch
literal 109568
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