CAN功能移植兼容

This commit is contained in:
hsc
2026-07-06 13:29:34 +08:00
parent aec0044595
commit 3b41459dcd
16 changed files with 1658 additions and 58 deletions

View File

@@ -1,6 +1,7 @@
using Logger;
using MaterialDesignThemes.Wpf;
using Microsoft.Win32;
using Model.Models;
using Notifications.Wpf.Core;
using System.Collections.Concurrent;
@@ -147,7 +148,9 @@ namespace ADP.ViewModels
public ICommand NewCommand { get; set; }
public ICommand SetDefaultCommand { get; set; }
public ICommand ShowDialogManagerViewCommand { get; set; }
public ICommand SelectCANSignalMonitorCommand { get; set; }
public ICommand SelectCANMessageCommand { get; set; }
public ICommand GetFileStringCommand { get; set; }
#endregion
public ShellViewModel(IContainerProvider containerProvider)
@@ -177,7 +180,9 @@ namespace ADP.ViewModels
SaveAsCommand = new DelegateCommand(SaveAs);
SaveCommand = new DelegateCommand(Save);
SetDefaultCommand = new DelegateCommand(SetDefault);
SelectCANSignalMonitorCommand = new DelegateCommand(SelectCANSignalMonitor);
SelectCANMessageCommand = new DelegateCommand(SelectCANMessage);
GetFileStringCommand = new DelegateCommand(GetFileString);
_globalInfo.ContextDic.Add("default", new ScopedContext());
@@ -200,7 +205,7 @@ namespace ADP.ViewModels
_uiRefreshTimer.Start();
}
private void RefreshAllContextProperties()
{
RaisePropertyChanged(nameof(RunState));
@@ -211,6 +216,37 @@ namespace ADP.ViewModels
}
#region
private void GetFileString()
{
var openFileDialog = new OpenFileDialog
{
Title = "选择文件",
Filter = "所有文件 (*.*)|*.*",
};
if (openFileDialog.ShowDialog() == true)
{
string filePath = openFileDialog.FileName;
Application.Current.Dispatcher.Invoke(() =>
{
Clipboard.SetDataObject(filePath);
});
}
}
private void SelectCANSignalMonitor()
{
var canfd = CurrentConfig?.CANFD;
if (canfd == null || _globalInfo.CurrentScope == "default")
{
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
return;
}
_dialogService.ShowDialog("CollectCANSignalSettingView", new DialogParameters
{
{ "SystemConfig", CurrentConfig }
}, _ => { });
}
private void SelectCANMessage()
{
var canfd = CurrentConfig?.CANFD;

View File

@@ -158,13 +158,30 @@
Foreground="Black" />
</MenuItem.Icon>
</MenuItem>
<!-- CAN信号监控 -->
<MenuItem Header="CAN信号监控"
Foreground="Black"
Command="{Binding SelectCANSignalMonitorCommand}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Message"
Foreground="Black" />
</MenuItem.Icon>
</MenuItem>
<!-- CAN报文选择 -->
<MenuItem Header="CAN报文选择"
Foreground="Black"
Command="{Binding SelectCANMessageCommand}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Message"
Foreground="Black" />
</MenuItem.Icon>
</MenuItem>
<!-- 获得文件路径字符串 -->
<MenuItem Header="获得文件路径字符串"
Foreground="Black"
Command="{Binding GetFileStringCommand}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="File"
Foreground="Black" />
</MenuItem.Icon>
</MenuItem>

View File

@@ -1,4 +1,3 @@
using System.Reflection;
using CANModule.Views;
namespace CANModule
@@ -12,6 +11,8 @@ namespace CANModule
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<SelectCanMessageView>("SelectCanMessageView");
containerRegistry.RegisterDialog<CollectCANSignalSettingView>("CollectCANSignalSettingView");
containerRegistry.RegisterForNavigation<ZLGCANFDView>("ZLGCANFDView");
}
}
}

View File

