| | |
| | | using HMI.Core.Util; |
| | | using HMI.UI.Util; |
| | | using log4net; |
| | | using Plugin.DeviceDebug.Entity; |
| | | using Plugin.DeviceDebug.Model; |
| | | using Plugin.DeviceDebug.Panels; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Threading; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | using System.Windows.Media; |
| | | |
| | | namespace Plugin.DeviceDebug |
| | | { |
| | | /// <summary> |
| | | /// DeviceDebug.xaml 的交互逻辑 |
| | | /// 硬件调试插件(壳):装配并持有共享基础设施(会话管理器/设备服务/配置库路径), |
| | | /// 以 TabControl 承载多个 <see cref="DeviceDebugTab"/>,支持同时调试多个设备, |
| | | /// 每个 Tab 拥有独立会话与维护租约。设备下拉仅列出尚未打开的设备。 |
| | | /// </summary> |
| | | [ObservableObject] |
| | | public partial class DeviceDebug : SimpleMaskControl |
| | | { |
| | | private static readonly ILog _log = LogManager.GetLogger(typeof(DeviceDebug)); |
| | | private static readonly ILog log = LogManager.GetLogger(typeof(DeviceDebug)); |
| | | private DeviceDebugModel _debugModel; |
| | | private DebugSessionManager _sessionManager; |
| | | |
| | | // 基础设施层服务 |
| | | private IDeviceManager? _deviceManager; |
| | | private IDeviceService? _deviceService; |
| | | private IResourceManager? _resourceManager; |
| | | private IDeviceManager _deviceManager; |
| | | private IDeviceService _deviceService; |
| | | private IResourceManager _resourceManager; |
| | | |
| | | // 设备配置数据库路径(与 DeviceConfig 插件共享) |
| | | private string _deviceConfigDbPath; |
| | | |
| | | // 全量设备("id [kind]")与已打开标签映射(deviceId → TabItem) |
| | | private readonly List<string> _allDevices = new(); |
| | | private readonly Dictionary<string, TabItem> _openTabs = new(StringComparer.OrdinalIgnoreCase); |
| | | |
| | | [ObservableProperty] private ObservableCollection<string> _deviceList = new(); |
| | | [ObservableProperty] private string _selectedDevice; |
| | | [ObservableProperty] private DebugSession _currentSession; |
| | | [ObservableProperty] private ObservableCollection<DebugOperation> _operationLogs = new(); |
| | | [ObservableProperty] private string _connectionStateText = "未连接"; |
| | | [ObservableProperty] private string _sessionStateText = ""; |
| | | [ObservableProperty] private string _reservationStatusText = ""; |
| | | [ObservableProperty] private string _currentDeviceId = ""; |
| | | [ObservableProperty] private string _currentDeviceKind = ""; |
| | | [ObservableProperty] private string _currentEffectiveVersion = ""; |
| | | [ObservableProperty] private string _currentEndpoint = ""; |
| | | [ObservableProperty] private string _currentDriverType = ""; |
| | | [ObservableProperty] private Brush _connectionBrush = new SolidColorBrush(Colors.Gray); |
| | | [ObservableProperty] private Brush _sessionStateBrush = new SolidColorBrush(Colors.Gray); |
| | | [ObservableProperty] private bool _canEnterMaintenance; |
| | | [ObservableProperty] private bool _canExitMaintenance; |
| | | [ObservableProperty] private bool _hasDevice; |
| | | |
| | | // 缓存当前选中的设备类型 |
| | | private string _currentDeviceKindStr; |
| | | |
| | | public DeviceDebug() |
| | | { |
| | |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 强制刷新设备列表(供集成测试预置设备后调用) |
| | | /// </summary> |
| | | public void ForceRefreshForTest() |
| | | { |
| | | try { LoadDeviceList(); } |
| | | catch (Exception ex) { _log.Error("ForceRefreshForTest Error.", ex); } |
| | | catch (Exception ex) { log.Error("ForceRefreshForTest Error.", ex); } |
| | | } |
| | | |
| | | public override void InitFinish() |
| | |
| | | base.InitFinish(); |
| | | try |
| | | { |
| | | // 初始化调试数据库 |
| | | _debugModel = new DeviceDebugModel(); |
| | | string dbPath = Path.Combine(BaseFolder, "debug.db"); |
| | | _debugModel.InitDatabase(dbPath); |
| | | |
| | | // 从 UtilContainer 获取基础设施服务 |
| | | var container = new UtilContainer(); |
| | | _deviceManager = container.GetUtil<IDeviceManager>("DeviceManager"); |
| | | _deviceService = container.GetUtil<IDeviceService>("DeviceService"); |
| | | _resourceManager = container.GetUtil<IResourceManager>("ResourceManager"); |
| | | |
| | | // 创建会话管理器(传入 ResourceManager) |
| | | _sessionManager = new DebugSessionManager(_debugModel, _resourceManager); |
| | | |
| | | // 设备配置数据库路径(与 DeviceConfig 共享) |
| | | _deviceConfigDbPath = Path.Combine(BaseFolder, "data.db"); |
| | | |
| | | log.Info("硬件调试模块初始化完成"); |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 加载设备列表 |
| | | /// </summary> |
| | | private void SimpleMaskControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | LoadDeviceList(); |
| | | Loaded -= SimpleMaskControl_Loaded; |
| | | } |
| | | |
| | | private void RfhBtn_Click(object sender, RoutedEventArgs e) => LoadDeviceList(); |
| | | |
| | | private void LoadDeviceList() |
| | | { |
| | | try |
| | |
| | | var sqliteHelper = new SqliteHelper(_deviceConfigDbPath); |
| | | var dt = sqliteHelper.GetDataTable("SELECT DeviceId, Kind FROM DeviceDefinition ORDER BY DeviceId"); |
| | | |
| | | var list = new ObservableCollection<string>(); |
| | | _allDevices.Clear(); |
| | | foreach (System.Data.DataRow row in dt.Rows) |
| | | { |
| | | string deviceId = row["DeviceId"].ToString(); |
| | | string kind = row["Kind"].ToString(); |
| | | list.Add($"{deviceId} [{kind}]"); |
| | | } |
| | | _allDevices.Add($"{row["DeviceId"]} [{row["Kind"]}]"); |
| | | |
| | | DeviceList = list; |
| | | log.Info($"加载设备列表完成,共 {list.Count} 个设备"); |
| | | RefreshAvailableList(); |
| | | log.Info($"加载设备列表完成,共 {_allDevices.Count} 个设备"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析设备选择字符串,获取 DeviceId |
| | | /// </summary> |
| | | private string ParseDeviceId(string selection) |
| | | /// <summary>下拉仅列出未打开的设备(一会一设备)。</summary> |
| | | private void RefreshAvailableList() |
| | | { |
| | | var avail = _allDevices |
| | | .Where(d => !_openTabs.ContainsKey(ParseDeviceId(d))) |
| | | .ToList(); |
| | | DeviceList = new ObservableCollection<string>(avail); |
| | | } |
| | | |
| | | private static string ParseDeviceId(string selection) |
| | | { |
| | | if (string.IsNullOrEmpty(selection)) return string.Empty; |
| | | int idx = selection.IndexOf(" ["); |
| | | return idx > 0 ? selection.Substring(0, idx) : selection; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析设备选择字符串,获取 Kind |
| | | /// </summary> |
| | | private string ParseDeviceKind(string selection) |
| | | private static string ParseDeviceKind(string selection) |
| | | { |
| | | if (string.IsNullOrEmpty(selection)) return string.Empty; |
| | | int start = selection.IndexOf(" ["); |
| | |
| | | return string.Empty; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 加载设备详细信息 |
| | | /// </summary> |
| | | private void LoadDeviceInfo(string deviceId) |
| | | { |
| | | try |
| | | { |
| | | if (!File.Exists(_deviceConfigDbPath)) return; |
| | | |
| | | var sqliteHelper = new SqliteHelper(_deviceConfigDbPath); |
| | | var dt = sqliteHelper.GetDataTable( |
| | | $"SELECT DeviceId, Kind, EffectiveVersion, Endpoint, DriverType FROM DeviceDefinition WHERE DeviceId = '{deviceId}'"); |
| | | |
| | | if (dt.Rows.Count > 0) |
| | | { |
| | | var row = dt.Rows[0]; |
| | | CurrentDeviceId = row["DeviceId"].ToString(); |
| | | CurrentDeviceKind = row["Kind"].ToString(); |
| | | CurrentEffectiveVersion = row["EffectiveVersion"]?.ToString() ?? ""; |
| | | CurrentEndpoint = row["Endpoint"]?.ToString() ?? ""; |
| | | CurrentDriverType = row["DriverType"]?.ToString() ?? ""; |
| | | |
| | | // 缓存设备类型 |
| | | _currentDeviceKindStr = CurrentDeviceKind; |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"加载设备信息失败: {deviceId}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 选择设备时:打开会话(ReadOnly),加载对应面板 |
| | | /// </summary> |
| | | private void OnDeviceSelected() |
| | | private void AddDeviceBtn_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | try |
| | | { |
| | | if (string.IsNullOrEmpty(SelectedDevice)) |
| | | { |
| | | HasDevice = false; |
| | | panelContainer.Content = new TextBlock |
| | | { |
| | | Text = "请选择设备", |
| | | HorizontalAlignment = HorizontalAlignment.Center, |
| | | VerticalAlignment = VerticalAlignment.Center, |
| | | FontSize = 16, |
| | | Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999")) |
| | | }; |
| | | ToastHelper.Warning("请先在设备下拉中选择设备"); |
| | | return; |
| | | } |
| | | |
| | | string deviceId = ParseDeviceId(SelectedDevice); |
| | | string kind = ParseDeviceKind(SelectedDevice); |
| | | |
| | | // 加载设备详细信息 |
| | | LoadDeviceInfo(deviceId); |
| | | |
| | | HasDevice = true; |
| | | |
| | | // 打开会话(ReadOnly) |
| | | CurrentSession = _sessionManager.OpenSession(deviceId, "", "operator", CurrentEffectiveVersion); |
| | | |
| | | // 通过 DeviceManager 连接设备 |
| | | if (_deviceManager != null) |
| | | // 已打开则聚焦其 Tab(防御性:下拉本已过滤) |
| | | if (_openTabs.TryGetValue(deviceId, out var existing)) |
| | | { |
| | | try |
| | | { |
| | | _deviceManager.ConnectAsync(deviceId, CancellationToken.None) |
| | | .GetAwaiter().GetResult(); |
| | | ConnectionStateText = "已连接"; |
| | | ConnectionBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50")); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Warn($"通过 DeviceManager 连接设备失败: {ex.Message}"); |
| | | ConnectionStateText = "连接失败"; |
| | | ConnectionBrush = new SolidColorBrush(Colors.OrangeRed); |
| | | } |
| | | } |
| | | |
| | | // 加载操作日志 |
| | | LoadOperationLogs(); |
| | | |
| | | // 更新按钮状态 |
| | | UpdateButtonStates(); |
| | | |
| | | // 加载对应面板 |
| | | LoadPanel(kind); |
| | | |
| | | log.Info($"已选择设备: {deviceId}, 类型: {kind}"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("选择设备失败", ex); |
| | | ToastHelper.Error($"选择设备失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据设备类型加载对应的调试面板 |
| | | /// </summary> |
| | | private void LoadPanel(string deviceKind) |
| | | { |
| | | try |
| | | { |
| | | UIElement panel = null; |
| | | |
| | | switch (deviceKind) |
| | | { |
| | | case "PowerSupply": |
| | | var powerPanel = new PowerSupplyDebugPanel(); |
| | | powerPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive; |
| | | powerPanel.OnCommandExecute += Panel_OnCommandExecute; |
| | | panel = powerPanel; |
| | | break; |
| | | case "CanDevice": |
| | | var canPanel = new CanDebugPanel |
| | | { |
| | | DeviceId = CurrentDeviceId, |
| | | InitialBusName = ParseCanBusName(CurrentEndpoint), |
| | | InitialNodeIndex = ParseCanNodeIndex(CurrentEndpoint), |
| | | AutoConnectOnLoad = true |
| | | }; |
| | | canPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive; |
| | | canPanel.OnCommandExecute += Panel_OnCommandExecute; |
| | | panel = canPanel; |
| | | break; |
| | | case "PlcDevice": |
| | | var plcPanel = new PlcDebugPanel(); |
| | | plcPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive; |
| | | plcPanel.OnCommandExecute += Panel_OnCommandExecute; |
| | | panel = plcPanel; |
| | | break; |
| | | default: |
| | | panel = new TextBlock |
| | | { |
| | | Text = $"不支持的设备类型: {deviceKind}", |
| | | HorizontalAlignment = HorizontalAlignment.Center, |
| | | VerticalAlignment = VerticalAlignment.Center, |
| | | FontSize = 14, |
| | | Foreground = new SolidColorBrush(Colors.OrangeRed) |
| | | }; |
| | | break; |
| | | } |
| | | |
| | | panelContainer.Content = panel; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"加载调试面板失败: {deviceKind}", ex); |
| | | ToastHelper.Error($"加载面板失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 面板命令执行事件处理 |
| | | /// </summary> |
| | | private void Panel_OnCommandExecute(object sender, DebugCommand command) |
| | | { |
| | | try |
| | | { |
| | | if (CurrentSession == null) |
| | | { |
| | | ToastHelper.Warning("请先选择设备"); |
| | | return; |
| | | } |
| | | |
| | | // 验证命令权限 |
| | | if (!_sessionManager.ValidateCommand(CurrentSession.SessionId, command)) |
| | | { |
| | | ToastHelper.Warning("当前会话状态不允许执行此操作,请先进入维护模式"); |
| | | return; |
| | | } |
| | | |
| | | // 通过 DeviceService 执行实际命令 |
| | | if (_deviceService != null) |
| | | { |
| | | ExecuteViaDeviceService(command); |
| | | } |
| | | else |
| | | { |
| | | // 降级模式:基础设施未初始化时保留原有行为 |
| | | log.Warn("DeviceService 未初始化,使用降级模式记录命令"); |
| | | ExecuteFallback(command); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"执行命令失败: {command.CommandType}", ex); |
| | | ToastHelper.Error($"命令执行失败: {ex.Message}"); |
| | | |
| | | // 记录失败操作 |
| | | var operation = new DebugOperation |
| | | { |
| | | SessionId = CurrentSession?.SessionId ?? "", |
| | | CommandType = command.CommandType, |
| | | Parameters = SerializeParameters(command), |
| | | Outcome = "Failed", |
| | | ErrorMessage = ex.Message, |
| | | Timestamp = DateTime.Now |
| | | }; |
| | | _debugModel.RecordOperation(operation); |
| | | OperationLogs.Insert(0, operation); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 通过 DeviceService 执行设备命令 |
| | | /// </summary> |
| | | private void ExecuteViaDeviceService(DebugCommand command) |
| | | { |
| | | try |
| | | { |
| | | var ownerToken = new ResourceOwnerToken( |
| | | OwnerType.Maintenance, |
| | | CurrentSession.SessionId); |
| | | |
| | | var deviceCommand = BuildDeviceCommand(command); |
| | | |
| | | var result = _deviceService.ExecuteCommandAsync( |
| | | command.DeviceId, |
| | | deviceCommand, |
| | | ownerToken, |
| | | CancellationToken.None).GetAwaiter().GetResult(); |
| | | |
| | | var operation = new DebugOperation |
| | | { |
| | | SessionId = CurrentSession.SessionId, |
| | | CommandType = command.CommandType, |
| | | Parameters = SerializeParameters(command), |
| | | Outcome = result.Success ? "Success" : "Failed", |
| | | DeviceReadback = result.Readback ?? "", |
| | | ErrorCode = result.ErrorCode ?? "", |
| | | ErrorMessage = result.ErrorMessage ?? "", |
| | | Timestamp = DateTime.Now |
| | | }; |
| | | |
| | | _debugModel.RecordOperation(operation); |
| | | OperationLogs.Insert(0, operation); |
| | | |
| | | if (result.Success) |
| | | ToastHelper.Success($"命令 {command.CommandType} 执行成功"); |
| | | else |
| | | ToastHelper.Warning($"命令执行失败: {result.ErrorMessage}"); |
| | | |
| | | log.Info($"执行命令: {command.CommandType}, 设备: {command.DeviceId}, 结果: {(result.Success ? "成功" : "失败")}"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"DeviceService 执行命令失败: {ex.Message}", ex); |
| | | ToastHelper.Error($"设备通信失败: {ex.Message}"); |
| | | |
| | | var operation = new DebugOperation |
| | | { |
| | | SessionId = CurrentSession.SessionId, |
| | | CommandType = command.CommandType, |
| | | Parameters = SerializeParameters(command), |
| | | Outcome = "Failed", |
| | | ErrorMessage = ex.Message, |
| | | Timestamp = DateTime.Now |
| | | }; |
| | | _debugModel.RecordOperation(operation); |
| | | OperationLogs.Insert(0, operation); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 降级模式:无 DeviceService 时的后备处理 |
| | | /// </summary> |
| | | private void ExecuteFallback(DebugCommand command) |
| | | { |
| | | var operation = new DebugOperation |
| | | { |
| | | SessionId = CurrentSession.SessionId, |
| | | CommandType = command.CommandType, |
| | | Parameters = SerializeParameters(command), |
| | | Outcome = "Success", |
| | | Timestamp = DateTime.Now |
| | | }; |
| | | |
| | | _debugModel.RecordOperation(operation); |
| | | OperationLogs.Insert(0, operation); |
| | | |
| | | ToastHelper.Success($"命令 {command.CommandType} 已发送(降级模式)"); |
| | | log.Info($"[降级模式] 执行命令: {command.CommandType}, 设备: {command.DeviceId}"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据命令类型构建实际设备命令字符串 |
| | | /// </summary> |
| | | private string BuildDeviceCommand(DebugCommand command) |
| | | { |
| | | if (command is PowerSetCommand psc) |
| | | { |
| | | return $"VOLT {psc.Voltage};CURR {psc.CurrentLimit};OUTP {(psc.Output ? "ON" : "OFF")}"; |
| | | } |
| | | else if (command is PowerMeasureCommand) |
| | | { |
| | | return "MEAS:ALL?"; |
| | | } |
| | | else if (command is CanSendCommand csc) |
| | | { |
| | | return $"CAN:SEND {csc.Frame}"; |
| | | } |
| | | else if (command is PlcWriteCommand pwc) |
| | | { |
| | | return $"WRITE:{pwc.IoAddress}:{(pwc.Value ? "TRUE" : "FALSE")}"; |
| | | } |
| | | return command.CommandType; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 序列化命令参数为JSON字符串 |
| | | /// </summary> |
| | | private string SerializeParameters(DebugCommand command) |
| | | { |
| | | try |
| | | { |
| | | switch (command) |
| | | { |
| | | case PowerSetCommand psc: |
| | | return $"{{Voltage:{psc.Voltage},CurrentLimit:{psc.CurrentLimit},Output:{psc.Output}}}"; |
| | | case PowerMeasureCommand _: |
| | | return "{}"; |
| | | case CanSendCommand csc: |
| | | return $"{{Frame:{csc.Frame},Data:{BitConverter.ToString(csc.Data)}}}"; |
| | | case PlcWriteCommand pwc: |
| | | return $"{{IoAddress:{pwc.IoAddress},Value:{pwc.Value}}}"; |
| | | default: |
| | | return "{}"; |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | return "{}"; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 加载操作日志 |
| | | /// </summary> |
| | | private void LoadOperationLogs() |
| | | { |
| | | try |
| | | { |
| | | if (CurrentSession == null) return; |
| | | |
| | | var ops = _debugModel.GetOperationsBySession(CurrentSession.SessionId); |
| | | OperationLogs = new ObservableCollection<DebugOperation>(ops.Take(100)); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("加载操作日志失败", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 更新按钮启用状态 |
| | | /// </summary> |
| | | private void UpdateButtonStates() |
| | | { |
| | | if (CurrentSession == null) |
| | | { |
| | | CanEnterMaintenance = false; |
| | | CanExitMaintenance = false; |
| | | SessionStateText = ""; |
| | | return; |
| | | } |
| | | |
| | | var state = CurrentSession.State; |
| | | CanEnterMaintenance = state == DebugSessionState.ReadOnly; |
| | | CanExitMaintenance = state == DebugSessionState.MaintenanceActive; |
| | | |
| | | // 更新会话状态文本和颜色 |
| | | SessionStateText = state.ToString(); |
| | | SessionStateBrush = state switch |
| | | { |
| | | DebugSessionState.ReadOnly => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2196F3")), |
| | | DebugSessionState.MaintenanceActive => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50")), |
| | | DebugSessionState.Faulted => new SolidColorBrush(Colors.Red), |
| | | DebugSessionState.RecoveryRequired => new SolidColorBrush(Colors.Orange), |
| | | _ => new SolidColorBrush(Colors.Gray) |
| | | }; |
| | | |
| | | // 更新面板的维护模式 |
| | | if (panelContainer.Content is PowerSupplyDebugPanel powerPanel) |
| | | powerPanel.IsMaintenanceMode = state == DebugSessionState.MaintenanceActive; |
| | | else if (panelContainer.Content is CanDebugPanel canPanel) |
| | | canPanel.IsMaintenanceMode = state == DebugSessionState.MaintenanceActive; |
| | | else if (panelContainer.Content is PlcDebugPanel plcPanel) |
| | | plcPanel.IsMaintenanceMode = state == DebugSessionState.MaintenanceActive; |
| | | } |
| | | |
| | | #region 事件处理 |
| | | |
| | | private void SimpleMaskControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | LoadDeviceList(); |
| | | Loaded -= SimpleMaskControl_Loaded; |
| | | } |
| | | |
| | | private void DeviceSelector_SelectionChanged(object sender, SelectionChangedEventArgs e) |
| | | { |
| | | OnDeviceSelected(); |
| | | } |
| | | |
| | | private void RfhBtn_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | LoadDeviceList(); |
| | | } |
| | | |
| | | private void EnterMaintenanceBtn_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | try |
| | | { |
| | | if (CurrentSession == null) return; |
| | | |
| | | // 请求维护模式 |
| | | if (_sessionManager.RequestMaintenance(CurrentSession.SessionId)) |
| | | { |
| | | // 激活维护模式 |
| | | _sessionManager.ActivateMaintenance(CurrentSession.SessionId); |
| | | UpdateButtonStates(); |
| | | |
| | | // 重新加载面板以更新IsMaintenanceMode |
| | | LoadPanel(CurrentDeviceKind); |
| | | |
| | | ToastHelper.Success("已进入维护控制模式"); |
| | | log.Info($"会话 {CurrentSession.SessionId} 进入维护模式"); |
| | | } |
| | | else |
| | | { |
| | | ToastHelper.Warning("无法进入维护模式,请检查设备状态"); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("进入维护模式失败", ex); |
| | | ToastHelper.Error($"进入维护模式失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void ExitMaintenanceBtn_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | try |
| | | { |
| | | if (CurrentSession == null) return; |
| | | |
| | | if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "确定要退出维护模式吗?") == MessageBoxResult.OK) |
| | | { |
| | | _sessionManager.CloseSession(CurrentSession.SessionId); |
| | | CurrentSession = null; |
| | | UpdateButtonStates(); |
| | | |
| | | // 清空面板 |
| | | panelContainer.Content = new TextBlock |
| | | { |
| | | Text = "请选择设备", |
| | | HorizontalAlignment = HorizontalAlignment.Center, |
| | | VerticalAlignment = VerticalAlignment.Center, |
| | | FontSize = 16, |
| | | Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999")) |
| | | }; |
| | | |
| | | HasDevice = false; |
| | | debugTabs.SelectedItem = existing; |
| | | SelectedDevice = null; |
| | | |
| | | ToastHelper.Success("已退出维护模式"); |
| | | log.Info("已退出维护模式"); |
| | | return; |
| | | } |
| | | |
| | | var tabContent = new DeviceDebugTab(); |
| | | tabContent.Initialize(_debugModel, _sessionManager, _deviceManager, _deviceService, |
| | | _deviceConfigDbPath, deviceId, kind); |
| | | |
| | | var tab = new TabItem { Header = deviceId, Content = tabContent, Tag = deviceId }; |
| | | debugTabs.Items.Add(tab); |
| | | debugTabs.SelectedItem = tab; |
| | | _openTabs[deviceId] = tab; |
| | | |
| | | RefreshAvailableList(); |
| | | SelectedDevice = null; |
| | | log.Info($"已加入调试设备: {deviceId}(当前 {debugTabs.Items.Count} 个标签)"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("退出维护模式失败", ex); |
| | | ToastHelper.Error($"退出维护模式失败: {ex.Message}"); |
| | | log.Error("加入调试设备失败", ex); |
| | | ToastHelper.Error($"加入调试失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从 CAN Endpoint 解析总线名称(格式: 总线名:节点编号) |
| | | /// </summary> |
| | | private string ParseCanBusName(string endpoint) |
| | | private void CloseTabBtn_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | if (string.IsNullOrEmpty(endpoint)) return "CAN_BUS_1"; |
| | | int idx = endpoint.IndexOf(':'); |
| | | return idx > 0 ? endpoint.Substring(0, idx) : endpoint; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从 CAN Endpoint 解析节点编号 |
| | | /// </summary> |
| | | private int? ParseCanNodeIndex(string endpoint) |
| | | try |
| | | { |
| | | if (string.IsNullOrEmpty(endpoint)) return 0; |
| | | int idx = endpoint.IndexOf(':'); |
| | | if (idx > 0 && idx < endpoint.Length - 1 && int.TryParse(endpoint.Substring(idx + 1), out int n)) |
| | | return n; |
| | | return 0; |
| | | if (debugTabs.SelectedItem is not TabItem tab) return; |
| | | string deviceId = tab.Tag as string ?? ""; |
| | | if (tab.Content is DeviceDebugTab content) |
| | | content.CloseSessionSilently(); // 关闭即释放维护租约,杜绝泄漏 |
| | | debugTabs.Items.Remove(tab); |
| | | if (!string.IsNullOrEmpty(deviceId)) _openTabs.Remove(deviceId); |
| | | RefreshAvailableList(); |
| | | log.Info($"已关闭调试标签并释放会话: {deviceId}"); |
| | | } |
| | | |
| | | #endregion |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("关闭调试标签失败", ex); |
| | | ToastHelper.Error($"关闭失败: {ex.Message}"); |
| | | } |
| | | } |
| | | } |
| | | } |