678 lines
22 KiB
C#
678 lines
22 KiB
C#
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;
|
|
_canfd.OnRawMessageReceived += OnRawMessageReceived;
|
|
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;
|
|
_canfd.OnRawMessageReceived -= OnRawMessageReceived;
|
|
}
|
|
_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 void OnRawMessageReceived(uint channel, uint canId, byte[] data, ulong timestamp, bool isFD, byte dlcLength)
|
|
{
|
|
// 原始报文回调:不依赖 DBC,自定义报文也能收到
|
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
|
{
|
|
// 提取实际 CAN ID(去掉扩展帧标志位)
|
|
int actualId = (int)(canId & 0x1FFFFFFF);
|
|
var find = CanMessageList.FirstOrDefault(s => s.报文ID == actualId && s.通道 == channel);
|
|
if (find != null)
|
|
{
|
|
find.时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
|
find.长度 = dlcLength;
|
|
}
|
|
else
|
|
{
|
|
CanMessageList.Add(new CanMessageShowVM
|
|
{
|
|
通道 = (byte)channel,
|
|
报文ID = actualId,
|
|
时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(),
|
|
长度 = dlcLength
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|