周立功换同星

This commit is contained in:
“hsc”
2026-07-29 14:20:32 +08:00
parent d8a22a15b1
commit 3bf33bb561
29 changed files with 1913 additions and 3255 deletions

12
ACP.sln
View File

@@ -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

View File

@@ -31,9 +31,9 @@
<ProjectReference Include="..\Service\Service.csproj" />
<ProjectReference Include="..\SettingModule\SettingModule.csproj" />
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
<ProjectReference Include="..\TSMasterCAN\TSMasterCAN.csproj" />
<ProjectReference Include="..\UIShare\UIShare.csproj" />
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
<ProjectReference Include="..\加密狗\加密狗.csproj" />
</ItemGroup>

View File

@@ -12,7 +12,7 @@ namespace CANModule
{
containerRegistry.RegisterDialog<SelectCanMessageView>("SelectCanMessageView");
containerRegistry.RegisterDialog<CollectCANSignalSettingView>("CollectCANSignalSettingView");
containerRegistry.RegisterForNavigation<ZLGCANFDView>("ZLGCANFDView");
containerRegistry.RegisterForNavigation<CANView>("CANView");
}
}
}

View File

@@ -1,14 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\UIShare\UIShare.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TSMasterCAN\TSMasterCAN.csproj" />
<ProjectReference Include="..\UIShare\UIShare.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interop.TSMasterAPI">
<HintPath>..\TSMasterCAN\Interop.TSMasterAPI.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -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<CanMessageShowVM> _CanMessageList = new();
public ObservableCollection<CanMessageShowVM> 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<string> _Message;
public ObservableCollection<string> Message
{
get { return _Message; }
set { SetProperty(ref _Message, value); }
}
private ObservableCollection<string> _Signal;
public ObservableCollection<string> 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<AutoDBCLoadItem> DBCAutoLoadList
{
get => _systemConfig?.DBCAutoLoadList ?? new ObservableCollection<AutoDBCLoadItem>();
}
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<IEventAggregator>();
_systemConfig = containerProvider.Resolve<SystemConfig>();
_deviceManager = containerProvider.Resolve<DeviceManager>();
_canfd = _deviceManager.CANFD;
_dialogService = containerProvider.Resolve<IDialogService>();
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<byte> 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
}
}

View File

@@ -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<CANSignalConfig> ConfigurationList
{
get => _systemConfig?.ConfigurationList ?? new ObservableCollection<CANSignalConfig>();
get => _systemConfig.ConfigurationList;
set
{
if(_systemConfig.ConfigurationList != value)
{
_systemConfig.ConfigurationList = value;
RaisePropertyChanged();
}
}
}
private ObservableCollection<int> _ChannelList;
public ObservableCollection<int> ChannelList
{
get => _ChannelList;
set => SetProperty(ref _ChannelList, value);
}
private ObservableCollection<string> _messageList;
public ObservableCollection<string> 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<string>(targetMsg.signal_Name);
}
else
{
SignalList = new ObservableCollection<string>();
}
}
}
else
{
SignalList = new ObservableCollection<string>();
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,116 +94,81 @@ 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<string>();
if (_canfd != null)
{
var db = _canfd.DBCParser.MsgDatabase;
if (db != null && value >= 0 && value < db.Count)
{
MessageList = new ObservableCollection<string>(
db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
);
}
else
{
MessageList = new ObservableCollection<string>();
}
}
else
{
MessageList = new ObservableCollection<string>();
}
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 LoadCommand { get; set; }
public ICommand SaveCommand { get; set; }
public ICommand DeleteCommand { get; set; }
#endregion
public CollectCANSignalSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
public DialogCloseListener RequestClose { get; set; }
public IEventAggregator _eventAggregator { get; set; }
private SystemConfig _systemConfig;
public CollectCANSignalSettingViewModel(IContainerProvider containerProvider)
{
_eventAggregator=containerProvider.Resolve<IEventAggregator>();
_systemConfig= containerProvider.Resolve<SystemConfig>();
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>("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<CollectedCANMessageChangedEvent>().Publish();
ConfigService.Save(_systemConfig);
}
private void Load()
{
ChannelList = new ObservableCollection<int>(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),
MessageName = ExtractMessageName(SelectedMessage),
SignalName = SelectedSignal,
MessageID = decimalValue,
CollectionInterval = CollectionInterval
});
}
private string ExtractHex(string input)
{
int startIndex = input.IndexOf("0x") + 2;
@@ -223,7 +179,6 @@ namespace CANModule.ViewModels
}
return string.Empty;
}
private int ConvertHexToDecimal(string hex)
{
try
@@ -235,7 +190,6 @@ namespace CANModule.ViewModels
return -1;
}
}
private string ExtractMessageName(string input)
{
var parts = input.Split(']');
@@ -246,6 +200,24 @@ namespace CANModule.ViewModels
{
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}"));
}
}
}

View File

@@ -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<int> _ChannelList;
public ObservableCollection<int> ChannelList
{
get => _ChannelList;
set => SetProperty(ref _ChannelList, value);
}
private ObservableCollection<string> _messageList;
public ObservableCollection<string> 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<string>(targetMsg.signal_Name);
}
else
{
SignalList = new ObservableCollection<string>();
}
}
}
else
{
SignalList = new ObservableCollection<string>();
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<string>();
// 2. 刷新当前通道的报文列表
if (_canfd != null)
{
var db = _canfd.DBCParser.MsgDatabase;
if (db != null && value >= 0 && value < db.Count)
{
MessageList = new ObservableCollection<string>(
db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
);
}
else
{
MessageList = new ObservableCollection<string>();
}
}
else
{
MessageList = new ObservableCollection<string>();
}
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<ZLGCANFD>("CANFD");
}
#endregion
#region
private void Close()
{
RequestClose.Invoke();
}
private void Load()
{
int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
ChannelList = new ObservableCollection<int>(Enumerable.Range(0, maxCh));
ChannelList = new ObservableCollection<int>(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
}
}

View File

@@ -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<CanMessageShowVM> _CanMessageList = new();
public ObservableCollection<CanMessageShowVM> 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<string> _Message;
public ObservableCollection<string> Message
{
get => _Message;
set => SetProperty(ref _Message, value);
}
private ObservableCollection<string> _Signal;
public ObservableCollection<string> 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<string>(targetMsg.signal_Name);
}
else
{
Signal = new ObservableCollection<string>();
}
}
}
else
{
Signal = new ObservableCollection<string>();
}
_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<AutoDBCLoadItem> DBCAutoLoadList
{
get => _systemConfig?.DBCAutoLoadList ?? new ObservableCollection<AutoDBCLoadItem>();
}
private AutoDBCLoadItem _SelectedDcAutoLoad;
public AutoDBCLoadItem SelectedDcAutoLoad
{
get => _SelectedDcAutoLoad;
set => SetProperty(ref _SelectedDcAutoLoad, value);
}
// ===== 自定义报文发送属性 =====
private APP_CHANNEL[] _appChannelNames = Enum.GetValues<APP_CHANNEL>();
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<int> _ChannelList;
public ObservableCollection<int> 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<SystemConfig>();
_deviceManager = containerProvider.Resolve<DeviceManager>();
_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<int>(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<string>(
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<byte> 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
}
}

View File

