wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
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>
    /// 单通道 CAN 调试视图(承载连接控制/发送/接收记录/状态统计全部逻辑)。
    /// 由 <see cref="CanDebugPanel"/> 多通道壳按 Tab 托管;也可独立使用(旧单面板语义)。
    /// 驱动统一为 <see cref="CanDeviceDriver"/>,Local/Broker 差异对视图完全透明。
    /// </summary>
    public partial class CanChannelView : UserControl
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(CanChannelView));
 
        /// <summary>是否为维护模式(控制输入可用性)</summary>
        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);
        }
 
        /// <summary>命令执行事件</summary>
        public event EventHandler<DebugCommand>? OnCommandExecute;
 
        /// <summary>接收帧列表</summary>
        public ObservableCollection<CanFrameDisplay> ReceivedFrames { get; } = new();
 
        /// <summary>
        /// CAN 驱动:优先复用 DeviceManager 已注册驱动(Broker 共享设备为代理驱动),
        /// 无则自建统一驱动(Local TCP NodeChannel 连模拟器)
        /// </summary>
        private Aging.Infrastructure.Contracts.IDeviceDriver? _driver;
 
        /// <summary>驱动所有权:外部注入的代理驱动归 DeviceManager,断开时不得 Dispose</summary>
        private bool _ownsDriver;
 
        /// <summary>外部已注册驱动(共享设备由 DeviceDebug 传入统一 CanDeviceDriver,禁止自建物理会话)</summary>
        public Aging.Infrastructure.Contracts.IDeviceDriver? ExistingDriver { get; set; }
 
        /// <summary>轮询定时器(代理驱动兜底补显接收帧)</summary>
        private DispatcherTimer? _pollTimer;
 
        private bool _isConnected;
 
        /// <summary>设备ID(从 DeviceDebug 传入)</summary>
        public string DeviceId
        {
            get => _deviceId;
            set
            {
                _deviceId = value;
                if (!string.IsNullOrEmpty(value))
                {
                    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; }
 
        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}");
            }
        }
 
        /// <summary>
        /// 兜底补显(仅当事件链路未覆盖时手动调用):统一驱动已对 Local/Broker 均提供
        /// DriverFrameReceived 事件,面板默认只走事件显示,避免与轮询双报重复。
        /// </summary>
        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);
            }
        }
 
        /// <summary>供多通道壳在关闭标签时安全断开本通道(不弹 UI 错误)。</summary>
        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);
            }
        }
    }
}