@@ -0,0 +1,249 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
using ZLGUSBCANFD;
namespace CANModule.ViewModels
{
public class CollectCANSignalSettingViewModel : DialogViewModelBase
{
#region
private ZLGCANFD? _canfd;
private SystemConfig? _systemConfig;
#endregion
#region
public ObservableCollection<CANSignalConfig> ConfigurationList
{
get => _systemConfig?.ConfigurationList ?? new ObservableCollection<CANSignalConfig>();
}
private ObservableCollection<string> _messageList;
public ObservableCollection<string> MessageList
{
get => _messageList;
set => SetProperty(ref _messageList, value);
}
private ObservableCollection<string> _signalList;
public ObservableCollection<string> SignalList
{
get => _signalList;
set => SetProperty(ref _signalList, value);
}
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 (!string.IsNullOrEmpty(value) && _canfd != null)
{
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>();
}
}
}
}
private CANSignalConfig _SelectedItem;
public CANSignalConfig SelectedItem
{
get => _SelectedItem;
set => SetProperty(ref _SelectedItem, value);
}
private string _selectedSignal;
public string SelectedSignal
{
get => _selectedSignal;
set => SetProperty(ref _selectedSignal, value);
}
private int _MessageChannel=-1;
public int MessageChannel
{
get => _MessageChannel;
set
{
if (_MessageChannel != value)
{
_MessageChannel = value;
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;
public int CollectionInterval
{
get => _collectionInterval;
set => SetProperty(ref _collectionInterval, value);
}
#endregion
#region
public ICommand AddCommand { get; set; }
public ICommand CloseCommand { get; set; }
public ICommand SaveCommand { get; set; }
public ICommand DeleteCommand { get; set; }
#endregion
public CollectCANSignalSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
CloseCommand = new DelegateCommand(Close);
AddCommand = new DelegateCommand(Add);
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;
RaisePropertyChanged(nameof(ConfigurationList));
}
#endregion
#region
private void Delete()
{
if (SelectedItem != null)
{
_systemConfig?.ConfigurationList.Remove(SelectedItem);
}
}
private void Save()
{
ConfigService.Save(_systemConfig);
}
private void Add()
{
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))
{
return;
}
_systemConfig.ConfigurationList.Add(new CANSignalConfig
{
CollectionID = Guid.NewGuid(),
Channel = MessageChannel,
MessageName = ExtractMessageName(SelectedMessage),
SignalName = SelectedSignal,
MessageID = decimalValue,
CollectionInterval = CollectionInterval
});
}
private string ExtractHex(string input)
{
int startIndex = input.IndexOf("0x") + 2;
int endIndex = input.IndexOf("]", startIndex);
if (startIndex >= 2 && endIndex > startIndex)
{
return input.Substring(startIndex, endIndex - startIndex);
}
return string.Empty;
}
private int ConvertHexToDecimal(string hex)
{
try
{
return Convert.ToInt32(hex, 16);
}
catch (FormatException)
{
return -1;
}
}
private string ExtractMessageName(string input)
{
var parts = input.Split(']');
return parts.Length > 1 ? parts[1] : string.Empty;
}
private void Close()
{
RequestClose.Invoke();
}
#endregion
}
}

View File

