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);
}
}
}
}