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 { /// /// CAN 帧显示模型 /// 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; } /// /// 单通道 CAN 调试视图(承载连接控制/发送/接收记录/状态统计全部逻辑)。 /// 由 多通道壳按 Tab 托管;也可独立使用(旧单面板语义)。 /// 驱动统一为 ,Local/Broker 差异对视图完全透明。 /// public partial class CanChannelView : UserControl { private static readonly ILog log = LogManager.GetLogger(typeof(CanChannelView)); /// 是否为维护模式(控制输入可用性) public static readonly DependencyProperty IsMaintenanceModeProperty = DependencyProperty.Register(nameof(IsMaintenanceMode), typeof(bool), typeof(CanChannelView), new PropertyMetadata(false)); public bool IsMaintenanceMode { get => (bool)GetValue(IsMaintenanceModeProperty); set => SetValue(IsMaintenanceModeProperty, value); } /// 命令执行事件 public event EventHandler? OnCommandExecute; /// 接收帧列表 public ObservableCollection ReceivedFrames { get; } = new(); /// /// CAN 驱动:优先复用 DeviceManager 已注册驱动(Broker 共享设备为代理驱动), /// 无则自建统一驱动(Local TCP NodeChannel 连模拟器) /// private Aging.Infrastructure.Contracts.IDeviceDriver? _driver; /// 驱动所有权:外部注入的代理驱动归 DeviceManager,断开时不得 Dispose private bool _ownsDriver; /// 外部已注册驱动(共享设备由 DeviceDebug 传入统一 CanDeviceDriver,禁止自建物理会话) public Aging.Infrastructure.Contracts.IDeviceDriver? ExistingDriver { get; set; } /// 轮询定时器(代理驱动兜底补显接收帧) private DispatcherTimer? _pollTimer; private bool _isConnected; /// 设备ID(从 DeviceDebug 传入) public string DeviceId { get => _deviceId; set { _deviceId = value; if (!string.IsNullOrEmpty(value)) { UpdateDeviceIdDisplay(); } } } private string _deviceId = string.Empty; /// 从设备 Endpoint 解析出的总线名称 public string InitialBusName { get; set; } /// 从设备 Endpoint 解析出的节点编号 public int? InitialNodeIndex { get; set; } /// 是否自动连接(从 DeviceDebug 打开时=true) public bool AutoConnectOnLoad { get; set; } public CanChannelView() { InitializeComponent(); receiveList.ItemsSource = ReceivedFrames; InitializePollTimer(); Loaded += CanChannelView_Loaded; } private void CanChannelView_Loaded(object sender, RoutedEventArgs e) { Loaded -= CanChannelView_Loaded; if (!string.IsNullOrEmpty(InitialBusName)) busNameInput.Text = InitialBusName; if (InitialNodeIndex.HasValue) nodeIndexInput.Text = InitialNodeIndex.Value.ToString(); if (AutoConnectOnLoad) { Dispatcher.BeginInvoke(new Action(() => { try { DoConnect(); } catch (Exception ex) { log.Warn($"自动连接失败: {ex.Message}"); } }), DispatcherPriority.Background); } } private void UpdateDeviceIdDisplay() { if (!string.IsNullOrEmpty(_deviceId) && busNameInput != null) log.Info($"[CanChannelView] 关联设备: {_deviceId}"); } private void InitializePollTimer() { _pollTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; _pollTimer.Tick += PollTimer_Tick; } private void PollTimer_Tick(object? sender, EventArgs e) { if (!_isConnected || _driver == null) return; try { // 统计每 tick 刷新;STATUS 查询同时触发 Broker 宿主捎带回推(事件链路) UpdateStats(); } catch (Exception ex) { log.Error($"轮询更新失败: {ex.Message}"); } } /// /// 兜底补显(仅当事件链路未覆盖时手动调用):统一驱动已对 Local/Broker 均提供 /// DriverFrameReceived 事件,面板默认只走事件显示,避免与轮询双报重复。 /// private void DrainBrokerMailbox() { string resp; try { resp = _driver!.QueryAsync("RECV_ALL", CancellationToken.None).GetAwaiter().GetResult(); } catch (Exception ex) { log.Debug($"接收兜底查询失败(不影响事件链路,下轮重试): {ex.Message}"); return; } if (string.IsNullOrEmpty(resp) || !resp.StartsWith("RECV_ALL:")) return; var head = resp.Substring("RECV_ALL:".Length); var split = head.IndexOf(':'); string body = split >= 0 ? head.Substring(split + 1) : ""; if (!int.TryParse(split >= 0 ? head.Substring(0, split) : head, out int count) || count <= 0) return; foreach (var entry in body.Split('|')) { var f = entry.Split(':'); if (f.Length < 2 || string.IsNullOrEmpty(f[0])) continue; AddReceivedFrame(f[0], f[1], "RX", "BUS"); } } private void ConnectBtn_Click(object sender, RoutedEventArgs e) { try { DoConnect(); } catch (Exception ex) { log.Error($"CAN 连接失败: {ex.Message}", ex); MessageBox.Show($"连接失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); UpdateConnectionStatus(false); } } private void DoConnect() { if (_isConnected) return; string busName = busNameInput.Text.Trim(); if (!int.TryParse(nodeIndexInput.Text.Trim(), out int nodeIndex)) throw new ArgumentException("节点编号格式错误"); string deviceId = string.IsNullOrEmpty(_deviceId) ? $"CAN_Test_{nodeIndex}_{DateTime.Now:HHmmss}" : _deviceId; if (ExistingDriver != null) { // 共享/代理设备:复用宿主通路,不新建本地物理会话 _driver = ExistingDriver; _ownsDriver = false; if (!_driver.IsConnected) _driver.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); // 统一驱动对 Broker 也提供接收事件(经宿主捎带回流),与本地一致地订阅 if (_driver is CanDeviceDriver shared) shared.DriverFrameReceived += OnDriverFrameReceived; log.Info($"[CanChannelView] 复用已注册驱动: {deviceId} ({_driver.GetType().Name})"); } else { var adapter = new CanDeviceDriver(deviceId, $"{busName}:{nodeIndex}"); adapter.DriverFrameReceived += OnDriverFrameReceived; _driver = adapter; _ownsDriver = true; adapter.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); } _isConnected = true; // 模拟器模式下连接后自动启用维护模式(允许发送) IsMaintenanceMode = true; if (maintenanceToggleBtn != null) { maintenanceToggleBtn.IsChecked = true; maintenanceToggleBtn.IsEnabled = true; } UpdateConnectionStatus(true); _pollTimer?.Start(); log.Info($"CAN 驱动已连接: Bus={busName}, Node={nodeIndex}, DeviceId={deviceId}"); } private void OnDriverFrameReceived(object? sender, CanSimulator.CanFrame frame) { try { AddReceivedFrame(frame.FrameIdHex, frame.DataHex, "RX", frame.SourceNodeId); log.Debug($"[即时显示] 接收帧: {frame}"); } catch (Exception ex) { log.Error($"即时显示帧失败: {ex.Message}", ex); } } private void DisconnectBtn_Click(object sender, RoutedEventArgs e) { try { _pollTimer?.Stop(); if (_driver != null) { if (_driver is CanDeviceDriver adapter) adapter.DriverFrameReceived -= OnDriverFrameReceived; _driver.DisconnectAsync().GetAwaiter().GetResult(); if (_ownsDriver) _driver.Dispose(); } _driver = null; _ownsDriver = false; _isConnected = false; IsMaintenanceMode = false; UpdateConnectionStatus(false); log.Info("CAN 驱动已断开"); } catch (Exception ex) { log.Error($"CAN 断开失败: {ex.Message}", ex); } } 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}"); } } 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("维护模式已关闭(只读监控)"); } } 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); } } 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; } } private void UpdateStats() { if (_driver == null) return; try { var response = _driver.QueryAsync("STATUS", CancellationToken.None).GetAwaiter().GetResult(); statsText.Text = $"TX: {GetStatValue(response, "Tx")} RX: {GetStatValue(response, "Rx")} Err: {GetStatValue(response, "Err")}"; } catch { /* 忽略统计更新失败 */ } } private string GetStatValue(string statusStr, string key) { try { foreach (var part in statusStr.Split(',')) { var kv = part.Trim().Split('='); if (kv.Length == 2 && kv[0].Trim() == key) return kv[1].Trim(); } } catch { } return "0"; } 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 }; // 必须用异步 BeginInvoke:推送常在驱动后台读循环线程触发,若用同步 Invoke, // 会与“UI 线程阻塞等待该读循环投递 RPC 响应”形成环形等待 → 开启应答器后推送骤增即冻结。 if (Application.Current == null) return; Application.Current.Dispatcher.BeginInvoke(new Action(() => { ReceivedFrames.Insert(0, frame); while (ReceivedFrames.Count > 500) ReceivedFrames.RemoveAt(ReceivedFrames.Count - 1); })); } catch (Exception ex) { log.Error($"添加接收帧失败: {ex.Message}", ex); } } private void SendBtn_Click(object sender, RoutedEventArgs e) { try { if (!IsMaintenanceMode) { MessageBox.Show("当前为只读模式,无法发送数据", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!_isConnected || _driver == null) { MessageBox.Show("请先连接 CAN 总线", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } string frameIdStr = frameIdInput.Text.Trim(); string dataStr = dataInput.Text.Trim(); 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(); 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); } } /// 供多通道壳在关闭标签时安全断开本通道(不弹 UI 错误)。 public void DisconnectInternalSafe() { try { if (_isConnected) DisconnectBtn_Click(this, new RoutedEventArgs()); _pollTimer?.Stop(); } catch (Exception ex) { log.Warn($"通道安全断开异常: {ex.Message}"); } } private void ClearBtn_Click(object sender, RoutedEventArgs e) { try { ReceivedFrames.Clear(); log.Info("清空 CAN 接收记录"); } catch (Exception ex) { log.Error($"清空接收记录失败: {ex.Message}", ex); } } } }