@@ -1,10 +1,11 @@
<UserControl x:Class="CANModule.Views.ZLGCANFDView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<UserControl x:Class="CANModule.Views.CANView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:CANModule.Views"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:CANModule.Views"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
@@ -17,16 +18,11 @@
<Style BasedOn="{StaticResource DialogUserManageStyle}"
TargetType="Window" />
</prism:Dialog.WindowStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<UserControl.Resources>
<cons:HexConverter x:Key="HexConverter" />
</UserControl.Resources>
<GroupBox Padding="0,0,0,0"
>
MouseLeftButtonDown="MouseLeftButtonDown">
<GroupBox.Header>
<Grid Margin="0">
<Grid.ColumnDefinitions>
@@ -38,59 +34,46 @@
Foreground="White"
VerticalAlignment="Center"
Margin="5,0,10,0" />
<Button Content="关闭"
Grid.Column="2"
Margin="150 0 0 0"
Width="60"
Command="{Binding CloseCommand}"
Height="25" />
</Grid>
</GroupBox.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="829*" />
<ColumnDefinition Width="69*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- CAN卡连接 -->
<StackPanel Orientation="Horizontal"
Margin="5"
Grid.Row="0">
<Button Content="打开设备"
Command="{Binding ConnectCanCommand}"
Margin="5" />
<Button Content="关闭设备"
Command="{Binding CloseCanCommand}"
Margin="5" />
<Button Content="清空循环发送报文"
Command="{Binding ClearCycleCommand}"
Margin="5" />
<!-- TreeView 用于显示不同选项 -->
<TreeView Grid.Row="1"
Grid.ColumnSpan="2">
<!-- 调试选项 -->
<TreeViewItem Header="调试选项">
<Button Content="初始化TSMaster"
Command="{Binding InitTSMasterCommand}"
CommandParameter="初始化TSMaster" />
<Button Content="释放TSMaster"
Command="{Binding ReleaseTSMasterCommand}"
CommandParameter="释放TSMaster" />
</TreeViewItem>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="5"
Grid.Row="1">
<Label Content="选择通道初始化(选择前先打开设备)"
VerticalAlignment="Center" />
<ComboBox SelectedItem="{Binding SelectedChannel}"
ItemsSource="{Binding ChannelList}"
materialDesign:HintAssist.Hint=""
MinWidth="80"
Margin="5"
VerticalAlignment="Center" />
</StackPanel>
<TreeView Grid.Row="2">
<!-- 监视选项 -->
<TreeViewItem Header="监视">
<DataGrid x:Name="表格"
Width="550"
Height="350"
<DataGrid x:Name="表格" Width="800"
Height="450"
VirtualizingPanel.IsVirtualizing="True"
IsReadOnly="True"
AutoGenerateColumns="False"
ItemsSource="{Binding CanMessageList, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="通道" Binding="{Binding 通道}" Width="90" />
<DataGridTextColumn Header="报文ID" Binding="{Binding 报文ID, Converter={StaticResource HexConverter}}" Width="150" />
<DataGridTextColumn Header="长度" Binding="{Binding 长度}" Width="90" />
<DataGridTextColumn Header="时间戳" Binding="{Binding 时间戳}" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</TreeViewItem>
@@ -98,11 +81,13 @@
<TreeViewItem Header="DBC">
<Label Content="请注意,要在断开CAN卡的情况下才能操作DBC!"
Foreground="Red" />
<Button Content="卸载当前通道DBC"
Command="{Binding UpLoadDBCCommand}" />
<Button Content="卸载所有DBC"
Command="{Binding UpLoadDBCCommand}"
CommandParameter="卸载DBC" />
<StackPanel Orientation="Horizontal">
<Button Content="更换DBC"
Command="{Binding ChangeDBCCommand}" />
Command="{Binding ChangeDBCCommand}"
CommandParameter="更换DBC" />
<Label Content="DBC文件路径"
VerticalContentAlignment="Center" />
<TextBox Text="{Binding DBCPath,Mode=TwoWay}"
@@ -112,7 +97,8 @@
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="加载DBC"
Command="{Binding LoadDBCCommand}" />
Command="{Binding LoadDBCCommand}"
CommandParameter="加载DBC" />
<Label Content="通道(从0开始)"
VerticalContentAlignment="Center" />
<TextBox Text="{Binding DBCChannel,Mode=TwoWay}"
@@ -145,28 +131,27 @@
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
<Button Content="查看DBC"
Command="{Binding CheckDBCCommand}"
CommandParameter="查看DBC" />
</TreeViewItem>
<!-- CAN卡连接选项 -->
<TreeViewItem Header="CAN卡连接">
<Button Content="连接"
Command="{Binding ConnectCanCommand}"
CommandParameter="连接" />
<Button Content="断开"
Command="{Binding CloseCanCommand}"
CommandParameter="断开" />
<Button Content="清空循环发送报文"
Command="{Binding ClearCycleCommand}"
CommandParameter="清空循环发送报文" />
</TreeViewItem>
<!-- DBC报文发送 -->
<TreeViewItem Header="DBC报文发送">
<StackPanel Orientation="Horizontal">
<Label Content="通道(从0开始)"
VerticalContentAlignment="Center" />
<ComboBox SelectedValue="{Binding MessageSendChannel}"
SelectedValuePath="Content"
materialDesign:HintAssist.Hint=""
MinWidth="60"
VerticalAlignment="Center"
Margin="0,5,5,5">
<ComboBoxItem Content="0"/>
<ComboBoxItem Content="1"/>
<ComboBoxItem Content="2"/>
<ComboBoxItem Content="3"/>
</ComboBox>
<Button Content="加载报文"
Command="{Binding LoadMessageCommand}"
Margin="5" />
</StackPanel>
<DockPanel>
<Label Content="报文" />
<ComboBox MinWidth="100"
@@ -194,21 +179,33 @@
MinWidth="100" />
</DockPanel>
<StackPanel Orientation="Horizontal">
<Label Content="通道(从0开始)"
VerticalContentAlignment="Center" />
<TextBox Text="{Binding MessageSendChannel}"
materialDesign:HintAssist.Hint=""
MinWidth="100"
VerticalAlignment="Center"
Margin="0,10,10,10" />
<Button Content="加载报文"
Command="{Binding LoadMessageCommand}"
CommandParameter="加载报文"
Margin="10" />
<Button Content="设置信号并发送"
Command="{Binding SetAndSendMessageCommand}" />
Command="{Binding SetAndSendMessageCommand}"
CommandParameter="设置" />
<CheckBox Content="直接发送"
IsChecked="{Binding IsDirectlySend}" />
</StackPanel>
</TreeViewItem>
<!-- 自定义报文发送 -->
<TreeViewItem Header="自定义报文发送">
<DockPanel>
<Label Content="通道" />
<ComboBox MinWidth="100"
materialDesign:HintAssist.Hint=""
SelectedItem="{Binding AIdxChn}"
ItemsSource="{Binding APP_CHANNEL}" />
ItemsSource="{Binding APP_CHANNEL}"
/>
</DockPanel>
<DockPanel>
<Label Content="ID" />
@@ -250,10 +247,11 @@
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="发送"
Command="{Binding SendCustomMessageCommand}" />
<!--<CheckBox Margin="10"
Command="{Binding SendCustomMessageCommand}"
/>
<CheckBox Margin="10"
Content="更新到数据库"
IsChecked="{Binding UpdateToDB}" />-->
IsChecked="{Binding UpdateToDB}" />
</StackPanel>
</TreeViewItem>
@@ -268,16 +266,20 @@
VerticalContentAlignment="Center" />
<Button Content="选择路径"
Margin="10 0"
Command="{Binding SelectBLFPathCommand}" />
Command="{Binding SelectBLFPathCommand}"
CommandParameter="选择路径" />
<Button Content="保存路径"
Command="{Binding SaveBLFPathCommand}" />
Command="{Binding SaveBLFPathCommand}"
CommandParameter="保存路径" />
</DockPanel>
<StackPanel Orientation="Horizontal">
<Button Content="开始录制"
Command="{Binding StartTranscribeCommand}" />
Command="{Binding StartTranscirbeCommand}"
CommandParameter="开始录制" />
<Button Content="结束录制"
Margin="10 0"
Command="{Binding EndTranscribeCommand}" />
Command="{Binding EndTranscirbeCommand}"
CommandParameter="结束录制" />
</StackPanel>
</TreeViewItem>
</TreeView>

View File

@@ -16,14 +16,15 @@ using System.Windows.Shapes;
namespace CANModule.Views
{
/// <summary>
/// ZLGCANFDView.xaml 的交互逻辑
/// CANView.xaml 的交互逻辑
/// </summary>
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();
}
}
}
}

View File

@@ -1,23 +1,27 @@
<UserControl x:Class="CANModule.Views.CollectCANSignalSettingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:CANModule.Views"
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">
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">
<prism:Dialog.WindowStyle>
<Style BasedOn="{StaticResource DialogUserManageStyle}"
TargetType="Window" />
</prism:Dialog.WindowStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<UserControl.Resources>
<cons:HexConverter x:Key="HexConverter" />
</UserControl.Resources>
@@ -79,11 +83,12 @@
Margin="10">
<Label Content="通道(从0开始)"
VerticalAlignment="Center" />
<TextBox Name="SignalComboBox"
Text="{Binding MessageChannel}"
<ComboBox Name="SignalComboBox"
SelectedItem="{Binding MessageChannel,Mode=TwoWay}"
MaxWidth="200"
MinWidth="120"
Margin="10"
ItemsSource="{Binding ChannelList}"
VerticalAlignment="Center"
materialDesign:HintAssist.Hint="信号通道" />
<Label Content="报文"

