Files
ACP/CANModule/ViewModels/CANViewModel.cs
T
2026-10-26 10:46:44 +08:00

637 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}