Files
CapMachine/CapMachine.Wpf/ViewModels/ZlgCanDriveConfigViewModel.cs
2026-05-08 15:48:49 +08:00

2685 lines
116 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 AutoMapper;
using CapMachine.Core;
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.CanDrive;
using CapMachine.Wpf.Dtos;
using CapMachine.Wpf.PrismEvent;
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
{
/// <summary>
/// ZLG CAN/CANFD 合并配置 ViewModel模式切换单选
/// </summary>
/// <remarks>
/// 该 ViewModel 负责 ZLG CAN/CANFD 的 UI 配置与用户交互编排,典型职责包括:
/// - 打开/关闭设备、切换模式CAN/CANFD并与 <see cref="ZlgCanDriveService"/> 同步状态;
/// - 加载 DBC 并维护可绑定的信号集合(用于读写设置与实时显示);
/// - 管理“读写设置”弹窗的数据准备与回写(写入/读取规则、信号候选集合);
/// - 管理调度表/循环发送相关 UI 状态(软件调度 vs 硬件 auto_send 的选择由 Service/Driver 实现)。
///
/// 线程说明:
/// - 本类绝大多数方法由 UI 线程调用;
/// - Driver 接收线程产生的数据更新事件若需要影响 UI应通过属性通知或 Dispatcher 进行线程切换(具体由 Service/Driver 侧处理)。
/// </remarks>
public class ZlgCanDriveConfigViewModel : NavigationViewModel
{
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="dialogService">Prism 弹窗服务,用于打开读写设置等对话框。</param>
/// <param name="freeSql">数据访问组件,用于读取/保存配置程序与读写配置等持久化数据。</param>
/// <param name="eventAggregator">事件聚合器,用于跨模块消息通知(如状态变化/日志等)。</param>
/// <param name="regionManager">区域导航服务,用于页面/视图切换。</param>
/// <param name="sysRunService">系统运行服务(与全局运行态/权限等相关)。</param>
/// <param name="comActionService">通信互斥控制服务,用于保证 CAN/LIN 等通道操作的互斥与安全。</param>
/// <param name="logService">日志服务,用于输出 UI 层操作过程与异常。</param>
/// <param name="logicRuleService">逻辑规则服务,为“写入规则/触发规则”下拉框提供数据源。</param>
/// <param name="configService">配置服务,用于读取/应用系统配置参数。</param>
/// <param name="zlgCanDriveService">ZLG CAN/CANFD 服务层负责驱动生命周期、DBC、发送/调度表编排。</param>
/// <param name="zlgLinDriveService">ZLG LIN 服务层,用于互斥判断或组合配置场景。</param>
/// <param name="mapper">对象映射器,用于 DTO/实体之间转换。</param>
/// <remarks>
/// 构造时会初始化:
/// - 波特率下拉数据源;
/// - 写入规则下拉数据源;
/// - CAN 配置程序列表(用于 UI 选择)。
///
/// 注意:构造函数仅做“数据源初始化与依赖注入”,不会直接打开设备。
/// </remarks>
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;
EventAggregator.GetEvent<CanLinConfigChangedEvent>().Subscribe(CanLinConfigChangedEventCall);
SelectedMode = ZlgCanMode.Can;
ArbBaudRateCbxItems = new ObservableCollection<CbxItems>()
{
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<CbxItems>()
{
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();
}
/// <summary>
/// 初始化“写入规则”下拉框数据源。
/// </summary>
/// <remarks>
/// 数据源来自 <see cref="LogicRuleService.LogicRuleDtos"/>。
/// - Key规则 Id
/// - Text规则名称
///
/// 该集合通常被“读写设置”弹窗中的 ComboBox 绑定使用。
/// </remarks>
private void InitWriteRuleCbx()
{
WriteRuleCbxItems = new ObservableCollection<CbxItems>();
foreach (var itemRule in LogicRuleService.LogicRuleDtos)
{
WriteRuleCbxItems.Add(new CbxItems()
{
Key = itemRule.Id.ToString(),
Text = itemRule.Name
});
}
}
/// <summary>
/// 打开“读写设置”弹窗。
/// </summary>
/// <remarks>
/// 弹窗数据准备流程:
/// - 基于当前页面的 <see cref="ListWriteCanLinRWConfigDto"/> / <see cref="ListReadCanLinRWConfigDto"/> 克隆出可编辑副本;
/// - 根据 <see cref="ListCanDbcModel"/> 构建信号候选集合MsgName/SignalName/描述等);
/// - 通过 <see cref="DialogService"/> 打开 <c>DialogZlgCanLinRwConfigView</c>
/// - 用户点击 OK 后回调中调用 <c>ReloadCurrentConfigPro</c> 刷新当前配置并提示。
///
/// 前置条件:
/// - 必须先选择配置程序(<see cref="SelectCanLinConfigPro"/> 不为 null
/// - 推荐调用方在进入本方法前确保已连接设备且 DBC 已加载,否则候选信号集合可能为空。
/// </remarks>
private void OpenRwDialog()
{
try
{
if (SelectCanLinConfigPro == null)
{
MessageBox.Show("选中CAN配置名称后再操作", "提示", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
var writeClones = new ObservableCollection<CanLinRWConfigDto>(
(ListWriteCanLinRWConfigDto ?? new ObservableCollection<CanLinRWConfigDto>())
.Select(CloneRwDto));
foreach (var item in writeClones)
{
item.RWInfo = RW.Write;
}
var readClones = new ObservableCollection<CanLinRWConfigDto>(
(ListReadCanLinRWConfigDto ?? new ObservableCollection<CanLinRWConfigDto>())
.Select(CloneRwDto));
foreach (var item in readClones)
{
item.RWInfo = RW.Read;
}
var candidates = new ObservableCollection<DialogZlgCanLinRwConfigViewModel.SignalCandidate>();
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);
}
}
/// <summary>
/// 克隆一个读写配置 DTO。
/// </summary>
/// <param name="src">源对象。</param>
/// <returns>新实例(字段逐个复制)。</returns>
/// <remarks>
/// 目的:
/// - 弹窗编辑时不直接修改页面当前集合,避免用户取消时污染原始配置;
/// - 用户确认OK后再由弹窗 ViewModel 负责持久化,页面侧随后通过 Reload 刷新。
/// </remarks>
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,
};
}
/// <summary>
/// Dialog 服务。
/// </summary>
public IDialogService DialogService { get; }
/// <summary>
/// FreeSql。
/// </summary>
public IFreeSql FreeSql { get; }
/// <summary>
/// 事件聚合。
/// </summary>
public IEventAggregator EventAggregator { get; }
/// <summary>
/// 区域导航。
/// </summary>
public IRegionManager RegionManager { get; }
/// <summary>
/// 系统运行服务。
/// </summary>
public SysRunService SysRunService { get; }
/// <summary>
/// 通信互斥控制。
/// </summary>
public ComActionService ComActionService { get; }
/// <summary>
/// 日志。
/// </summary>
public ILogService LogService { get; }
/// <summary>
/// 逻辑规则服务。
/// </summary>
public LogicRuleService LogicRuleService { get; }
/// <summary>
/// 配置服务。
/// </summary>
public ConfigService ConfigService { get; }
/// <summary>
/// ZLG CAN 服务。
/// </summary>
public ZlgCanDriveService ZlgCanDriveService { get; }
/// <summary>
/// ZLG LIN 服务(用于互斥判断)。
/// </summary>
public ZlgLinDriveService ZlgLinDriveService { get; }
/// <summary>
/// Mapper。
/// </summary>
public IMapper Mapper { get; }
/// <summary>
/// <see cref="ArbBaudRateCbxItems"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CbxItems> _arbBaudRateCbxItems = new ObservableCollection<CbxItems>();
/// <summary>
/// 仲裁波特率下拉项bps
/// </summary>
public ObservableCollection<CbxItems> ArbBaudRateCbxItems
{
get { return _arbBaudRateCbxItems; }
set { _arbBaudRateCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="DataBaudRateCbxItems"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CbxItems> _dataBaudRateCbxItems = new ObservableCollection<CbxItems>();
/// <summary>
/// 数据波特率下拉项bps
/// </summary>
public ObservableCollection<CbxItems> DataBaudRateCbxItems
{
get { return _dataBaudRateCbxItems; }
set { _dataBaudRateCbxItems = value; RaisePropertyChanged(); }
}
/// <summary>
/// 配置程序列表的本地缓存(数据库读取结果)。
/// </summary>
/// <remarks>
/// 与 <see cref="ListCanLinConfigPro"/> 的区别:
/// - 本字段用于保存从数据库一次性读取的实体列表;
/// - UI 绑定通常使用 <see cref="ListCanLinConfigPro"/>ObservableCollection
/// </remarks>
private List<CanLinConfigPro> _canLinConfigPros = new List<CanLinConfigPro>();
private ObservableCollection<CbxItems> _writeRuleCbxItems = new ObservableCollection<CbxItems>();
/// <summary>
/// 写入规则下拉项(逻辑规则列表)。
/// </summary>
/// <remarks>
/// 该集合由 <see cref="InitWriteRuleCbx"/> 初始化,通常绑定到读写设置弹窗或列表编辑处的 ComboBox。
/// </remarks>
public ObservableCollection<CbxItems> WriteRuleCbxItems
{
get { return _writeRuleCbxItems; }
set { _writeRuleCbxItems = value; RaisePropertyChanged(); }
}
private ObservableCollection<CanLinRWConfigDto> _listWriteCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>();
/// <summary>
/// 写入配置集合(绑定到写入 DataGrid
/// </summary>
/// <remarks>
/// 每条记录对应一个“写入信号”配置项,包含:
/// - MsgFrameName/SignalName定位 DBC 信号
/// - LogicRuleId写入规则
/// - DefautValue默认写入值
///
/// 注意:
/// - 配置程序激活(<see cref="IsCanConfigProActive"/> true时应禁止编辑。
/// - 读写互斥/重复校验主要在读写设置弹窗 ViewModel 中实现。
/// </remarks>
public ObservableCollection<CanLinRWConfigDto> ListWriteCanLinRWConfigDto
{
get { return _listWriteCanLinRWConfigDto; }
set { _listWriteCanLinRWConfigDto = value; RaisePropertyChanged(); }
}
private ObservableCollection<CanLinRWConfigDto> _listReadCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>();
/// <summary>
/// 读取配置集合(绑定到读取 DataGrid
/// </summary>
/// <remarks>
/// 每条记录对应一个“读取信号”配置项,包含:
/// - MsgFrameName/SignalName定位 DBC 信号
/// - DefautValue默认值/初始值(视业务语义而定)
///
/// 读取配置通常用于“从接收帧解码后更新 UI/变量”的目标信号集合。
/// </remarks>
public ObservableCollection<CanLinRWConfigDto> ListReadCanLinRWConfigDto
{
get { return _listReadCanLinRWConfigDto; }
set { _listReadCanLinRWConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 当前在写入 DataGrid 中选中的行。
/// </summary>
/// <remarks>
/// 用于 Delete 等操作定位目标记录。该属性不直接通知 UI由 SelectionChanged 命令更新)。
/// </remarks>
private CanLinRWConfigDto? SelectedWriteCanLinRWConfigDto { get; set; }
/// <summary>
/// 当前在读取 DataGrid 中选中的行。
/// </summary>
/// <remarks>
/// 用于 Delete 等操作定位目标记录。该属性不直接通知 UI由 SelectionChanged 命令更新)。
/// </remarks>
private CanLinRWConfigDto? SelectedReadCanLinRWConfigDto { get; set; }
private string? _opTip;
/// <summary>
/// 操作提示(用于 UI 状态展示)。
/// </summary>
public string? OpTip
{
get { return _opTip; }
set { _opTip = value; RaisePropertyChanged(); }
}
private string? _lastError;
/// <summary>
/// 最近一次错误信息(用于 UI 状态展示)。
/// </summary>
public string? LastError
{
get { return _lastError; }
set { _lastError = value; RaisePropertyChanged(); }
}
private bool _isCanConfigProActive;
/// <summary>
/// 当前配置程序是否已激活(对齐图莫斯 Active 语义)。
/// 激活后禁止切换配置程序。
/// </summary>
public bool IsCanConfigProActive
{
get { return _isCanConfigProActive; }
set
{
_isCanConfigProActive = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(IsRwEditable));
}
}
public bool IsRwEditable
{
get { return !IsCanConfigProActive; }
}
private bool _isCANConfigDatagridActive = true;
/// <summary>
/// 配置程序 DataGrid 是否可操作(与 IsCanConfigProActive 取反)。
/// </summary>
public bool IsCANConfigDatagridActive
{
get { return _isCANConfigDatagridActive; }
set { _isCANConfigDatagridActive = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="SelectedMode"/> 的 backing 字段。
/// </summary>
private ZlgCanMode _selectedMode;
/// <summary>
/// 模式选择CAN/CANFD单选
/// </summary>
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<CanDbcModel>();
}
}
/// <summary>
/// 写入 DataGrid 选中项变化命令。
/// </summary>
/// <remarks>
/// 由 XAML 绑定到 SelectionChanged用于把当前选中行同步到 <see cref="SelectedWriteCanLinRWConfigDto"/>。
/// </remarks>
private DelegateCommand<object>? _writeGridSelectionChangedCmd;
/// <summary>
/// 写入 DataGrid 选中项变化命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<object> WriteGridSelectionChangedCmd
{
get
{
if (_writeGridSelectionChangedCmd == null)
{
_writeGridSelectionChangedCmd = new DelegateCommand<object>(WriteGridSelectionChangedCmdMethod);
}
return _writeGridSelectionChangedCmd;
}
}
/// <summary>
/// 写入 DataGrid SelectionChanged 命令处理。
/// </summary>
/// <param name="par">选中项(期望为 <see cref="CanLinRWConfigDto"/>)。</param>
private void WriteGridSelectionChangedCmdMethod(object par)
{
if (par is CanLinRWConfigDto dto)
{
SelectedWriteCanLinRWConfigDto = dto;
}
}
/// <summary>
/// 读取 DataGrid 选中项变化命令。
/// </summary>
/// <remarks>
/// 由 XAML 绑定到 SelectionChanged用于把当前选中行同步到 <see cref="SelectedReadCanLinRWConfigDto"/>。
/// </remarks>
private DelegateCommand<object>? _readGridSelectionChangedCmd;
/// <summary>
/// 读取 DataGrid 选中项变化命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<object> ReadGridSelectionChangedCmd
{
get
{
if (_readGridSelectionChangedCmd == null)
{
_readGridSelectionChangedCmd = new DelegateCommand<object>(ReadGridSelectionChangedCmdMethod);
}
return _readGridSelectionChangedCmd;
}
}
/// <summary>
/// 读取 DataGrid SelectionChanged 命令处理。
/// </summary>
/// <param name="par">选中项(期望为 <see cref="CanLinRWConfigDto"/>)。</param>
private void ReadGridSelectionChangedCmdMethod(object par)
{
if (par is CanLinRWConfigDto dto)
{
SelectedReadCanLinRWConfigDto = dto;
}
}
/// <summary>
/// 写入配置操作命令(保存/删除)。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 约定:
/// - "Edit":把 DataGrid 中当前列表逐条更新到数据库;
/// - "Delete":删除当前选中项。
/// </remarks>
private DelegateCommand<string>? _writeCmd;
/// <summary>
/// 写入配置操作命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<string> WriteCmd
{
get
{
if (_writeCmd == null)
{
_writeCmd = new DelegateCommand<string>(WriteCmdMethod);
}
return _writeCmd;
}
}
/// <summary>
/// 写入配置操作命令处理。
/// </summary>
/// <param name="par">操作类型("Edit"/"Delete")。</param>
/// <remarks>
/// 前置条件:
/// - 必须已选择配置程序(<see cref="SelectCanLinConfigPro"/> 不为 null
/// - 若配置已激活(<see cref="IsCanConfigProActive"/> true禁止修改写入配置。
/// </remarks>
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<CanLinRWConfig>(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<CanLinRWConfig>(SelectedWriteCanLinRWConfigDto.Id).ExecuteAffrows();
ReloadCurrentConfigPro();
OpTip = "写入配置已删除";
break;
}
}
/// <summary>
/// 读取配置操作命令(保存/删除)。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 约定:
/// - "Edit":把 DataGrid 中当前列表逐条更新到数据库;
/// - "Delete":删除当前选中项。
/// </remarks>
private DelegateCommand<string>? _readCmd;
/// <summary>
/// 读取配置操作命令(供 XAML 绑定)。
/// </summary>
public DelegateCommand<string> ReadCmd
{
get
{
if (_readCmd == null)
{
_readCmd = new DelegateCommand<string>(ReadCmdMethod);
}
return _readCmd;
}
}
/// <summary>
/// 读取配置操作命令处理。
/// </summary>
/// <param name="par">操作类型("Edit"/"Delete")。</param>
/// <remarks>
/// 前置条件:
/// - 必须已选择配置程序(<see cref="SelectCanLinConfigPro"/> 不为 null
/// - 若配置已激活(<see cref="IsCanConfigProActive"/> true禁止修改读取配置。
/// </remarks>
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<CanLinRWConfig>(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<CanLinRWConfig>(SelectedReadCanLinRWConfigDto.Id).ExecuteAffrows();
ReloadCurrentConfigPro();
OpTip = "读取配置已删除";
break;
}
}
/// <summary>
/// 连接按钮文字(对齐 Toomoss 风格)。
/// </summary>
public string ConnectButtonText
{
get { return SelectedMode == ZlgCanMode.Can ? "连接CAN" : "连接CANFD"; }
}
/// <summary>
/// 当前是否为 CANFD 模式(用于 UI 启用/禁用某些仅 CANFD 支持的参数)。
/// </summary>
public bool IsCanFdMode
{
get { return SelectedMode == ZlgCanMode.CanFd; }
}
/// <summary>
/// 当前是否为 CAN 模式(用于 UI 启用/禁用某些仅 CANFD 支持的参数)。
/// </summary>
public bool IsCanMode
{
get { return SelectedMode == ZlgCanMode.Can; }
}
/// <summary>
/// 波特率标题CAN 为“波特率”CANFD 为“仲裁波特率”。
/// </summary>
public string BaudRateTitle
{
get { return SelectedMode == ZlgCanMode.Can ? "波特率" : "仲裁波特率"; }
}
/// <summary>
/// 数据波特率标题:仅 CANFD 有意义。
/// </summary>
public string DataBaudRateTitle
{
get { return "数据波特率"; }
}
/// <summary>
/// 关闭按钮文字(对齐 Toomoss 风格)。
/// </summary>
public string CloseButtonText
{
get { return SelectedMode == ZlgCanMode.Can ? "关闭CAN" : "关闭CANFD"; }
}
/// <summary>
/// 绑定用:模式 KeyCan/CanFd
/// </summary>
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;
}
}
}
/// <summary>
/// DBC 路径标题。
/// </summary>
public string DbcPathTitle
{
get { return SelectedMode == ZlgCanMode.Can ? "CAN-DBC文件路径:" : "CANFD-DBC文件路径:"; }
}
/// <summary>
/// <see cref="ListCanLinConfigPro"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CanLinConfigPro>? _listCanLinConfigPro;
/// <summary>
/// 配置程序集合。
/// </summary>
public ObservableCollection<CanLinConfigPro>? ListCanLinConfigPro
{
get { return _listCanLinConfigPro; }
set { _listCanLinConfigPro = value; RaisePropertyChanged(); }
}
/// <summary>
/// 选中的配置程序。
/// </summary>
private CanLinConfigPro? _selectCanLinConfigPro;
/// <summary>
/// 选中的配置程序。
/// </summary>
public CanLinConfigPro? SelectCanLinConfigPro
{
get { return _selectCanLinConfigPro; }
set
{
_selectCanLinConfigPro = value;
RaisePropertyChanged();
}
}
private ObservableCollection<CanDbcModel>? _listCanDbcModel;
/// <summary>
/// DBC 信号集合。
/// </summary>
public ObservableCollection<CanDbcModel>? ListCanDbcModel
{
get { return _listCanDbcModel; }
set { _listCanDbcModel = value; RaisePropertyChanged(); }
}
/// <summary>
/// 当前选中的 DBC 信号。
/// </summary>
public CanDbcModel? SelectedCanDbcModel
{
get { return _selectedCanDbcModel; }
set { _selectedCanDbcModel = value; RaisePropertyChanged(); }
}
private CanDbcModel? _selectedCanDbcModel;
/// <summary>
/// <see cref="SelectedCANConfigExdDto"/> 的 backing 字段。
/// </summary>
private CANConfigExdDto? _selectedCANConfigExdDto;
/// <summary>
/// CAN 配置 DTO。
/// </summary>
public CANConfigExdDto? SelectedCANConfigExdDto
{
get { return _selectedCANConfigExdDto; }
set { _selectedCANConfigExdDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="SelectedCANFdConfigExdDto"/> 的 backing 字段。
/// </summary>
private CANFdConfigExdDto? _selectedCANFdConfigExdDto;
/// <summary>
/// CANFD 配置 DTO。
/// </summary>
public CANFdConfigExdDto? SelectedCANFdConfigExdDto
{
get { return _selectedCANFdConfigExdDto; }
set { _selectedCANFdConfigExdDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 从数据库加载 CAN/CANFD 配置程序列表。
/// </summary>
/// <remarks>
/// 加载内容包含:
/// - 主表 <see cref="CanLinConfigPro"/>
/// - 扩展配置CAN/CANFD
/// - 读写配置项(包含逻辑规则)
/// - 调度表配置项CAN/CANFD
///
/// 该方法会刷新本地缓存 <see cref="_canLinConfigPros"/> 与 UI 绑定集合 <see cref="ListCanLinConfigPro"/>。
/// </remarks>
private void InitLoadCanConfigPro()
{
_canLinConfigPros = FreeSql.Select<CanLinConfigPro>()
.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<CanLinConfigPro>(_canLinConfigPros);
}
/// <summary>
/// 同步选中的配置。
/// </summary>
/// <remarks>
/// 当 UI 选中不同配置程序时,本方法会同步以下内容到 ViewModel
/// - 根据配置程序的 CANLINInfo 自动切换 <see cref="SelectedMode"/>(避免右侧参数/DBC 显示与配置类型不一致);
/// - 映射 CAN/CANFD 两套扩展 DTO若不存在则为 null/默认);
/// - 从配置内容构建读写配置 DTO 列表;
/// - 根据模式加载对应调度表集合;
/// - 构建并注入 CmdData 到 <see cref="ZlgCanDriveService"/>(事件驱动发送依赖);
/// - 最后重置并尝试加载当前配置的 DBC 信号集合(未连接时也会尝试解析)。
///
/// 注意:
/// - 已连接或已激活时,外层命令会禁止切换配置程序;该方法通常只在允许切换时被调用。
/// </remarks>
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<CANConfigExdDto>(SelectCanLinConfigPro.CANConfigExd)
: null;
SelectedCANFdConfigExdDto = SelectCanLinConfigPro.CANFdConfigExd != null
? Mapper.Map<CANFdConfigExdDto>(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<CanLinRWConfig>();
ListWriteCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>(Mapper.Map<List<CanLinRWConfigDto>>(writeData));
var readData = SelectCanLinConfigPro.CanLinConfigContents?.Where(a => a.RWInfo == RW.Read).ToList() ?? new List<CanLinRWConfig>();
ListReadCanLinRWConfigDto = new ObservableCollection<CanLinRWConfigDto>(Mapper.Map<List<CanLinRWConfigDto>>(readData));
// 调度表(按模式)
if (SelectedMode == ZlgCanMode.Can)
{
if (SelectCanLinConfigPro.CanScheduleConfigs != null && SelectCanLinConfigPro.CanScheduleConfigs.Count > 0)
{
ListCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>(Mapper.Map<List<CANScheduleConfigDto>>(SelectCanLinConfigPro.CanScheduleConfigs));
}
else
{
ListCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>();
}
RaisePropertyChanged(nameof(ListCANScheduleConfigDto));
}
else
{
if (SelectCanLinConfigPro.CanFdScheduleConfigs != null && SelectCanLinConfigPro.CanFdScheduleConfigs.Count > 0)
{
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>(Mapper.Map<List<CANFdScheduleConfigDto>>(SelectCanLinConfigPro.CanFdScheduleConfigs));
}
else
{
ListCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
}
RaisePropertyChanged(nameof(ListCANFdScheduleConfigDto));
}
ResetAndTryLoadDbcSignalsForSelectedConfig();
}
/// <summary>
/// 切换配置程序时重置并尝试加载当前配置对应的 DBC 信号集合。
/// </summary>
/// <remarks>
/// 背景当“未连接设备”时如果切换配置程序但不清空信号集合UI 会残留上一次配置的信号列表。
///
/// 行为:
/// - 先清空 <see cref="SelectedCanDbcModel"/>
/// - 未连接时:
/// - 若当前配置未设置 DBC 路径,则信号集合置空;
/// - 若有 DBC 路径,则调用 <see cref="ZlgCanDriveService.StartDbc"/> 解析并加载信号集合;
/// - 已连接时:理论上禁止切换配置程序;这里做保底清空,避免 UI 残留。
/// </remarks>
private void ResetAndTryLoadDbcSignalsForSelectedConfig()
{
SelectedCanDbcModel = null;
// 未连接时:信号集合应以当前配置的 DBC 为准(或为空),禁止沿用上一个配置的信号列表。
if (!ZlgCanDriveService.OpenState)
{
if (string.IsNullOrWhiteSpace(CurrentDbcPath))
{
ListCanDbcModel = new ObservableCollection<CanDbcModel>();
return;
}
try
{
ListCanDbcModel = ZlgCanDriveService.StartDbc(CurrentDbcPath);
if (SelectCanLinConfigPro != null)
{
ZlgCanDriveService.InitCanConfig(SelectCanLinConfigPro);
}
MatchSeletedAndCanDbcModel();
}
catch (Exception ex)
{
ListCanDbcModel = new ObservableCollection<CanDbcModel>();
OpTip = "DBC 解析失败";
LogService.Warn($"切换配置程序时解析 DBC 失败:{CurrentDbcPath}{ex.Message}");
}
return;
}
// 已连接时:禁止切换配置程序。
// 注意:此处不应清空信号集合,否则在“保存读写设置/刷新配置”这类操作中会导致信号表瞬间变空。
return;
}
/// <summary>
/// <see cref="ListCANScheduleConfigDto"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CANScheduleConfigDto> _listCANScheduleConfigDto = new ObservableCollection<CANScheduleConfigDto>();
/// <summary>
/// CAN 调度表集合。
/// </summary>
public ObservableCollection<CANScheduleConfigDto> ListCANScheduleConfigDto
{
get { return _listCANScheduleConfigDto; }
set { _listCANScheduleConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// <see cref="ListCANFdScheduleConfigDto"/> 的 backing 字段。
/// </summary>
private ObservableCollection<CANFdScheduleConfigDto> _listCANFdScheduleConfigDto = new ObservableCollection<CANFdScheduleConfigDto>();
/// <summary>
/// CANFD 调度表集合。
/// </summary>
public ObservableCollection<CANFdScheduleConfigDto> ListCANFdScheduleConfigDto
{
get { return _listCANFdScheduleConfigDto; }
set { _listCANFdScheduleConfigDto = value; RaisePropertyChanged(); }
}
/// <summary>
/// 当前仲裁波特率(对 CAN等同 BaudRate对 CANFDArbBaudRate
/// </summary>
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));
}
}
/// <summary>
/// 当前数据波特率(对 CAN等同 BaudRate对 CANFDDataBaudRate
/// </summary>
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));
}
}
/// <summary>
/// 当前 ISO 标准使能(仅 CANFD 有意义CAN 固定为 false
/// </summary>
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();
}
}
/// <summary>
/// 当前终端电阻使能(仅 CANFD 有意义CAN 固定为 true驱动侧可忽略
/// </summary>
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();
}
}
/// <summary>
/// 构建并加载命令数据到驱动。
/// </summary>
/// <remarks>
/// 数据流:
/// - 从当前配置程序的写入项RW=Write构建 <see cref="CanCmdData"/> 列表;
/// - 每条写入项会携带 MsgName/SignalName/默认值/逻辑规则;
/// - 最终调用 <see cref="ZlgCanDriveService.LoadCmdDataToDrive"/> 注入驱动。
///
/// 作用:
/// - 事件驱动发送:信号值变化可触发增量发送/覆盖 auto_send
/// - 调度表/循环发送:编码发送依赖当前 CmdData 中的信号值。
/// </remarks>
private void BuildAndLoadCmdDataToDrive()
{
try
{
if (SelectCanLinConfigPro?.CanLinConfigContents == null)
{
// 防御性清空:
// - 当前未选择配置程序或无配置内容时,服务层应收到空 CmdData避免沿用旧配置继续触发事件驱动发送。
ZlgCanDriveService.LoadCmdDataToDrive(new List<CanCmdData>());
return;
}
var writeItems = SelectCanLinConfigPro.CanLinConfigContents
.Where(a => a.RWInfo == RW.Write)
.ToList();
var cmdList = new List<CanCmdData>();
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<LogicRuleDto>(item.LogicRule),
});
}
// 统一入口:将 CmdData 注入 Service/Driver建立事件驱动增量发送能力。
ZlgCanDriveService.LoadCmdDataToDrive(cmdList);
}
catch (Exception ex)
{
LogService.Warn($"ZLG CAN 构建/下发 CmdData 失败:{ex.Message}");
}
}
/// <summary>
/// 当前 DBC 路径(随模式切换映射到对应 DTO
/// </summary>
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();
}
}
/// <summary>
/// 当前周期(随模式切换映射到对应 DTO
/// </summary>
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();
}
}
/// <summary>
/// 当前调度使能(随模式切换映射到对应 DTO
/// </summary>
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();
}
}
/// <summary>
/// CAN 波特率。
/// </summary>
public int CanBaudRate
{
get { return SelectedCANConfigExdDto?.BaudRate ?? 0; }
set
{
if (SelectedCANConfigExdDto == null) return;
SelectedCANConfigExdDto.BaudRate = value;
RaisePropertyChanged();
}
}
/// <summary>
/// CANFD 仲裁波特率。
/// </summary>
public int CanFdArbBaudRate
{
get { return SelectedCANFdConfigExdDto?.ArbBaudRate ?? 0; }
set
{
if (SelectedCANFdConfigExdDto == null) return;
SelectedCANFdConfigExdDto.ArbBaudRate = value;
RaisePropertyChanged();
}
}
/// <summary>
/// CANFD 数据波特率。
/// </summary>
public int CanFdDataBaudRate
{
get { return SelectedCANFdConfigExdDto?.DataBaudRate ?? 0; }
set
{
if (SelectedCANFdConfigExdDto == null) return;
SelectedCANFdConfigExdDto.DataBaudRate = value;
RaisePropertyChanged();
}
}
/// <summary>
/// 匹配选中的配置和 DBC 模型。
/// </summary>
/// <remarks>
/// 用途:对信号集合做“是否已被配置为写入/读取”的标记,便于 UI 颜色或状态提示。
/// - RW.Write 记为 1
/// - RW.Read 记为 2
/// - 未配置记为 0
///
/// 该标记依赖 <see cref="SelectCanLinConfigPro.CanLinConfigContents"/> 与 <see cref="ListCanDbcModel"/>。
/// </remarks>
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;
}
}
}
/// <summary>
/// 配置程序 DataGrid 选中项变化命令的 backing 字段。
/// </summary>
private DelegateCommand<object>? _canConfigProGridSelectionChangedCmd;
/// <summary>
/// 配置程序选中变化。
/// </summary>
/// <remarks>
/// - 打开/激活状态下禁止切换配置程序;
/// - 允许切换时会更新 <see cref="SelectCanLinConfigPro"/> 并调用 <see cref="SyncSelectedConfig"/>。
/// </remarks>
public DelegateCommand<object> CanConfigProGridSelectionChangedCmd
{
get
{
if (_canConfigProGridSelectionChangedCmd == null)
{
_canConfigProGridSelectionChangedCmd = new DelegateCommand<object>(CanConfigProGridSelectionChangedCmdMethod);
}
return _canConfigProGridSelectionChangedCmd;
}
}
/// <summary>
/// 配置程序 DataGrid SelectionChanged 命令处理。
/// </summary>
/// <param name="par">
/// 可能为:
/// - <see cref="CanLinConfigPro"/>(直接传入选中项);
/// - <see cref="SelectionChangedEventArgs"/>WPF SelectionChanged 参数)。
/// </param>
/// <remarks>
/// WPF 的 SelectionChanged 可能被重复触发/不同类型参数混用,因此这里做了多种分支兼容。
/// </remarks>
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();
}
/// <summary>
/// 配置程序切换前拦截命令的 backing 字段。
/// </summary>
private DelegateCommand<object>? _canConfigProGridPreviewMouseLeftButtonDownCmd;
/// <summary>
/// 配置程序切换前拦截(对齐图莫斯 PreviewMouseLeftButtonDown
/// 打开/激活状态下禁止切换,防止状态错乱。
/// </summary>
public DelegateCommand<object> CanConfigProGridPreviewMouseLeftButtonDownCmd
{
get
{
if (_canConfigProGridPreviewMouseLeftButtonDownCmd == null)
{
_canConfigProGridPreviewMouseLeftButtonDownCmd = new DelegateCommand<object>(CanConfigProGridPreviewMouseLeftButtonDownCmdMethod);
}
return _canConfigProGridPreviewMouseLeftButtonDownCmd;
}
}
/// <summary>
/// 配置程序切换前拦截命令处理。
/// </summary>
/// <param name="par">鼠标事件参数(可能为 <see cref="System.Windows.Input.MouseButtonEventArgs"/>)。</param>
/// <remarks>
/// 若拦截命中,会将 MouseButtonEventArgs.Handled 置为 true阻止 DataGrid 继续改变选中项。
/// </remarks>
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}");
}
}
/// <summary>
/// 选择 DBC 文件命令的 backing 字段。
/// </summary>
private DelegateCommand? _loadDbcCmd;
/// <summary>
/// 选择 DBC 文件。
/// </summary>
/// <remarks>
/// 该命令只负责让用户选择文件并写回 <see cref="CurrentDbcPath"/>
/// 不会自动解析;解析由 CAN 操作命令中的 "Parse" 或连接流程中的自动解析触发。
/// </remarks>
public DelegateCommand LoadDbcCmd
{
get
{
if (_loadDbcCmd == null)
{
_loadDbcCmd = new DelegateCommand(LoadDbcCmdMethod);
}
return _loadDbcCmd;
}
}
/// <summary>
/// 选择 DBC 文件命令处理。
/// </summary>
/// <remarks>
/// 前置条件:必须已选择配置程序(<see cref="SelectCanLinConfigPro"/>)。
///
/// 选择成功后会更新 <see cref="CurrentDbcPath"/>;用户取消则保持原值。
/// </remarks>
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);
}
}
/// <summary>
/// 调度使能下发命令的 backing 字段。
/// </summary>
private DelegateCommand<object>? _schEnableCmd;
/// <summary>
/// 调度使能写入驱动。
/// </summary>
/// <remarks>
/// 该命令用于把 UI 的 <see cref="CurrentSchEnable"/> 立即同步到 <see cref="ZlgCanDriveService.SchEnable"/>。
/// - SchEnable 影响事件驱动发送与调度表/auto_send 相关行为。
/// </remarks>
public DelegateCommand<object> SchEnableCmd
{
get
{
if (_schEnableCmd == null)
{
_schEnableCmd = new DelegateCommand<object>(SchEnableCmdCall);
}
return _schEnableCmd;
}
}
/// <summary>
/// 调度使能同步到驱动。
/// </summary>
/// <param name="par">未使用(与 XAML CommandParameter 兼容)。</param>
private void SchEnableCmdCall(object par)
{
// 与旧 Toomoss 行为对齐UI 勾选后立即下发到驱动
// 说明:
// - SchEnable 控制“事件驱动发送/覆盖更新 auto_send”的总开关
// - 仅修改 CurrentSchEnableDTO不会影响驱动必须执行该命令或在 CycleSend 开启前同步)。
ZlgCanDriveService.SchEnable = CurrentSchEnable;
}
/// <summary>
/// 配置程序操作命令的 backing 字段。
/// </summary>
private DelegateCommand<string>? _canLinConfigProCmd;
/// <summary>
/// 配置程序操作(新建/修改/删除)。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 约定:
/// - "Add":新建配置程序(按当前 <see cref="SelectedMode"/> 创建 CAN 或 CANFD 扩展表记录);
/// - "Edit":修改配置程序名称;
/// - "Delete":删除配置程序及其内容(包含读写配置、调度表、扩展表);
/// - "Active":激活/取消激活当前配置程序(激活后禁止切换配置程序)。
///
/// 激活语义:
/// - 激活要求设备已连接且 DBC 已解析成功;
/// - 激活后会把当前配置下发到服务层CmdData + 调度表),用于循环发送/硬件调度表等功能。
/// </remarks>
public DelegateCommand<string> CanLinConfigProCmd
{
get
{
if (_canLinConfigProCmd == null)
{
_canLinConfigProCmd = new DelegateCommand<string>(CanLinConfigProCmdMethod);
}
return _canLinConfigProCmd;
}
}
/// <summary>
/// 配置程序操作命令处理。
/// </summary>
/// <param name="par">操作类型Add/Edit/Delete/Active。</param>
/// <remarks>
/// 约束说明:
/// - 打开 CAN<see cref="ZlgCanDriveService.OpenState"/>)或已激活(<see cref="IsCanConfigProActive"/>)时,
/// 禁止新建/修改/删除与切换配置程序,避免“驱动正在运行但底层配置被替换”导致状态错乱。
/// - Delete 会同时清理 CAN/CANFD 两套扩展配置(如果都存在),避免孤儿记录残留。
/// </remarks>
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<string>("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<CanLinConfigPro>()
.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<CANConfigExd>(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<CanLinConfigPro>(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<CANFdConfigExd>(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<CanLinConfigPro>(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<string>("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<CanLinConfigPro>()
.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<CanLinConfigPro>()
.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<CanLinConfigPro>();
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<CANConfigExd>(item.CANConfigExdId).ExecuteAffrows();
}
if (item.CANFdConfigExdId > 0)
{
FreeSql.Delete<CANFdConfigExd>(item.CANFdConfigExdId).ExecuteAffrows();
}
}
SelectCanLinConfigPro = null;
SelectedCANConfigExdDto = null;
SelectedCANFdConfigExdDto = null;
InitLoadCanConfigPro();
ListCanDbcModel = new ObservableCollection<CanDbcModel>();
break;
case "Active":
// 激活到取消的状态的判断
if (IsCanConfigProActive)
{
IsCanConfigProActive = false;
IsCANConfigDatagridActive = true;
// 停止调度/发送,避免“取消激活但仍在发送”
ZlgCanDriveService.StopSchedule();
ZlgCanDriveService.IsCycleSend = false;
ZlgCanDriveService.LoadCmdDataToDrive(new List<CanCmdData>());
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<CANScheduleConfigDto>());
}
else
{
ZlgCanDriveService.SetScheduleConfigs(ListCANFdScheduleConfigDto?.ToList() ?? new List<CANFdScheduleConfigDto>());
}
OpTip = "已激活当前配置";
break;
}
}
catch (Exception ex)
{
LogService.Error($"ZLG 配置程序操作失败:{par}{ex}");
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 调度表配置弹窗命令的 backing 字段。
/// </summary>
private DelegateCommand? _scheduleConfigCmd;
/// <summary>
/// 调度表配置(弹窗)。
/// </summary>
/// <remarks>
/// 打开调度表弹窗时,会从 <see cref="ZlgCanDriveService.CmdData"/> 中提取 MsgName 列表作为候选消息帧。
/// - CAN 模式:打开 DialogCANSchConfigView
/// - CANFD 模式:打开 DialogCANFdSchConfigView
///
/// 弹窗确认后会把返回的调度表 DTO 写回当前配置程序实体,并调用 <c>ReloadCurrentConfigPro</c> 刷新。
/// </remarks>
public DelegateCommand ScheduleConfigCmd
{
get
{
if (_scheduleConfigCmd == null)
{
_scheduleConfigCmd = new DelegateCommand(ScheduleConfigCmdMethod);
}
return _scheduleConfigCmd;
}
}
/// <summary>
/// 调度表配置弹窗命令处理。
/// </summary>
/// <remarks>
/// 前置条件:
/// - 必须选中配置程序;
/// - 必须存在可发送消息CmdData 非空且 MsgName 有效),否则无法配置调度表。
///
/// 注意:
/// - 调度表本身并不直接发送;真正启动发送由 CAN 操作命令中的 "CycleSend" 触发。
/// </remarks>
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<ObservableCollection<CANScheduleConfigDto>>("ReturnValue") ?? new ObservableCollection<CANScheduleConfigDto>();
// DTO -> 实体:用于持久化(保存到数据库),并在后续激活/循环发送前由 SyncSelectedConfig 再同步到 Service。
SelectCanLinConfigPro.CanScheduleConfigs = Mapper.Map<List<CANScheduleConfig>>(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<ObservableCollection<CANFdScheduleConfigDto>>("ReturnValue") ?? new ObservableCollection<CANFdScheduleConfigDto>();
SelectCanLinConfigPro.CanFdScheduleConfigs = Mapper.Map<List<CANFdScheduleConfig>>(ListCANFdScheduleConfigDto.ToList());
ReloadCurrentConfigPro();
});
}
}
catch (Exception ex)
{
LogService.Error($"调度表配置失败:{ex}");
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 读写配置操作命令的 backing 字段。
/// </summary>
private DelegateCommand<string>? _writeReadConfigCmd;
/// <summary>
/// 写入/读取/删除 信号配置项。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 常见取值:
/// - "OpenDialog":打开“读写设置”弹窗(需要已连接且信号集合非空);
/// - "Write":将当前选中信号加入写入配置;
/// - "Read":将当前选中信号加入读取配置;
/// - "Delete":删除当前选中信号的配置项。
/// </remarks>
public DelegateCommand<string> WriteReadConfigCmd
{
get
{
if (_writeReadConfigCmd == null)
{
_writeReadConfigCmd = new DelegateCommand<string>(WriteReadConfigCmdMethod);
}
return _writeReadConfigCmd;
}
}
/// <summary>
/// 写入/读取/删除信号配置项命令处理。
/// </summary>
/// <param name="par">操作类型。</param>
/// <remarks>
/// 数据来源:
/// - 操作对象为右侧信号列表中当前选中的 <see cref="SelectedCanDbcModel"/>。
///
/// 重要约束:
/// - 配置程序激活后禁止变更读写配置(避免发送/解析过程中配置被修改);
/// - "OpenDialog" 会强校验:设备已连接 + 信号集合非空,防止弹窗候选集合为空。
///
/// 注意:
/// - 读写互斥与更严格的冲突校验主要在读写设置弹窗 ViewModel 中实现;
/// - 此处只做“同一 RWInfo 下禁止重复”与基础防御校验。
/// </remarks>
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<CanLinRWConfig>()
.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<CanLinRWConfig>()
.Where(a => a.CanLinConfigProId == SelectCanLinConfigPro.Id)
.Where(a => a.SignalName == SelectedCanDbcModel.SignalName)
.Where(a => a.RWInfo == rw)
.Any();
if (exists)
{
OpTip = "配置已存在";
return;
}
FreeSql.Insert<CanLinRWConfig>(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);
}
}
/// <summary>
/// 重新从数据库加载配置程序,并保持当前选中项。
/// </summary>
/// <remarks>
/// 用途:
/// - 在新增/删除/保存读写配置、保存调度表等操作后,刷新 UI 绑定对象,确保界面与数据库一致;
/// - 刷新后会重新执行 <see cref="SyncSelectedConfig"/>,从而重建 CmdData、读写列表与调度表列表。
/// </remarks>
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();
}
/// <summary>
/// CAN/LIN 配置更改事件(导入后刷新数据)。
/// </summary>
/// <param name="msg"></param>
private void CanLinConfigChangedEventCall(string msg)
{
InitLoadCanConfigPro();
}
/// <summary>
/// CAN 操作命令的 backing 字段。
/// </summary>
private DelegateCommand<string>? _canOpCmd;
/// <summary>
/// CAN 操作命令。
/// </summary>
/// <remarks>
/// <paramref name="par"/> 常见取值:
/// - "Open":按当前模式与参数打开设备并启动接收线程
/// - "Close":关闭设备(同时停止循环发送/循环接收,并退出激活态)
/// - "Parse":解析当前 DBC需要已连接且 DBC 路径有效)
/// - "CycleSend":循环发送开关(第一次开启,第二次关闭)
/// - "CycleRecive":循环接收开关
/// - "Save":保存当前配置(包括 CAN/CANFD 扩展配置与模式切换)
/// </remarks>
public DelegateCommand<string> CanOpCmd
{
get
{
if (_canOpCmd == null)
{
_canOpCmd = new DelegateCommand<string>(CanOpCmdMethod);
}
return _canOpCmd;
}
}
/// <summary>
/// CAN 操作命令处理。
/// </summary>
/// <param name="par">操作类型。</param>
/// <remarks>
/// 循环发送两种实现:
/// - 若 <see cref="CurrentSchEnable"/> 为 true使用调度表硬件 auto_send会校验每个 MsgName 在调度表中都有配置。
/// - 若 <see cref="CurrentSchEnable"/> 为 false使用软件精确定时循环发送周期取 <see cref="CurrentCycle"/>)。
///
/// 互斥约束:
/// - 开启 CAN 前会检查 <see cref="ComActionService"/> 与 <see cref="ZlgLinDriveService"/> 状态,防止 CAN/LIN 同时占用。
/// - Close 时会通过 Service 层确保停止发送/接收与硬件 auto_send 清理。
/// </remarks>
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<CANConfigExd>(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<CANConfigExdDto>(exd);
}
FreeSql.Update<CANConfigExd>()
.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<CanLinConfigPro>()
.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<CANFdConfigExd>(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<CANFdConfigExdDto>(exd);
}
FreeSql.Update<CANFdConfigExd>()
.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<CanLinConfigPro>()
.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);
}
}
}
}