View File

@@ -1,11 +1,22 @@
using System.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace CANModule.Views
{
/// <summary>
/// CollectCANSignalSettingView.xaml 的交互逻辑
/// CollectCANSignalSetting.xaml 的交互逻辑
/// </summary>
public partial class CollectCANSignalSettingView : UserControl
{

View File

@@ -1,9 +1,8 @@
<UserControl x:Class="CANModule.Views.SelectCanMessageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:CANModule.Views"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
@@ -44,26 +43,26 @@
Height="25" />
</Grid>
</GroupBox.Header>
<Grid>
<StackPanel Grid.Row="1" Margin="10">
<Label Content="通道" VerticalAlignment="Center"/>
<Grid>
<StackPanel Grid.Row="1" Margin="10">
<Label Content="通道" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding ChannelList}"
SelectedItem="{Binding MessageChannel,Mode=TwoWay}"
MinWidth="100"
VerticalAlignment="Center"
materialDesign:HintAssist.Hint="" />
<Label Content="报文" VerticalAlignment="Center"/>
<Label Content="报文" VerticalAlignment="Center"/>
<ComboBox materialDesign:HintAssist.Hint=""
MinWidth="100"
SelectedItem="{Binding SelectedMessage,Mode=TwoWay}"
ItemsSource="{Binding MessageList}" />
<Label Content="信号" VerticalAlignment="Center"/>
<Label Content="信号" VerticalAlignment="Center"/>
<ComboBox materialDesign:HintAssist.Hint=""
SelectedItem="{Binding SelectedSignal,Mode=TwoWay}"
ItemsSource="{Binding SignalList}" MinWidth="100" />
<Button Content="选择报文名" Margin="10" Command="{Binding GetSelectedMessageCommand}"/>
<Button Content="选择报文名" Margin="10" Command="{Binding GetSelectedMessageCommand}"/>
<Button Content="选择信号名" Margin="10" Command="{Binding GetSelectedSignalCommand}"/>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</GroupBox>
</UserControl>

View File

@@ -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
/// </summary>
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();

View File

@@ -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]);

679
TSMasterCAN/CAN.cs Normal file
View File

@@ -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;
/// <summary>
/// 实时接收到的 CAN 报文缓存(按报文 ID 索引,保留最新帧)。
/// 供 CANViewModel.Refresh 等界面轮询读取。
/// </summary>
public static ConcurrentDictionary<int, TLIBCANFD> RealTimeMessages { get; } = new();
#endregion
#region
/// <summary>设备类型号43 = USBCANFD-400U 等TSMaster 中作为配置参考保留)</summary>
public uint DeviceType { get; }
/// <summary>设备索引</summary>
public uint DeviceIndex { get; }
/// <summary>最大通道数</summary>
public int MaxChannels { get; }
/// <summary>仲裁域波特率</summary>
public string ABitBaud { get; }
/// <summary>数据域波特率</summary>
public string DBitBaud { get; }
/// <summary>是否开启终端电阻</summary>
public bool EnableTerminalResistance { get; }
/// <summary>DBC 解析器(提供 MsgDatabase、MaxChannels 等访问)</summary>
public DBCParse DBCParser => DBCParse.Instance;
/// <summary>
/// DBC 报文解码事件:当接收到 CAN 帧并通过 DBC 解码后触发。
/// 参数:(通道号, 解码后的报文信息)
/// </summary>
public event Action<uint, DBCMessage>? OnDbcMessageDecoded;
private bool _disposed;
private bool _isOpened;
private TCANFDQueueEvent_Win32? _instanceListener;
#endregion
#region
/// <summary>默认构造函数(兼容 SystemConfig.CANFD = new() 等场景)</summary>
public CAN() : this(43, 0, 4, "500000", "2000000", true) { }
/// <summary>
/// 带配置参数的构造函数(兼容原 ZLG 设备管理流程)。
/// </summary>
/// <param name="deviceType">设备类型号</param>
/// <param name="deviceIndex">设备索引</param>
/// <param name="maxChannels">最大通道数</param>
/// <param name="aBitBaud">仲裁域波特率</param>
/// <param name="dBitBaud">数据域波特率</param>
/// <param name="enableTerminalResistance">是否开启终端电阻</param>
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
/// <summary>
/// 打开设备:初始化 TSMaster 库 → 设置通道数 → 配置波特率 → 连接 → 启动 RBS → 注册监听。
/// </summary>
/// <returns>true 表示成功</returns>
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;
}
}
/// <summary>
/// 关闭 CAN 卡设备:注销监听 → 断开连接。
/// </summary>
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;
}
}
/// <summary>
/// 初始化并启动指定通道配置波特率TSMaster 在 Connect 时已自动启动所有通道)。
/// </summary>
/// <param name="channel">通道号</param>
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}");
}
}
/// <summary>
/// 加载通道 DBC 文件。
/// </summary>
/// <param name="channel">通道号</param>
/// <param name="filePath">DBC 文件路径</param>
/// <returns>true 表示加载成功</returns>
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;
}
}
/// <summary>
/// CAN FD 接收回调:接收原始帧 → 查找 DBC → 解码信号 → 触发 OnDbcMessageDecoded 事件。
/// </summary>
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
/// <summary>
/// 初始化TSMasterCAN
/// </summary>
/// <param name="ProjectName"></param>
/// <param name="filePath"></param>
/// <returns></returns>
[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));
}
}
/// <summary>
/// 释放TSMasterCAN
/// </summary>
[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;
}
/// <summary>
/// 连接
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 断开
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 加载DBC
/// </summary>
/// <param name="filePath"></param>
/// <param name="channel"></param>
/// <param name="databaseID"></param>
/// <returns></returns>
[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;
}
/// <summary>
/// 卸载DBC
/// </summary>
/// <param name="databaseID"></param>
/// <returns></returns>
[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);
}
}
/// <summary>
/// 开始记录日志
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static int StartLogging(string filePath)
{
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
}
/// <summary>
/// 结束记录日志
/// </summary>
/// <returns></returns>
public static int StopLogging()
{
return TsMasterApi.tsapp_stop_logging();
}
/// <summary>
/// 弹出通道映射窗口
/// </summary>
/// <param name="isWait"></param>
/// <returns></returns>
[Browsable(false)]
public static int ShowChannelMappingWindow(bool isWait = false)
{
return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait);
}
/// <summary>
/// 获取信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 获取信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="value"></param>
/// <returns></returns>
[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);
}
/// <summary>
/// 获取信号值
/// </summary>
/// <param name="ACANFD"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="value"></param>
/// <returns></returns>
[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);
}
/// <summary>
/// 设置信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="AValue"></param>
/// <param name="isSend"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
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;
}
/// <summary>
/// 设置信号值
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// 发送自定义报文
/// </summary>
/// <returns></returns>
[Browsable(false)]
public static int SetMsg(TLIBCANFD msg)
{
return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
}
/// <summary>
/// 设置报文
/// </summary>
/// <param name="channel">通道</param>
/// <param name="ID">DBC数据库ID</param>
/// <param name="bytes">报文数组</param>
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));
}
}
/// <summary>
/// 发送自定义报文
/// </summary>
/// <param name="AIdxChn"></param>
/// <param name="AID"></param>
/// <param name="AIsTx"></param>
/// <param name="AIsExt"></param>
/// <param name="AIsRemote"></param>
/// <param name="ADLC"></param>
/// <param name="ADataArray"></param>
/// <param name="AIsFD"></param>
/// <param name="AIsBRS"></param>
/// <param name="isUpdateDBCDatabase"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 添加循环发送报文
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 清除循环发送的报文
/// </summary>
/// <returns></returns>
public static int DeleteCyclicMsgs()
{
return TsMasterApi.tsapp_delete_cyclic_msgs();
}
/// <summary>
/// 获取错误提示
/// </summary>
/// <param name="errorCode">错误代码</param>
/// <returns></returns>
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}";
}
}
}

305
TSMasterCAN/DBCParse.cs Normal file
View File

