using Aging.Infrastructure.Contracts;
using CommunityToolkit.Mvvm.ComponentModel;
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.ObjectModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Plugin.DeviceDebug
{
///
/// 单设备调试工作区(自带独立调试会话与维护租约)。由 DeviceDebug 壳按 Tab 承载,
/// 支持同时打开多个设备互不干扰。会话状态机沿用 DebugSessionManager(共享单例,
/// 内存按 sessionId 缓存多会话),进入/退出维护只作用于本设备资源键。
///
[ObservableObject]
public partial class DeviceDebugTab : UserControl
{
private static readonly ILog log = LogManager.GetLogger(typeof(DeviceDebugTab));
// 共享服务(由壳注入,生命周期属壳)
private DeviceDebugModel _debugModel;
private DebugSessionManager _sessionManager;
private IDeviceManager _deviceManager;
private IDeviceService _deviceService;
private string _deviceConfigDbPath;
[ObservableProperty] private DebugSession _currentSession;
[ObservableProperty] private ObservableCollection _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;
/// 本 Tab 关联的设备ID(供壳过滤已打开设备、关闭时定位会话)。
public string BoundDeviceId { get; private set; } = "";
public DeviceDebugTab()
{
DataContext = this;
InitializeComponent();
}
///
/// 注入共享服务并打开该设备的调试会话(ReadOnly)+ 加载面板。
///
public void Initialize(DeviceDebugModel debugModel, DebugSessionManager sessionManager,
IDeviceManager deviceManager, IDeviceService deviceService, string deviceConfigDbPath,
string deviceId, string kind)
{
_debugModel = debugModel;
_sessionManager = sessionManager;
_deviceManager = deviceManager;
_deviceService = deviceService;
_deviceConfigDbPath = deviceConfigDbPath;
BoundDeviceId = deviceId;
OpenDevice(deviceId, kind);
}
private void OpenDevice(string deviceId, string kind)
{
try
{
LoadDeviceInfo(deviceId);
HasDevice = true;
CurrentSession = _sessionManager.OpenSession(deviceId, "", "operator", CurrentEffectiveVersion);
if (_deviceManager != null)
{
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($"调试 Tab 已打开设备: {deviceId}, 类型: {kind}");
}
catch (Exception ex)
{
log.Error("打开设备调试失败", ex);
ToastHelper.Error($"打开设备失败: {ex.Message}");
}
}
private void LoadDeviceInfo(string deviceId)
{
try
{
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() ?? "";
}
}
catch (Exception ex)
{
log.Error($"加载设备信息失败: {deviceId}", ex);
}
}
private void LoadPanel(string deviceKind)
{
try
{
UIElement panel = null;
switch (deviceKind)
{
case "PowerSupply":
var powerPanel = new PowerSupplyDebugPanel
{
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
};
// 共享/Broker 设备复用 DeviceManager 已注册驱动,禁止自建物理会话
try
{
var registeredDriver = _deviceManager?.GetDriver(CurrentDeviceId);
if (registeredDriver != null)
{
canPanel.ExistingDriver = registeredDriver;
log.Info($"设备 {CurrentDeviceId} 调试面板复用已注册驱动: {registeredDriver.GetType().Name}");
}
}
catch (Exception ex)
{
log.Warn($"获取已注册驱动失败,面板降级本地直驱: {ex.Message}");
}
canPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive;
canPanel.OnCommandExecute += Panel_OnCommandExecute;
panel = canPanel;
break;
case "PlcDevice":
var plcPanel = new PlcDebugPanel
{
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}");
}
}
private void Panel_OnCommandExecute(object sender, DebugCommand command)
{
try
{
if (CurrentSession == null)
{
ToastHelper.Warning("请先选择设备");
return;
}
if (!_sessionManager.ValidateCommand(CurrentSession.SessionId, command))
{
ToastHelper.Warning("当前会话状态不允许执行此操作,请先进入维护模式");
return;
}
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);
}
}
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);
}
}
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} 已发送(降级模式)");
}
private string BuildDeviceCommand(DebugCommand command)
{
if (command is PowerSetCommand psc)
return $"VOLT {psc.Voltage};CURR {psc.CurrentLimit};OUTP {(psc.Output ? "ON" : "OFF")}";
if (command is PowerMeasureCommand)
return "MEAS:ALL?";
if (command is CanSendCommand csc)
return $"CAN:SEND {csc.Frame}";
if (command is PlcWriteCommand pwc)
return $"WRITE:{pwc.IoAddress}:{(pwc.Value ? "TRUE" : "FALSE")}";
return command.CommandType;
}
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 "{}"; }
}
private void LoadOperationLogs()
{
try
{
if (CurrentSession == null) return;
var ops = _debugModel.GetOperationsBySession(CurrentSession.SessionId);
OperationLogs = new ObservableCollection(ops.Take(100));
}
catch (Exception ex) { log.Error("加载操作日志失败", ex); }
}
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;
}
private void EnterMaintenanceBtn_Click(object sender, RoutedEventArgs e)
{
try
{
if (CurrentSession == null) return;
if (_sessionManager.RequestMaintenance(CurrentSession.SessionId))
{
_sessionManager.ActivateMaintenance(CurrentSession.SessionId);
UpdateButtonStates();
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)
CloseSessionSilently();
}
catch (Exception ex)
{
log.Error("退出维护模式失败", ex);
ToastHelper.Error($"退出维护模式失败: {ex.Message}");
}
}
///
/// 关闭本 Tab 会话并释放维护租约(供退出维护按钮与壳关闭 Tab 共用)。
/// 经 sessionId 复用 token 释放,杜绝租约泄漏导致的“第二次进维护失败”。
///
public void CloseSessionSilently()
{
try
{
if (CurrentSession != null)
{
_sessionManager.CloseSession(CurrentSession.SessionId);
CurrentSession = null;
log.Info($"设备 {BoundDeviceId} 会话已关闭,维护租约已释放");
}
// 重新打开 ReadOnly 会话,让用户可以再次进入维护模式
CurrentSession = _sessionManager.OpenSession(BoundDeviceId, "", "operator", CurrentEffectiveVersion);
UpdateButtonStates();
LoadOperationLogs();
}
catch (Exception ex)
{
log.Warn($"关闭会话异常: {ex.Message}");
}
}
private string ParseCanBusName(string endpoint)
{
if (string.IsNullOrEmpty(endpoint)) return "CAN_BUS_1";
int idx = endpoint.IndexOf(':');
return idx > 0 ? endpoint.Substring(0, idx) : endpoint;
}
private int? ParseCanNodeIndex(string endpoint)
{
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;
}
}
}