637 lines
22 KiB
C#
637 lines
22 KiB
C#
|
||
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;
|
||
using UIShare.ViewModelBase;
|
||
|
||
namespace CANModule.ViewModels
|
||
{
|
||
public class CANViewModel : NavigateViewModelBase
|
||
{
|
||
|
||
#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 SystemConfig? _systemConfig;
|
||
private readonly DeviceManager _deviceManager;
|
||
private IDialogService _dialogService { get; set; }
|
||
CancellationTokenSource cts = new CancellationTokenSource();
|
||
public CANViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||
{
|
||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||
_systemConfig = containerProvider.Resolve<SystemConfig>();
|
||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||
_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);
|
||
Task.Run(() => Refresh(cts.Token), cts.Token);
|
||
}
|
||
|
||
#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("MessageBox", new DialogParameters
|
||
{
|
||
{ "Message", $"报文转换失败:{ex.Message}" },
|
||
{ "ShowOk", true },
|
||
});
|
||
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))
|
||
{
|
||
ShowMessage("发送参数无效:请检查发送周期、信号值和发送通道。");
|
||
return;
|
||
}
|
||
channel = c;
|
||
var parts = SelectedMessage.Split(']');
|
||
var message = parts.Length > 1 ? parts[1] : SelectedMessage;
|
||
|
||
try
|
||
{
|
||
var re = CAN.SetSignalValue(channel, message, SelectedSignal, SValue, IsDirectlySend, sendPeriod);
|
||
if (re != 0)
|
||
{
|
||
// -1 表示托管缓存里没有这条报文:DBC 没加载到该通道,或加载通道与发送通道不一致
|
||
ShowMessage(re == -1
|
||
? $"通道 {channel} 的 DBC 缓存中没有报文 [{message}],请先加载 DBC 并确认加载通道与发送通道一致。"
|
||
: $"发送报文失败,返回代码 {re}:{CAN.GetErrorDescription(re)}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 93(message not found in database) 等原生错误在这里提示,避免冒泡到全局异常处理器
|
||
ShowMessage(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>统一的提示弹窗</summary>
|
||
private void ShowMessage(string message)
|
||
{
|
||
_dialogService.ShowDialog("MessageBox", new DialogParameters
|
||
{
|
||
{ "Message", message },
|
||
{ "ShowOk", true },
|
||
});
|
||
}
|
||
|
||
private void ReleaseTSMaster()
|
||
{
|
||
CAN.Release();
|
||
}
|
||
|
||
private void InitTSMaster()
|
||
{
|
||
var re = CAN.Init("ACP");
|
||
if (re != 0)
|
||
{
|
||
ShowMessage($"TSMaster 初始化失败,返回代码 {re}:{CAN.GetErrorDescription(re)}");
|
||
return;
|
||
}
|
||
|
||
// 驱动被释放后原生 DBC 数据库为空、托管缓存也已被清空,
|
||
// 这里按保存的自动加载列表把 DBC 重新下发,否则发送会得到 93
|
||
if (CAN.NeedReloadDbc) ReloadAutoDbc();
|
||
}
|
||
|
||
/// <summary>重新下发配置中保存的 DBC(驱动重启后调用)</summary>
|
||
private void ReloadAutoDbc()
|
||
{
|
||
foreach (var item in DBCAutoLoadList)
|
||
{
|
||
if (item.DBCChannel < 0 || item.DBCChannel >= 4) continue;
|
||
if (string.IsNullOrWhiteSpace(item.DBCFilePath) || !File.Exists(item.DBCFilePath)) continue;
|
||
|
||
var re = CAN.LoadDBC(item.DBCFilePath, new[] { item.DBCChannel }, out _);
|
||
if (re != 0)
|
||
ShowMessage($"DBC 重新加载失败(通道 {item.DBCChannel}):{item.DBCFilePath},返回代码 {re}");
|
||
}
|
||
}
|
||
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 = _deviceManager._CANMonitoringService.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)
|
||
{
|
||
|
||
}
|
||
public override void OnNavigatedTo(NavigationContext context)
|
||
{
|
||
Task.Run(() => Refresh(cts.Token), cts.Token);
|
||
}
|
||
|
||
|
||
#endregion
|
||
}
|
||
}
|