using AutoMapper; using CapMachine.Core; using CapMachine.Model.CANLIN; using CapMachine.Wpf.CanDrive; using CapMachine.Wpf.Dtos; using CapMachine.Wpf.Services; using CapMachine.Wpf.Views; using ImTools; using Microsoft.Win32; using Prism.Commands; using Prism.Events; using Prism.Regions; using Prism.Services.Dialogs; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using static CapMachine.Wpf.Models.ComEnum; namespace CapMachine.Wpf.ViewModels { /// /// ZLG CAN/CANFD 合并配置 ViewModel(模式切换:单选)。 /// /// /// 该 ViewModel 负责 ZLG CAN/CANFD 的 UI 配置与用户交互编排,典型职责包括: /// - 打开/关闭设备、切换模式(CAN/CANFD),并与 同步状态; /// - 加载 DBC 并维护可绑定的信号集合(用于读写设置与实时显示); /// - 管理“读写设置”弹窗的数据准备与回写(写入/读取规则、信号候选集合); /// - 管理调度表/循环发送相关 UI 状态(软件调度 vs 硬件 auto_send 的选择由 Service/Driver 实现)。 /// /// 线程说明: /// - 本类绝大多数方法由 UI 线程调用; /// - Driver 接收线程产生的数据更新事件若需要影响 UI,应通过属性通知或 Dispatcher 进行线程切换(具体由 Service/Driver 侧处理)。 /// public class ZlgCanDriveConfigViewModel : NavigationViewModel { /// /// 构造函数。 /// /// Prism 弹窗服务,用于打开读写设置等对话框。 /// 数据访问组件,用于读取/保存配置程序与读写配置等持久化数据。 /// 事件聚合器,用于跨模块消息通知(如状态变化/日志等)。 /// 区域导航服务,用于页面/视图切换。 /// 系统运行服务(与全局运行态/权限等相关)。 /// 通信互斥控制服务,用于保证 CAN/LIN 等通道操作的互斥与安全。 /// 日志服务,用于输出 UI 层操作过程与异常。 /// 逻辑规则服务,为“写入规则/触发规则”下拉框提供数据源。 /// 配置服务,用于读取/应用系统配置参数。 /// ZLG CAN/CANFD 服务层,负责驱动生命周期、DBC、发送/调度表编排。 /// ZLG LIN 服务层,用于互斥判断或组合配置场景。 /// 对象映射器,用于 DTO/实体之间转换。 /// /// 构造时会初始化: /// - 波特率下拉数据源; /// - 写入规则下拉数据源; /// - CAN 配置程序列表(用于 UI 选择)。 /// /// 注意:构造函数仅做“数据源初始化与依赖注入”,不会直接打开设备。 /// public ZlgCanDriveConfigViewModel(IDialogService dialogService, IFreeSql freeSql, IEventAggregator eventAggregator, IRegionManager regionManager, SysRunService sysRunService, ComActionService comActionService, ILogService logService, LogicRuleService logicRuleService, ConfigService configService, ZlgCanDriveService zlgCanDriveService, ZlgLinDriveService zlgLinDriveService, IMapper mapper) { DialogService = dialogService; FreeSql = freeSql; EventAggregator = eventAggregator; RegionManager = regionManager; SysRunService = sysRunService; ComActionService = comActionService; LogService = logService; LogicRuleService = logicRuleService; ConfigService = configService; ZlgCanDriveService = zlgCanDriveService; ZlgLinDriveService = zlgLinDriveService; Mapper = mapper; SelectedMode = ZlgCanMode.Can; ArbBaudRateCbxItems = new ObservableCollection() { new CbxItems(){ Key="10000",Text="10 Kbps"}, new CbxItems(){ Key="20000",Text="20 Kbps"}, new CbxItems(){ Key="33000",Text="33 Kbps"}, new CbxItems(){ Key="50000",Text="50 Kbps"}, new CbxItems(){ Key="83000",Text="83 Kbps"}, new CbxItems(){ Key="100000",Text="100 Kbps"}, new CbxItems(){ Key="125000",Text="125 Kbps"}, new CbxItems(){ Key="150000",Text="150 Kbps"}, new CbxItems(){ Key="200000",Text="200 Kbps"}, new CbxItems(){ Key="250000",Text="250 Kbps"}, new CbxItems(){ Key="300000",Text="300 Kbps"}, new CbxItems(){ Key="400000",Text="400 Kbps"}, new CbxItems(){ Key="500000",Text="500 Kbps"}, new CbxItems(){ Key="666000",Text="666 Kbps"}, new CbxItems(){ Key="800000",Text="800 Kbps"}, new CbxItems(){ Key="1000000",Text="1.0 Mbps"}, }; DataBaudRateCbxItems = new ObservableCollection() { new CbxItems(){ Key="100000",Text="100 Kbps"}, new CbxItems(){ Key="125000",Text="125 Kbps"}, new CbxItems(){ Key="200000",Text="200 Kbps"}, new CbxItems(){ Key="250000",Text="250 Kbps"}, new CbxItems(){ Key="400000",Text="400 Kbps"}, new CbxItems(){ Key="500000",Text="500 Kbps"}, new CbxItems(){ Key="666000",Text="666 Kbps"}, new CbxItems(){ Key="800000",Text="800 Kbps"}, new CbxItems(){ Key="1000000",Text="1.0 Mbps"}, new CbxItems(){ Key="1500000",Text="1.5 Mbps"}, new CbxItems(){ Key="2000000",Text="2.0 Mbps"}, new CbxItems(){ Key="3000000",Text="3.0 Mbps"}, new CbxItems(){ Key="4000000",Text="4.0 Mbps"}, new CbxItems(){ Key="5000000",Text="5.0 Mbps"}, new CbxItems(){ Key="6700000",Text="6.7 Mbps"}, new CbxItems(){ Key="8000000",Text="8.0 Mbps"}, new CbxItems(){ Key="10000000",Text="10.0 Mbps"}, }; InitWriteRuleCbx(); InitLoadCanConfigPro(); } /// /// 初始化“写入规则”下拉框数据源。 /// /// /// 数据源来自 。 /// - Key:规则 Id /// - Text:规则名称 /// /// 该集合通常被“读写设置”弹窗中的 ComboBox 绑定使用。 /// private void InitWriteRuleCbx() { WriteRuleCbxItems = new ObservableCollection(); foreach (var itemRule in LogicRuleService.LogicRuleDtos) { WriteRuleCbxItems.Add(new CbxItems() { Key = itemRule.Id.ToString(), Text = itemRule.Name }); } } /// /// 打开“读写设置”弹窗。 /// /// /// 弹窗数据准备流程: /// - 基于当前页面的 / 克隆出可编辑副本; /// - 根据 构建信号候选集合(MsgName/SignalName/描述等); /// - 通过 打开 DialogZlgCanLinRwConfigView; /// - 用户点击 OK 后回调中调用 ReloadCurrentConfigPro 刷新当前配置并提示。 /// /// 前置条件: /// - 必须先选择配置程序( 不为 null)。 /// - 推荐调用方在进入本方法前确保已连接设备且 DBC 已加载,否则候选信号集合可能为空。 /// private void OpenRwDialog() { try { if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } var writeClones = new ObservableCollection( (ListWriteCanLinRWConfigDto ?? new ObservableCollection()) .Select(CloneRwDto)); foreach (var item in writeClones) { item.RWInfo = RW.Write; } var readClones = new ObservableCollection( (ListReadCanLinRWConfigDto ?? new ObservableCollection()) .Select(CloneRwDto)); foreach (var item in readClones) { item.RWInfo = RW.Read; } var candidates = new ObservableCollection(); if (ListCanDbcModel != null) { foreach (var sig in ListCanDbcModel) { candidates.Add(new DialogZlgCanLinRwConfigViewModel.SignalCandidate { MsgName = sig.MsgName, SignalName = sig.SignalName, Name = sig.Name, Desc = sig.SignalDesc, }); } } var pars = new DialogParameters { { "Title", "读写设置" }, { "CanLinConfigProId", SelectCanLinConfigPro.Id }, { "IsEditable", IsRwEditable }, { "IsCanFdSchedule", SelectedMode == ZlgCanMode.CanFd }, { "WriteConfigs", writeClones }, { "ReadConfigs", readClones }, { "SignalCandidates", candidates }, }; DialogService.ShowDialog(nameof(DialogZlgCanLinRwConfigView), pars, r => { if (r.Result == ButtonResult.OK) { ReloadCurrentConfigPro(); OpTip = "读写设置已保存"; } }); } catch (Exception ex) { LogService.Error($"ZLG 打开读写设置弹窗失败:{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 克隆一个读写配置 DTO。 /// /// 源对象。 /// 新实例(字段逐个复制)。 /// /// 目的: /// - 弹窗编辑时不直接修改页面当前集合,避免用户取消时污染原始配置; /// - 用户确认(OK)后再由弹窗 ViewModel 负责持久化,页面侧随后通过 Reload 刷新。 /// private static CanLinRWConfigDto CloneRwDto(CanLinRWConfigDto src) { return new CanLinRWConfigDto { Id = src.Id, RWInfo = src.RWInfo, Name = src.Name, MsgFrameName = src.MsgFrameName, SignalName = src.SignalName, DefautValue = src.DefautValue, LogicRuleId = src.LogicRuleId, LogicRuleDto = src.LogicRuleDto, }; } /// /// Dialog 服务。 /// public IDialogService DialogService { get; } /// /// FreeSql。 /// public IFreeSql FreeSql { get; } /// /// 事件聚合。 /// public IEventAggregator EventAggregator { get; } /// /// 区域导航。 /// public IRegionManager RegionManager { get; } /// /// 系统运行服务。 /// public SysRunService SysRunService { get; } /// /// 通信互斥控制。 /// public ComActionService ComActionService { get; } /// /// 日志。 /// public ILogService LogService { get; } /// /// 逻辑规则服务。 /// public LogicRuleService LogicRuleService { get; } /// /// 配置服务。 /// public ConfigService ConfigService { get; } /// /// ZLG CAN 服务。 /// public ZlgCanDriveService ZlgCanDriveService { get; } /// /// ZLG LIN 服务(用于互斥判断)。 /// public ZlgLinDriveService ZlgLinDriveService { get; } /// /// Mapper。 /// public IMapper Mapper { get; } /// /// 的 backing 字段。 /// private ObservableCollection _arbBaudRateCbxItems = new ObservableCollection(); /// /// 仲裁波特率下拉项(bps)。 /// public ObservableCollection ArbBaudRateCbxItems { get { return _arbBaudRateCbxItems; } set { _arbBaudRateCbxItems = value; RaisePropertyChanged(); } } /// /// 的 backing 字段。 /// private ObservableCollection _dataBaudRateCbxItems = new ObservableCollection(); /// /// 数据波特率下拉项(bps)。 /// public ObservableCollection DataBaudRateCbxItems { get { return _dataBaudRateCbxItems; } set { _dataBaudRateCbxItems = value; RaisePropertyChanged(); } } /// /// 配置程序列表的本地缓存(数据库读取结果)。 /// /// /// 与 的区别: /// - 本字段用于保存从数据库一次性读取的实体列表; /// - UI 绑定通常使用 (ObservableCollection)。 /// private List _canLinConfigPros = new List(); private ObservableCollection _writeRuleCbxItems = new ObservableCollection(); /// /// 写入规则下拉项(逻辑规则列表)。 /// /// /// 该集合由 初始化,通常绑定到读写设置弹窗或列表编辑处的 ComboBox。 /// public ObservableCollection WriteRuleCbxItems { get { return _writeRuleCbxItems; } set { _writeRuleCbxItems = value; RaisePropertyChanged(); } } private ObservableCollection _listWriteCanLinRWConfigDto = new ObservableCollection(); /// /// 写入配置集合(绑定到写入 DataGrid)。 /// /// /// 每条记录对应一个“写入信号”配置项,包含: /// - MsgFrameName/SignalName:定位 DBC 信号 /// - LogicRuleId:写入规则 /// - DefautValue:默认写入值 /// /// 注意: /// - 配置程序激活( true)时应禁止编辑。 /// - 读写互斥/重复校验主要在读写设置弹窗 ViewModel 中实现。 /// public ObservableCollection ListWriteCanLinRWConfigDto { get { return _listWriteCanLinRWConfigDto; } set { _listWriteCanLinRWConfigDto = value; RaisePropertyChanged(); } } private ObservableCollection _listReadCanLinRWConfigDto = new ObservableCollection(); /// /// 读取配置集合(绑定到读取 DataGrid)。 /// /// /// 每条记录对应一个“读取信号”配置项,包含: /// - MsgFrameName/SignalName:定位 DBC 信号 /// - DefautValue:默认值/初始值(视业务语义而定) /// /// 读取配置通常用于“从接收帧解码后更新 UI/变量”的目标信号集合。 /// public ObservableCollection ListReadCanLinRWConfigDto { get { return _listReadCanLinRWConfigDto; } set { _listReadCanLinRWConfigDto = value; RaisePropertyChanged(); } } /// /// 当前在写入 DataGrid 中选中的行。 /// /// /// 用于 Delete 等操作定位目标记录。该属性不直接通知 UI(由 SelectionChanged 命令更新)。 /// private CanLinRWConfigDto? SelectedWriteCanLinRWConfigDto { get; set; } /// /// 当前在读取 DataGrid 中选中的行。 /// /// /// 用于 Delete 等操作定位目标记录。该属性不直接通知 UI(由 SelectionChanged 命令更新)。 /// private CanLinRWConfigDto? SelectedReadCanLinRWConfigDto { get; set; } private string? _opTip; /// /// 操作提示(用于 UI 状态展示)。 /// public string? OpTip { get { return _opTip; } set { _opTip = value; RaisePropertyChanged(); } } private string? _lastError; /// /// 最近一次错误信息(用于 UI 状态展示)。 /// public string? LastError { get { return _lastError; } set { _lastError = value; RaisePropertyChanged(); } } private bool _isCanConfigProActive; /// /// 当前配置程序是否已激活(对齐图莫斯 Active 语义)。 /// 激活后禁止切换配置程序。 /// public bool IsCanConfigProActive { get { return _isCanConfigProActive; } set { _isCanConfigProActive = value; RaisePropertyChanged(); RaisePropertyChanged(nameof(IsRwEditable)); } } public bool IsRwEditable { get { return !IsCanConfigProActive; } } private bool _isCANConfigDatagridActive = true; /// /// 配置程序 DataGrid 是否可操作(与 IsCanConfigProActive 取反)。 /// public bool IsCANConfigDatagridActive { get { return _isCANConfigDatagridActive; } set { _isCANConfigDatagridActive = value; RaisePropertyChanged(); } } /// /// 的 backing 字段。 /// private ZlgCanMode _selectedMode; /// /// 模式选择:CAN/CANFD(单选)。 /// public ZlgCanMode SelectedMode { get { return _selectedMode; } set { // 打开状态下不允许切换模式,避免驱动状态错乱。 if (ZlgCanDriveService.OpenState && _selectedMode != value) { MessageBox.Show("请先关闭 CAN 后再切换 CAN/CANFD 模式", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); RaisePropertyChanged(nameof(SelectedModeKey)); return; } _selectedMode = value; RaisePropertyChanged(); RaisePropertyChanged(nameof(SelectedModeKey)); RaisePropertyChanged(nameof(IsCanMode)); RaisePropertyChanged(nameof(IsCanFdMode)); RaisePropertyChanged(nameof(DbcPathTitle)); RaisePropertyChanged(nameof(ConnectButtonText)); RaisePropertyChanged(nameof(CloseButtonText)); RaisePropertyChanged(nameof(CurrentDbcPath)); RaisePropertyChanged(nameof(CurrentCycle)); RaisePropertyChanged(nameof(CurrentSchEnable)); RaisePropertyChanged(nameof(CurrentArbBaudRate)); RaisePropertyChanged(nameof(CurrentDataBaudRate)); RaisePropertyChanged(nameof(CurrentISOEnable)); RaisePropertyChanged(nameof(CurrentResEnable)); RaisePropertyChanged(nameof(BaudRateTitle)); RaisePropertyChanged(nameof(DataBaudRateTitle)); ZlgCanDriveService.Mode = value; // 当用户手动切换 CAN/CANFD 时,如果当前配置缺少对应扩展 DTO,需要先生成默认 DTO, // 否则界面控件无法编辑(setter 会因 DTO 为空直接 return)。 if (SelectCanLinConfigPro != null) { if (value == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) { SelectedCANConfigExdDto = new CANConfigExdDto() { Id = 0, BaudRate = 500000, DbcPath = string.Empty, Cycle = 100, SchEnable = false, }; } } else { if (SelectedCANFdConfigExdDto == null) { SelectedCANFdConfigExdDto = new CANFdConfigExdDto() { Id = 0, ArbBaudRate = 500000, DataBaudRate = 2000000, ISOEnable = true, ResEnable = false, DbcPath = string.Empty, Cycle = 100, SchEnable = false, }; } } } // 切换模式后,为避免界面仍显示旧的 DBC 信号列表,清空信号集合。 // 配置程序列表不应随模式切换而过滤/刷新。 ListCanDbcModel = new ObservableCollection(); } } /// /// 写入 DataGrid 选中项变化命令。 /// /// /// 由 XAML 绑定到 SelectionChanged,用于把当前选中行同步到 。 /// private DelegateCommand? _writeGridSelectionChangedCmd; /// /// 写入 DataGrid 选中项变化命令(供 XAML 绑定)。 /// public DelegateCommand WriteGridSelectionChangedCmd { get { if (_writeGridSelectionChangedCmd == null) { _writeGridSelectionChangedCmd = new DelegateCommand(WriteGridSelectionChangedCmdMethod); } return _writeGridSelectionChangedCmd; } } /// /// 写入 DataGrid SelectionChanged 命令处理。 /// /// 选中项(期望为 )。 private void WriteGridSelectionChangedCmdMethod(object par) { if (par is CanLinRWConfigDto dto) { SelectedWriteCanLinRWConfigDto = dto; } } /// /// 读取 DataGrid 选中项变化命令。 /// /// /// 由 XAML 绑定到 SelectionChanged,用于把当前选中行同步到 。 /// private DelegateCommand? _readGridSelectionChangedCmd; /// /// 读取 DataGrid 选中项变化命令(供 XAML 绑定)。 /// public DelegateCommand ReadGridSelectionChangedCmd { get { if (_readGridSelectionChangedCmd == null) { _readGridSelectionChangedCmd = new DelegateCommand(ReadGridSelectionChangedCmdMethod); } return _readGridSelectionChangedCmd; } } /// /// 读取 DataGrid SelectionChanged 命令处理。 /// /// 选中项(期望为 )。 private void ReadGridSelectionChangedCmdMethod(object par) { if (par is CanLinRWConfigDto dto) { SelectedReadCanLinRWConfigDto = dto; } } /// /// 写入配置操作命令(保存/删除)。 /// /// /// 约定: /// - "Edit":把 DataGrid 中当前列表逐条更新到数据库; /// - "Delete":删除当前选中项。 /// private DelegateCommand? _writeCmd; /// /// 写入配置操作命令(供 XAML 绑定)。 /// public DelegateCommand WriteCmd { get { if (_writeCmd == null) { _writeCmd = new DelegateCommand(WriteCmdMethod); } return _writeCmd; } } /// /// 写入配置操作命令处理。 /// /// 操作类型("Edit"/"Delete")。 /// /// 前置条件: /// - 必须已选择配置程序( 不为 null); /// - 若配置已激活( true),禁止修改写入配置。 /// private void WriteCmdMethod(string par) { if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (IsCanConfigProActive) { MessageBox.Show("当前配置已激活,请先取消激活后再修改写入配置", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } switch (par) { case "Edit": foreach (var item in ListWriteCanLinRWConfigDto) { FreeSql.Update(item.Id) .Set(a => a.Name, item.Name) .Set(a => a.LogicRuleId, item.LogicRuleId) .Set(a => a.DefautValue, item.DefautValue) .ExecuteAffrows(); } ReloadCurrentConfigPro(); OpTip = "写入配置已保存"; break; case "Delete": if (SelectedWriteCanLinRWConfigDto == null) { MessageBox.Show("请选中写入列表中的一行后再删除", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } FreeSql.Delete(SelectedWriteCanLinRWConfigDto.Id).ExecuteAffrows(); ReloadCurrentConfigPro(); OpTip = "写入配置已删除"; break; } } /// /// 读取配置操作命令(保存/删除)。 /// /// /// 约定: /// - "Edit":把 DataGrid 中当前列表逐条更新到数据库; /// - "Delete":删除当前选中项。 /// private DelegateCommand? _readCmd; /// /// 读取配置操作命令(供 XAML 绑定)。 /// public DelegateCommand ReadCmd { get { if (_readCmd == null) { _readCmd = new DelegateCommand(ReadCmdMethod); } return _readCmd; } } /// /// 读取配置操作命令处理。 /// /// 操作类型("Edit"/"Delete")。 /// /// 前置条件: /// - 必须已选择配置程序( 不为 null); /// - 若配置已激活( true),禁止修改读取配置。 /// private void ReadCmdMethod(string par) { if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (IsCanConfigProActive) { MessageBox.Show("当前配置已激活,请先取消激活后再修改读取配置", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } switch (par) { case "Edit": foreach (var item in ListReadCanLinRWConfigDto) { FreeSql.Update(item.Id) .Set(a => a.Name, item.Name) .Set(a => a.DefautValue, item.DefautValue) .ExecuteAffrows(); } ReloadCurrentConfigPro(); OpTip = "读取配置已保存"; break; case "Delete": if (SelectedReadCanLinRWConfigDto == null) { MessageBox.Show("请选中读取列表中的一行后再删除", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } FreeSql.Delete(SelectedReadCanLinRWConfigDto.Id).ExecuteAffrows(); ReloadCurrentConfigPro(); OpTip = "读取配置已删除"; break; } } /// /// 连接按钮文字(对齐 Toomoss 风格)。 /// public string ConnectButtonText { get { return SelectedMode == ZlgCanMode.Can ? "连接CAN" : "连接CANFD"; } } /// /// 当前是否为 CANFD 模式(用于 UI 启用/禁用某些仅 CANFD 支持的参数)。 /// public bool IsCanFdMode { get { return SelectedMode == ZlgCanMode.CanFd; } } /// /// 当前是否为 CAN 模式(用于 UI 启用/禁用某些仅 CANFD 支持的参数)。 /// public bool IsCanMode { get { return SelectedMode == ZlgCanMode.Can; } } /// /// 波特率标题:CAN 为“波特率”,CANFD 为“仲裁波特率”。 /// public string BaudRateTitle { get { return SelectedMode == ZlgCanMode.Can ? "波特率" : "仲裁波特率"; } } /// /// 数据波特率标题:仅 CANFD 有意义。 /// public string DataBaudRateTitle { get { return "数据波特率"; } } /// /// 关闭按钮文字(对齐 Toomoss 风格)。 /// public string CloseButtonText { get { return SelectedMode == ZlgCanMode.Can ? "关闭CAN" : "关闭CANFD"; } } /// /// 绑定用:模式 Key(Can/CanFd)。 /// public string SelectedModeKey { get { return SelectedMode == ZlgCanMode.Can ? "Can" : "CanFd"; } set { if (ZlgCanDriveService.OpenState) { MessageBox.Show("请先关闭 CAN 后再切换 CAN/CANFD 模式", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); RaisePropertyChanged(); return; } if (string.Equals(value, "Can", StringComparison.OrdinalIgnoreCase)) { SelectedMode = ZlgCanMode.Can; return; } if (string.Equals(value, "CanFd", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "CanFD", StringComparison.OrdinalIgnoreCase)) { SelectedMode = ZlgCanMode.CanFd; return; } } } /// /// DBC 路径标题。 /// public string DbcPathTitle { get { return SelectedMode == ZlgCanMode.Can ? "CAN-DBC文件路径:" : "CANFD-DBC文件路径:"; } } /// /// 的 backing 字段。 /// private ObservableCollection? _listCanLinConfigPro; /// /// 配置程序集合。 /// public ObservableCollection? ListCanLinConfigPro { get { return _listCanLinConfigPro; } set { _listCanLinConfigPro = value; RaisePropertyChanged(); } } /// /// 选中的配置程序。 /// private CanLinConfigPro? _selectCanLinConfigPro; /// /// 选中的配置程序。 /// public CanLinConfigPro? SelectCanLinConfigPro { get { return _selectCanLinConfigPro; } set { _selectCanLinConfigPro = value; RaisePropertyChanged(); } } private ObservableCollection? _listCanDbcModel; /// /// DBC 信号集合。 /// public ObservableCollection? ListCanDbcModel { get { return _listCanDbcModel; } set { _listCanDbcModel = value; RaisePropertyChanged(); } } /// /// 当前选中的 DBC 信号。 /// public CanDbcModel? SelectedCanDbcModel { get { return _selectedCanDbcModel; } set { _selectedCanDbcModel = value; RaisePropertyChanged(); } } private CanDbcModel? _selectedCanDbcModel; /// /// 的 backing 字段。 /// private CANConfigExdDto? _selectedCANConfigExdDto; /// /// CAN 配置 DTO。 /// public CANConfigExdDto? SelectedCANConfigExdDto { get { return _selectedCANConfigExdDto; } set { _selectedCANConfigExdDto = value; RaisePropertyChanged(); } } /// /// 的 backing 字段。 /// private CANFdConfigExdDto? _selectedCANFdConfigExdDto; /// /// CANFD 配置 DTO。 /// public CANFdConfigExdDto? SelectedCANFdConfigExdDto { get { return _selectedCANFdConfigExdDto; } set { _selectedCANFdConfigExdDto = value; RaisePropertyChanged(); } } /// /// 从数据库加载 CAN/CANFD 配置程序列表。 /// /// /// 加载内容包含: /// - 主表 /// - 扩展配置(CAN/CANFD) /// - 读写配置项(包含逻辑规则) /// - 调度表配置项(CAN/CANFD) /// /// 该方法会刷新本地缓存 与 UI 绑定集合 。 /// private void InitLoadCanConfigPro() { _canLinConfigPros = FreeSql.Select() .Where(a => a.CANLINInfo == CANLIN.CAN || a.CANLINInfo == CANLIN.CANFD) .Include(a => a.CANConfigExd) .Include(a => a.CANFdConfigExd) .IncludeMany(a => a.CanLinConfigContents, then => then.Include(b => b.LogicRule)) .IncludeMany(a => a.CanScheduleConfigs) .IncludeMany(a => a.CanFdScheduleConfigs) .ToList(); ListCanLinConfigPro = new ObservableCollection(_canLinConfigPros); } /// /// 同步选中的配置。 /// /// /// 当 UI 选中不同配置程序时,本方法会同步以下内容到 ViewModel: /// - 根据配置程序的 CANLINInfo 自动切换 (避免右侧参数/DBC 显示与配置类型不一致); /// - 映射 CAN/CANFD 两套扩展 DTO(若不存在则为 null/默认); /// - 从配置内容构建读写配置 DTO 列表; /// - 根据模式加载对应调度表集合; /// - 构建并注入 CmdData 到 (事件驱动发送依赖); /// - 最后重置并尝试加载当前配置的 DBC 信号集合(未连接时也会尝试解析)。 /// /// 注意: /// - 已连接或已激活时,外层命令会禁止切换配置程序;该方法通常只在允许切换时被调用。 /// private void SyncSelectedConfig() { if (SelectCanLinConfigPro == null) { return; } // 选中配置程序后,模式应跟随该配置自身的 CANLINInfo,避免“列表全量但右侧模式不匹配”。 var proMode = SelectCanLinConfigPro.CANLINInfo == CANLIN.CAN ? ZlgCanMode.Can : ZlgCanMode.CanFd; if (SelectedMode != proMode) { SelectedMode = proMode; } // 同时映射两套配置(如不存在则为 null),这样用户可以在 UI 上切换模式后再保存。 SelectedCANConfigExdDto = SelectCanLinConfigPro.CANConfigExd != null ? Mapper.Map(SelectCanLinConfigPro.CANConfigExd) : null; SelectedCANFdConfigExdDto = SelectCanLinConfigPro.CANFdConfigExd != null ? Mapper.Map(SelectCanLinConfigPro.CANFdConfigExd) : null; RaisePropertyChanged(nameof(CurrentDbcPath)); RaisePropertyChanged(nameof(CurrentCycle)); RaisePropertyChanged(nameof(CurrentSchEnable)); RaisePropertyChanged(nameof(CurrentArbBaudRate)); RaisePropertyChanged(nameof(CurrentDataBaudRate)); RaisePropertyChanged(nameof(CurrentISOEnable)); RaisePropertyChanged(nameof(CurrentResEnable)); RaisePropertyChanged(nameof(CanBaudRate)); RaisePropertyChanged(nameof(CanFdArbBaudRate)); RaisePropertyChanged(nameof(CanFdDataBaudRate)); BuildAndLoadCmdDataToDrive(); var writeData = SelectCanLinConfigPro.CanLinConfigContents?.Where(a => a.RWInfo == RW.Write).ToList() ?? new List(); ListWriteCanLinRWConfigDto = new ObservableCollection(Mapper.Map>(writeData)); var readData = SelectCanLinConfigPro.CanLinConfigContents?.Where(a => a.RWInfo == RW.Read).ToList() ?? new List(); ListReadCanLinRWConfigDto = new ObservableCollection(Mapper.Map>(readData)); // 调度表(按模式) if (SelectedMode == ZlgCanMode.Can) { if (SelectCanLinConfigPro.CanScheduleConfigs != null && SelectCanLinConfigPro.CanScheduleConfigs.Count > 0) { ListCANScheduleConfigDto = new ObservableCollection(Mapper.Map>(SelectCanLinConfigPro.CanScheduleConfigs)); } else { ListCANScheduleConfigDto = new ObservableCollection(); } RaisePropertyChanged(nameof(ListCANScheduleConfigDto)); } else { if (SelectCanLinConfigPro.CanFdScheduleConfigs != null && SelectCanLinConfigPro.CanFdScheduleConfigs.Count > 0) { ListCANFdScheduleConfigDto = new ObservableCollection(Mapper.Map>(SelectCanLinConfigPro.CanFdScheduleConfigs)); } else { ListCANFdScheduleConfigDto = new ObservableCollection(); } RaisePropertyChanged(nameof(ListCANFdScheduleConfigDto)); } ResetAndTryLoadDbcSignalsForSelectedConfig(); } /// /// 切换配置程序时重置并尝试加载当前配置对应的 DBC 信号集合。 /// /// /// 背景:当“未连接设备”时,如果切换配置程序但不清空信号集合,UI 会残留上一次配置的信号列表。 /// /// 行为: /// - 先清空 ; /// - 未连接时: /// - 若当前配置未设置 DBC 路径,则信号集合置空; /// - 若有 DBC 路径,则调用 解析并加载信号集合; /// - 已连接时:理论上禁止切换配置程序;这里做保底清空,避免 UI 残留。 /// private void ResetAndTryLoadDbcSignalsForSelectedConfig() { SelectedCanDbcModel = null; // 未连接时:信号集合应以当前配置的 DBC 为准(或为空),禁止沿用上一个配置的信号列表。 if (!ZlgCanDriveService.OpenState) { if (string.IsNullOrWhiteSpace(CurrentDbcPath)) { ListCanDbcModel = new ObservableCollection(); return; } try { ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath); if (SelectCanLinConfigPro != null) { ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro); } MatchSeletedAndCanDbcModel(); } catch (Exception ex) { ListCanDbcModel = new ObservableCollection(); OpTip = "DBC 解析失败"; LogService.Warn($"切换配置程序时解析 DBC 失败:{CurrentDbcPath},{ex.Message}"); } return; } // 已连接时:禁止切换配置程序。 // 注意:此处不应清空信号集合,否则在“保存读写设置/刷新配置”这类操作中会导致信号表瞬间变空。 return; } /// /// 的 backing 字段。 /// private ObservableCollection _listCANScheduleConfigDto = new ObservableCollection(); /// /// CAN 调度表集合。 /// public ObservableCollection ListCANScheduleConfigDto { get { return _listCANScheduleConfigDto; } set { _listCANScheduleConfigDto = value; RaisePropertyChanged(); } } /// /// 的 backing 字段。 /// private ObservableCollection _listCANFdScheduleConfigDto = new ObservableCollection(); /// /// CANFD 调度表集合。 /// public ObservableCollection ListCANFdScheduleConfigDto { get { return _listCANFdScheduleConfigDto; } set { _listCANFdScheduleConfigDto = value; RaisePropertyChanged(); } } /// /// 当前仲裁波特率(对 CAN:等同 BaudRate;对 CANFD:ArbBaudRate)。 /// public int CurrentArbBaudRate { get { return SelectedMode == ZlgCanMode.Can ? (SelectedCANConfigExdDto?.BaudRate ?? 0) : (SelectedCANFdConfigExdDto?.ArbBaudRate ?? 0); } set { if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) return; SelectedCANConfigExdDto.BaudRate = value; } else { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.ArbBaudRate = value; } RaisePropertyChanged(); RaisePropertyChanged(nameof(CanBaudRate)); RaisePropertyChanged(nameof(CanFdArbBaudRate)); } } /// /// 当前数据波特率(对 CAN:等同 BaudRate;对 CANFD:DataBaudRate)。 /// public int CurrentDataBaudRate { get { return SelectedMode == ZlgCanMode.Can ? (SelectedCANConfigExdDto?.BaudRate ?? 0) : (SelectedCANFdConfigExdDto?.DataBaudRate ?? 0); } set { if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) return; SelectedCANConfigExdDto.BaudRate = value; } else { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.DataBaudRate = value; } RaisePropertyChanged(); RaisePropertyChanged(nameof(CanBaudRate)); RaisePropertyChanged(nameof(CanFdDataBaudRate)); } } /// /// 当前 ISO 标准使能(仅 CANFD 有意义;CAN 固定为 false)。 /// public bool CurrentISOEnable { get { return SelectedMode == ZlgCanMode.Can ? false : (SelectedCANFdConfigExdDto?.ISOEnable ?? false); } set { if (SelectedMode == ZlgCanMode.Can) { return; } if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.ISOEnable = value; RaisePropertyChanged(); } } /// /// 当前终端电阻使能(仅 CANFD 有意义;CAN 固定为 true(驱动侧可忽略))。 /// public bool CurrentResEnable { get { return SelectedMode == ZlgCanMode.Can ? true : (SelectedCANFdConfigExdDto?.ResEnable ?? false); } set { if (SelectedMode == ZlgCanMode.Can) { return; } if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.ResEnable = value; RaisePropertyChanged(); } } /// /// 构建并加载命令数据到驱动。 /// /// /// 数据流: /// - 从当前配置程序的写入项(RW=Write)构建 列表; /// - 每条写入项会携带 MsgName/SignalName/默认值/逻辑规则; /// - 最终调用 注入驱动。 /// /// 作用: /// - 事件驱动发送:信号值变化可触发增量发送/覆盖 auto_send; /// - 调度表/循环发送:编码发送依赖当前 CmdData 中的信号值。 /// private void BuildAndLoadCmdDataToDrive() { try { if (SelectCanLinConfigPro?.CanLinConfigContents == null) { // 防御性清空: // - 当前未选择配置程序或无配置内容时,服务层应收到空 CmdData,避免沿用旧配置继续触发事件驱动发送。 ZlgCanDriveService.LoadCmdDataToDrive(new List()); return; } var writeItems = SelectCanLinConfigPro.CanLinConfigContents .Where(a => a.RWInfo == RW.Write) .ToList(); var cmdList = new List(); foreach (var item in writeItems) { // 构建 CanCmdData: // - MsgName/SignalName 决定 DBC 编码定位; // - 默认值 DefautValue 作为 SignalCmdValue 初始值,确保首次启动循环发送时有可编码数据; // - LogicRule 透传到发送链路(驱动侧在事件触发时可能按规则决定是否发送/如何发送)。 cmdList.Add(new CanCmdData() { ConfigName = item.Name, MsgName = item.MsgFrameName, SignalName = item.SignalName, SignalCmdValue = double.TryParse(item.DefautValue, out double result) ? result : 0, LogicRuleDto = Mapper.Map(item.LogicRule), }); } // 统一入口:将 CmdData 注入 Service/Driver,建立事件驱动增量发送能力。 ZlgCanDriveService.LoadCmdDataToDrive(cmdList); } catch (Exception ex) { LogService.Warn($"ZLG CAN 构建/下发 CmdData 失败:{ex.Message}"); } } /// /// 当前 DBC 路径(随模式切换映射到对应 DTO)。 /// public string? CurrentDbcPath { get { return SelectedMode == ZlgCanMode.Can ? SelectedCANConfigExdDto?.DbcPath : SelectedCANFdConfigExdDto?.DbcPath; } set { if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) return; SelectedCANConfigExdDto.DbcPath = value; } else { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.DbcPath = value; } RaisePropertyChanged(); } } /// /// 当前周期(随模式切换映射到对应 DTO)。 /// public int CurrentCycle { get { return SelectedMode == ZlgCanMode.Can ? (SelectedCANConfigExdDto?.Cycle ?? 0) : (SelectedCANFdConfigExdDto?.Cycle ?? 0); } set { if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) return; SelectedCANConfigExdDto.Cycle = value; } else { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.Cycle = value; } RaisePropertyChanged(); } } /// /// 当前调度使能(随模式切换映射到对应 DTO)。 /// public bool CurrentSchEnable { get { return SelectedMode == ZlgCanMode.Can ? (SelectedCANConfigExdDto?.SchEnable ?? false) : (SelectedCANFdConfigExdDto?.SchEnable ?? false); } set { // 说明: // - 该属性是“配置程序的持久化字段”(DTO/DB)的映射; // - 它本身不一定立即下发到驱动(驱动下发由 SchEnableCmdCall 或 CycleSend 开启前的同步逻辑完成)。 if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) return; SelectedCANConfigExdDto.SchEnable = value; } else { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.SchEnable = value; } RaisePropertyChanged(); } } /// /// CAN 波特率。 /// public int CanBaudRate { get { return SelectedCANConfigExdDto?.BaudRate ?? 0; } set { if (SelectedCANConfigExdDto == null) return; SelectedCANConfigExdDto.BaudRate = value; RaisePropertyChanged(); } } /// /// CANFD 仲裁波特率。 /// public int CanFdArbBaudRate { get { return SelectedCANFdConfigExdDto?.ArbBaudRate ?? 0; } set { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.ArbBaudRate = value; RaisePropertyChanged(); } } /// /// CANFD 数据波特率。 /// public int CanFdDataBaudRate { get { return SelectedCANFdConfigExdDto?.DataBaudRate ?? 0; } set { if (SelectedCANFdConfigExdDto == null) return; SelectedCANFdConfigExdDto.DataBaudRate = value; RaisePropertyChanged(); } } /// /// 匹配选中的配置和 DBC 模型。 /// /// /// 用途:对信号集合做“是否已被配置为写入/读取”的标记,便于 UI 颜色或状态提示。 /// - RW.Write 记为 1 /// - RW.Read 记为 2 /// - 未配置记为 0 /// /// 该标记依赖 。 /// private void MatchSeletedAndCanDbcModel() { if (ListCanDbcModel == null || ListCanDbcModel.Count == 0) return; if (SelectCanLinConfigPro?.CanLinConfigContents == null || SelectCanLinConfigPro.CanLinConfigContents.Count == 0) return; foreach (var itemCanDbcModel in ListCanDbcModel) { var findData = SelectCanLinConfigPro.CanLinConfigContents.FindFirst(a => a.SignalName == itemCanDbcModel.SignalName); if (findData != null) { switch (findData.RWInfo) { case RW.Write: itemCanDbcModel.IsSeletedInfo = 1; break; case RW.Read: itemCanDbcModel.IsSeletedInfo = 2; break; default: itemCanDbcModel.IsSeletedInfo = 0; break; } } else { itemCanDbcModel.IsSeletedInfo = 0; } } } /// /// 配置程序 DataGrid 选中项变化命令的 backing 字段。 /// private DelegateCommand? _canConfigProGridSelectionChangedCmd; /// /// 配置程序选中变化。 /// /// /// - 打开/激活状态下禁止切换配置程序; /// - 允许切换时会更新 并调用 。 /// public DelegateCommand CanConfigProGridSelectionChangedCmd { get { if (_canConfigProGridSelectionChangedCmd == null) { _canConfigProGridSelectionChangedCmd = new DelegateCommand(CanConfigProGridSelectionChangedCmdMethod); } return _canConfigProGridSelectionChangedCmd; } } /// /// 配置程序 DataGrid SelectionChanged 命令处理。 /// /// /// 可能为: /// - (直接传入选中项); /// - (WPF SelectionChanged 参数)。 /// /// /// WPF 的 SelectionChanged 可能被重复触发/不同类型参数混用,因此这里做了多种分支兼容。 /// private void CanConfigProGridSelectionChangedCmdMethod(object par) { if (par == null) return; if (ZlgCanDriveService.OpenState) { // 打开状态下禁止切换配置程序。 return; } if (IsCanConfigProActive) { // 激活状态下禁止切换配置程序。 return; } if (par is SelectionChangedEventArgs) return; if (par is CanLinConfigPro) { SelectCanLinConfigPro = par as CanLinConfigPro; SyncSelectedConfig(); return; } var args = par as SelectionChangedEventArgs; if (args == null || args.AddedItems == null || args.AddedItems.Count == 0) return; var selected = args.AddedItems[0] as CanLinConfigPro; if (selected == null) return; SelectCanLinConfigPro = selected; SyncSelectedConfig(); } /// /// 配置程序切换前拦截命令的 backing 字段。 /// private DelegateCommand? _canConfigProGridPreviewMouseLeftButtonDownCmd; /// /// 配置程序切换前拦截(对齐图莫斯 PreviewMouseLeftButtonDown)。 /// 打开/激活状态下禁止切换,防止状态错乱。 /// public DelegateCommand CanConfigProGridPreviewMouseLeftButtonDownCmd { get { if (_canConfigProGridPreviewMouseLeftButtonDownCmd == null) { _canConfigProGridPreviewMouseLeftButtonDownCmd = new DelegateCommand(CanConfigProGridPreviewMouseLeftButtonDownCmdMethod); } return _canConfigProGridPreviewMouseLeftButtonDownCmd; } } /// /// 配置程序切换前拦截命令处理。 /// /// 鼠标事件参数(可能为 )。 /// /// 若拦截命中,会将 MouseButtonEventArgs.Handled 置为 true,阻止 DataGrid 继续改变选中项。 /// private void CanConfigProGridPreviewMouseLeftButtonDownCmdMethod(object par) { try { if (ZlgCanDriveService.OpenState) { MessageBox.Show("CAN 已连接,请先关闭后再切换配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); if (par is System.Windows.Input.MouseButtonEventArgs e) e.Handled = true; return; } if (IsCanConfigProActive) { MessageBox.Show("当前配置程序已激活,请先取消激活后再切换", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); if (par is System.Windows.Input.MouseButtonEventArgs e) e.Handled = true; return; } } catch (Exception ex) { LogService.Warn($"配置切换拦截异常:{ex.Message}"); } } /// /// 选择 DBC 文件命令的 backing 字段。 /// private DelegateCommand? _loadDbcCmd; /// /// 选择 DBC 文件。 /// /// /// 该命令只负责让用户选择文件并写回 , /// 不会自动解析;解析由 CAN 操作命令中的 "Parse" 或连接流程中的自动解析触发。 /// public DelegateCommand LoadDbcCmd { get { if (_loadDbcCmd == null) { _loadDbcCmd = new DelegateCommand(LoadDbcCmdMethod); } return _loadDbcCmd; } } /// /// 选择 DBC 文件命令处理。 /// /// /// 前置条件:必须已选择配置程序()。 /// /// 选择成功后会更新 ;用户取消则保持原值。 /// private void LoadDbcCmdMethod() { try { if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } OpenFileDialog openFileDialogInfo = new OpenFileDialog(); openFileDialogInfo.Filter = "(*.dbc;*.dbc)|*.dbc;*.dbc|all|*.*"; openFileDialogInfo.CheckFileExists = true; openFileDialogInfo.CheckPathExists = true; var dialogResult = openFileDialogInfo.ShowDialog(); if (dialogResult != true) { // 用户取消:保持原值,不清空 CurrentDbcPath return; } var fileName = openFileDialogInfo.FileName; if (string.IsNullOrWhiteSpace(fileName)) { return; } if (!File.Exists(fileName)) { MessageBox.Show("文件不存在,请重新选择", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } CurrentDbcPath = fileName; } catch (Exception ex) { LogService.Error($"选择 DBC 文件失败:{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 调度使能下发命令的 backing 字段。 /// private DelegateCommand? _schEnableCmd; /// /// 调度使能写入驱动。 /// /// /// 该命令用于把 UI 的 立即同步到 。 /// - SchEnable 影响事件驱动发送与调度表/auto_send 相关行为。 /// public DelegateCommand SchEnableCmd { get { if (_schEnableCmd == null) { _schEnableCmd = new DelegateCommand(SchEnableCmdCall); } return _schEnableCmd; } } /// /// 调度使能同步到驱动。 /// /// 未使用(与 XAML CommandParameter 兼容)。 private void SchEnableCmdCall(object par) { // 与旧 Toomoss 行为对齐:UI 勾选后立即下发到驱动 // 说明: // - SchEnable 控制“事件驱动发送/覆盖更新 auto_send”的总开关; // - 仅修改 CurrentSchEnable(DTO)不会影响驱动,必须执行该命令(或在 CycleSend 开启前同步)。 ZlgCanDriveService.SchEnable = CurrentSchEnable; } /// /// 配置程序操作命令的 backing 字段。 /// private DelegateCommand? _canLinConfigProCmd; /// /// 配置程序操作(新建/修改/删除)。 /// /// /// 约定: /// - "Add":新建配置程序(按当前 创建 CAN 或 CANFD 扩展表记录); /// - "Edit":修改配置程序名称; /// - "Delete":删除配置程序及其内容(包含读写配置、调度表、扩展表); /// - "Active":激活/取消激活当前配置程序(激活后禁止切换配置程序)。 /// /// 激活语义: /// - 激活要求设备已连接且 DBC 已解析成功; /// - 激活后会把当前配置下发到服务层(CmdData + 调度表),用于循环发送/硬件调度表等功能。 /// public DelegateCommand CanLinConfigProCmd { get { if (_canLinConfigProCmd == null) { _canLinConfigProCmd = new DelegateCommand(CanLinConfigProCmdMethod); } return _canLinConfigProCmd; } } /// /// 配置程序操作命令处理。 /// /// 操作类型(Add/Edit/Delete/Active)。 /// /// 约束说明: /// - 打开 CAN()或已激活()时, /// 禁止新建/修改/删除与切换配置程序,避免“驱动正在运行但底层配置被替换”导致状态错乱。 /// - Delete 会同时清理 CAN/CANFD 两套扩展配置(如果都存在),避免孤儿记录残留。 /// private void CanLinConfigProCmdMethod(string par) { if (string.IsNullOrWhiteSpace(par)) return; try { switch (par) { case "Add": if (IsCanConfigProActive) { MessageBox.Show("当前配置已激活,请先取消激活后再新建/修改配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (ZlgCanDriveService.OpenState) { MessageBox.Show("请先关闭 CAN 后再新建配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } DialogService.ShowDialog("DialogCanLinConfigCreateView", new DialogParameters() { { "Name", "" } }, r => { if (r.Result != ButtonResult.OK) return; try { var name = r.Parameters.GetValue("Name")?.Trim(); if (string.IsNullOrWhiteSpace(name)) { MessageBox.Show("名称不能为空", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } var info = SelectedMode == ZlgCanMode.Can ? CANLIN.CAN : CANLIN.CANFD; var exists = FreeSql.Select() .Where(a => a.CANLINInfo == info) .Where(a => a.ConfigName == name) .Any(); if (exists) { MessageBox.Show("名称已存在,请更换名称", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } long newProId = 0; FreeSql.Transaction(() => { if (SelectedMode == ZlgCanMode.Can) { var exdList = FreeSql.Insert(new CANConfigExd() { BaudRate = 500000, DbcPath = string.Empty, Cycle = 100, SchEnable = false, }).ExecuteInserted(); var exd = exdList?.FirstOrDefault(); if (exd == null) { throw new InvalidOperationException("创建 CAN 扩展配置失败"); } var proList = FreeSql.Insert(new CanLinConfigPro() { ConfigName = name, CANLINInfo = CANLIN.CAN, CANConfigExdId = exd.Id, }).ExecuteInserted(); var pro = proList?.FirstOrDefault(); if (pro == null) { throw new InvalidOperationException("创建 CAN 配置程序失败"); } newProId = pro.Id; } else { var exdList = FreeSql.Insert(new CANFdConfigExd() { ArbBaudRate = 500000, DataBaudRate = 2000000, ISOEnable = true, ResEnable = false, DbcPath = string.Empty, Cycle = 100, SchEnable = false, }).ExecuteInserted(); var exd = exdList?.FirstOrDefault(); if (exd == null) { throw new InvalidOperationException("创建 CANFD 扩展配置失败"); } var proList = FreeSql.Insert(new CanLinConfigPro() { ConfigName = name, CANLINInfo = CANLIN.CANFD, CANFdConfigExdId = exd.Id, }).ExecuteInserted(); var pro = proList?.FirstOrDefault(); if (pro == null) { throw new InvalidOperationException("创建 CANFD 配置程序失败"); } newProId = pro.Id; } }); InitLoadCanConfigPro(); SelectCanLinConfigPro = _canLinConfigPros.Find(a => a.Id == newProId); SyncSelectedConfig(); MatchSeletedAndCanDbcModel(); } catch (Exception ex) { LogService.Error($"ZLG 新建配置程序失败:{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } }); break; case "Edit": if (SelectCanLinConfigPro == null) { MessageBox.Show("选中后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (IsCanConfigProActive) { MessageBox.Show("当前配置已激活,请先取消激活后再修改配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (ZlgCanDriveService.OpenState) { MessageBox.Show("请先关闭 CAN 后再修改配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } DialogService.ShowDialog("DialogCanLinConfigCreateView", new DialogParameters() { { "Name", SelectCanLinConfigPro.ConfigName } }, r => { if (r.Result != ButtonResult.OK) return; try { var name = r.Parameters.GetValue("Name")?.Trim(); if (string.IsNullOrWhiteSpace(name)) { MessageBox.Show("名称不能为空", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } var info = SelectedMode == ZlgCanMode.Can ? CANLIN.CAN : CANLIN.CANFD; var exists = FreeSql.Select() .Where(a => a.CANLINInfo == info) .Where(a => a.ConfigName == name) .Where(a => a.Id != SelectCanLinConfigPro.Id) .Any(); if (exists) { MessageBox.Show("名称已存在,请更换名称", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } FreeSql.Update() .Set(a => a.ConfigName, name) .Where(a => a.Id == SelectCanLinConfigPro.Id) .ExecuteAffrows(); ReloadCurrentConfigPro(); } catch (Exception ex) { LogService.Error($"ZLG 修改配置程序失败:{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } }); break; case "Delete": if (SelectCanLinConfigPro == null) { MessageBox.Show("选中后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (IsCanConfigProActive) { MessageBox.Show("当前配置已激活,请先取消激活后再删除配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (ZlgCanDriveService.OpenState) { MessageBox.Show("请先关闭 CAN 后再删除配置程序", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (MessageBox.Show($"确定删除配置程序:{SelectCanLinConfigPro.ConfigName}?", "确认", MessageBoxButton.OKCancel, MessageBoxImage.Warning) != MessageBoxResult.OK) { return; } // 删除主表与内容 var repo = FreeSql.GetRepository(); repo.DbContextOptions.EnableCascadeSave = true; var delList = repo.Select .Include(a => a.CANConfigExd) .Include(a => a.CANFdConfigExd) .IncludeMany(a => a.CanLinConfigContents) .IncludeMany(a => a.CanScheduleConfigs) .IncludeMany(a => a.CanFdScheduleConfigs) .Where(a => a.Id == SelectCanLinConfigPro.Id) .ToList(); repo.Delete(delList); foreach (var item in delList) { // 允许配置在 CAN/CANFD 间切换保存后同时具备两套扩展表,此处统一清理避免残留孤儿记录。 if (item.CANConfigExdId > 0) { FreeSql.Delete(item.CANConfigExdId).ExecuteAffrows(); } if (item.CANFdConfigExdId > 0) { FreeSql.Delete(item.CANFdConfigExdId).ExecuteAffrows(); } } SelectCanLinConfigPro = null; SelectedCANConfigExdDto = null; SelectedCANFdConfigExdDto = null; InitLoadCanConfigPro(); ListCanDbcModel = new ObservableCollection(); break; case "Active": // 激活到取消的状态的判断 if (IsCanConfigProActive) { IsCanConfigProActive = false; IsCANConfigDatagridActive = true; // 停止调度/发送,避免“取消激活但仍在发送” ZlgCanDriveService.StopSchedule(); ZlgCanDriveService.IsCycleSend = false; ZlgCanDriveService.LoadCmdDataToDrive(new List()); OpTip = "已取消激活"; return; } if (!ZlgCanDriveService.OpenState) { MessageBox.Show("请确保 CAN 已连接后再激活", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (SelectCanLinConfigPro == null) { MessageBox.Show("选中后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (string.IsNullOrWhiteSpace(CurrentDbcPath)) { MessageBox.Show("请先选择并解析 DBC 文件后再激活", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } // 激活前强制按“当前配置的 DBC 路径”重新解析,避免 DbcParserState 来自其它配置导致错配。 try { ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath); MatchSeletedAndCanDbcModel(); } catch (Exception ex) { LogService.Error($"ZLG 激活前解析 DBC 失败:{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); return; } IsCanConfigProActive = true; IsCANConfigDatagridActive = false; // 当前使用的配置程序 ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro); // 下发指令集合与调度表 BuildAndLoadCmdDataToDrive(); if (SelectedMode == ZlgCanMode.Can) { ZlgCanDriveService.SetScheduleConfigs(ListCANScheduleConfigDto?.ToList() ?? new List()); } else { ZlgCanDriveService.SetScheduleConfigs(ListCANFdScheduleConfigDto?.ToList() ?? new List()); } OpTip = "已激活当前配置"; break; } } catch (Exception ex) { LogService.Error($"ZLG 配置程序操作失败:{par},{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 调度表配置弹窗命令的 backing 字段。 /// private DelegateCommand? _scheduleConfigCmd; /// /// 调度表配置(弹窗)。 /// /// /// 打开调度表弹窗时,会从 中提取 MsgName 列表作为候选消息帧。 /// - CAN 模式:打开 DialogCANSchConfigView /// - CANFD 模式:打开 DialogCANFdSchConfigView /// /// 弹窗确认后会把返回的调度表 DTO 写回当前配置程序实体,并调用 ReloadCurrentConfigPro 刷新。 /// public DelegateCommand ScheduleConfigCmd { get { if (_scheduleConfigCmd == null) { _scheduleConfigCmd = new DelegateCommand(ScheduleConfigCmdMethod); } return _scheduleConfigCmd; } } /// /// 调度表配置弹窗命令处理。 /// /// /// 前置条件: /// - 必须选中配置程序; /// - 必须存在可发送消息(CmdData 非空且 MsgName 有效),否则无法配置调度表。 /// /// 注意: /// - 调度表本身并不直接发送;真正启动发送由 CAN 操作命令中的 "CycleSend" 触发。 /// private void ScheduleConfigCmdMethod() { try { if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } // 调度表弹窗需要 MsgName 候选列表: // - 来源于当前已下发的 CmdData(写入项聚合后的消息名); // - 这样能保证“可配置的调度表项”与“实际会被编码发送的消息帧”一致。 var msgList = ZlgCanDriveService.CmdData.GroupBy(a => a.MsgName).Select(a => a.Key).Where(a => !string.IsNullOrWhiteSpace(a)).ToList(); if ((msgList == null || msgList.Count == 0) && (ListCANScheduleConfigDto == null || ListCANScheduleConfigDto.Count == 0)) { MessageBox.Show("未发现写入指令数据(CmdData 为空),无法配置调度表", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (SelectedMode == ZlgCanMode.Can) { // CAN 调度表弹窗: // - 入参包含当前调度表 DTO(用于回显) // - 确认后返回新的调度表 DTO 集合并回写到配置程序实体 DialogService.ShowDialog(nameof(DialogZlgCANSchConfigView), new DialogParameters() { { "ListMsg", msgList }, { "ListCANScheduleConfigDto", ListCANScheduleConfigDto }, { "SelectCanLinConfigProId", SelectCanLinConfigPro.Id }, }, r => { if (r.Result != ButtonResult.OK) return; ListCANScheduleConfigDto = r.Parameters.GetValue>("ReturnValue") ?? new ObservableCollection(); // DTO -> 实体:用于持久化(保存到数据库),并在后续激活/循环发送前由 SyncSelectedConfig 再同步到 Service。 SelectCanLinConfigPro.CanScheduleConfigs = Mapper.Map>(ListCANScheduleConfigDto.ToList()); ReloadCurrentConfigPro(); }); } else { // CANFD 调度表弹窗:语义同 CAN。 DialogService.ShowDialog(nameof(DialogZlgCANFDSchConfigView), new DialogParameters() { { "ListMsg", msgList }, { "ListCANFdScheduleConfigDto", ListCANFdScheduleConfigDto }, { "SelectCanLinConfigProId", SelectCanLinConfigPro.Id }, }, r => { if (r.Result != ButtonResult.OK) return; ListCANFdScheduleConfigDto = r.Parameters.GetValue>("ReturnValue") ?? new ObservableCollection(); SelectCanLinConfigPro.CanFdScheduleConfigs = Mapper.Map>(ListCANFdScheduleConfigDto.ToList()); ReloadCurrentConfigPro(); }); } } catch (Exception ex) { LogService.Error($"调度表配置失败:{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 读写配置操作命令的 backing 字段。 /// private DelegateCommand? _writeReadConfigCmd; /// /// 写入/读取/删除 信号配置项。 /// /// /// 常见取值: /// - "OpenDialog":打开“读写设置”弹窗(需要已连接且信号集合非空); /// - "Write":将当前选中信号加入写入配置; /// - "Read":将当前选中信号加入读取配置; /// - "Delete":删除当前选中信号的配置项。 /// public DelegateCommand WriteReadConfigCmd { get { if (_writeReadConfigCmd == null) { _writeReadConfigCmd = new DelegateCommand(WriteReadConfigCmdMethod); } return _writeReadConfigCmd; } } /// /// 写入/读取/删除信号配置项命令处理。 /// /// 操作类型。 /// /// 数据来源: /// - 操作对象为右侧信号列表中当前选中的 。 /// /// 重要约束: /// - 配置程序激活后禁止变更读写配置(避免发送/解析过程中配置被修改); /// - "OpenDialog" 会强校验:设备已连接 + 信号集合非空,防止弹窗候选集合为空。 /// /// 注意: /// - 读写互斥与更严格的冲突校验主要在读写设置弹窗 ViewModel 中实现; /// - 此处只做“同一 RWInfo 下禁止重复”与基础防御校验。 /// private void WriteReadConfigCmdMethod(string par) { if (string.IsNullOrWhiteSpace(par)) return; if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (string.Equals(par, "OpenDialog", StringComparison.OrdinalIgnoreCase)) { if (!ZlgCanDriveService.OpenState) { MessageBox.Show("当前未连接 CAN/CANFD,无法打开【读写设置】。请先连接设备后再操作。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (ListCanDbcModel == null || ListCanDbcModel.Count == 0) { MessageBox.Show("当前信号集合为空,无法打开【读写设置】。请先解析 DBC 并确保信号列表已加载后再操作。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } OpenRwDialog(); return; } if (IsCanConfigProActive) { MessageBox.Show("当前配置已激活,请先取消激活后再修改写入/读取配置", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (SelectedCanDbcModel == null) { MessageBox.Show("请在右侧信号表中选中一个信号后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (string.IsNullOrWhiteSpace(SelectedCanDbcModel.SignalName) || string.IsNullOrWhiteSpace(SelectedCanDbcModel.MsgName)) { MessageBox.Show("选中的信号数据不完整(SignalName/MsgName 为空)", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } try { if (par == "Delete") { FreeSql.Delete() .Where(a => a.CanLinConfigProId == SelectCanLinConfigPro.Id) .Where(a => a.SignalName == SelectedCanDbcModel.SignalName) .ExecuteAffrows(); ReloadCurrentConfigPro(); OpTip = "已删除配置项"; return; } var rw = par == "Write" ? RW.Write : RW.Read; // 防重复:同一个 SignalName + RWInfo 只允许一条 var exists = FreeSql.Select() .Where(a => a.CanLinConfigProId == SelectCanLinConfigPro.Id) .Where(a => a.SignalName == SelectedCanDbcModel.SignalName) .Where(a => a.RWInfo == rw) .Any(); if (exists) { OpTip = "配置已存在"; return; } FreeSql.Insert(new CanLinRWConfig() { Name = SelectedCanDbcModel.SignalName, SignalName = SelectedCanDbcModel.SignalName, MsgFrameName = SelectedCanDbcModel.MsgName, CanLinConfigProId = SelectCanLinConfigPro.Id, DefautValue = "0", RWInfo = rw, LogicRuleId = 0, }).ExecuteInserted(); ReloadCurrentConfigPro(); OpTip = rw == RW.Write ? "已加入写入配置" : "已加入读取配置"; } catch (Exception ex) { LogService.Error($"ZLG 写入/读取配置操作失败:{par},{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 重新从数据库加载配置程序,并保持当前选中项。 /// /// /// 用途: /// - 在新增/删除/保存读写配置、保存调度表等操作后,刷新 UI 绑定对象,确保界面与数据库一致; /// - 刷新后会重新执行 ,从而重建 CmdData、读写列表与调度表列表。 /// private void ReloadCurrentConfigPro() { var id = SelectCanLinConfigPro?.Id; InitLoadCanConfigPro(); if (id != null) { SelectCanLinConfigPro = _canLinConfigPros.Find(a => a.Id == id.Value); SyncSelectedConfig(); } if (SelectCanLinConfigPro != null) { ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro); } MatchSeletedAndCanDbcModel(); } /// /// CAN 操作命令的 backing 字段。 /// private DelegateCommand? _canOpCmd; /// /// CAN 操作命令。 /// /// /// 常见取值: /// - "Open":按当前模式与参数打开设备并启动接收线程 /// - "Close":关闭设备(同时停止循环发送/循环接收,并退出激活态) /// - "Parse":解析当前 DBC(需要已连接且 DBC 路径有效) /// - "CycleSend":循环发送开关(第一次开启,第二次关闭) /// - "CycleRecive":循环接收开关 /// - "Save":保存当前配置(包括 CAN/CANFD 扩展配置与模式切换) /// public DelegateCommand CanOpCmd { get { if (_canOpCmd == null) { _canOpCmd = new DelegateCommand(CanOpCmdMethod); } return _canOpCmd; } } /// /// CAN 操作命令处理。 /// /// 操作类型。 /// /// 循环发送两种实现: /// - 若 为 true:使用调度表(硬件 auto_send);会校验每个 MsgName 在调度表中都有配置。 /// - 若 为 false:使用软件精确定时循环发送(周期取 )。 /// /// 互斥约束: /// - 开启 CAN 前会检查 状态,防止 CAN/LIN 同时占用。 /// - Close 时会通过 Service 层确保停止发送/接收与硬件 auto_send 清理。 /// private void CanOpCmdMethod(string par) { if (string.IsNullOrWhiteSpace(par)) { return; } try { switch (par) { case "Open": if (ComActionService.IsCanToDoWork() == false) { MessageBox.Show("请关闭LIN连接后才能开启CAN,同一个时刻只能有一个通信驱动压缩机", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (ZlgLinDriveService.OpenState) { MessageBox.Show("请先关闭 ZLG LIN 连接后再开启 ZLG CAN", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null) { MessageBox.Show("CAN配置为空", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } // ZLG 底层实际为 CANFD 通道,CAN 经典帧使用 arbitration 波特率(data 亦沿用,保持一致即可) ZlgCanDriveService.UpdateConfig(0, (uint)SelectedCANConfigExdDto.BaudRate, (uint)SelectedCANConfigExdDto.BaudRate, resEnable: true); } else { if (SelectedCANFdConfigExdDto == null) { MessageBox.Show("CANFD配置为空", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } ZlgCanDriveService.UpdateConfig(0, (uint)SelectedCANFdConfigExdDto.ArbBaudRate, (uint)SelectedCANFdConfigExdDto.DataBaudRate, SelectedCANFdConfigExdDto.ResEnable); } ZlgCanDriveService.StartCanDrive(); if (ZlgCanDriveService.OpenState) { LastError = null; OpTip = "CAN 已连接"; ConfigService.CanLinRunStateModel.CurSysSelectedCanLin = SelectedMode == ZlgCanMode.Can ? CanLinEnum.Can : CanLinEnum.CANFD; } else { OpTip = "CAN 连接失败"; MessageBox.Show("CAN 连接失败(OpenState=false),请检查设备/驱动/参数。", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } // 自动解析 DBC if (!string.IsNullOrWhiteSpace(CurrentDbcPath)) { ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath); if (SelectCanLinConfigPro != null) { ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro); } MatchSeletedAndCanDbcModel(); } // 将当前配置的 CmdData 下发到驱动(用于事件驱动增量发送) BuildAndLoadCmdDataToDrive(); break; case "Close": ZlgCanDriveService.CloseDevice(); ConfigService.CanLinRunStateModel.CurSysSelectedCanLin = CanLinEnum.No; LastError = null; OpTip = "CAN 已关闭"; // 关闭设备时自动退出激活,恢复可切换状态 IsCanConfigProActive = false; IsCANConfigDatagridActive = true; break; case "Parse": if (!ZlgCanDriveService.OpenState) { MessageBox.Show("请先打开CAN后再解析", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (string.IsNullOrWhiteSpace(CurrentDbcPath)) { MessageBox.Show("请选择DBC文件后再操作", "提示", MessageBoxButton.OKCancel, MessageBoxImage.Hand); return; } ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath); if (SelectCanLinConfigPro != null) { ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro); } MatchSeletedAndCanDbcModel(); LastError = null; OpTip = "DBC 已解析"; break; case "CycleSend": if (!ZlgCanDriveService.OpenState) { MessageBox.Show("请先打开 CAN 后再进行循环发送", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (!ZlgCanDriveService.DbcParserState) { MessageBox.Show("请先解析 DBC 后再进行循环发送", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } // 与图莫斯一致:第一次点击开启,第二次点击关闭 if (!ZlgCanDriveService.IsCycleSend) { // 开启前:确保最新 CmdData 已下发 BuildAndLoadCmdDataToDrive(); // 同步 SchEnable(防止 UI 勾选但未触发 Command 导致服务端状态不一致) // 说明: // - SchEnable=true 时,Service 会走“硬件调度表(auto_send)”路径; // - SchEnable=false 时,Service 会走“软件精确定时循环发送”路径; // - 因此 CycleSend 开启前必须保证 Service.SchEnable 与 UI/DTO 一致。 ZlgCanDriveService.SchEnable = CurrentSchEnable; if (ZlgCanDriveService.SchEnable) { // 使用调度表:校验每个 MsgName 都配置了周期 // 注意: // - 这里校验的是“当前会被发送的消息帧集合”(CmdData 聚合)是否都在调度表中配置; // - 缺失配置会导致硬件 auto_send 无法周期性发送对应帧,因此直接阻止开启。 var groupMsg = ZlgCanDriveService.CmdData .Where(a => !string.IsNullOrWhiteSpace(a.MsgName)) .GroupBy(a => a.MsgName) .Select(g => g.Key) .ToList(); if (groupMsg.Count == 0) { MessageBox.Show("未发现可发送的消息帧(CmdData.MsgName 为空)", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } if (SelectedMode == ZlgCanMode.Can) { foreach (var msg in groupMsg) { if (!ListCANScheduleConfigDto.Any(a => string.Equals(a.MsgName, msg, StringComparison.Ordinal))) { MessageBox.Show($"你使能了调度表,但是调度表中没有设置【{msg}】信息,请设置后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } } if (ListCANScheduleConfigDto == null || ListCANScheduleConfigDto.Count == 0) { MessageBox.Show("调度表配置为空数据,请检查!将无法发送数据", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } ZlgCanDriveService.SetScheduleConfigs(ListCANScheduleConfigDto.ToList()); } else { foreach (var msg in groupMsg) { if (!ListCANFdScheduleConfigDto.Any(a => string.Equals(a.MsgName, msg, StringComparison.Ordinal))) { MessageBox.Show($"你使能了调度表,但是调度表中没有设置【{msg}】信息,请设置后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } } if (ListCANFdScheduleConfigDto == null || ListCANFdScheduleConfigDto.Count == 0) { MessageBox.Show("调度表配置为空数据,请检查!将无法发送数据", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } ZlgCanDriveService.SetScheduleConfigs(ListCANFdScheduleConfigDto.ToList()); } // 启动硬件调度表: // - Service 层会 StopSchedule 清理旧任务/旧 auto_send,然后下发 auto_send 并 apply; // - 运行中 CmdData 信号变化会由 Driver 覆盖更新对应 index 的帧数据(需要 IsCycleSend=true 且 SchEnable=true)。 ZlgCanDriveService.StartSchedule(); } else { // 不使用调度表:按当前配置周期做精确循环发送 // 说明:该路径完全在 PC 侧按周期触发发送,精度受系统调度影响,但不依赖硬件 auto_send。 var cycle = Math.Max(1, CurrentCycle); ZlgCanDriveService.StartPrecisionCycleSend(cycle); } OpTip = "循环发送:已开启"; } else { // 关闭循环发送: // - 先关闭事件驱动发送标志(避免 CmdData 变化继续触发增量发送/覆盖更新); // - 再调用 StopSchedule:停止软件任务 + clear_auto_send(防止硬件继续发)。 ZlgCanDriveService.IsCycleSend = false; ZlgCanDriveService.StopSchedule(); OpTip = "循环发送:已关闭"; } break; case "CycleRecive": ZlgCanDriveService.SetReceiveEnabled(!ZlgCanDriveService.IsCycleRevice); OpTip = ZlgCanDriveService.IsCycleRevice ? "循环接收:已开启" : "循环接收:已关闭"; break; case "Save": if (SelectCanLinConfigPro == null) { MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand); return; } // 允许在 UI 上修改当前配置的模式(CAN/CANFD),保存时同步更新 CanLinConfigPro.CANLINInfo。 FreeSql.Transaction(() => { if (SelectedMode == ZlgCanMode.Can) { if (SelectedCANConfigExdDto == null || SelectedCANConfigExdDto.Id <= 0) { // 若原配置为 CANFD,切换到 CAN 时可能尚未存在 CAN 扩展配置,这里按默认值创建。 var inserted = FreeSql.Insert(new CANConfigExd() { BaudRate = 500000, DbcPath = string.Empty, Cycle = 100, SchEnable = false, }).ExecuteInserted(); var exd = inserted?.FirstOrDefault(); if (exd == null) throw new InvalidOperationException("创建 CAN 扩展配置失败"); SelectedCANConfigExdDto = Mapper.Map(exd); } FreeSql.Update() .Set(a => a.DbcPath, SelectedCANConfigExdDto.DbcPath) .Set(a => a.Cycle, SelectedCANConfigExdDto.Cycle) .Set(a => a.BaudRate, SelectedCANConfigExdDto.BaudRate) .Set(a => a.SchEnable, SelectedCANConfigExdDto.SchEnable) .Where(a => a.Id == SelectedCANConfigExdDto.Id) .ExecuteUpdated(); FreeSql.Update() .Set(a => a.CANLINInfo, CANLIN.CAN) .Set(a => a.CANConfigExdId, SelectedCANConfigExdDto.Id) .Where(a => a.Id == SelectCanLinConfigPro.Id) .ExecuteAffrows(); } else { if (SelectedCANFdConfigExdDto == null || SelectedCANFdConfigExdDto.Id <= 0) { // 若原配置为 CAN,切换到 CANFD 时可能尚未存在 CANFD 扩展配置,这里按默认值创建。 var inserted = FreeSql.Insert(new CANFdConfigExd() { ArbBaudRate = 500000, DataBaudRate = 2000000, ISOEnable = true, ResEnable = false, DbcPath = string.Empty, Cycle = 100, SchEnable = false, }).ExecuteInserted(); var exd = inserted?.FirstOrDefault(); if (exd == null) throw new InvalidOperationException("创建 CANFD 扩展配置失败"); SelectedCANFdConfigExdDto = Mapper.Map(exd); } FreeSql.Update() .Set(a => a.DbcPath, SelectedCANFdConfigExdDto.DbcPath) .Set(a => a.Cycle, SelectedCANFdConfigExdDto.Cycle) .Set(a => a.DataBaudRate, SelectedCANFdConfigExdDto.DataBaudRate) .Set(a => a.ArbBaudRate, SelectedCANFdConfigExdDto.ArbBaudRate) .Set(a => a.ISOEnable, SelectedCANFdConfigExdDto.ISOEnable) .Set(a => a.ResEnable, SelectedCANFdConfigExdDto.ResEnable) .Set(a => a.SchEnable, SelectedCANFdConfigExdDto.SchEnable) .Where(a => a.Id == SelectedCANFdConfigExdDto.Id) .ExecuteUpdated(); FreeSql.Update() .Set(a => a.CANLINInfo, CANLIN.CANFD) .Set(a => a.CANFdConfigExdId, SelectedCANFdConfigExdDto.Id) .Where(a => a.Id == SelectCanLinConfigPro.Id) .ExecuteAffrows(); } }); ReloadCurrentConfigPro(); LastError = null; OpTip = "配置已保存"; break; } } catch (Exception ex) { LastError = ex.Message; OpTip = "操作失败"; LogService.Error($"ZLG CAN 操作失败:{par},{ex}"); MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } } }