wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
Plugin.DeviceDebug/Panels/CanDebugPanel.xaml.cs
@@ -1,44 +1,25 @@
using Aging.Infrastructure.Drivers;
using log4net;
using Plugin.DeviceDebug.Entity;
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace Plugin.DeviceDebug.Panels
{
    /// <summary>
    /// CAN 帧显示模型
    /// </summary>
    public class CanFrameDisplay
    {
        public DateTime Timestamp { get; set; }
        public string FrameId { get; set; } = string.Empty;
        public int Dlc { get; set; }
        public string Data { get; set; } = string.Empty;
        public string Direction { get; set; } = string.Empty; // TX / RX
        public string Source { get; set; } = string.Empty;
    }
    /// <summary>
    /// CanDebugPanel.xaml 的交互逻辑
    /// CAN 调试面板多通道壳:以 TabControl 承载若干 <see cref="CanChannelView"/>,
    /// 支持同屏并列多个 CAN 通道独立收发(验收场景 4)。
    /// 对外保持与旧单面板一致的属性契约(DeviceId/InitialBusName/InitialNodeIndex/
    /// AutoConnectOnLoad/ExistingDriver/IsMaintenanceMode/OnCommandExecute),
    /// DeviceDebug 选设备时这些配置被转发到"设备通道"(首个 Tab)。
    /// </summary>
    public partial class CanDebugPanel : UserControl
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(CanDebugPanel));
        /// <summary>
        /// 是否为维护模式(控制输入可用性)
        /// </summary>
        public static readonly DependencyProperty IsMaintenanceModeProperty =
            DependencyProperty.Register(nameof(IsMaintenanceMode), typeof(bool), typeof(CanDebugPanel),
                new PropertyMetadata(false));
                new PropertyMetadata(false, OnMaintenanceModeChanged));
        public bool IsMaintenanceMode
        {
@@ -46,498 +27,135 @@
            set => SetValue(IsMaintenanceModeProperty, value);
        }
        /// <summary>
        /// 命令执行事件
        /// </summary>
        private static void OnMaintenanceModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var panel = (CanDebugPanel)d;
            if (panel._deviceChannel != null)
                panel._deviceChannel.IsMaintenanceMode = (bool)e.NewValue;
        }
        /// <summary>命令执行事件(任一通道触发均上抛,供 DeviceDebug 统一处理)</summary>
        public event EventHandler<DebugCommand>? OnCommandExecute;
        /// <summary>
        /// 接收帧列表
        /// </summary>
        public ObservableCollection<CanFrameDisplay> ReceivedFrames { get; } = new();
        private CanChannelView? _deviceChannel;
        private int _manualSeq;
        /// <summary>
        /// CAN 驱动适配器
        /// </summary>
        private CanDriverAdapter? _driver;
        /// <summary>
        /// 轮询定时器(用于检查接收邮箱)
        /// </summary>
        private DispatcherTimer? _pollTimer;
        /// <summary>
        /// 是否已连接
        /// </summary>
        private bool _isConnected;
        /// <summary>
        /// 设备ID(从 DeviceDebug 传入,面板自动拼接 deviceId)
        /// </summary>
        public string DeviceId
        {
            get => _deviceId;
            set
            {
                _deviceId = value;
                if (!string.IsNullOrEmpty(value))
                {
                    // 将设备ID前缀拼接到默认设备标识中,避免与模拟器面板重名
                    UpdateDeviceIdDisplay();
                }
            }
        }
        // 待转发到设备通道的配置(可能早于通道创建)
        private string _deviceId = string.Empty;
        /// <summary>
        /// 从设备 Endpoint 解析出的总线名称
        /// </summary>
        public string InitialBusName { get; set; }
        /// <summary>
        /// 从设备 Endpoint 解析出的节点编号
        /// </summary>
        public int? InitialNodeIndex { get; set; }
        /// <summary>
        /// 是否自动连接(从 DeviceDebug 打开时=true)
        /// </summary>
        public bool AutoConnectOnLoad { get; set; }
        private string _initialBusName;
        private int? _initialNodeIndex;
        private bool _autoConnectOnLoad;
        private Aging.Infrastructure.Contracts.IDeviceDriver? _existingDriver;
        public CanDebugPanel()
        {
            InitializeComponent();
            receiveList.ItemsSource = ReceivedFrames;
            InitializePollTimer();
            Loaded += CanDebugPanel_Loaded;
        }
        private void CanDebugPanel_Loaded(object sender, RoutedEventArgs e)
        /// <summary>设备ID(转发到设备通道)</summary>
        public string DeviceId
        {
            Loaded -= CanDebugPanel_Loaded;
            // 从设备信息初始化总线/节点输入
            if (!string.IsNullOrEmpty(InitialBusName))
            {
                busNameInput.Text = InitialBusName;
            }
            if (InitialNodeIndex.HasValue)
            {
                nodeIndexInput.Text = InitialNodeIndex.Value.ToString();
            get => _deviceId;
            set { _deviceId = value; EnsureDeviceChannel().DeviceId = value; }
            }
            // 自动连接
            if (AutoConnectOnLoad)
        /// <summary>总线名称(转发到设备通道)</summary>
        public string InitialBusName
            {
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    try { DoConnect(); }
                    catch (Exception ex) { log.Warn($"自动连接失败: {ex.Message}"); }
                }), DispatcherPriority.Background);
            }
            get => _initialBusName;
            set { _initialBusName = value; EnsureDeviceChannel().InitialBusName = value; }
        }
        private void UpdateDeviceIdDisplay()
        /// <summary>节点编号(转发到设备通道)</summary>
        public int? InitialNodeIndex
        {
            if (!string.IsNullOrEmpty(_deviceId) && busNameInput != null)
            {
                // 保留默认总线名,仅记录 deviceId 以供日志使用
                log.Info($"[CanDebugPanel] 关联设备: {_deviceId}");
            }
            get => _initialNodeIndex;
            set { _initialNodeIndex = value; EnsureDeviceChannel().InitialNodeIndex = value; }
        }
        /// <summary>
        /// 初始化轮询定时器
        /// </summary>
        private void InitializePollTimer()
        /// <summary>加载即自动连接(转发到设备通道)</summary>
        public bool AutoConnectOnLoad
        {
            _pollTimer = new DispatcherTimer
            {
                Interval = TimeSpan.FromMilliseconds(100) // 100ms 轮询一次
            };
            _pollTimer.Tick += PollTimer_Tick;
            get => _autoConnectOnLoad;
            set { _autoConnectOnLoad = value; EnsureDeviceChannel().AutoConnectOnLoad = value; }
        }
        /// <summary>
        /// 轮询定时器回调 - 仅更新统计信息(帧显示由事件驱动)
        /// </summary>
        private void PollTimer_Tick(object? sender, EventArgs e)
        /// <summary>外部已注册驱动(转发到设备通道,共享设备复用宿主通路)</summary>
        public Aging.Infrastructure.Contracts.IDeviceDriver? ExistingDriver
        {
            if (!_isConnected || _driver == null) return;
            try
            {
                // 更新统计
                UpdateStats();
            }
            catch (Exception ex)
            {
                log.Error($"轮询统计更新失败: {ex.Message}");
            }
            get => _existingDriver;
            set { _existingDriver = value; EnsureDeviceChannel().ExistingDriver = value; }
        }
        /// <summary>
        /// 连接按钮点击
        /// </summary>
        private void ConnectBtn_Click(object sender, RoutedEventArgs e)
        /// <summary>惰性创建"设备通道"(首个 Tab),并挂接命令事件与当前维护态。</summary>
        private CanChannelView EnsureDeviceChannel()
        {
            try
            {
                DoConnect();
            }
            catch (Exception ex)
            {
                log.Error($"CAN 连接失败: {ex.Message}", ex);
                MessageBox.Show($"连接失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                UpdateConnectionStatus(false);
            }
            if (_deviceChannel != null) return _deviceChannel;
            _deviceChannel = new CanChannelView();
            WireChannel(_deviceChannel, MakeHeader(_deviceId, _initialBusName, _initialNodeIndex));
            // 应用已到达的维护态
            _deviceChannel.IsMaintenanceMode = IsMaintenanceMode;
            var devTab = MakeTab(_deviceChannel);
            devTab.Header = MakeHeader(_deviceId, _initialBusName, _initialNodeIndex);
            channelTabs.Items.Add(devTab);
            return _deviceChannel;
        }
        /// <summary>
        /// 实际连接逻辑(从 UI 或自动连接调用)
        /// </summary>
        private void DoConnect()
        private void WireChannel(CanChannelView view, string header)
        {
            if (_isConnected) return;
            string busName = busNameInput.Text.Trim();
            if (!int.TryParse(nodeIndexInput.Text.Trim(), out int nodeIndex))
            {
                throw new ArgumentException("节点编号格式错误");
            view.OnCommandExecute += (s, e) => OnCommandExecute?.Invoke(this, e);
            }
            // 优先使用 DeviceDebug 传入的设备 ID,否则自动生成一个
            string deviceId = string.IsNullOrEmpty(_deviceId)
                ? $"CAN_Test_{nodeIndex}_{DateTime.Now:HHmmss}"
                : _deviceId;
            _driver = new CanDriverAdapter(deviceId, busName, nodeIndex);
            // 订阅驱动的帧接收事件(即时显示接收到的帧)
            _driver.DriverFrameReceived += OnDriverFrameReceived;
            // 连接
            _driver.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
            _isConnected = true;
            // 模拟器模式下,连接后自动启用维护模式(允许发送)
            IsMaintenanceMode = true;
            if (maintenanceToggleBtn != null)
        private TabItem MakeTab(CanChannelView view)
            {
                maintenanceToggleBtn.IsChecked = true;
                maintenanceToggleBtn.IsEnabled = true;
            // 复用视图自身的标题区不显示设备名,这里用 Tab 头标注通道身份
            return new TabItem { Content = view };
            }
            // 更新 UI
            UpdateConnectionStatus(true);
            _pollTimer?.Start();
            log.Info($"CAN 驱动已连接: Bus={busName}, Node={nodeIndex}, DeviceId={deviceId}");
        private static string MakeHeader(string deviceId, string bus, int? node)
        {
            string id = string.IsNullOrEmpty(deviceId) ? "通道" : deviceId;
            if (!string.IsNullOrEmpty(bus))
                return $"{id} @{bus}:{node}";
            return id;
        }
        /// <summary>
        /// 驱动帧接收事件处理 - 即时显示接收到的帧
        /// </summary>
        private void OnDriverFrameReceived(object? sender, CanSimulator.CanFrame frame)
        /// <summary>手动添加一个可编辑的空白通道(Local TCP 直连,用户自填总线/节点后连接)。</summary>
        private void AddChannelBtn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                // 判断方向:如果帧的来源是当前节点,则为自动响应(显示为 RX)
                string direction = "RX";
                string source = frame.SourceNodeId;
                AddReceivedFrame(
                    frame.FrameIdHex,
                    frame.DataHex,
                    direction,
                    source
                );
                log.Debug($"[即时显示] 接收帧: {frame}");
            }
            catch (Exception ex)
            {
                log.Error($"即时显示帧失败: {ex.Message}", ex);
            }
            var view = new CanChannelView { AutoConnectOnLoad = false };
            _manualSeq++;
            string header = $"CAN 通道 #{_manualSeq}";   // 手动通道具体总线/节点由用户页内填写,标签不预设
            WireChannel(view, header);
            var tab = MakeTab(view);
            tab.Header = header;
            channelTabs.Items.Add(tab);
            channelTabs.SelectedItem = tab;
            log.Info($"[CanDebugPanel] 手动添加通道 {header}");
        }
        /// <summary>
        /// 断开按钮点击
        /// </summary>
        private void DisconnectBtn_Click(object sender, RoutedEventArgs e)
        /// <summary>关闭当前选中的通道(设备通道不可关闭,避免误退出 DeviceDebug 会话视图)。</summary>
        private void CloseChannelBtn_Click(object sender, RoutedEventArgs e)
        {
            try
            if (channelTabs.SelectedItem is not TabItem tab) return;
            if (tab.Content == _deviceChannel)
            {
                _pollTimer?.Stop();
                if (_driver != null)
                {
                    _driver.DriverFrameReceived -= OnDriverFrameReceived;
                    _driver.DisconnectAsync().GetAwaiter().GetResult();
                    _driver.Dispose();
                }
                _driver = null;
                _isConnected = false;
                IsMaintenanceMode = false;
                UpdateConnectionStatus(false);
                log.Info("CAN 驱动已断开");
            }
            catch (Exception ex)
            {
                log.Error($"CAN 断开失败: {ex.Message}", ex);
            }
        }
        /// <summary>
        /// 混杂模式按钮点击
        /// </summary>
        private void PromiscuousBtn_Click(object sender, RoutedEventArgs e)
        {
            if (!_isConnected || _driver == null) return;
            try
            {
                _driver.QueryAsync("PROMISCUOUS", CancellationToken.None).GetAwaiter().GetResult();
                log.Info("已设置混杂模式(接收所有帧)");
                MessageBox.Show("已设置混杂模式,将接收所有 CAN 帧", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch (Exception ex)
            {
                log.Error($"设置混杂模式失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 维护模式切换按钮点击
        /// </summary>
        private void MaintenanceToggleBtn_Click(object sender, RoutedEventArgs e)
        {
            bool isOn = maintenanceToggleBtn.IsChecked == true;
            IsMaintenanceMode = isOn;
            if (isOn)
            {
                maintenanceToggleBtn.Content = "维护模式: 开";
                maintenanceToggleBtn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
                log.Info("维护模式已开启(可发送 CAN 帧)");
            }
            else
            {
                maintenanceToggleBtn.Content = "维护模式: 关";
                maintenanceToggleBtn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9E9E9E"));
                log.Info("维护模式已关闭(只读监控)");
            }
        }
        /// <summary>
        /// 自动响应模式切换按钮点击
        /// </summary>
        private void AutoResponseToggleBtn_Click(object sender, RoutedEventArgs e)
        {
            if (!_isConnected || _driver == null) return;
            try
            {
                bool isOn = autoResponseToggleBtn.IsChecked == true;
                // 通过驱动发送命令启用/禁用自动响应
                string cmd = isOn ? "AUTO_RESPONSE:ON" : "AUTO_RESPONSE:OFF";
                _driver.QueryAsync(cmd, CancellationToken.None).GetAwaiter().GetResult();
                if (isOn)
                {
                    autoResponseToggleBtn.Content = "自动响应: 开";
                    autoResponseToggleBtn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF9800"));
                    log.Info("自动响应模式已开启(收到帧将自动回复)");
                }
                else
                {
                    autoResponseToggleBtn.Content = "自动响应: 关";
                    autoResponseToggleBtn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9E9E9E"));
                    log.Info("自动响应模式已关闭");
                }
            }
            catch (Exception ex)
            {
                log.Error($"切换自动响应模式失败: {ex.Message}");
                MessageBox.Show($"切换失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        /// <summary>
        /// 更新连接状态显示
        /// </summary>
        private void UpdateConnectionStatus(bool connected)
        {
            _isConnected = connected;
            if (connected)
            {
                statusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
                statusText.Text = "已连接";
                connectBtn.IsEnabled = false;
                disconnectBtn.IsEnabled = true;
                promiscuousBtn.IsEnabled = true;
                maintenanceToggleBtn.IsEnabled = true;
                autoResponseToggleBtn.IsEnabled = true;
                busNameInput.IsEnabled = false;
                nodeIndexInput.IsEnabled = false;
            }
            else
            {
                statusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#9E9E9E"));
                statusText.Text = "未连接";
                connectBtn.IsEnabled = true;
                disconnectBtn.IsEnabled = false;
                promiscuousBtn.IsEnabled = false;
                maintenanceToggleBtn.IsEnabled = false;
                autoResponseToggleBtn.IsEnabled = false;
                busNameInput.IsEnabled = true;
                nodeIndexInput.IsEnabled = true;
            }
        }
        /// <summary>
        /// 更新统计信息
        /// </summary>
        private void UpdateStats()
        {
            if (_driver == null) return;
            try
            {
                var status = _driver.GetStatus();
                var response = _driver.QueryAsync("STATUS", CancellationToken.None).GetAwaiter().GetResult();
                // 解析状态: "STATUS:Node=..., Active=True, Rx=0, Tx=0, Err=0, Mailbox=0|Bus=..."
                statsText.Text = $"TX: {GetStatValue(response, "Tx")}  RX: {GetStatValue(response, "Rx")}  Err: {GetStatValue(response, "Err")}";
            }
            catch
            {
                // 忽略统计更新失败
            }
        }
        /// <summary>
        /// 从状态字符串中提取数值值
        /// </summary>
        private string GetStatValue(string statusStr, string key)
        {
            try
            {
                var parts = statusStr.Split(',');
                foreach (var part in parts)
                {
                    var kv = part.Trim().Split('=');
                    if (kv.Length == 2 && kv[0].Trim() == key)
                    {
                        return kv[1].Trim();
                    }
                }
            }
            catch { }
            return "0";
        }
        /// <summary>
        /// 添加接收帧
        /// </summary>
        public void AddReceivedFrame(string frameId, string data, string direction = "RX", string source = "")
        {
            try
            {
                var frame = new CanFrameDisplay
                {
                    Timestamp = DateTime.Now,
                    FrameId = frameId,
                    Dlc = data.Split(new[] { ' ', '-' }, StringSplitOptions.RemoveEmptyEntries).Length,
                    Data = data,
                    Direction = direction,
                    Source = source
                };
                Application.Current.Dispatcher.Invoke(() =>
                {
                    ReceivedFrames.Insert(0, frame);
                    // 限制显示数量
                    if (ReceivedFrames.Count > 500)
                    {
                        var toRemove = ReceivedFrames.Skip(500).ToList();
                        foreach (var item in toRemove)
                        {
                            ReceivedFrames.Remove(item);
                        }
                    }
                });
            }
            catch (Exception ex)
            {
                log.Error($"添加接收帧失败: {ex.Message}", ex);
            }
        }
        /// <summary>
        /// 发送按钮点击
        /// </summary>
        private void SendBtn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (!IsMaintenanceMode)
                {
                    MessageBox.Show("当前为只读模式,无法发送数据", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
                MessageBox.Show("设备通道不可在此关闭,请从设备选择切换。", "提示",
                    MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }
            if (tab.Content is CanChannelView view)
                view.DisconnectInternalSafe();
            channelTabs.Items.Remove(tab);
        }
                if (!_isConnected || _driver == null)
        /// <summary>更新设备通道 Tab 头(在配置齐备后调用一次即可)。</summary>
        public void RefreshDeviceHeader()
                {
                    MessageBox.Show("请先连接 CAN 总线", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
                string frameIdStr = frameIdInput.Text.Trim();
                string dataStr = dataInput.Text.Trim();
                // 验证帧 ID
                uint frameId = CanSimulator.CanFrame.ParseFrameId(frameIdStr);
                // 验证数据
                byte[] dataBytes = CanSimulator.CanFrame.ParseData(dataStr);
                if (dataBytes.Length > 8)
                {
                    MessageBox.Show("数据长度不能超过 8 字节", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
                // 通过驱动发送
                string command = $"SEND:{frameIdStr}:{dataStr}";
                var response = _driver.SendCommandAsync(command, CancellationToken.None).GetAwaiter().GetResult();
                // 添加到显示列表(TX)
                AddReceivedFrame($"0x{frameId:X3}", BitConverter.ToString(dataBytes).Replace("-", " "), "TX", "LOCAL");
                log.Info($"发送 CAN 帧: ID=0x{frameId:X3}, Data={BitConverter.ToString(dataBytes)}, Response={response}");
            }
            catch (Exception ex)
            {
                log.Error($"发送 CAN 帧失败: {ex.Message}", ex);
                MessageBox.Show($"发送失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        /// <summary>
        /// 清空按钮点击
        /// </summary>
        private void ClearBtn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                ReceivedFrames.Clear();
                log.Info("清空 CAN 接收记录");
            }
            catch (Exception ex)
            {
                log.Error($"清空接收记录失败: {ex.Message}", ex);
            }
            if (_deviceChannel == null || channelTabs.Items.Count == 0) return;
            if (channelTabs.Items[0] is TabItem t)
                t.Header = MakeHeader(_deviceId, _initialBusName, _initialNodeIndex);
        }
    }
}