@@ -0,0 +1,649 @@
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);
}
}
}
}
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.Invoke(() =>
{
var msgName = System.Text.Encoding.Default.GetString(msg.strName).TrimEnd('\0');
var find = CanMessageList.FirstOrDefault(s => s.ID == (int)msg.nID);
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

@@ -0,0 +1,120 @@
<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">
<prism:Dialog.WindowStyle>
<Style BasedOn="{StaticResource DialogUserManageStyle}"
TargetType="Window" />
</prism:Dialog.WindowStyle>
<UserControl.Resources>
<cons:HexConverter x:Key="HexConverter" />
</UserControl.Resources>
<GroupBox Padding="0,0,0,0"
MouseLeftButtonDown="MouseLeftButtonDown">
<GroupBox.Header>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="CAN信号采集设置"
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.RowDefinitions>
<RowDefinition />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<DataGrid x:Name="配置表格"
AutoGenerateColumns="False"
ItemsSource="{Binding ConfigurationList}"
SelectedItem="{Binding SelectedItem}"
IsReadOnly="True">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="删除"
Command="{Binding DeleteCommand}"
/>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="通道"
Binding="{Binding Channel}" />
<DataGridTextColumn Header="报文ID"
Binding="{Binding MessageID,Converter={StaticResource HexConverter}}" />
<DataGridTextColumn Header="报文名称"
Binding="{Binding MessageName}" />
<DataGridTextColumn Header="信号名称"
Binding="{Binding SignalName}" />
<DataGridTextColumn Header="采集间隔"
Binding="{Binding CollectionInterval}" />
<DataGridTextColumn Header="采集id"
Binding="{Binding CollectionID}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal"
Grid.Row="1"
Margin="10">
<Label Content="通道(从0开始)"
VerticalAlignment="Center" />
<TextBox Name="SignalComboBox"
Text="{Binding MessageChannel}"
MaxWidth="200"
MinWidth="120"
Margin="10"
VerticalAlignment="Center"
materialDesign:HintAssist.Hint="信号通道" />
<Label Content="报文"
VerticalAlignment="Center" />
<ComboBox MinWidth="100"
materialDesign:HintAssist.Hint=""
SelectedItem="{Binding SelectedMessage,Mode=TwoWay}"
ItemsSource="{Binding MessageList}"
/>
<Label Content="信号"
VerticalAlignment="Center" />
<ComboBox SelectedItem="{Binding SelectedSignal}"
materialDesign:HintAssist.Hint=""
MaxWidth="200"
ItemsSource="{Binding SignalList,Mode=TwoWay}"
MinWidth="100" />
<Label Content="采集间隔(ms)"
VerticalAlignment="Center" />
<TextBox Text="{Binding CollectionInterval}"
MinWidth="100"
materialDesign:HintAssist.Hint=""
VerticalAlignment="Center" />
<Button Content="添加"
Margin="10"
Command="{Binding AddCommand}"
/>
<Button Content="保存"
Margin="10"
Command="{Binding SaveCommand}"
/>
</StackPanel>
</Grid>
</GroupBox>
</UserControl>

View File

@@ -0,0 +1,25 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace CANModule.Views
{
/// <summary>
/// CollectCANSignalSettingView.xaml 的交互逻辑
/// </summary>
public partial class CollectCANSignalSettingView : UserControl
{
public CollectCANSignalSettingView()
{
InitializeComponent();
}
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Window.GetWindow(this)?.DragMove();
}
}
}
}

View File

