using Aging.Infrastructure.Contracts;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
using HMI.Base;
|
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.IO;
|
using System.Linq;
|
using System.Threading;
|
using System.Windows;
|
using System.Windows.Controls;
|
using System.Windows.Media;
|
|
namespace Plugin.DeviceDebug
|
{
|
/// <summary>
|
/// DeviceDebug.xaml 的交互逻辑
|
/// </summary>
|
[ObservableObject]
|
public partial class DeviceDebug : SimpleMaskControl
|
{
|
private static readonly ILog _log = LogManager.GetLogger(typeof(DeviceDebug));
|
private DeviceDebugModel _debugModel;
|
private DebugSessionManager _sessionManager;
|
|
// 基础设施层服务
|
private IDeviceManager? _deviceManager;
|
private IDeviceService? _deviceService;
|
private IResourceManager? _resourceManager;
|
|
// 设备配置数据库路径(与 DeviceConfig 插件共享)
|
private string _deviceConfigDbPath;
|
|
[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()
|
{
|
DataContext = this;
|
InitializeComponent();
|
}
|
|
/// <summary>
|
/// 强制刷新设备列表(供集成测试预置设备后调用)
|
/// </summary>
|
public void ForceRefreshForTest()
|
{
|
try { LoadDeviceList(); }
|
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("硬件调试模块初始化完成");
|
}
|
catch (Exception ex)
|
{
|
log.Error("硬件调试模块初始化失败", ex);
|
ToastHelper.Error($"硬件调试初始化失败: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 加载设备列表
|
/// </summary>
|
private void LoadDeviceList()
|
{
|
try
|
{
|
if (!File.Exists(_deviceConfigDbPath))
|
{
|
log.Warn($"设备配置数据库不存在: {_deviceConfigDbPath}");
|
ToastHelper.Warning("设备配置数据库不存在,请先在设备配置模块中添加设备");
|
return;
|
}
|
|
var sqliteHelper = new SqliteHelper(_deviceConfigDbPath);
|
var dt = sqliteHelper.GetDataTable("SELECT DeviceId, Kind FROM DeviceDefinition ORDER BY DeviceId");
|
|
var list = new ObservableCollection<string>();
|
foreach (System.Data.DataRow row in dt.Rows)
|
{
|
string deviceId = row["DeviceId"].ToString();
|
string kind = row["Kind"].ToString();
|
list.Add($"{deviceId} [{kind}]");
|
}
|
|
DeviceList = list;
|
log.Info($"加载设备列表完成,共 {list.Count} 个设备");
|
}
|
catch (Exception ex)
|
{
|
log.Error("加载设备列表失败", ex);
|
ToastHelper.Error($"加载设备列表失败: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 解析设备选择字符串,获取 DeviceId
|
/// </summary>
|
private 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)
|
{
|
if (string.IsNullOrEmpty(selection)) return string.Empty;
|
int start = selection.IndexOf(" [");
|
int end = selection.IndexOf("]");
|
if (start >= 0 && end > start)
|
return selection.Substring(start + 2, end - start - 2);
|
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()
|
{
|
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"))
|
};
|
return;
|
}
|
|
string deviceId = ParseDeviceId(SelectedDevice);
|
string kind = ParseDeviceKind(SelectedDevice);
|
|
// 加载设备详细信息
|
LoadDeviceInfo(deviceId);
|
|
HasDevice = true;
|
|
// 打开会话(ReadOnly)
|
CurrentSession = _sessionManager.OpenSession(deviceId, "", "operator", CurrentEffectiveVersion);
|
|
// 通过 DeviceManager 连接设备
|
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($"已选择设备: {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;
|
SelectedDevice = null;
|
|
ToastHelper.Success("已退出维护模式");
|
log.Info("已退出维护模式");
|
}
|
}
|
catch (Exception ex)
|
{
|
log.Error("退出维护模式失败", ex);
|
ToastHelper.Error($"退出维护模式失败: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 从 CAN Endpoint 解析总线名称(格式: 总线名:节点编号)
|
/// </summary>
|
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;
|
}
|
|
/// <summary>
|
/// 从 CAN Endpoint 解析节点编号
|
/// </summary>
|
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;
|
}
|
|
#endregion
|
}
|
}
|