@@ -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,
}
/// <summary>
/// DBC 文件信息类型枚举,用于指定 tsdb_get_can_db_info 查询的内容类型。
/// </summary>
public enum DBCINFO
{
/// <summary>网络名称</summary>
NETWORK_Name = 0,
/// <summary>CAN 报文总数</summary>
CAN_MSG_COUNT = 11,
/// <summary>CAN FD 报文总数</summary>
CANFD_MSG_COUNT = 12,
/// <summary>节点总数</summary>
NODE_COUNT = 14,
/// <summary>信号名称</summary>
SIGNAL_NAME = 33,
/// <summary>CAN 报文类型</summary>
CAN_MSG_TYPE = 40,
/// <summary>CAN 报文 DLC数据长度码</summary>
CAN_MSG_DLC = 41,
/// <summary>CAN 报文 ID</summary>
CAN_MSG_ID = 42,
/// <summary>CAN 报文周期</summary>
CAN_MSG_CYCLIC = 43,
/// <summary>CAN 报文名称</summary>
CAN_MSG_NAME = 44,
/// <summary>CAN 报文信号数量</summary>
CAN_MSG_SIGNAL_COUNT = 47,
/// <summary>CAN 报文信号名称索引</summary>
CAN_MSG_SIGNAL_NAME_INDEX = 48,
/// <summary>CAN FD 报文类型</summary>
CANFD_MSG_TYPE = 60,
/// <summary>CAN FD 报文 DLC数据长度码</summary>
CANFD_MSG_DLC = 61,
/// <summary>CAN FD 报文 ID</summary>
CANFD_MSG_ID = 62,
/// <summary>CAN FD 报文周期</summary>
CANFD_MSG_CYCLIC = 63,
/// <summary>CAN FD 报文名称</summary>
CANFD_MSG_NAME = 64,
/// <summary>CAN FD 报文信号数量</summary>
CANFD_MSG_SIGNAL_COUNT = 67,
/// <summary>CAN FD 报文信号名称索引</summary>
CANFD_MSG_SIGNAL_NAME_INDEX = 68,
/// <summary>节点名称</summary>
NODE_Name = 101,
/// <summary>节点发送 CAN 报文数量</summary>
NODE_TX_CAN_COUNT = 103,
/// <summary>节点发送 CAN 报文索引</summary>
NODE_TX_CAN_MESSAGE_INDEX = 104,
/// <summary>节点接收 CAN 报文数量</summary>
NODE_RX_CAN_COUNT = 105,
/// <summary>节点接收 CAN 报文索引</summary>
NODE_RX_CAN_MESSAGE_INDEX = 106,
/// <summary>节点发送 CAN FD 报文数量</summary>
NODE_TX_CANFD_COUNT = 107,
/// <summary>节点发送 CAN FD 报文索引</summary>
NODE_TX_CANFD_MESSAGE_INDEX = 108,
/// <summary>节点接收 CAN FD 报文数量</summary>
NODE_RX_CANFD_COUNT = 109,
/// <summary>节点接收 CAN FD 报文索引</summary>
NODE_RX_CANFD_MESSAGE_INDEX = 110,
}
/// <summary>
/// DBC 解码后的信号信息(替代原 ZLG ZDBC.DBCSignal
/// </summary>
public struct DBCSignal
{
/// <summary>信号名称(原始字节,兼容 Encoding.Default 解码)</summary>
public byte[] strName;
/// <summary>原始值TSMaster tsdb_get_signal_value_canfd 返回的物理值)</summary>
public double nRawvalue;
/// <summary>因子TSMaster 返回物理值,因子固定为 1</summary>
public double nFactor;
/// <summary>偏移TSMaster 返回物理值,偏移固定为 0</summary>
public double nOffset;
}
/// <summary>
/// DBC 解码后的报文信息(替代原 ZLG ZDBC.DBCMessage
/// 在 CAN 接收回调中对原始 TLIBCANFD 帧解码后构建此对象并触发 OnDbcMessageDecoded 事件。
/// </summary>
public class DBCMessage
{
/// <summary>报文 ID</summary>
public uint nID;
/// <summary>信号数量</summary>
public int nSignalCount;
/// <summary>信号数组</summary>
public DBCSignal[] vSignals;
}
public class DBCParse
{
private static DBCParse? _instance;
/// <summary>单例实例,供 CAN.DBCParser 属性使用MsgDatabase 等成员为 static实例仅用于属性访问兼容。</summary>
public static DBCParse Instance => _instance ??= new DBCParse();
/// <summary>最大通道数(等于 MsgDatabase 的维度)</summary>
public int MaxChannels => MsgDatabase.Count;
public static List<List<_Msg_>> 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);
}
}
}
}
}
}

Binary file not shown.

BIN
TSMasterCAN/TSMaster.dll Normal file

Binary file not shown.

View File