@@ -0,0 +1,286 @@
<UserControl x:Class="CANModule.Views.ZLGCANFDView"
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: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="850"
Width="900">
<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>
<GroupBox Padding="0,0,0,0"
>
<GroupBox.Header>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="CAN手动设置"
Foreground="White"
VerticalAlignment="Center"
Margin="5,0,10,0" />
</Grid>
</GroupBox.Header>
<Grid>
<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" />
</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"
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>
<!-- DBC 配置 -->
<TreeViewItem Header="DBC">
<Label Content="请注意,要在断开CAN卡的情况下才能操作DBC!"
Foreground="Red" />
<Button Content="卸载当前通道DBC"
Command="{Binding UpLoadDBCCommand}" />
<StackPanel Orientation="Horizontal">
<Button Content="更换DBC"
Command="{Binding ChangeDBCCommand}" />
<Label Content="DBC文件路径"
VerticalContentAlignment="Center" />
<TextBox Text="{Binding DBCPath,Mode=TwoWay}"
MinWidth="100"
materialDesign:HintAssist.Hint=""
VerticalAlignment="Center" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="加载DBC"
Command="{Binding LoadDBCCommand}" />
<Label Content="通道(从0开始)"
VerticalContentAlignment="Center" />
<TextBox Text="{Binding DBCChannel,Mode=TwoWay}"
materialDesign:HintAssist.Hint=""
MinWidth="100"
VerticalAlignment="Center" />
<CheckBox Margin="5 0 0 0"
IsChecked="{Binding IsSaved}" />
<Label Content="加载记录保存"
VerticalContentAlignment="Center" />
</StackPanel>
<DataGrid ItemsSource="{Binding dbcAutoLoadList}"
Width="650"
IsReadOnly="True"
AutoGenerateColumns="False"
SelectedItem="{Binding SelectedDcAutoLoad}">
<DataGrid.Columns>
<DataGridTextColumn Header="通道"
Binding="{Binding DBCChannel}"
Width="60" />
<DataGridTextColumn Header="文件路径"
Binding="{Binding DBCFilePath}"
Width="*" />
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="删除"
Foreground="Red"
Command="{Binding DeleteDcAutoLoadCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</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"
materialDesign:HintAssist.Hint=""
SelectedItem="{Binding SelectedMessage}"
ItemsSource="{Binding Message}" />
</DockPanel>
<DockPanel>
<Label Content="信号" />
<ComboBox SelectedItem="{Binding SelectedSignal}"
ItemsSource="{Binding Signal}"
materialDesign:HintAssist.Hint=""
MinWidth="100" />
</DockPanel>
<DockPanel>
<Label Content="信号值" />
<TextBox Text="{Binding SignalValue}"
materialDesign:HintAssist.Hint=""
MinWidth="100" />
</DockPanel>
<DockPanel>
<Label Content="循环发送时间间隔(ms)(0为单次发送)" />
<TextBox Text="{Binding CycleSendInterval}"
materialDesign:HintAssist.Hint=""
MinWidth="100" />
</DockPanel>
<StackPanel Orientation="Horizontal">
<Button Content="设置信号并发送"
Command="{Binding SetAndSendMessageCommand}" />
<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}" />
</DockPanel>
<DockPanel>
<Label Content="ID" />
<TextBox MinWidth="100"
materialDesign:HintAssist.Hint=""
Text="{Binding AID, Converter={StaticResource HexConverter}, Mode=TwoWay}" />
</DockPanel>
<DockPanel>
<Label Content="DLC" />
<TextBox MinWidth="200"
materialDesign:HintAssist.Hint=""
Text="{Binding ADLC}" />
<CheckBox Content="自动设定"
IsChecked="{Binding AutoSetting}" />
</DockPanel>
<DockPanel>
<Label Content="报文" />
<TextBox MinWidth="200"
materialDesign:HintAssist.Hint=""
Text="{Binding CustomMessage}" />
</DockPanel>
<DockPanel>
<Label Content="循环发送时间间隔(ms)(0为单次发送)" />
<TextBox Text="{Binding CustomSendCycleInterval}"
materialDesign:HintAssist.Hint=""
MinWidth="100" />
</DockPanel>
<StackPanel Orientation="Horizontal">
<CheckBox Content="发送帧"
IsChecked="{Binding AIsTx}" />
<CheckBox Content="扩展帧"
IsChecked="{Binding AIsExt}" />
<CheckBox Content="远程帧"
IsChecked="{Binding AIsRemote}" />
<CheckBox Content="FD"
IsChecked="{Binding AIsFD}" />
<CheckBox Content="BRS"
IsChecked="{Binding AIsBRS}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="发送"
Command="{Binding SendCustomMessageCommand}" />
<!--<CheckBox Margin="10"
Content="更新到数据库"
IsChecked="{Binding UpdateToDB}" />-->
</StackPanel>
</TreeViewItem>
<!-- 报文录制 -->
<TreeViewItem Header="报文录制">
<DockPanel>
<Label Content="路径(开始录制时使用)"
VerticalContentAlignment="Center" />
<TextBox MinWidth="100"
materialDesign:HintAssist.Hint=""
Text="{Binding BLFPath}"
VerticalContentAlignment="Center" />
<Button Content="选择路径"
Margin="10 0"
Command="{Binding SelectBLFPathCommand}" />
<Button Content="保存路径"
Command="{Binding SaveBLFPathCommand}" />
</DockPanel>
<StackPanel Orientation="Horizontal">
<Button Content="开始录制"
Command="{Binding StartTranscribeCommand}" />
<Button Content="结束录制"
Margin="10 0"
Command="{Binding EndTranscribeCommand}" />
</StackPanel>
</TreeViewItem>
</TreeView>
</Grid>
</GroupBox>
</UserControl>

View File

@@ -0,0 +1,35 @@
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>
/// ZLGCANFDView.xaml 的交互逻辑
/// </summary>
public partial class ZLGCANFDView : UserControl
{
public ZLGCANFDView()
{
InitializeComponent();
}
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Window.GetWindow(this)?.DragMove();
}
}
}
}