@@ -1,14 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>false</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Logger\Logger.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interop.TSMasterAPI">
<HintPath>..\CanDriver\bin\Debug\net8.0\Interop.TSMasterAPI.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -4,8 +4,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TSMasterCAN;
using UIShare.PubEvent;
using ZLGUSBCANFD;
namespace UIShare.GlobalVariable
{
@@ -22,8 +22,7 @@ namespace UIShare.GlobalVariable
private bool _disposed;
/// <summary>已订阅解码事件的 CANFD 实例 → 委托 映射</summary>
private readonly Dictionary<ZLGCANFD, Action<uint, ZDBC.DBCMessage>> _handlers = new();
private readonly Dictionary<CAN, Action<uint, DBCMessage>> _handlers = new();
public CANSignalBroadcaster(GlobalInfo globalInfo, IEventAggregator eventAggregator)
{
_globalInfo = globalInfo;
@@ -48,7 +47,7 @@ namespace UIShare.GlobalVariable
if (_handlers.ContainsKey(canfd)) continue;
// 使用闭包捕获指纹,回调时即可区分不同 CAN 卡
Action<uint, ZDBC.DBCMessage> handler = (channel, msg) => OnDbcMessageDecoded(fingerprint, channel, msg);
Action<uint, DBCMessage> handler = (channel, msg) => OnDbcMessageDecoded(fingerprint, channel, msg);
canfd.OnDbcMessageDecoded += handler;
_handlers[canfd] = handler;
}
@@ -65,7 +64,7 @@ namespace UIShare.GlobalVariable
/// <summary>
/// DBC 解码回调:提取所有信号,向引用该 CAN 设备的作用域广播,并检查报警。
/// </summary>
private void OnDbcMessageDecoded(string canFingerprint, uint channel, ZDBC.DBCMessage msg)
private void OnDbcMessageDecoded(string canFingerprint, uint channel, DBCMessage msg)
{
if (_disposed) return;

View File

@@ -10,9 +10,10 @@ using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Reflection;
using TSMasterCAN;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using ZLGUSBCANFD;
using TSMasterCAN;
namespace UIShare.GlobalVariable
{
@@ -34,7 +35,7 @@ namespace UIShare.GlobalVariable
/// <summary>类名 → Type 的反射缓存(仅扫描一次)。</summary>
private static readonly IReadOnlyDictionary<string, Type> _deviceTypeMap = BuildDeviceTypeMap();
public ZLGCANFD CANFD { get; set; }
public CAN CANFD { get; set; }
public IOBoardGroup IOGroup { get; set; }
public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo, IEventAggregator eventAggregator)
@@ -116,10 +117,10 @@ namespace UIShare.GlobalVariable
try
{
// 按指纹全局唯一创建 ZLGCANFD 实例maxChannels 默认 4对应 USBCANFD-400U
// 按指纹全局唯一创建同星 CAN 实例maxChannels 默认 4对应 USBCANFD-400U
// 波特率与终端电阻从 CANConfigVM 传入,初始化并启动通道 时直接使用
var canLazy = _globalInfo.CanPool.GetOrAdd(fingerprint, key => new Lazy<ZLGCANFD>(() =>
new ZLGCANFD(config.CANConfig.DeviceType, config.CANConfig.DeviceIndex, 4,
var canLazy = _globalInfo.CanPool.GetOrAdd(fingerprint, key => new Lazy<CAN>(() =>
new CAN(config.CANConfig.DeviceType, config.CANConfig.DeviceIndex, 4,
config.CANConfig.ABitBaud, config.CANConfig.DBitBaud, config.CANConfig.EnableTerminalResistance)));
_systemConfig.CANFD = canLazy.Value;

View File

@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ZLGUSBCANFD;
using TSMasterCAN;
namespace UIShare.GlobalVariable
{
@@ -21,7 +21,7 @@ namespace UIShare.GlobalVariable
public ConcurrentDictionary<string, Lazy<IBaseInterface>> HardwarePool { get; set; }
/// <summary>CAN 硬件指纹 → ZLGCANFD 实例的并发池,确保同一 CAN 卡全局只创建一个驱动实例。</summary>
public ConcurrentDictionary<string, Lazy<ZLGCANFD>> CanPool { get; set; }
public ConcurrentDictionary<string, Lazy<CAN>> CanPool { get; set; }
/// <summary>硬件指纹 → 正在使用该设备的作用域名称列表,用于引用计数与安全销毁。</summary>
public ConcurrentDictionary<string, Lazy<List<string>>> DeviceAndScopeDic { get; set; }
@@ -49,7 +49,7 @@ namespace UIShare.GlobalVariable
ConfigDic = new();
ScopeDic = new();
HardwarePool = new ConcurrentDictionary<string, Lazy<IBaseInterface>>(StringComparer.OrdinalIgnoreCase);
CanPool = new ConcurrentDictionary<string, Lazy<ZLGCANFD>>(StringComparer.OrdinalIgnoreCase);
CanPool = new ConcurrentDictionary<string, Lazy<CAN>>(StringComparer.OrdinalIgnoreCase);
DeviceAndScopeDic = new ConcurrentDictionary<string, Lazy<List<string>>>(StringComparer.OrdinalIgnoreCase);
CurrentScope = "default";
}

View File

@@ -8,8 +8,8 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TSMasterCAN;
using UIShare.UIViewModel;
using ZLGUSBCANFD;
using static UIShare.UIViewModel.ParameterVM;
namespace UIShare.GlobalVariable
@@ -40,7 +40,7 @@ namespace UIShare.GlobalVariable
/// 监测通道持久化配置列表(包含 Fingerprint、MethodName、IsDisplayed
/// </summary>
public ObservableCollection<MonitorChannelConfig> MonitorChannels = new();
public ZLGCANFD CANFD = new();
public CAN CANFD = new();
[JsonIgnore]
public ObservableCollection<ParameterVM> ParameterList = new()
{

View File

@@ -23,6 +23,6 @@
<ProjectReference Include="..\Logger\Logger.csproj" />
<ProjectReference Include="..\ORM\ORM.csproj" />
<ProjectReference Include="..\Service\Service.csproj" />
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
<ProjectReference Include="..\TSMasterCAN\TSMasterCAN.csproj" />
</ItemGroup>
</Project>

View File

@@ -7,7 +7,10 @@
private int _报文ID;
private double _时间戳;
private byte _长度;
private bool _FD;
private bool _IsTx;
private byte[] _Bytes = new byte[64];
private string _报文 = "[]";
// 通道属性
public byte
@@ -37,6 +40,32 @@
set { SetProperty(ref _长度, value); }
}
// 是否FD帧
public bool FD
{
get { return _FD; }
set { SetProperty(ref _FD, value); }
}
// 是否发送帧
public bool IsTx
{
get { return _IsTx; }
set { SetProperty(ref _IsTx, value); }
}
// 原始字节数据
public byte[] Bytes
{
get { return _Bytes; }
set { SetProperty(ref _Bytes, value); }
}
// 报文十六进制字符串
public string
{
get { return _报文; }
set { SetProperty(ref _报文, value); }
}
}
}

View File

@@ -1,983 +0,0 @@
using Common.Attributes;
using Logger;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ZLGUSBCANFD
{
[ACPCommand]
public class ZLGCANFD : IDisposable
{
// 硬件设备与通道句柄
private IntPtr _deviceHandle = IntPtr.Zero;
private readonly IntPtr[] _channelHandles;
// 每个通道分配独立的 DBC 引擎句柄与锁彻底解决台架间DBC冲突与锁竞争
private readonly uint[] _dbcHandles;
private readonly bool[] _isDbcLoadedArray;
private readonly object[] _channelLocks; // 通道级细粒度锁
// 异步高性能接收线程控制
private volatile bool _isRunning = false;
private volatile bool _isClosing = false; // 关闭流程标志:通知循环发送任务尽快退出
private readonly List<Thread> _receiveThreads = new List<Thread>();
// DBC 循环发送任务管理Key = (通道号, 帧ID)Value = (CTS, Task)
private readonly ConcurrentDictionary<(uint , uint ID), (CancellationTokenSource Cts, Task SendTask)> _cyclicSenders = new ConcurrentDictionary<(uint, uint), (CancellationTokenSource, Task)>();
// 信号值持久化覆盖表Key = (通道号, 帧ID)Value = 信号名 → 物理值
// 每次 设置报文 会累积写入此表,发送时以 DBC 初始值为底再叠加此表覆盖值
private readonly ConcurrentDictionary<(uint , uint ID), Dictionary<string, double>> _signalOverrides = new ConcurrentDictionary<(uint, uint), Dictionary<string, double>>();
// 动态硬件参数
private readonly uint _deviceType; // 76: USBCANFD-400U
private readonly uint _deviceIndex; // 设备索引
private readonly int _maxChannels; // 动态通道数
// 通道波特率与终端电阻配置(构造时传入,所有通道共用)
private readonly string _abitBaud;
private readonly string _dbitBaud;
private readonly bool _enableTerminalResistance;
/// <summary>
/// 当前 CAN 卡专属的 DBC 解析器,按通道隔离报文数据库。
/// </summary>
public ZLGDBCParser DBCParser { get; }
/// <summary>
/// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
/// 参数uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
/// </summary>
public event Action<uint, ZDBC.DBCMessage>? OnDbcMessageDecoded;
public ZLGCANFD(uint deviceType = 76, uint deviceIndex = 0, int maxChannels = 4,
string abitBaud = "500000", string dbitBaud = "2000000", bool enableTerminalResistance = true)
{
_deviceType = deviceType;
_deviceIndex = deviceIndex;
_maxChannels = maxChannels;
_abitBaud = abitBaud;
_dbitBaud = dbitBaud;
_enableTerminalResistance = enableTerminalResistance;
// 初始化通道相关状态数组
_channelHandles = new IntPtr[_maxChannels];
_dbcHandles = new uint[_maxChannels];
_isDbcLoadedArray = new bool[_maxChannels];
_channelLocks = new object[_maxChannels];
DBCParser = new ZLGDBCParser(_maxChannels);
for (int i = 0; i < _maxChannels; i++)
{
_channelHandles[i] = IntPtr.Zero;
_dbcHandles[i] = 0;
_isDbcLoadedArray[i] = false;
_channelLocks[i] = new object();
}
}
#region 1.
/// <summary>
/// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)
/// </summary>
public virtual bool ()
{
if (_deviceHandle != IntPtr.Zero) return true;
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_400U, _deviceIndex, 0);
return _deviceHandle != IntPtr.Zero;
}
/// <summary>
/// 针对特定通道进行参数初始化并启动(使用构造时传入的波特率与终端电阻配置)
/// </summary>
public virtual bool (uint )
{
_isClosing = false; // 重置关闭标志,允许循环发送
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
if ( >= _maxChannels) return false;
lock (_channelLocks[])
{
// 如果已经启动过,直接退出
if (_channelHandles[] != IntPtr.Zero)
{
return true;
//ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]);
}
// 1. 设置该通道专属的仲裁域与数据域波特率
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_abit_baud_rate", _abitBaud) != 1) return false;
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_dbit_baud_rate", _dbitBaud) != 1) return false;
// 2. 设置该通道专属内部终端电阻状态
string resistanceStr = _enableTerminalResistance ? "1" : "0";
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false;
// 3. 规范配置通道结构体
ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG config = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG();
config.can_type = 1; // 1 代表 CANFD 模式
config.config.canfd.mode = 0; // 0 代表正常工作模式
IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config));
Marshal.StructureToPtr(config, pConfig, true);
_channelHandles[] = ZLGCAN.ZCAN_InitCAN(_deviceHandle, , pConfig);
Marshal.FreeHGlobal(pConfig);
if (_channelHandles[] == IntPtr.Zero) return false;
// 4. 启动 CAN 通道
if (ZLGCAN.ZCAN_StartCAN(_channelHandles[]) != 1) return false;
// 5. 为该通道启动专属的独立后台高性能轮询接收线程(如果尚未启动轮询)
if (!_isRunning) _isRunning = true;
int chnIdx = (int);
Thread rxThread = new Thread(() => (_channelHandles[chnIdx], (uint)chnIdx))
{
IsBackground = true,
Name = $"ZLGCANFD_Dev{_deviceIndex}_CH{chnIdx}_RxThread"
};
_receiveThreads.Add(rxThread);
rxThread.Start();
return true;
}
}
public virtual void CAN卡设备()
{
_isClosing = true; // 通知循环发送任务尽快退出
_isRunning = false; // 通知接收轮询线程退出
(); // 内部会等待所有循环发送 Task 退出
// 等待所有接收轮询线程真正退出(每个最多 1 秒)
foreach (var thread in _receiveThreads)
{
if (thread.IsAlive)
thread.Join(1000);
}
_receiveThreads.Clear();
// 动态复位所有通道并释放 DBC使用 TryEnter 防止死锁)
for (uint i = 0; i < _maxChannels; i++)
{
if (Monitor.TryEnter(_channelLocks[i], TimeSpan.FromSeconds(2)))
{
try
{
if (_channelHandles[i] != IntPtr.Zero)
{
ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
_channelHandles[i] = IntPtr.Zero;
}
DBC(i);
}
finally
{
Monitor.Exit(_channelLocks[i]);
}
}
else
{
// 锁超时:循环发送 Task 可能仍阻塞在原生 API 调用中,强制清理
LoggerHelper.Info($"[ZLGCANFD] 关闭通道 {i} 时获取锁超时,强制清理");
if (_channelHandles[i] != IntPtr.Zero)
{
try { ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); } catch { }
_channelHandles[i] = IntPtr.Zero;
}
DBC(i);
}
}
// 关闭设备主句柄
if (_deviceHandle != IntPtr.Zero)
{
ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
_deviceHandle = IntPtr.Zero;
}
_isClosing = false;
}
#endregion
#region 2. DBC
/// <summary>
/// 指定通道加载特定的 DBC 文件支持不同通道加载不同的DBC矩阵
/// </summary>
public virtual bool DBC文件(uint , string dbcFilePath)
{
if ( >= _maxChannels) return false;
lock (_channelLocks[])
{
try
{
if (_isDbcLoadedArray[]) return true;
//加载前先卸载
DBC();
uint chDbcHandle = ZDBC.ZDBC_Init();
if (chDbcHandle == 0) return false;
IntPtr ptrPath = Marshal.StringToHGlobalAnsi(dbcFilePath);
bool success = ZDBC.ZDBC_LoadFile(chDbcHandle, ptrPath);
Marshal.FreeHGlobal(ptrPath);
if (success)
{
_dbcHandles[] = chDbcHandle;
_isDbcLoadedArray[] = true;
DBCParser.ParseAndLoadToDatabase(chDbcHandle, (int));
}
else
{
ZDBC.ZDBC_Release(chDbcHandle);
}
return success;
}
catch
{
return false;
}
}
}
public virtual void DBC(uint )
{
if ( >= _maxChannels) return;
lock (_channelLocks[])
{
if (_isDbcLoadedArray[] && _dbcHandles[] != 0)
{
ZDBC.ZDBC_Release(_dbcHandles[]);
_dbcHandles[] = 0;
_isDbcLoadedArray[] = false;
DBCParser.MsgDatabase[(int)].Clear();
}
}
}
#endregion
#region 3. DBC
private void (IntPtr channelHandle, uint channelIndex)
{
const int bufferSize = 100;
int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
int dbcMsgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage));
IntPtr ptrCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferSize);
IntPtr ptrCanFDBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferSize);
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(dbcMsgSize);
try
{
while (_isRunning)
{
bool currentTurnHasData = false;
// 1. 自动提取并解析经典 CAN 帧
uint canNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 0);
if (canNum > 0)
{
uint actualRecv = ZLGCAN.ZCAN_Receive(channelHandle, ptrCanBuffer, bufferSize, 5);
for (int i = 0; i < actualRecv; i++)
{
IntPtr framePtr = IntPtr.Add(ptrCanBuffer, i * canStructSize);
// 只锁定当前通道的DBC句柄进行解码高并发完全不卡顿
lock (_channelLocks[channelIndex])
{
if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 0))
{
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
OnDbcMessageDecoded?.Invoke(channelIndex, msg);
}
}
}
currentTurnHasData = true;
}
// 2. 自动提取并解析高速 CANFD 帧
uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 1);
if (canfdNum > 0)
{
uint actualRecvFd = ZLGCAN.ZCAN_ReceiveFD(channelHandle, ptrCanFDBuffer, bufferSize, 5);
for (int i = 0; i < actualRecvFd; i++)
{
IntPtr framePtr = IntPtr.Add(ptrCanFDBuffer, i * canfdStructSize);
lock (_channelLocks[channelIndex])
{
if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 1))
{
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
OnDbcMessageDecoded?.Invoke(channelIndex, msg);
}
}
}
currentTurnHasData = true;
}
if (!currentTurnHasData)
{
Thread.Sleep(2);
}
}
}
finally
{
Marshal.FreeHGlobal(ptrCanBuffer);
Marshal.FreeHGlobal(ptrCanFDBuffer);
Marshal.FreeHGlobal(ptrDbcMsg);
}
}
#endregion
#region 4. DBC
/// <summary>
/// 发送一条 DBC 定义报文。
/// </summary>
/// <param name="通道号">通道号</param>
/// <param name="帧ID">DBC 中定义的帧 ID</param>
/// <param name="信号物理值字典">信号名称 → 物理值;只设置字典中包含的信号,其余信号保持 DBC 默认值</param>
/// <param name="循环间隔毫秒">0 = 只发送一次;>0 = 按指定间隔循环发送(毫秒)</param>
/// <param name="是否使用CANFD">1 = CANFD0 = CAN</param>
/// <returns>是否成功启动/发送</returns>
public virtual bool DBC定义报文(
uint ,
uint ID,
Dictionary<string, double> ,
int = 0,
int 使CANFD = 1)
{
if ( >= _maxChannels || _channelHandles[] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[]) throw new InvalidOperationException($"通道 {通道号} 的 DBC 协议未加载,无法执行打包发送指令。");
if ( < 0) throw new ArgumentOutOfRangeException(nameof(), "循环间隔必须大于或等于 0。");
if ( == null || .Count == 0)
throw new ArgumentException("信号物理值字典不能为空。", nameof());
if ( == 0)
{
// 单次发送:同步执行
lock (_channelLocks[])
{
return DBC定义报文单次(, ID, , 使CANFD);
}
}
// 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环
(, ID);
var cts = new CancellationTokenSource();
var sendTask = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
if (_isClosing) break; // 关闭流程中立即退出
try
{
lock (_channelLocks[])
{
if (_channelHandles[] == IntPtr.Zero || _isClosing) break;
DBC定义报文单次(, ID, , 使CANFD);
}
await Task.Delay(, cts.Token);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}");
break;
}
}
// 清理:仅当字典中存的仍然是本任务创建的 CTS 时才移除,避免误删新任务的 CTS
if (_cyclicSenders.TryGetValue((, ID), out var current) && ReferenceEquals(current.Cts, cts))
{
_cyclicSenders.TryRemove((, ID), out _);
}
cts.Dispose();
}, cts.Token);
_cyclicSenders[(, ID)] = (cts, sendTask);
return true;
}
/// <summary>
/// 停止指定通道、指定帧 ID 的循环发送。
/// </summary>
public virtual void (uint , uint ID)
{
if (_cyclicSenders.TryRemove((, ID), out var entry))
{
entry.Cts.Cancel();
// 不在这里 Dispose由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token
}
}
/// <summary>
/// 停止所有 DBC 循环发送任务。
/// </summary>
public virtual void ()
{
var entries = _cyclicSenders.ToArray();
// 1. 取消所有 CTS不 Dispose由 Task 清理代码负责)
foreach (var kvp in entries)
{
kvp.Value.Cts.Cancel();
}
// 2. 等待所有循环发送 Task 退出(最多 3 秒)
var tasks = new List<Task>();
foreach (var kvp in entries)
{
if (!kvp.Value.SendTask.IsCompleted)
tasks.Add(kvp.Value.SendTask);
}
if (tasks.Count > 0)
{
try
{
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(3));
}
catch { /* 忽略等待异常 */ }
}
_cyclicSenders.Clear();
}
/// <summary>
/// 清除指定通道、指定帧 ID 的信号覆盖值,恢复为 DBC 初始值。
/// </summary>
public virtual void (uint , uint ID)
{
if (_signalOverrides.TryRemove((, ID), out var dict))
{
lock (dict) dict.Clear();
}
}
/// <summary>
/// 清除指定通道所有帧的信号覆盖值。
/// </summary>
public virtual void (uint )
{
foreach (var key in _signalOverrides.Keys)
{
if (key. == && _signalOverrides.TryRemove(key, out var dict))
{
lock (dict) dict.Clear();
}
}
}
/// <summary>
/// 单次发送 DBC 报文(调用方已持有通道锁)。
/// </summary>
private bool DBC定义报文单次(uint , uint ID, Dictionary<string, double> , int 使CANFD)
{
uint chDbcHandle = _dbcHandles[];
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
IntPtr ptrCount = Marshal.AllocHGlobal(sizeof(uint));
Marshal.WriteInt32(ptrCount, 1);
try
{
if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, ID, ptrDbcMsg)) return false;
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
// 根据传入的物理值设置对应信号的原始值
for (int i = 0; i < msg.nSignalCount; i++)
{
var signal = msg.vSignals[i];
string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (.TryGetValue(signalName, out double physicalValue))
{
signal.nRawvalue = (signal, physicalValue);
msg.vSignals[i] = signal;
}
}
Marshal.StructureToPtr(msg, ptrDbcMsg, true);
if (使CANFD == 0)
{
IntPtr ptrCanFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.can_frame)));
try
{
if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFrame, ptrCount, ptrDbcMsg, 0)) return false;
ZLGCAN.can_frame canFrame = (ZLGCAN.can_frame)Marshal.PtrToStructure(ptrCanFrame, typeof(ZLGCAN.can_frame));
canFrame.__pad |= 0x20;
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
Marshal.StructureToPtr(txData, pTx, true);
return ZLGCAN.ZCAN_Transmit(_channelHandles[], pTx, 1) == 1;
}
finally
{
Marshal.FreeHGlobal(pTx);
}
}
finally
{
Marshal.FreeHGlobal(ptrCanFrame);
}
}
else
{
IntPtr ptrCanFDFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.canfd_frame)));
try
{
if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFDFrame, ptrCount, ptrDbcMsg, 1)) return false;
ZLGCAN.canfd_frame canfdFrame = (ZLGCAN.canfd_frame)Marshal.PtrToStructure(ptrCanFDFrame, typeof(ZLGCAN.canfd_frame));
canfdFrame.flags |= 0x20;
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
Marshal.StructureToPtr(txFdData, pTxFd, true);
return ZLGCAN.ZCAN_TransmitFD(_channelHandles[], pTxFd, 1) == 1;
}
finally
{
Marshal.FreeHGlobal(pTxFd);
}
}
finally
{
Marshal.FreeHGlobal(ptrCanFDFrame);
}
}
}
finally
{
Marshal.FreeHGlobal(ptrDbcMsg);
Marshal.FreeHGlobal(ptrCount);
}
}
/// <summary>
/// 不依赖通道锁的物理值到原始值转换。
/// </summary>
private static ulong (ZDBC.DBCSignal , double )
{
IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
try
{
Marshal.StructureToPtr(, pSignal, true);
Marshal.StructureToPtr(, pValue, true);
return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
}
finally
{
Marshal.FreeHGlobal(pSignal);
Marshal.FreeHGlobal(pValue);
}
}
#endregion
#region 4.1 ()
/// <summary>
/// 设置并发送指定信号值(帧 ID 支持 string 兼容模式)。
/// 其余未指定信号使用 DBC 中定义的初始值填充,避免总线数据被意外清零。
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">对应报文的帧 ID可以是 "26"、"0x1A"、"1A"</param>
/// <param name="信号名称">DBC 中定义的英文信号名称(不区分大小写)</param>
/// <param name="物理值">要写入的实际物理数值</param>
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(单位毫秒)</param>
/// <returns>操作是否成功</returns>
public virtual bool (uint , string IDStr, string , double , int = 0, bool = false)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 设置报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if ( >= _maxChannels || _channelHandles[] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[]) return false;
if (string.IsNullOrWhiteSpace()) return false;
// 将本次设置的信号值写入持久化覆盖表(累积,不会丢失之前设置的值)
var overrides = _signalOverrides.GetOrAdd((, ID), _ => new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase));
lock (overrides)
{
overrides[] = ;
}
// 构建发送字典DBC 初始值 + 持久化覆盖表(已包含本次设置)
var merged = (, ID);
if () return DBC定义报文(, ID, merged, );
else return true;
}
/// <summary>
/// 发送报文:以 DBC 中所有信号的初始值作为默认值发送(帧 ID 支持 string 兼容模式)。
/// 如果之前调用 设置报文 保存过覆盖值,则会优先使用覆盖值。
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">DBC 中定义的帧 ID可以是 "26"、"0x1A"、"1A"</param>
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(毫秒)</param>
/// <param name="是否使用CANFD">1 = 以 CANFD 格式发送0 = 经典 CAN 格式</param>
public virtual bool (uint , string IDStr, int = 0, int 使CANFD = 1)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 发送报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if ( >= _maxChannels || _channelHandles[] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[]) return false;
if ( < 0) throw new ArgumentOutOfRangeException(nameof());
var merged = (, ID);
return DBC定义报文(, ID, merged, , 使CANFD);
}
/// <summary>
/// 发送自定义报文(原始字节,帧 ID 支持 string 兼容模式)。
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">帧 ID可以是 "26"、"0x1A"、"1A");扩展帧会自动置位 0x80000000</param>
/// <param name="原始数据">原始帧数据</param>
/// <param name="dlcLength">数据长度CANFD 仅允许 0-8,12,16,20,24,32,48,64</param>
/// <param name="是否是扩展帧">是否为 29 位扩展帧</param>
/// <param name="是否是CANFD">true = CANFDfalse = 经典 CAN</param>
/// <param name="开启波特率加速BRS">CANFD 是否开启波特率加速</param>
public virtual bool (
uint ,
string IDStr,
byte[] ,
byte dlcLength,
bool = false,
bool CANFD = true,
bool BRS = true)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 发送自定义报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if ( >= _maxChannels || _channelHandles[] == IntPtr.Zero) return false;
if ( == null) return false;
// 扩展帧需要把最高位标志位置起来
uint canId = ID & 0x7FFFFFFF;
if () canId |= 0x80000000;
lock (_channelLocks[])
{
if (CANFD)
{
// CANFD 有效 DLC 校验
byte validFdDlc = CANFD的Dlc(dlcLength);
ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = validFdDlc,
flags = (byte)(( ? 0x01 : 0x00) | (BRS ? 0x02 : 0x00) | 0x20), // 0x20 本地回显
data = new byte[64]
};
Array.Copy(, 0, fdFrame.data, 0, Math.Min(.Length, 64));
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
Marshal.StructureToPtr(txFdData, pTxFd, true);
return ZLGCAN.ZCAN_TransmitFD(_channelHandles[], pTxFd, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTxFd); }
}
else
{
ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame
{
can_id = canId,
can_dlc = dlcLength > 8 ? (byte)8 : dlcLength,
__pad = 0x20, // 发送回显
data = new byte[8]
};
Array.Copy(, 0, standardFrame.data, 0, Math.Min(.Length, 8));
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
Marshal.StructureToPtr(txData, pTx, true);
return ZLGCAN.ZCAN_Transmit(_channelHandles[], pTx, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTx); }
}
}
}
/// <summary>
/// 直接发送 8 字节原始报文(经典 CAN 或 CANFD 均可)。
/// 无需加载 DBC适合手动构造简单报文。
/// </summary>
/// <param name="通道号">通道号0 ~ MaxChannels-1</param>
/// <param name="帧ID">帧 ID标准 11 位,扩展帧自动置位 0x80000000</param>
/// <param name="data">原始数据;超过 8 字节会自动截断,不足 8 字节自动补零</param>
/// <param name="是否是扩展帧">是否为 29 位扩展帧</param>
/// <param name="是否是CANFD">true = CANFD 格式false = 经典 CAN 格式</param>
/// <param name="开启波特率加速BRS">CANFD 是否开启波特率加速(经典 CAN 忽略此参数)</param>
/// <returns>是否发送成功</returns>
public virtual bool (
uint ,
uint ID,
byte[] data,
bool = false,
bool CANFD = true,
bool BRS = true)
{
if ( >= _maxChannels || _channelHandles[] == IntPtr.Zero) return false;
byte[] buffer = new byte[8];
if (data != null)
{
Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, 8));
}
uint canId = ID & 0x7FFFFFFF;
if () canId |= 0x80000000;
lock (_channelLocks[])
{
if (CANFD)
{
ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = 8,
flags = (byte)(( ? 0x01 : 0x00) | (BRS ? 0x02 : 0x00) | 0x20),
data = new byte[64]
};
Array.Copy(buffer, 0, fdFrame.data, 0, 8);
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
Marshal.StructureToPtr(txFdData, pTxFd, true);
return ZLGCAN.ZCAN_TransmitFD(_channelHandles[], pTxFd, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTxFd); }
}
else
{
ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame
{
can_id = canId,
can_dlc = 8,
__pad = 0x20,
data = new byte[8]
};
Array.Copy(buffer, 0, standardFrame.data, 0, 8);
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
Marshal.StructureToPtr(txData, pTx, true);
return ZLGCAN.ZCAN_Transmit(_channelHandles[], pTx, 1) == 1;
}
finally { Marshal.FreeHGlobal(pTx); }
}
}
}
/// <summary>
/// 构建发送用信号字典DBC 初始值 + 额外覆盖。
/// 调用方需自行保证通道已初始化且 DBC 已加载。
/// </summary>
private Dictionary<string, double> (uint , uint ID, Dictionary<string, double>? = null)
{
uint chDbcHandle = _dbcHandles[];
IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
try
{
lock (_channelLocks[])
{
if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, ID, ptrDbcMsg))
return new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
var dict = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
// 1. DBC 初始值打底
for (int i = 0; i < msg.nSignalCount; i++)
{
var signal = msg.vSignals[i];
string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (!string.IsNullOrEmpty(signalName) && signal.initialValueValid != 0)
{
dict[signalName] = signal.initialValue;
}
}
// 2. 叠加持久化覆盖表(之前 设置报文 累积的值)
if (_signalOverrides.TryGetValue((, ID), out var persisted))
{
lock (persisted)
{
foreach (var kvp in persisted)
dict[kvp.Key] = kvp.Value;
}
}
// 3. 叠加本次调用传入的额外覆盖(优先级最高)
if ( != null)
{
foreach (var kvp in )
{
dict[kvp.Key] = kvp.Value;
}
}
return dict;
}
}
finally
{
Marshal.FreeHGlobal(ptrDbcMsg);
}
}
/// <summary>
/// CANFD 有效 DLC 校验,非法值自动就近取到下一个合法 DLC。
/// </summary>
private static byte CANFD的Dlc(byte dlc)
{
if (dlc <= 8) return dlc;
if (dlc <= 12) return 12;
if (dlc <= 16) return 16;
if (dlc <= 20) return 20;
if (dlc <= 24) return 24;
if (dlc <= 32) return 32;
if (dlc <= 48) return 48;
return 64;
}
#endregion
#region 5.
/// <summary>
/// 计算物理值也需要传入对应通道使用通道专属的DBC句柄进行计算
/// </summary>
/// <summary>
/// 智能解析帧 ID 字符串,兼容 10 进制、16 进制(如 "0x1A", "1A", "26"
/// </summary>
/// <param name="idStr">输入的帧 ID 字符串</param>
/// <param name="frameId">解析成功后的 uint 帧 ID</param>
/// <returns>是否解析成功</returns>
[Browsable(false)]
public static bool TryParseFrameId(string idStr, out uint frameId)
{
frameId = 0;
if (string.IsNullOrWhiteSpace(idStr)) return false;
idStr = idStr.Trim();
try
{
// 1. 处理带 0x 或 0X 的标准 16 进制
if (idStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
frameId = Convert.ToUInt32(idStr.Substring(2), 16);
return true;
}
// 2. 尝试当做纯 10 进制解析(如 "26"
if (uint.TryParse(idStr, out frameId))
{
return true;
}
// 3. 如果不是纯数字,但符合 16 进制特征(如 "1A"、"ABC"),尝试按 16 进制解析
// 使用 System.Globalization.NumberStyles.HexNumber
if (uint.TryParse(idStr, System.Globalization.NumberStyles.HexNumber, null, out frameId))
{
return true;
}
}
catch
{
return false;
}
return false;
}
[Browsable(false)]
public ulong (uint , ZDBC.DBCSignal , double )
{
if ( >= _maxChannels) return 0;
IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
try
{
Marshal.StructureToPtr(, pSignal, true);
Marshal.StructureToPtr(, pValue, true);
lock (_channelLocks[])
{
return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
}
}
finally
{
Marshal.FreeHGlobal(pSignal);
Marshal.FreeHGlobal(pValue);
}
}
#endregion
[Browsable(false)]
public void Dispose()
{
CAN卡设备();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,125 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ZLGUSBCANFD
{
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 enum Msg_Type
{
CAN = 0,
CANFD = 1,
}
/// <summary>
/// DBC 报文解析器(实例类,每个 ZLGCANFD 拥有独立的解析器实例)。
/// 不同 CAN 卡的作用域各自维护自己的 MsgDatabase互不干扰。
/// </summary>
public class ZLGDBCParser
{
/// <summary>
/// 每个通道的报文数据库缓存,供 ViewModel 直接读取。
/// 索引 = 通道号 (0 ~ maxChannels-1)。
/// </summary>
public List<List<_Msg_>> MsgDatabase { get; private set; }
/// <summary>
/// 最大通道数(与所属 ZLGCANFD 一致)。
/// </summary>
public int MaxChannels { get; }
public ZLGDBCParser(int maxChannels = 4)
{
MaxChannels = maxChannels;
MsgDatabase = new List<List<_Msg_>>(
Enumerable.Range(0, maxChannels).Select(_ => new List<_Msg_>())
);
}
/// <summary>
/// 解析特定通道的 DBC 并自动存入对应的 MsgDatabase 缓存中。
/// </summary>
/// <param name="chDbcHandle">ZDBC 句柄</param>
/// <param name="channel">通道号索引 (0 ~ maxChannels-1)</param>
public void ParseAndLoadToDatabase(uint chDbcHandle, int channel)
{
if (channel < 0 || channel >= MsgDatabase.Count) return;
// 清空当前通道旧缓存
MsgDatabase[channel].Clear();
// 解析并填充
var parsedList = ParseZlgDbc(chDbcHandle);
MsgDatabase[channel].AddRange(parsedList);
}
/// <summary>
/// 全手工解析周立功 DBC 句柄下的所有消息和信号。
/// </summary>
/// <param name="chDbcHandle">对应通道的 ZDBC 句柄</param>
/// <returns>返回对齐同星业务层的自定义报文列表</returns>
public static List<_Msg_> ParseZlgDbc(uint chDbcHandle)
{
List<_Msg_> msgList = new List<_Msg_>();
if (chDbcHandle == 0 || chDbcHandle == ZDBC.INVALID_DBC_HANDLE)
return msgList;
int msgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage));
IntPtr pMsg = Marshal.AllocHGlobal(msgSize);
try
{
bool hasMessage = ZDBC.ZDBC_GetFirstMessage(chDbcHandle, pMsg);
while (hasMessage)
{
var zlgMsg = (ZDBC.DBCMessage)Marshal.PtrToStructure(pMsg, typeof(ZDBC.DBCMessage));
int nullIndex = Array.IndexOf(zlgMsg.strName, (byte)0);
_Msg_ myMsg = new _Msg_
{
msg_id = (int)zlgMsg.nID,
msg_name = nullIndex >= 0
? Encoding.Default.GetString(zlgMsg.strName, 0, nullIndex)
: Encoding.Default.GetString(zlgMsg.strName).TrimEnd('\0'),
msg_dlc = (byte)zlgMsg.nSize,
msg_cyclic = (int)zlgMsg.nCycleTime,
msg_type = zlgMsg.nExtend == 1 ? (int)Msg_Type.CANFD : (int)Msg_Type.CAN,
signal_Name = new string[zlgMsg.nSignalCount]
};
for (int i = 0; i < zlgMsg.nSignalCount; i++)
{
var signal = zlgMsg.vSignals[i];
string sigName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
myMsg.signal_Name[i] = sigName;
}
msgList.Add(myMsg);
hasMessage = ZDBC.ZDBC_GetNextMessage(chDbcHandle, pMsg);
}
}
finally
{
Marshal.FreeHGlobal(pMsg);
}
msgList.Sort((a, b) => a.msg_id.CompareTo(b.msg_id));
return msgList;
}
}
}