View File

@@ -96,8 +96,10 @@ namespace UIShare.GlobalVariable
try
{
// 按指纹全局唯一创建 ZLGCANFD 实例maxChannels 默认 4对应 USBCANFD-400U
// 波特率与终端电阻从 CANConfigVM 传入,初始化并启动通道 时直接使用
var canLazy = _globalInfo.CanPool.GetOrAdd(fingerprint, key => new Lazy<ZLGCANFD>(() =>
new ZLGCANFD(config.CANConfig.DeviceType, config.CANConfig.DeviceIndex, 4)));
new ZLGCANFD(config.CANConfig.DeviceType, config.CANConfig.DeviceIndex, 4,
config.CANConfig.ABitBaud, config.CANConfig.DBitBaud, config.CANConfig.EnableTerminalResistance)));
_systemConfig.CANFD = canLazy.Value;
CANFD = canLazy.Value;
@@ -190,15 +192,22 @@ namespace UIShare.GlobalVariable
public async Task ConnectAllDevices(CancellationToken ct = default)
{
if (_systemConfig?.DeviceList == null || DeviceMap.Count == 0) return;
if (_systemConfig?.DeviceList == null) return;
var tasks = new List<Task>();
foreach (var info in _systemConfig.DeviceList)
{
if (info == null || !info.IsEnabled) continue;
if (string.IsNullOrWhiteSpace(info.DeviceName)) continue;
if (!DeviceMap.TryGetValue(info.DeviceName, out var device)) continue;
// CAN 设备:直接打开 CAN 卡
if (string.Equals(info.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
{
tasks.Add(ConnectCanAsync(info));
continue;
}
if (!DeviceMap.TryGetValue(info.DeviceName, out var device)) continue;
tasks.Add(ConnectInternalAsync(info, device, ct));
}
@@ -214,15 +223,28 @@ namespace UIShare.GlobalVariable
return;
}
var info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
if (info == null)
{
LoggerHelper.Warn($"ConnectSpecifiedDevice未找到设备配置 [{deviceName}]。");
return;
}
// CAN 设备:直接打开 CAN 卡
if (string.Equals(info.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
{
await ConnectCanAsync(info);
return;
}
if (!DeviceMap.TryGetValue(deviceName, out var device))
{
LoggerHelper.Warn($"ConnectSpecifiedDevice未找到设备 [{deviceName}]。");
return;
}
var info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
await ConnectInternalAsync(info, device, ct);
}
@@ -233,15 +255,21 @@ namespace UIShare.GlobalVariable
{
if (string.IsNullOrWhiteSpace(deviceName)) return;
var info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
// CAN 设备:直接关闭 CAN 卡
if (info != null && string.Equals(info.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
{
await CloseCanAsync(info);
return;
}
IBaseInterface? device;
DeviceInfoVM? info;
lock (_lockObj)
{
if (!DeviceMap.TryGetValue(deviceName, out device)) return;
info = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase));
}
await CloseInternalAsync(info, device);
@@ -256,7 +284,7 @@ namespace UIShare.GlobalVariable
lock (_lockObj)
{
if (DeviceMap.Count == 0) return;
if (DeviceMap.Count == 0 && (CANFD == null)) return;
foreach (var kvp in DeviceMap)
{
@@ -269,6 +297,17 @@ namespace UIShare.GlobalVariable
}
}
// CAN 设备:直接关闭 CAN 卡
if (CANFD != null)
{
var canInfo = _systemConfig?.DeviceList?
.FirstOrDefault(d => d != null && string.Equals(d.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase));
if (canInfo != null)
{
tasks.Add(CloseCanAsync(canInfo));
}
}
await Task.WhenAll(tasks);
LoggerHelper.Info("所有设备已执行关闭操作。");
}
@@ -366,6 +405,88 @@ namespace UIShare.GlobalVariable
}
return await sp.ConnectAsync(ct);
}
/// <summary>
/// 打开 CAN 卡:打开设备 + 初始化并启动所有通道
/// </summary>
private async Task<bool> ConnectCanAsync(DeviceInfoVM info)
{
string name = info.DeviceName ?? "CAN";
try
{
if (CANFD == null)
{
LoggerHelper.Warn($"CAN 设备 [{name}] 尚未实例化,无法连接。");
info.IsConnected = false;
return false;
}
if (info.IsConnected)
{
LoggerHelper.Info($"CAN 设备 [{name}] 已连接,跳过。");
return true;
}
bool ok = await Task.Run(() =>
{
if (!CANFD.()) return false;
return true;
});
info.IsConnected = ok;
if (ok)
LoggerHelper.Info($"CAN 设备 [{name}] 连接成功,已初始化 {CANFD.DBCParser.MaxChannels} 个通道。");
else
LoggerHelper.Warn($"CAN 设备 [{name}] 连接失败。");
return ok;
}
catch (Exception ex)
{
info.IsConnected = false;
var inner = ex.InnerException?.Message ?? ex.Message;
LoggerHelper.ErrorWithNotify(_scopeName, $"CAN 设备 [{name}] 连接异常:{inner}");
return false;
}
}
/// <summary>
/// 关闭 CAN 卡
/// </summary>
private async Task CloseCanAsync(DeviceInfoVM info)
{
string name = info.DeviceName ?? "CAN";
try
{
if (CANFD == null)
{
info.IsConnected = false;
return;
}
if (!info.IsConnected)
{
LoggerHelper.Info($"CAN 设备 [{name}] 本就处于断开状态。");
return;
}
await Task.Run(() => CANFD.CAN卡设备());
LoggerHelper.Info($"CAN 设备 [{name}] 已成功关闭连接。");
}
catch (Exception ex)
{
var inner = ex.InnerException?.Message ?? ex.Message;
LoggerHelper.Error($"CAN 设备 [{name}] 关闭连接时出现异常: {inner}");
}
finally
{
info.IsConnected = false;
}
}
private static IReadOnlyDictionary<string, Type> BuildDeviceTypeMap()
{
try

View File

@@ -30,6 +30,8 @@ namespace UIShare.GlobalVariable
public string DefaultDBCFilePath { get; set; } = "";
public ObservableCollection<DeviceInfoVM> DeviceList = new();
public ObservableCollection<SharedParameter> SharedParameterList = new();
public ObservableCollection<CANSignalConfig> ConfigurationList = new();
public ObservableCollection<AutoDBCLoadItem> dbcAutoLoadList = new();
public ZLGCANFD CANFD = new();
[JsonIgnore]
public ObservableCollection<ParameterVM> ParameterList = new()

View File

@@ -0,0 +1,33 @@
namespace UIShare.UIViewModel
{
/// <summary>
/// CAN 通道枚举(对应 ZLGCANFD 通道选择)
/// </summary>
public enum APP_CHANNEL
{
CH0 = 0,
CH1 = 1,
CH2 = 2,
CH3 = 3
}
/// <summary>
/// DBC 自动加载配置项
/// </summary>
public class AutoDBCLoadItem : BindableBase
{
private int _dbcChannel;
public int DBCChannel
{
get => _dbcChannel;
set => SetProperty(ref _dbcChannel, value);
}
private string _dbcFilePath;
public string DBCFilePath
{
get => _dbcFilePath;
set => SetProperty(ref _dbcFilePath, value);
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace UIShare.UIViewModel
{
public class CANSignalConfig
{
public int Channel { get; set; }
public int MessageID { get; set; }
public string MessageName { get; set; }
public string SignalName { get; set; }
public int CollectionInterval { get; set; }
public Guid CollectionID { get; set; }
}
}

View File

@@ -5,12 +5,9 @@
// 字段声明
private byte _通道;
private int _报文ID;
private ulong _时间戳;
private double _时间戳;
private byte _长度;
private bool _fd;
private bool _isTx;
private byte[] _bytes = new byte[64];
private string _报文;
// 通道属性
public byte
@@ -27,7 +24,7 @@
}
// 时间戳属性
public ulong
public double
{
get { return _时间戳; }
set { SetProperty(ref _时间戳, value); }
@@ -40,32 +37,6 @@
set { SetProperty(ref _长度, value); }
}
// FD属性
public bool FD
{
get { return _fd; }
set { SetProperty(ref _fd, value); }
}
// IsTx属性
public bool IsTx
{
get { return _isTx; }
set { SetProperty(ref _isTx, value); }
}
// Bytes属性
public byte[] Bytes
{
get { return _bytes; }
set { SetProperty(ref _bytes, value); }
}
// 报文属性
public string
{
get { return _报文; }
set { SetProperty(ref _报文, value); }
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Prism.Dialogs;
namespace UIShare.ViewModelBase
{
@@ -11,12 +12,41 @@ namespace UIShare.ViewModelBase
{
public DialogCloseListener RequestClose { get; set; }
public IEventAggregator _eventAggregator;
public IDialogService _dialogService;
private INotificationManager _notificationManager;
public DialogViewModelBase(IContainerProvider containerProvider)
{
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
_dialogService = containerProvider.Resolve<IDialogService>();
_notificationManager = containerProvider.Resolve<INotificationManager>();
}
protected void ShowInfoMessageBox(string Message, Action callback)
{
var dialogParams = new DialogParameters();
dialogParams.Add("Title", "提示");
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
{
callback();
});
}
protected void ShowErrorMessageBox(string Message, Action callback)
{
var dialogParams = new DialogParameters();
dialogParams.Add("Title", "错误");
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
{
callback();
});
}
#region Dialog
public virtual bool CanCloseDialog() => true;

View File

@@ -34,6 +34,11 @@ namespace ZLGUSBCANFD
private readonly uint _deviceIndex; // 设备索引
private readonly int _maxChannels; // 动态通道数
// 通道波特率与终端电阻配置(构造时传入,所有通道共用)
private readonly string _abitBaud;
private readonly string _dbitBaud;
private readonly bool _enableTerminalResistance;
/// <summary>
/// 当前 CAN 卡专属的 DBC 解析器,按通道隔离报文数据库。
/// </summary>
@@ -45,11 +50,15 @@ namespace ZLGUSBCANFD
/// </summary>
public event Action<uint, ZDBC.DBCMessage>? OnDbcMessageDecoded;
public ZLGCANFD(uint deviceType = 76, uint deviceIndex = 0, int maxChannels = 4)
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];
@@ -80,9 +89,9 @@ namespace ZLGUSBCANFD
}
/// <summary>
/// 针对特定通道进行参数初始化并启动
/// 针对特定通道进行参数初始化并启动(使用构造时传入的波特率与终端电阻配置)
/// </summary>
public virtual bool (uint , string abitBaud = "500000", string dbitBaud = "2000000", bool = true)
public virtual bool (uint )
{
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
if ( >= _maxChannels) return false;
@@ -96,11 +105,11 @@ namespace ZLGUSBCANFD
}
// 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;
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 = ? "1" : "0";
string resistanceStr = _enableTerminalResistance ? "1" : "0";
if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false;
// 3. 规范配置通道结构体
@@ -179,7 +188,8 @@ namespace ZLGUSBCANFD
try
{
if (_isDbcLoadedArray[]) return true;
//加载前先卸载
DBC();
uint chDbcHandle = ZDBC.ZDBC_Init();
if (chDbcHandle == 0) return false;
@@ -217,6 +227,7 @@ namespace ZLGUSBCANFD
ZDBC.ZDBC_Release(_dbcHandles[]);
_dbcHandles[] = 0;
_isDbcLoadedArray[] = false;
DBCParser.MsgDatabase[(int)].Clear();
}
}
}
@@ -440,7 +451,7 @@ namespace ZLGUSBCANFD
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 = 0 };
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{
@@ -467,7 +478,7 @@ namespace ZLGUSBCANFD
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 = 0 };
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
@@ -617,7 +628,7 @@ namespace ZLGUSBCANFD
};
Array.Copy(, 0, fdFrame.data, 0, Math.Min(.Length, 64));
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 0 };
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 };
IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
try
{
@@ -637,7 +648,7 @@ namespace ZLGUSBCANFD
};
Array.Copy(, 0, standardFrame.data, 0, Math.Min(.Length, 8));
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 0 };
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 };
IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
try
{